前言
设计模式简介
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
什么是 GOF(四人帮,全拼 Gang of Four)?
在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。
四位作者合称 GOF(四人帮,全拼 Gang of Four)。他们所提出的设计模式主要是基于以下的面向对象设计原则。
- 对接口编程而不是对实现编程。
- 优先使用对象组合而不是继承。
设计模式的使用
设计模式在软件开发中的两个主要用途。
-
开发人员的共同平台
设计模式提供了一个标准的术语系统,且具体到特定的情景。例如,单例设计模式意味着使用单个对象,这样所有熟悉单例设计模式的开发人员都能使用单个对象,并且可以通过这种方式告诉对方,程序使用的是单例模式。
-
最佳的实践
设计模式已经经历了很长一段时间的发展,它们提供了软件开发过程中面临的一般问题的最佳解决方案。学习这些模式有助于经验不足的开发人员通过一种简单快捷的方式来学习软件设计。
设计模式的类型
-
创建型模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 单例模式(Singleton Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
-
结构型模式
这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
- 适配器模式(Adapter Pattern)
- 桥接模式(Bridge Pattern)
- 过滤器模式(Filter、Criteria Pattern)
- 组合模式(Composite Pattern)
- 装饰器模式(Decorator Pattern)
- 外观模式(Facade Pattern)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy Pattern)
-
行为型模式
这些设计模式特别关注对象之间的通信。
- 责任链模式(Chain of Responsibility Pattern)
- 命令模式(Command Pattern)
- 解释器模式(Interpreter Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 备忘录模式(Memento Pattern)
- 观察者模式(Observer Pattern)
- 状态模式(State Pattern)
- 策略模式(Strategy Pattern)
- 模板模式(Template Pattern)
- 访问者模式(Visitor Pattern)
设计模式的六大原则
-
开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。
简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
-
里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现(父类变量里面装载子类对象)。
LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。
里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
-
依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
-
接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
-
迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
-
合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
采用的语言
设计模式是针对针对编程的一种思想和解决方法,和使用哪种语言无关。为了更直观地反映设计模式的好处与作用,此文章采用 C# 的语法进行展示。C# 的基本用法,可以参照C#基础语法 | 🪐 星空鸟 🪐 (yuilexi.cn)这篇文章。
简单工厂模式(创造型)
工厂模式是 最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式**。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。**
介绍
- **意图:**定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
- **主要解决:**主要解决接口选择的问题。
- **何时使用:**我们明确地计划不同条件下创建不同实例时。
- **如何解决:**让其子类实现工厂接口,返回的也是一个抽象的产品。
- **关键代码:**创建过程在其子类执行。
- 优点:
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
- 屏蔽产品的具体实现,调用者只关心产品的接口。
- **缺点:**每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
当存在多个子类时,我们可以创建一个父类。每当需要子类对象时,我们都给它一个父类对象,而父类对象中具体装载哪个子类对象,就需要根据用户的需求而定。
具体实现
下面例子将实现一个简单的工厂模式。情景想象:一个工厂可以生产三种笔记本电脑,分别是:联想、惠普和戴尔。工厂事先不知道用户要选择哪种品牌的电脑,但是无论选什么品牌,必定是“电脑”这一物品。
第一步,先创建一个接口,以便于子类能够继承这个接口。也可以使用抽象类、一般父类实现。
//先创建一个接口:电脑
public interface IComputer
{
void PrintComputer();
}
然后创建三个笔记本电脑的具体品牌的子类:联想、惠普、戴尔,这三个子类继承上面的IComputer接口。
//在创建三个类,分别是:联想、惠普、戴尔
public class Lenovo : IComputer
{
public void PrintComputer()
{
Console.WriteLine("联想笔记本");
}
}
public class HP : IComputer
{
public void PrintComputer()
{
Console.WriteLine("惠普笔记本");
}
}
public class Dell : IComputer
{
public void PrintComputer()
{
Console.WriteLine("戴尔笔记本");
}
}
然后创建一个工厂类,用于实现具体的电脑。
//创建一个简单工厂类,用来创建电脑
public class ComputerFactory
{
public static IComputer CreateComputer(string type)
{
IComputer computer = null;
switch (type)
{
case "Lenovo":
computer = new Lenovo();
break;
case "HP":
computer = new HP();
break;
case "Dell":
computer = new Dell();
break;
}
return computer;
}
}
最后,作为顾客,要去购买电脑。具体代码在主函数里面实现:
public class Program
{
public static void Main(string[] args)
{
//创建电脑对象,返回一个接口实例,而接口中转载不同需求的电脑品牌
IComputer computer1 = ComputerFactory.CreateComputer("Lenovo");
computer1.PrintComputer();
IComputer computer2 = ComputerFactory.CreateComputer("HP");
computer2.PrintComputer();
IComputer computer3 = ComputerFactory.CreateComputer("Dell");
computer3.PrintComputer();
Console.ReadKey();
}
}
这样,不管顾客想买什么品牌的电脑,可以直接给他一个电脑(基类),电脑具体是什么品牌(基类里面装什么子类),根据顾客的需求,就能实现对应的品牌(public static IComputer CreateComputer(string type)实现)。这样,就能向用户隐藏内部的逻辑,因为用户接收的是一个基类,而子类的实现与装载已经封装起来。
抽象工厂模式(创造型)
抽象工厂模式是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
介绍
**意图:**提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
**主要解决:**主要解决接口选择的问题。
**何时使用:**系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
**如何解决:**在一个产品族里面,定义多个产品。
关键代码: 在一个工厂里聚合多个同类产品。
**优点:**当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
**缺点:**产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
使用场景: 1、QQ 换皮肤,一整套一起换。 2、生成不同操作系统的程序。
**注意事项:**产品族难扩展,产品等级易扩展。
具体实现
基于简单工厂模式进行扩展。我们不仅可以选择笔记本的品牌,还可以选择手机的品牌。笔记本和手机之间没有任何关联,都有单独的工厂。而电脑与手机的生产购买逻辑是一样的,因此先创建一个工厂,在选择是哪种工厂,最后选择该工厂的哪个产品。
基于上面的代码:
//再创建一个接口:手机
public interface IPhone
{
void PrintPhone();
}
然后构造三个手机的品牌的子类:苹果、三星、OPPO,这三个子类继承上面的IPhone接口。
//创建三个类,分别是:苹果、三星、OPPO
public class Apple : IPhone
{
public void PrintPhone()
{
Console.WriteLine("苹果手机");
}
}
public class Samsung : IPhone
{
public void PrintPhone()
{
Console.WriteLine("三星手机");
}
}
public class OPPO : IPhone
{
public void PrintPhone()
{
Console.WriteLine("OPPO手机");
}
}
接着构造一个超级工厂的抽象类,用于具体的工厂的继承。
//构造一个抽象的超级工厂类,用于实现某个工厂
public abstract class AbstractFactory
{
public virtual IComputer CreateComputer(){return null};
public virtual IColor CreateColor(){return null};
}
接着构造电脑品牌和手机品牌的两个工厂,继承于AbstractFactory这个抽象类。
//构造“电脑品牌”的工厂类
public class ComputerFactort : AbstractFactory
{
public override IComputer CreateComputer(string name)
{
switch(name)
{
case "Lenovo":
return new Lenovo();
case "HP":
return new HP();
case "Dell":
return new Dell();
default:
return null;
}
}
}
//构造“手机品牌”的工厂类
public class PhoneFactory : AbstractFactory
{
public override IPhone CreateColor(string name)
{
switch(name)
{
case "Apple":
return new Apple();
case "Samsung":
return new Samsung();
case "OPPO":
return new OPPO();
default:
return null;
}
}
}
接着构造一个工厂生成器,通过对输入的需求信息分析,进行选择对应的工厂。
//创建一个工厂创造器/生成器类,通过传递品牌或颜色信息来获取工厂
public class FactoryProducer
{
public static AbstractFactory GetFactory(string choice)
{
switch(choice)
{
case "Computer":
return new ComputerFactort();
case "Phone":
return new PhoneFactory();
default:
return null;
}
}
}
最后,在主函数中,创建上述工厂,并获得对应的产品。
public class Program
{
public static void Main(string[] args)
{
//创建电脑工厂
AbstractFactory factoryComputer = FactoryProducer.GetFactory("Computer");
//创建联想电脑
IComputer computer1 = factoryComputer.CreateComputer("Lenovo");
computer1.PrintComputer();
//创建惠普电脑
IComputer computer2 = factoryComputer.CreateComputer("HP");
computer2.PrintComputer();
//创建戴尔电脑
IComputer computer3 = factoryComputer.CreateComputer("Dell");
computer3.PrintComputer();
//创建手机工厂
AbstractFactory factoryPhone = FactoryProducer.GetFactory("Phone");
//创建苹果手机
IPhone phone1 = factoryPhone.CreateColor("Apple");
phone1.PrintPhone();
//创建三星手机
IPhone phone2 = factoryPhone.CreateColor("Samsung");
phone2.PrintPhone();
//创建OPPO手机
IPhone phone3 = factoryPhone.CreateColor("OPPO");
phone3.PrintPhone();
Console.WriteLine("程序结束!");
Console.ReadKey();
}
}
结果是:
联想笔记本
惠普笔记本
戴尔笔记本
苹果手机
三星手机
OPPO手机
程序结束!
完整代码如下:
using System;
using System.Diagnostics;
//创建一个接口:电脑品牌
public interface IComputer
{
void PrintComputer();
}
//再创建一个接口:手机品牌
public interface IPhone
{
void PrintPhone();
}
//构造一个抽象的超级工厂类,用于实现某个工厂
public abstract class AbstractFactory
{
public abstract IComputer CreateComputer(string name);
public abstract IPhone CreateColor(string name);
}
public class Program
{
public static void Main(string[] args)
{
//创建电脑工厂
AbstractFactory factoryComputer = FactoryProducer.GetFactory("Computer");
//创建联想电脑
IComputer computer1 = factoryComputer.CreateComputer("Lenovo");
computer1.PrintComputer();
//创建惠普电脑
IComputer computer2 = factoryComputer.CreateComputer("HP");
computer2.PrintComputer();
//创建戴尔电脑
IComputer computer3 = factoryComputer.CreateComputer("Dell");
computer3.PrintComputer();
//创建手机工厂
AbstractFactory factoryPhone = FactoryProducer.GetFactory("Phone");
//创建苹果手机
IPhone phone1 = factoryPhone.CreateColor("Apple");
phone1.PrintPhone();
//创建三星手机
IPhone phone2 = factoryPhone.CreateColor("Samsung");
phone2.PrintPhone();
//创建OPPO手机
IPhone phone3 = factoryPhone.CreateColor("OPPO");
phone3.PrintPhone();
Console.WriteLine("程序结束!");
Console.ReadKey();
}
}
//在创建三个类,分别是:联想、惠普、戴尔
public class Lenovo : IComputer
{
public void PrintComputer()
{
Console.WriteLine("联想笔记本");
}
}
public class HP : IComputer
{
public void PrintComputer()
{
Console.WriteLine("惠普笔记本");
}
}
public class Dell : IComputer
{
public void PrintComputer()
{
Console.WriteLine("戴尔笔记本");
}
}
//创建三个类,分别是:苹果、三星、OPPO
public class Apple : IPhone
{
public void PrintPhone()
{
Console.WriteLine("苹果手机");
}
}
public class Samsung : IPhone
{
public void PrintPhone()
{
Console.WriteLine("三星手机");
}
}
public class OPPO : IPhone
{
public void PrintPhone()
{
Console.WriteLine("OPPO手机");
}
}
//构造“电脑品牌”的工厂类
public class ComputerFactort : AbstractFactory
{
public override IComputer CreateComputer(string name)
{
switch (name)
{
case "Lenovo":
return new Lenovo();
case "HP":
return new HP();
case "Dell":
return new Dell();
default:
return null;
}
}
public override IPhone CreateColor(string color)
{
return null;
}
}
//构造“手机品牌”的工厂类
public class PhoneFactory : AbstractFactory
{
public override IComputer CreateComputer(string name)
{
return null;
}
public override IPhone CreateColor(string name)
{
switch (name)
{
case "Apple":
return new Apple();
case "Samsung":
return new Samsung();
case "OPPO":
return new OPPO();
default:
return null;
}
}
}
//创建一个工厂创造器/生成器类,通过传递品牌或颜色信息来获取工厂
public class FactoryProducer
{
public static AbstractFactory GetFactory(string choice)
{
switch (choice)
{
case "Computer":
return new ComputerFactort();
case "Phone":
return new PhoneFactory();
default:
return null;
}
}
}
单例模式(创造型)
单例模式(Singleton Pattern)是最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
介绍
**意图:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。
**主要解决:**一个全局使用的类频繁地创建与销毁。
**何时使用:**当您想控制实例数目,节省系统资源的时候。
**如何解决:**判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
**关键代码:**构造函数是私有的。
应用实例:
- Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
**缺点:**没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
具体实现
单线程,懒汉式
具体代码如下:
public class Singleton
{
//创建一个单例类的对象,用于存放当前类的唯一实例
private static Singleton _instance;
//构造函数私有化,防止外界创建实例
private Singleton()
{
}
//利用属性的访问器来生成单例
public static Singleton Instance
{
get
{
//如果当前类的实例为空,则创建一个新的实例,否则返回当前实例
if (_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
}
不仅可以用访问器,还可以使用方法来进行访问实例化的对象。例如将上面代码改写为下面的:
public class Singleton
{
//创建一个单例类的对象,用于存放当前类的唯一实例
private static Singleton _instance;
//构造函数私有化,防止外界创建实例
private Singleton()
{
}
//获取当前对象的方法,修饰为静态方法,以便于直接能“类名.方法”进行调用,而不是使用“对象名.方法”
public static Singleton GetInstance()
{
//判断当前类的唯一实例是否为空
if (_instance == null)
{
//如果为空,则创建一个新的实例
_instance = new Singleton();
}
//如果不为空,则直接返回当前实例
return _instance;
}
}
线程安全,加锁
public class Singleton
{
// 定义一个静态变量来保存类的实例
private static Singleton uniqueInstance;
// 定义一个标识确保线程同步
private static readonly object locker = new object();
// 定义私有构造函数,使外界不能创建该类实例
private Singleton()
{
}
// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
public static Singleton GetInstance()
{
// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
lock (locker)
{
// 如果类的实例不存在则创建,否则直接返回
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
}
return uniqueInstance;
}
}
线程安全,双重锁定
上面代码对于每个线程都会对线程辅助对象locker加锁之后再判断实例是否存在,对于这个操作完全没有必要的,因为当第一个线程创建了该类的实例之后,后面的线程此时只需要直接判断(uniqueInstance==null)为假,此时完全没必要对线程辅助对象加锁之后再去判断,所以上面的实现方式增加了额外的开销,损失了性能。
为了改进上面实现方式的缺陷,我们只需要在lock语句前面加一句(uniqueInstance==null)的判断就可以避免锁所增加的额外开销,这种实现方式我们就叫它**双重锁定**
public class Singleton
{
// 定义一个静态变量来保存类的实例
private static Singleton uniqueInstance;
// 定义一个标识确保线程同步
private static readonly object locker = new object();
// 定义私有构造函数,使外界不能创建该类实例
private Singleton()
{
}
// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
public static Singleton GetInstance()
{
// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
// 双重锁定只需要一句判断就可以了
if (uniqueInstance == null)
{
lock (locker)
{
// 如果类的实例不存在则创建,否则直接返回
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
建造者模式(创造型)
建造者模式使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
介绍
**意图:**将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
**主要解决:**主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
**何时使用:**一些基本部件不会变,而其组合经常变化的时候。
**如何解决:**将变与不变分离开。
**关键代码:**建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。
**应用实例:**去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。
优点: 1、建造者独立,易扩展。 2、便于控制细节风险。
缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。
使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。
**注意事项:**与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
具体实现
情景如下:我们假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。
我们将创建一个表示食物条目(比如汉堡和冷饮)的 Item 接口和实现 Item 接口的实体类,以及一个表示食物包装的 Packing 接口和实现 Packing 接口的实体类,汉堡是包在纸盒中,冷饮是装在瓶子中。
首先,构造两个接口,分别是包装和食物,其中食物里面包含有包装的实现。代码如下:
//构造一个食物的接口,方便后面继承
public interface IFood
{
string GetFoodName();
IPacking GetFoodPacking();
float GetFoodPrice();
}
//构造一个包装的接口,方便后面继承
public interface IPacking
{
string GetPackingType();
}
然后构造两个具体的包装类,并继承自IPacking接口。具体代码如下:
//包装:纸盒
public class Wrapper : IPacking
{
public string GetPackingType()
{
return "Wrapper";
}
}
//包装:瓶子
public class Bottle : IPacking
{
public string GetPackingType()
{
return "Bottle";
}
}
由于食物有两大类:汉堡和饮料,因此,这里不直接构造具体食物的类,而是先构造两个抽象类:汉堡和冷饮,然后再构造它们的子类:素食汉堡、鸡肉汉堡、可口可乐、百事可乐。在抽象类中,会具体实现包装类型的选择,而不是在具体的某一食物类中实现选择。具体代码如下:
//食物:抽象汉堡
public abstract class Burger : IFood
{
public abstract string GetFoodName();
public IPacking GetFoodPacking()
{
return new Wrapper();
}
public abstract float GetFoodPrice();
}
//食物:抽象冷饮
public abstract class ColdDrink : IFood
{
public abstract string GetFoodName();
public IPacking GetFoodPacking()
{
return new Bottle();
}
public abstract float GetFoodPrice();
}
构造实现四种具体的食物类:素食汉堡类、鸡肉汉堡类、可口可乐类、百事可乐类。
//素食汉堡
public class VegBurger : Burger
{
public override string GetFoodName()
{
return "Veg Burger";
}
public override float GetFoodPrice()
{
return 25.0f;
}
}
//鸡肉汉堡
public class ChickenBurger : Burger
{
public override string GetFoodName()
{
return "Chicken Burger";
}
public override float GetFoodPrice()
{
return 50.5f;
}
}
//可口可乐
public class Coke : ColdDrink
{
public override string GetFoodName()
{
return "Coke";
}
public override float GetFoodPrice()
{
return 30.0f;
}
}
//百事可乐
public class Pepsi : ColdDrink
{
public override string GetFoodName()
{
return "Pepsi";
}
public override float GetFoodPrice()
{
return 35.0f;
}
}
接下来,构造一个菜单管理的类Meal,该类可以实现的功能有:
- 向菜单中添加具体的食物
- 计算出食物的价格
- 打印输出当前菜单的所有情况
//构造一个套餐类,用于组合食物
public class Meal
{
private List<IFood> _foodList = new List<IFood>(); //创建一个列表,用于存储当前所有食物
//向列表中添加食物的方法
public void AddFood(IFood food)
{
this._foodList.Add(food);
}
//获取当前套餐的总价
public float GetCost()
{
float cost = 0.0f;
foreach (var food in this._foodList)
{
cost += food.GetFoodPrice();
}
return cost;
}
//展示当前套餐的所有食物
public void ShowFood()
{
Console.WriteLine("你的食物如下:");
foreach (var food in this._foodList)
{
Console.Write("食物: {0} ;", food.GetFoodName());
Console.Write("包装: {0};", food.GetFoodPacking().GetPackingType());
Console.WriteLine("单价: {0};", food.GetFoodPrice());
}
Console.WriteLine("总价:{0}", GetCost());
}
}
上面的所有过程,均由售卖方执行,而作为顾客,只需要点餐就能获得对应的食物。这一部分在函数中进行,用于最终实现点餐的过程。具体代码如下;
internal class Program
{
private static void Main(string[] args)
{
Meal meal1 = new Meal(); //先生成一个菜单管理器实例
meal1.AddFood(new VegBurger()); //向菜单管理器中添加食物
meal1.AddFood(new Coke()); //向菜单管理器中添加食物
meal1.ShowFood();
Meal meal2 = new Meal();
meal2.AddFood(new ChickenBurger());
meal2.AddFood(new ChickenBurger());
meal2.AddFood(new Pepsi());
meal2.ShowFood();
Console.ReadKey();
}
}
最终,结果如下:
你的食物如下:
食物: Veg Burger ;包装: Wrapper;单价: 25;
食物: Coke ;包装: Bottle;单价: 30;
总价:55
你的食物如下:
食物: Chicken Burger ;包装: Wrapper;单价: 50.5;
食物: Chicken Burger ;包装: Wrapper;单价: 50.5;
食物: Pepsi ;包装: Bottle;单价: 35;
总价:136
完整代码如下
using System;
using System.Collections.Generic;
internal class Program
{
private static void Main(string[] args)
{
Meal meal1 = new Meal(); //先生成一个菜单管理器实例
meal1.AddFood(new VegBurger()); //向菜单管理器中添加食物
meal1.AddFood(new Coke()); //向菜单管理器中添加食物
meal1.ShowFood();
Meal meal2 = new Meal();
meal2.AddFood(new ChickenBurger());
meal2.AddFood(new ChickenBurger());
meal2.AddFood(new Pepsi());
meal2.ShowFood();
Console.ReadKey();
}
}
//构造一个食物的接口,方便后面继承
public interface IFood
{
string GetFoodName();
IPacking GetFoodPacking();
float GetFoodPrice();
}
//构造一个包装的接口,方便后面继承
public interface IPacking
{
string GetPackingType();
}
//构造一个套餐类,用于组合食物
public class Meal
{
private List<IFood> _foodList = new List<IFood>(); //创建一个列表,用于存储当前所有食物
//像列表中添加食物的方法
public void AddFood(IFood food)
{
this._foodList.Add(food);
}
//获取当前套餐的总价
public float GetCost()
{
float cost = 0.0f;
foreach (var food in this._foodList)
{
cost += food.GetFoodPrice();
}
return cost;
}
//展示当前套餐的所有食物
public void ShowFood()
{
Console.WriteLine("你的食物如下:");
foreach (var food in this._foodList)
{
Console.Write("食物: {0} ;", food.GetFoodName());
Console.Write("包装: {0};", food.GetFoodPacking().GetPackingType());
Console.WriteLine("单价: {0};", food.GetFoodPrice());
}
Console.WriteLine("总价:{0}", GetCost());
}
}
#region 构造两个具体的包装类,并继承自`IPacking`接口
//包装:纸盒
public class Wrapper : IPacking
{
public string GetPackingType()
{
return "Wrapper";
}
}
//包装:瓶子
public class Bottle : IPacking
{
public string GetPackingType()
{
return "Bottle";
}
}
#endregion 构造两个具体的包装类,并继承自`IPacking`接口
#region 构造两个抽象的食物类,并继承自`IFood`接口
//食物:抽象汉堡
public abstract class Burger : IFood
{
public abstract string GetFoodName();
public IPacking GetFoodPacking()
{
return new Wrapper();
}
public abstract float GetFoodPrice();
}
//食物:抽象冷饮
public abstract class ColdDrink : IFood
{
public abstract string GetFoodName();
public IPacking GetFoodPacking()
{
return new Bottle();
}
public abstract float GetFoodPrice();
}
#endregion 构造两个抽象的食物类,并继承自`IFood`接口
#region 具体的食物类
//素食汉堡
public class VegBurger : Burger
{
public override string GetFoodName()
{
return "Veg Burger";
}
public override float GetFoodPrice()
{
return 25.0f;
}
}
//鸡肉汉堡
public class ChickenBurger : Burger
{
public override string GetFoodName()
{
return "Chicken Burger";
}
public override float GetFoodPrice()
{
return 50.5f;
}
}
//可口可乐
public class Coke : ColdDrink
{
public override string GetFoodName()
{
return "Coke";
}
public override float GetFoodPrice()
{
return 30.0f;
}
}
//百事可乐
public class Pepsi : ColdDrink
{
public override string GetFoodName()
{
return "Pepsi";
}
public override float GetFoodPrice()
{
return 35.0f;
}
}
#endregion 具体的食物类
原型模式(创造型)
介绍
原型模式是一种创建型设计模式,它允许我们通过复制现有对象来创建新的对象,而不必从头开始编写代码。在原型模式中,我们使用一个现有的对象作为原型,并通过克隆该对象来创建新的对象。
- 意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
- 主要解决:在运行期建立和删除原型。
- 如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
- 关键代码:
- 实现克隆操作。
- 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
- 优点: 性能提高,逃避构造函数的约束。
- 缺点:
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
- 必须实现 Cloneable 接口。
- 逃避构造函数的约束。
具体实现
首先定义一个可以克隆的接口 ICloneable,代码如下:
public interface ICloneable
{
object Clone();
}
定义一个具体的原型类 Prototype ,代码如下:
// 定义一个具体的原型类
public class Prototype : ICloneable
{
public int Id { get; set; }
public Prototype(int id)
{
Id = id;
}
// 实现ICloneable接口的Clone方法
public object Clone()
{
return new Prototype(Id);
}
}
然后测试原型,代码如下:
// 测试原型模式
public class Program
{
public static void Main()
{
// 创建一个原型对象
var prototype = new Prototype(1);
// 克隆原型对象
var clone = (Prototype)prototype.Clone();
// 比较两个对象的Id属性
Console.WriteLine(prototype.Id == clone.Id); // 输出 true
}
}
在上面的示例中,我们首先定义了一个 ICloneable 接口,然后创建了一个具体的原型类 Prototype 。该类实现了 ICloneable 接口,并在 Clone() 方法中返回一个新的 Prototype 对象,该对象与当前对象具有相同的 Id 属性。
接下来,我们在 Main() 方法中创建了一个原型对象,然后克隆了它,最后比较了两个对象的 Id 属性。由于克隆对象是从原型对象复制而来的,因此它们的 Id 属性是相同的。
这就是原型模式的基本实现方式。通过使用 ICloneable 接口和 Clone() 方法,我们可以轻松地实现对象的克隆,从而避免了从头开始编写代码的麻烦。
适配器模式(结构型)
适配器模式是一种结构型设计模式,它允许我们将不兼容的对象包装在一个适配器中,以便它们可以相互协作。适配器模式通常用于将现有类的接口转换为另一个接口,以便它们可以在不修改原始代码的情况下协同工作。
适配器模式由三个主要组件组成:客户端、适配器和适配者。
- 客户端是使用适配器的对象,
- 适配器是将适配者转换为所需接口的对象,
- 适配者是要转换的对象。
适配器通过实现目标接口并包装适配者来实现适配。
介绍
**意图:**将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
**主要解决:**主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
**如何解决:**继承或依赖(推荐)。
**优点:**可以让任何两个没有关联的类一起运行。提高了类的复用。增加了类的透明度。灵活性好。
缺点: 过多地使用适配器,会让系统非常零乱,不易整体进行把握。
具体实现
我们有一个音频播放的接口 IMediaPlayer ,并且还有一个实现了上述接口的类 AudioPlayer 。默认情况下,AudioPlayer 只能播放 mp3 格式的音频文件。
现在,我们需要让 AudioPlayer (目标/客户端)支持 vlc 和 mp4 格式的文件。
因此,我们先定义一个新的音频播放的接口 抽象类 INewMediaPlayerNewMediaPlayer ,并且还有两个实现了上述接口抽象类的类: VlcPlayer 和 Mp4Player
我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 IMediaPlayer 接口的适配器类 MediaAdapter,并使用 NewMediaPlayer 的子对象(适配者)来播放所需的格式。
AudioPlayer(目标/客户端) 使用适配器类 MediaAdapter 传递所需的音频类型,不需要知道能播放所需格式音频的实际类。
首先定义出上述的接口IMediaPlayer 代码如下:
说明
更新日志
{% folding 更新日志 %}
{% timeline 更新日志,orange %}
- 添加原型模式
{% endtimeline %}
{% endfolding %}
💬 评论
评论系统接入中...