知识需要不断积累、总结和沉淀,思考和写作是成长的催化剂
老生常谈
GOF23种设计模式,想必都有听过或学习过,就是有四个人搞了一本书总结了在面向对象开发过程中常见问题的解决方案。
啥是模式?就是发现一种可识别的规律,比如色彩模式(简历模版也算)。模式也往往和抽象思维有关,分析事物共性,抽取事物共同的部分来帮助人们认识规律。
啥又是设计模式?就是对事物的重新定位整合来解决问题的一种模版一种套路。它是成熟的解决方案,解决类似的问题,这样我们可以站在巨人的肩膀上,更加优雅的开发设计。
(本手册尽可能采用口语化表达,便于代入,很基础的细节会删减,了解过设计模式者服用更佳,查漏温故,浏览起来会比较轻松)
进入正题,设计模式按功能分为三大类
创建型
创建型设计模式,顾名思义用于对象的创建。提供创建对象就是如何New一个对象
的通用方法。
1、Singleton单例
单例模式使得一个类只有一个实例。通常像一些工具类,只需要初始化一个实例即可,不需要每次使用都去再实例化一个,这样可以解决些资源浪费。和静态类功能上类似,只不过单例是个对象实例。
套路1
:通常三步走,私有构造函数禁止外部构造,公开静态方法提供给外部使用,私有静态变量保证唯一(这样只是单线程模式下适用)。外部通过Singleton.GetInstance()获取唯一对象实例。
class Singleton
{
private static Singleton instance;
private Singleton()
{
}
public static Singleton GetSingleton()
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
套路2
:多线程下简单粗暴的方式就是lock加锁,让代码块只能有一个线程进入。
private static readonly object syncRoot = new object();
public static Singleton GetSingleton()
{
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
return instance;
}
再优化一下,上面这种方法会导致多个线程都需要等待,无论实例是否已经创建。我们想在实例已经创建的情况下多线程就不需要等待,直接返回就行。在lock外面加个判断null可以保证以后的多线程访问不用排队等待。这就是双重锁定。
public static Singleton GetSingleton()
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
套路3
:上面的已经完全实现了单例模式,但还有一个更简单的方式-静态初始化。CLR会在类加载时初始化静态字段,且是线程安全的,所以可以把类实例化放在静态字段上。
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton()
{
}
public static Singleton GetSingleton()
{
return instance;
}
}
这种称之为饿汉式单例,类一加载就实例化对象。前面的是懒汉式,在第一次引用时实例化。
2、Factory Method工厂方法
简单工厂模式:什么是工厂?现实中就是生产东西的地方,程序里也是,就是有一个单独的地方(类)来负责创建各种实例
。
以经典的加减乘除计算程序为例,如果按照面向过程流程开发,大概步骤就是用户输入数字1,然后输入运算符,输入数字2,然后根据运算符if或switch判断调用哪个数学方法,这种没有面向对象,都在一起,耦合太紧(代码就不贴了,都是过来人)
先看一下使用简单工厂模式之后的类图
把加减乘除各个运算操作封装成单独类,都继承自运算类,共用基类的成员变量AB,重写GetResult获取运算结果方法。封装之后,提供一个简单工厂类,通过传入不同的操作符,实例化不同的操作运算类。这样增加新的操作运算符时只需要修改工厂就行。(增加一个流水线)
简单工厂类核心:
public class OperationFactory
{
public static Operation CreateOperate(string operate)
{
Operation oper = null;
switch (operate)
{
case "+":
oper = new OperationAdd();
break;
case "-":
oper = new OperationSub();
break;
case "*":
oper = new OperationMul();
break;
case "/":
oper = new OperationDiv();
break;
}
return oper;
}
}
工厂方法模式:对简单工厂模式的进一步抽象。简单工厂是要求把逻辑放在在工厂类中,新增需要修改case分支,违背了开放封闭原则。工厂方法模式进一步在工厂上做文章,定义一个创建对象的接口,让子类决定实例化哪一个类。
工厂部分像下面这样
interface IFactory
{
Operation CreateOperation() ;
}
class AddFactory : IFactory
{
public Operation CreateOperation()
{
return new OperationAdd();
}
}
class SubFactory : IFactory
{
public Operation CreateOperation()
{
return new OperationSub();
}
}
客户端调用像下面这样。如果增加操作运算符,增加相应的运算类和工厂,不需要像简单工厂那样修改工厂类内的逻辑。
IFactory operFactory = new AddFactory();
Operation oper = operFactory.CreateOperation();
3、Abstract Factory抽象工厂
抽象工厂模式和工厂方法类似。它是提供一个创建一系列对象的工厂接口,无需
指定它们具体的类。我们看下结构图
如果说工厂方式模式只是提供单一产品创建接口,那抽象工厂就是让工厂抽象类拥有创建更多产品的能力,一个汽车生产线包括车架,底盘,轮毂等。抽象工厂的好处便于交换产品系列。如常见的数据库访问类。
interface IFactory
{
IUser CreateUser();
IDepartment CreateDepartment();
}
class SqlServerFactory : IFactory
{
public IUser CreateUser()
{
return new SqlserverUser();
}
public IDepartment CreateDepartment()
{
return new SqlserverDepartment();
}
}
class AccessFactory : IFactory
{
public IUser CreateUser()
{
return new AccessUser();
}
public IDepartment CreateDepartment()
{
return new AccessDepartment();
}
}
如果只是替换产品线比较容易,要是新增一个数据库访问表就要修改IFactory,SqlServerFactory ,AccessFactory。 这里可以用简单工厂改进。虽然简单工厂会多一些switch或if判断,但可以通过反射
配置去掉。
4、builder建造者
又叫生成器模式,将一个复杂产品的生成过程和它的表示分离
,这样给不同的表示就可以创建出不同的产品,就像去买咖啡,加不加糖,加几块,加不加奶,做出来就是不同的咖啡,用户只需要指定我要冰美式就行。
有多个产品Builder构建类生成不同的产品,用户Director指挥者指定一种产品就可以通过GetResult获取这款产品。这个比较好理解,产品创建过程内部完整高内聚,只对外暴露产品需求,需要什么产品,内部创建后给客户。
5、Prototype原型
原型模式用于创建重复的对象而不需要知道创建的细节。一般在初始化的信息不发生变化时,克隆Copy可以动态的获取一个运行时的对象,而且效率相比构造函数会提高。原型模式克隆对象应该是由于类型自己完成的
。
在dotNET中提供了一个ICloneable接口(代替原型抽象类Prototype的功能)。只需要实现这个接口就可以完成原型模式。
class MyClass : ICloneable
{
public object Clone()
{
return this.MemberwiseClone();
}
}
注意:MemberwiseClone是浅拷贝,就是对于引用类型只复制了引用,而没有真的把引用类型堆地址复制一份,值类型倒没问题是真的内存上复制一份。所以这样如果生成一个拷贝类,则修改拷贝类中的引用类型,原类也会跟着变动。因此使用深拷贝老老实实在Clone方法里调用重载构造函数(直到没有引用类型成员)初始化拷贝类,然后将值类型变量赋值。
结构型
结构型设计模式关注的是对象与对象之间的关系,像拼积木,组合合并给程序提供更好的灵活和扩展
1、Adapter 适配器
联想到电源的适配器,各个国家的电压不一样,为了满足电器使用电压就需要适配器转换成额定电压,那么适配器模式的定义就是将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。那么如何转换呢,核心就是适配器继承或依赖已有的对象,实现想要的目标接口
。
核心通过继承或依赖:
class Adapter : Target
{
private Adaptee adaptee = new Adaptee();
public override void Request()
{
adaptee.SpecificRequest();
}
}
主要解决现有类不能满足系统接口要求下,将现有类(可以是多个)包装到接口继承类下。这样接口继承类就能调用接口来实际调用现有类功能。虽然提高类的复用,增加灵活,但过多使用导致内部太透明,明明调用的是A接口,后台又实际调用的B接口。所以可以看出适配器是在项目服役时期做的灵活调整,属于临阵磨枪,设计期能重构不用最好。不便重构的情况下,适配器可以快速统一接口。
2、Decorator 装饰
人靠衣服马靠鞍,装饰模式就像给人搭配服饰,可以根据不同场合搭配不同的风格,装饰模式可以动态的给一个对象添加额外的功能职责
(比直接用子类灵活一些),在不增加子类的情况下扩展类。
核心套路
:Component 类充当抽象角色,不具体实现,ConcreteComponent子类代表实际的对象,现在要给这个对象添加一些新的功能。装饰类继承并且引用Component类,通过设置装饰类中的Component属性调用具体被装饰对象ConcreateComponent方法。可能有些绕,关键在于装饰类和具体被装饰对象都要继承相同的抽象Component 类。
看下代码也许会更容易理解
abstract class Component
{
public abstract void Operation();
}
class ConcreteComponent : Component
{
public override void Operation()
{
Console.WriteLine("具体被装饰对象的操作 ")
}
}
abstract class Decorator : Component
{
protected Component component;
public void SetComponent(Component component)
{
this.component = component;
}
public override void Operation()
{
if (component != null)
{
component.Operation();
}
}
}
class ConcreateDecoratorA : Decorator
{
private string addedState;
public override void Operation()
{
base.Operation();//执行原Component的功能
//可以添加需要装饰的东西,增加什么功能
addedState = "New State";
Console.WriteLine("具体装饰对象A的操作 ");
}
}
class ConcreateDecoratorB : Decorator
{
public override void Operation()
{
base.Operation();//执行原Component的功能
//可以添加需要装饰的东西,增加什么功能
AddedBehavior();
Console.WriteLine("具体装饰对象B的操作 ");
}
private void AddedBehavior()
{
}
}
客户端调用时像下面这样。
ConcreteComponent c = new ConcreteComponent();
ConcreateDecoratorA d1 = new ConcreateDecoratorA();
ConcreateDecoratorB d2 = new ConcreateDecoratorB();
d1.SetComponent(c);
d2.SetComponent(d1);
d2.Operation();
利用SetComponent将待装饰对象包装到装饰器中,然后调用装饰器的同名方法(因为都继承相同的抽象类)。像上面这样可以定义不同的装饰器,会按顺序逐个调用装饰的部分。
总之,装饰模式可以动态扩展
一个实现类的功能,当有实现类需要增加特殊行为时,可以用一个单独的装饰类去实现,(把被装饰类也就是实现类包装进去即可),像前面的也可以不用一个Component抽象类,直接用装饰类继承被装饰类就不需要修改原类。有一个不好的地方就是如果装饰行为多了,请注意装饰顺序。
3、Bridge 桥接
像路由器桥接一样,将两者解耦,通过一个桥接接口连通。桥接模式用于把抽象部分和实现部分分离解耦
,使得两者可以独立变化。
桥接模式中一个重要的概念就是用关联组合代替继承,从而降低类与类之间的耦合和臃肿。比如一个绘制图形的功能,有圆形、正方形等不同的图形,它们还有不同的颜色。用继承关系设计可能像下面这样。可以看见类比较多
还有一种方案就是对图形和颜色进行组合得到想要的颜色图形。
所以对于有多个维度变化的系统,采用第二种方案系统中的类个数会更小,扩展方便。
abstract class Implementor
{
public abstract void Operation();
}
class ConcreteImplementorA : Implementor
{
public override void Operation()
{
Console.WriteLine("具体实现A的方法执行");
}
}
class ConcreteImplementorB : Implementor
{
public override void Operation()
{
Console.WriteLine("具体实现B的方法执行");
}
}
class Abstraction
{
protected Implementor implementor;
public void SetImplementor(Implementor implementor)
this.implementor = implementor;
}
public virtual void Operation()
{
implementor.Operation();
}
}
class RefinedAbstraction : Abstraction
{
public override void Operation()
{
implementor.Operation();
}
}
客户端调用
Abstraction ab = new RefinedAbstraction();
ab.SetImplementor(new ConcreteImplementorA());
ab.Operation();
ab.SetImplementor(new ConcreteImplementorB());
ab.Operation();
如果系统可能有多个角度的分类,每一种角度都可能变化,就可以不用静态的继承连接,通过桥接模式可以使它们在抽象层建立关联关系。
4、Composite 组合
组合模式也就是部分整理关系
,将对象组合成树形结构以表示“部分整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性,用于把一组相似的对象当做一个单一的对象。
核心
就是让树枝和叶子实现统一的接口,树枝内部组合该接口,并且含有内部属性List放Component。
abstract class Component
{
protected string name;
public Component(string name)
{
this.name = name;
}
public abstract void Add(Component c);
public abstract void Remove(Component c);
public abstract void Display(int depth);
}
class Leaf : Component
{
public Leaf(string name) : base(name)
{ }
public override void Add(Component c)
{
Console.WriteLine("Cannot add to a leaf");
}
public override void Remove(Component c)
{
Console.WriteLine("Cannot remove to a leaf");
}
public override void Display(int depth)
{
Console.WriteLine(new string('-', depth) + name);
}
}
class Composite : Component
{
private List<Component> children = new List<Component> { };
public Composite(string name) : base(name)
{ }
public override void Add(Component c)
{
children.Add(c);
}
public override void Remove(Component c)
{
children.Remove(c);
}
public override void Display(int depth)
{
Console.WriteLine(new string('-', depth) + name);
foreach (Component component in children)
{
component.Display(depth + 2);
}
}
}
客户端使用像下面这样
Composite root = new Composite("root");
root.Add(new Leaf("Leaf A"));
root.Add(new Leaf("Leaf B"));
Composite comp = new Composite("Composite X");
comp.Add(new Leaf("Leaf XA"));
comp.Add(new Leaf("Leaf XB"));
root.Add(comp);
Composite comp2 = new Composite("Composite XY");
comp2.Add(new Leaf("Leaf XYA"));
comp2.Add(new Leaf("Leaf XYB"));
comp.Add(comp2);
root.Add(new Leaf("Leaf C"));
root.Display(1);
或者像公司体系结构这种当需求中体现部分整体层次结构,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,考虑使用组合模式。上面例子中客户端无需关心处理一个叶节点还是枝节点组合组件,都用统一方式访问。
5、Flyweight 享元
享元模式主要用于减少对象的数量,以减少内存占用和提供性能。享元模式尝试重用现有的同类对象
,如果未找到匹配的对象,则创建新对象。像棋盘中的棋子,不可能创建一整个所有的棋子对象。所以它主要解决当需要大量的对象,有可能对内存造成溢出,我们可以把共同的部分抽象出来,这样如果有相同的业务请求,直接返回内存中已有的对象,不去重复创建了。
核心
就是用一个字典存储这些需要重复使用的对象。
通过上面结构图可以看见就是把Flyweight享元类聚合到FlyweightFactory享元工厂中,享元工厂中用一个Hashtable存储这些具体的享元类。
class FlyweightFactory
{
private Hashtable flyweights = new Hashtable();
public FlyweightFactory()
{
flyweights.Add("X", new ConcreateFlyweight);
flyweights.Add("Y", new ConcreateFlyweight);
}
public Flyweight GetFlyweight(string key)
{
return (Flyweight)flyweights[key];
}
}
看上面代码,估计大都用过,设计模式好多都是这种普普通通,数据库连接池就是使用享元模式。总结下就是在系统中有大量相似对象或者需要缓冲池的场景,相似对象可以分离出内部和外部状态,内部状态就是固有的,不变的。外部状态可以通过外部调用传递进去。
6、Proxy 代理
像代购一样,我们拜托他人帮我们买某个东西。代理模式就是用一个类代表另一个类的功能
。通常在不方便直接访问原始对象功能,或者需要对原始对象功能增加一些权限或其他控制时使用代理模式。
核心
就是增加代理层,让代理类和真实类都实现相同的接口(或抽象类),然后把真实类关联到代理类中。
上述结构图中的核心代码如下
class Proxy : Subject
{
RealSubject realSubject;
public override void Request()
{
if (realSubject == null)
{
realSubject = new RealSubject();
}
realSubject.Request();
}
}
代理模式其实就是在访问对象时引入一定程度的间接性,因此可以附加多种用途。
7、Facade外观
外观模式隐藏系统的复杂性,向客户端提供一个高层访问系统接口
。这样降低访问复杂系统的内部子系统时的复杂度,简化客户端交互,就像公司前台。
核心
就是将复杂系统里的类关联到外观类上。上面结构图就很清晰,通过外观类方法调用各个复杂系统类。
这种方式对老系统尤其有用,新增功能需要用到旧类,如果怕改坏了,就可以简单实用外观封装。还有就是设计时经典的三层架构也是外观的体现。
行为型
行为型设计模式关注对象和行为的分离。行为型比较多,因为程序逻辑都需要行为来触发。
1、Interpreter 解释器
解释器模式,给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。有点拗口,简单理解就是对于一些固定文法构建一个解释句子的解释器
。像正则表达式就是这样,查询匹配字符问题发生的频率很高,就把该问题各个实例表述为一个简单语言中的句子,解释句子来解决问题。比例谍战片中的加密电报,构建一个解释器对每个文法解析。
核心
就是构建语法数,定义终结符与非终结符。实际利用的场景还是比较少的,而且文法太复杂也很难维护。
实际客户端调用时像下面这样遍历解释器文法
Context context = new Context();
List<AbstractExpression> list = new ArrayList<>();
list.Add(new TerminalExpression());
list.Add(new NonterminalExpression());
list.Add(new TerminalExpression());
list.Add(new TerminalExpression());
foreach (AbstractExpression abstractExpression in list)
{
abstractExpression.Interpret(context);
}
2、Template Method 模板方法
在模板方法中,一个抽象类公开定义一个算法的执行方式模板,而将一些步骤延迟到子类中
,子类可以按需要重写方法实现,但调用统一使用抽象类中定义的方式调用。
上面结构图中AbstractClass就是一个抽象类(抽象模板),定义并实现了一个模板方法。像下面这样
看见核心
就是将通用方法封装在超类中,所以在当子类方法中有一些不变的和可变的行为,可以将不变的行为通过模板方法搬到父类,这样子类就不需要重复这些不变的部分。是不是很简单,设计模式就是对面向对象编程,封装继承多态的灵活使用。
3、Chain of Responsibility 责任链
像公司内的请假流程,如果请很长时间,可能先有部门经理审批,部门经理说时间太长了,需要问下总经理。为请求创建了一个接受者对象的链,让请求者和接收者解耦。这种模式中,通常每个接收者都包含对另一个接收者的引用
,这样如果这个接收者对象不能处理该请求就传递给下一个接收者。
上述结构图中首先定义一个Handler处理请求抽象类,里面设置了下一个接收者和处理请求的抽象方法。然后再ConcreateHandler子类中实现具体的请求处理,如果处理不了,就转发给下一个接收者。
abstract class Handler
{
protected Handler successor;
public void SetSuccessor(Handler successor)
{
this.successor = successor;
}
public abstract void HandleRequest(int request);
}
class ConcreateHandler1 : Handler
{
public override void HandleRequest(int request)
{
if (request >= 0 && request < 10)
{
Console.WriteLine($"{this.GetType().Name}处理请求 {request}");
}
else if (successor != null)
{
successor.HandleRequest(request);
}
}
}
核心就是拦截类都实现统一接口
。Handler里聚合它自己,在HandlerRequest里判断是否能处理,如果不能就向下传递,要向谁传递就Set进去。所以这种方式是不确定哪个接收者会处理请求,通常在拦截器中使用,需要对消息的处理过滤很多道时。像击鼓传花。
4、Command 命令
服务员,再炒个方便面,10个腰子,服务员就记下来交给炒菜师傅和烤串师傅,这就是命令模式。请求以命名的形式包裹在对象中
,并传给调用对象。调用对象寻找可以处理该命名的合适的对象,并把该命名传给相应的对象执行命名。并且支持可撤销操作,如果腰子很久没上来,可以通知服务员不要了。
核心
就是定义三个角色:1、received真正的命令执行对象2、Command3、invoker使用命令对象的入口。最最终实现Invoker对象通过调用ExecuteCommand方法来调用具体命令Command的Excute方法,Excute方法里调用实际Received接收者动作。
abstract class Command
{
protected Receiver receiver;
public Command(Receiver receiver)
{
this.receiver = receiver;
}
abstract public void Execute();
}
class ConcreteCommand : Command
{
public ConcreteCommand(Receiver receiver) : base(receiver)
{ }
public override void Execute()
{
receiver.Action();
}
}
class Invoker
{
private Command command;
public void SetCommand(Command command)
{
this.command = command;
}
public void ExecuteCommand()
{
command.Execute();
}
}
class Receiver
{
public void Action()
{
Console.WriteLine("执行请求!");
}
}
调用时像下面这样
Receiver r = new Receiver();
Command c = new ConcreteCommand(r);
Invoker i = new Invoker();
i.SetCommand(c);
i.ExecuteCommand();
命令模式可以比较容易设计成命令队列,方便记录日志,支持撤销重做
5、Iterator 迭代器
迭代器提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。就是把元素之间的遍历游走的责任
交给迭代器,而不是集合对象本身,分离了集合对象的遍历行为,这样不暴露集合内部结构,又可让外部访问集合内部数据。
核心
就是定义一个有Next,CurrentItem等方法的Iterator迭代接口,然后在子类具体迭代器ConcreateIterator类(可以实现多个迭代方式)中定义一个具体的聚集对象,然后遍历迭代器的Next方法遍历聚集对象内部数据。
在dotNET框架中已经准备好了相关接口,只需要去实现去就好。
IEumerator支持对非泛型集合的简单迭代接口,就和上面Iterator一样
public interface IEnumerator
{
object? Current { get; }
bool MoveNext();
void Reset();
}
常用的foeach遍历,编译器也是转化为了IEnumerator遍历。因为太常用,高级语言都进行了封装,自己也就不常用了。
6、Mediator 中介者
中介者模式用一个中介对象来封装一系列的对象交互
。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。就是提供一个中介类,处理不同类之间的通信。
面向抽象编程要求我们类应该依赖于抽象
核心
就是定义中介者关联不同的需要通信的类,通信类内部也需要关联具体中介者。通信类发送信息时实际已经通过中介者来转发。
我们看下核心的具体中介者和通信者以及客户端调用实例
中介者模式将原来网状的结构变成星状结构,所以中介者可能会很庞大。一般应用在一组对象已定义良好但是复杂的方式进行通信的场合。
7、Memento备忘录
备忘录模式保存一个对象的某个状态,以便在适当的时候恢复对象
。很多时候我们需要记录一个对象的内部状态,这样可以让用户去恢复。像玩魔塔游戏存进度一样。
结构图中Originator发起人负责创建一个备忘录,保存备忘录的内部数据。Memento备忘录包含要备份的数据,Caretaker管理者得到或设置备忘录。
如果保存全部信息,我们可以使用Clone实现,但如果只保存部分信息,就应该有一个独立的Memento备忘录类。
8、Observer 观察者
观察者模式也叫发布订阅模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生改变时,会通知所有观察者对象,是它们能够主动更新自己
。这也是很常用的,像委托事件就是这种模式
核心
是将所有通知者抽象类中用一个集合放所有观察者们。当通知者ConcreateSubject发起Notify通知时,逐个调用集合中观察者进行Update更新。
abstract class Subject
{
private IList<Observer> observers = new List<Observer>();
// 增加观察者
public void Attach(Observer observer)
{
observers.Add(observer);
}
// 移除观察者
public void Detach(Observer observer)
{
observers.Remove(observer);
}
// 通知
public void Notify()
{
foreach (Observer o in observers)
{
o.Update();
}
}
}
当一个对象的改变需要通知其他对象时使用,一般这种通知可能通过异步来处理。
9、State 状态
在状态模式中,类的行为时基于它的状态改变的。就是当一个对象的内部状态发生改变时改变它的行为
。主要解决当控制一个对象状态转换的条件表达式过于复杂时,把判断的逻辑迁移到表示不同状态的一系列类中,达到给功能类瘦身的目的。
上面结构图中将状态单独从Context功能类中抽出来,先有一个抽象的状态类,然后具体不同的状态可以继承实现不同状态的Handle处理行为,最后把抽象状态聚合放到Context中去,最终调用Context的Request会根据不同的状态触发不同的Handle行为。
看下核心的代码。也可以在每一个状态子类Handle行为中设置Context的下一个状态,下次调用Request就触发相应的状态行为。
核心
就是将状态和行为放入一个对象中。这么多种设计模式都有很多相像的地方,反正就是面向对象,分分合合,像前后端一样,各有优劣。这里就和命令模式处理的问题很像,都可以用作if分支语句的代替。通过状态模式可以很容易的增加新的状态,把状态行为封装起来,减轻了功能类。
10、Strategy 策略
策略模式定义一个类的行为算法可以在运行时更改, 把这些算法一个个封装起来
,并使它们可以相互替换。
和前面状态模式结构图无差别,就是用策略代替了状态,描述不同的问题,解决方法一样。硬要找个不同,大概就是在Context中,状态模式调用时会传递本身引用到各个子状态的以实现状态的改变,策略模式中不需要传递,只在初始化时指定策略。
核心
和前面一样,不同策略实现同一个接口,聚合到Context类中。也是为了避免多重判断,扩展性好,可以随意切换新增算法。像商场里的打折促销满减不同活动,可能会有人想到,不同策略的选择还是需要判断语句,可以结合简单工厂进一步处理。追求极致那就反射喽。反射反射,程序员的快乐。
11、Visitor 访问者
访问者模式应该是这里面最复杂的,大多数时候你并不需要使用它。因为访问者模式表示作用于某对象结构中各元素的操作,它使你在不改变元素的类的前提下定义新操作,而对象数据结构是在不变
的情况下。
不要怕这个结构图,一共就两个部分,首先提供访问者Visitor类,它的子类就是具体的对元素的操作算法,然后ObjectStructure就是元素集合类,里面遍历每个元素执行元素相对应的算法。所以关键就在下面部分Element类中将Visitor作为输入参数。
核心
就是在被访问者的类中加一个对外提供接待访问者的接口,也就是在数据元素类中有一个方法接收访问者,将自身引用传入访问者,像下面示例这样
class ConcreateElementA : Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitorConcreateElementA(this);
}
}
class ObjectStructure
{
private List<Element> elements = new List<Element>();
public void Attach(Element element)
{
elements.Add(element);
}
public void Detach(Element element)
{
elements.Remove(element);
}
public void Accept(Visitor visitor)
{
foreach (Element e in elements)
{
e.Accept(visitor);
}
}
}
在对象结构很少变动,需要在此对象结构上定义新的操作或者本身它就有很多不相关的操作时可以考虑使用此模式。
谢谢
设计模式可能对于小白来说高大上,其实你实际也经常会使用到,只是不知道那就是设计模式,“优秀”总是那么相似
。
不用刻意去追求设计模式,一个问题也可能有很多解决方案,往良好的设计去优化。自己用的多了,就知道什么场景使用什么设计,最终会与大神不谋而合的。
主要参考程杰的《大话设计模式》
拜了拜