知识须要不断积累、总结和沉淀,思考和写做是成长的催化剂php
老生常谈建立型一、Singleton单例二、Factory Method工厂方法三、Abstract Factory抽象工厂四、builder建造者五、Prototype原型结构型一、Adapter 适配器二、Decorator 装饰三、Bridge 桥接四、Composite 组合五、Flyweight 享元六、Proxy 代理七、Facade外观行为型一、Interpreter 解释器二、Template Method 模板方法三、Chain of Responsibility 责任链四、Command 命令五、Iterator 迭代器六、Mediator 中介者七、Memento备忘录八、Observer 观察者九、State 状态十、Strategy 策略十一、Visitor 访问者谢谢nginx
GOF23种设计模式,想必都有听过或学习过,就是有四我的搞了一本书总结了在面向对象开发过程当中常见问题的解决方案。程序员
啥是模式?就是发现一种可识别的规律,好比色彩模式(简历模版也算)。模式也每每和抽象思惟有关,分析事物共性,抽取事物共同的部分来帮助人们认识规律。web
啥又是设计模式?就是对事物的从新定位整合来解决问题的一种模版一种套路。它是成熟的解决方案,解决相似的问题,这样咱们能够站在巨人的肩膀上,更加优雅的开发设计。正则表达式
(本手册尽量采用口语化表达,便于代入,很基础的细节会删减,了解过设计模式者服用更佳,查漏温故,浏览起来会比较轻松)算法
进入正题,设计模式按功能分为三大类数据库
建立型设计模式,顾名思义用于对象的建立。提供建立对象就是如何New一个对象
的通用方法。编程
单例模式使得一个类只有一个实例。一般像一些工具类,只须要初始化一个实例便可,不须要每次使用都去再实例化一个,这样能够解决些资源浪费。和静态类功能上相似,只不过单例是个对象实例。套路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;
}
}
这种称之为饿汉式单例,类一加载就实例化对象。前面的是懒汉式,在第一次引用时实例化。
简单工厂模式:什么是工厂?现实中就是生产东西的地方,程序里也是,就是有一个单独的地方(类)来负责建立各类实例
。
以经典的加减乘除计算程序为例,若是按照面向过程流程开发,大概步骤就是用户输入数字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();
抽象工厂模式和工厂方法相似。它是提供一个建立一系列对象的工厂接口,无需
指定它们具体的类。咱们看下结构图
若是说工厂方式模式只是提供单一产品建立接口,那抽象工厂就是让工厂抽象类拥有建立更多产品的能力,一个汽车生产线包括车架,底盘,轮毂等。抽象工厂的好处便于交换产品系列。如常见的数据库访问类。
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判断,但能够经过反射
配置去掉。
又叫生成器模式,将一个复杂产品的生成过程和它的表示分离
,这样给不一样的表示就能够建立出不一样的产品,就像去买咖啡,加不加糖,加几块,加不加奶,作出来就是不一样的咖啡,用户只须要指定我要冰美式就行。
有多个产品Builder构建类生成不一样的产品,用户Director指挥者指定一种产品就能够经过GetResult获取这款产品。这个比较好理解,产品建立过程内部完整高内聚,只对外暴露产品需求,须要什么产品,内部建立后给客户。
原型模式用于建立重复的对象而不须要知道建立的细节。通常在初始化的信息不发生变化时,克隆Copy能够动态的获取一个运行时的对象,并且效率相比构造函数会提升。原型模式克隆对象应该是因为类型本身完成的
。
在dotNET中提供了一个ICloneable接口(代替原型抽象类Prototype的功能)。只须要实现这个接口就能够完成原型模式。
class MyClass : ICloneable
{
public object Clone()
{
return this.MemberwiseClone();
}
}
注意:MemberwiseClone是浅拷贝,就是对于引用类型只复制了引用,而没有真的把引用类型堆地址复制一份,值类型倒没问题是真的内存上复制一份。因此这样若是生成一个拷贝类,则修改拷贝类中的引用类型,原类也会跟着变更。所以使用深拷贝老老实实在Clone方法里调用重载构造函数(直到没有引用类型成员)初始化拷贝类,而后将值类型变量赋值。
结构型设计模式关注的是对象与对象之间的关系,像拼积木,组合合并给程序提供更好的灵活和扩展
联想到电源的适配器,各个国家的电压不同,为了知足电器使用电压就须要适配器转换成额定电压,那么适配器模式的定义就是将一个类的接口转换成客户但愿的另一个接口,使得本来因为接口不兼容而不能一块儿工做的那些类能够一块儿工做。那么如何转换呢,核心就是适配器继承或依赖已有的对象,实现想要的目标接口
。
核心经过继承或依赖:
class Adapter : Target
{
private Adaptee adaptee = new Adaptee();
public override void Request()
{
adaptee.SpecificRequest();
}
}
主要解决现有类不能知足系统接口要求下,将现有类(能够是多个)包装到接口继承类下。这样接口继承类就能调用接口来实际调用现有类功能。虽然提升类的复用,增长灵活,但过多使用致使内部太透明,明明调用的是A接口,后台又实际调用的B接口。因此能够看出适配器是在项目服役时期作的灵活调整,属于临阵磨枪,设计期能重构不用最好。不便重构的状况下,适配器能够快速统一接口。
人靠衣服马靠鞍,装饰模式就像给人搭配服饰,能够根据不一样场合搭配不一样的风格,装饰模式能够动态的给一个对象添加额外的功能职责
(比直接用子类灵活一些),在不增长子类的状况下扩展类。
核心套路
: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抽象类,直接用装饰类继承被装饰类就不须要修改原类。有一个很差的地方就是若是装饰行为多了,请注意装饰顺序。
像路由器桥接同样,将二者解耦,经过一个桥接接口连通。桥接模式用于把抽象部分和实现部分分离解耦
,使得二者能够独立变化。
桥接模式中一个重要的概念就是用关联组合代替继承,从而下降类与类之间的耦合和臃肿。好比一个绘制图形的功能,有圆形、正方形等不一样的图形,它们还有不一样的颜色。用继承关系设计可能像下面这样。能够看见类比较多
还有一种方案就是对图形和颜色进行组合获得想要的颜色图形。
因此对于有多个维度变化的系统,采用第二种方案系统中的类个数会更小,扩展方便。
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();
若是系统可能有多个角度的分类,每一种角度均可能变化,就能够不用静态的继承链接,经过桥接模式可使它们在抽象层创建关联关系。
组合模式也就是部分整理关系
,将对象组合成树形结构以表示“部分总体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具备一致性,用于把一组类似的对象当作一个单一的对象。
核心
就是让树枝和叶子实现统一的接口,树枝内部组合该接口,而且含有内部属性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);
或者像公司体系结构这种当需求中体现部分总体层次结构,以及你但愿用户能够忽略组合对象与单个对象的不一样,统一地使用组合结构中的全部对象时,考虑使用组合模式。上面例子中客户端无需关心处理一个叶节点仍是枝节点组合组件,都用统一方式访问。
享元模式主要用于减小对象的数量,以减小内存占用和提供性能。享元模式尝试重用现有的同类对象
,若是未找到匹配的对象,则建立新对象。像棋盘中的棋子,不可能建立一整个全部的棋子对象。因此它主要解决当须要大量的对象,有可能对内存形成溢出,咱们能够把共同的部分抽象出来,这样若是有相同的业务请求,直接返回内存中已有的对象,不去重复建立了。
核心
就是用一个字典存储这些须要重复使用的对象。
经过上面结构图能够看见就是把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];
}
}
看上面代码,估计大都用过,设计模式好多都是这种普普统统,数据库链接池就是使用享元模式。总结下就是在系统中有大量类似对象或者须要缓冲池的场景,类似对象能够分离出内部和外部状态,内部状态就是固有的,不变的。外部状态能够经过外部调用传递进去。
像代购同样,咱们拜托他人帮咱们买某个东西。代理模式就是用一个类表明另外一个类的功能
。一般在不方便直接访问原始对象功能,或者须要对原始对象功能增长一些权限或其余控制时使用代理模式。
核心
就是增长代理层,让代理类和真实类都实现相同的接口(或抽象类),而后把真实类关联到代理类中。
上述结构图中的核心代码以下
class Proxy : Subject
{
RealSubject realSubject;
public override void Request()
{
if (realSubject == null)
{
realSubject = new RealSubject();
}
realSubject.Request();
}
}
代理模式其实就是在访问对象时引入必定程度的间接性,所以能够附加多种用途。
外观模式隐藏系统的复杂性,向客户端提供一个高层访问系统接口
。这样下降访问复杂系统的内部子系统时的复杂度,简化客户端交互,就像公司前台。
核心
就是将复杂系统里的类关联到外观类上。上面结构图就很清晰,经过外观类方法调用各个复杂系统类。
这种方式对老系统尤为有用,新增功能须要用到旧类,若是怕改坏了,就能够简单实用外观封装。还有就是设计时经典的三层架构也是外观的体现。
行为型设计模式关注对象和行为的分离。行为型比较多,由于程序逻辑都须要行为来触发。
解释器模式,给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。有点拗口,简单理解就是对于一些固定文法构建一个解释句子的解释器
。像正则表达式就是这样,查询匹配字符问题发生的频率很高,就把该问题各个实例表述为一个简单语言中的句子,解释句子来解决问题。比例谍战片中的加密电报,构建一个解释器对每一个文法解析。
核心
就是构建语法数,定义终结符与非终结符。实际利用的场景仍是比较少的,并且文法太复杂也很难维护。
实际客户端调用时像下面这样遍历解释器文法
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);
}
在模板方法中,一个抽象类公开定义一个算法的执行方式模板,而将一些步骤延迟到子类中
,子类能够按须要重写方法实现,但调用统一使用抽象类中定义的方式调用。
上面结构图中AbstractClass就是一个抽象类(抽象模板),定义并实现了一个模板方法。像下面这样
看见核心
就是将通用方法封装在超类中,因此在当子类方法中有一些不变的和可变的行为,能够将不变的行为经过模板方法搬到父类,这样子类就不须要重复这些不变的部分。是否是很简单,设计模式就是对面向对象编程,封装继承多态的灵活使用。
像公司内的请假流程,若是请很长时间,可能先有部门经理审批,部门经理说时间太长了,须要问下总经理。为请求建立了一个接受者对象的链,让请求者和接收者解耦。这种模式中,一般每一个接收者都包含对另外一个接收者的引用
,这样若是这个接收者对象不能处理该请求就传递给下一个接收者。
上述结构图中首先定义一个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进去。因此这种方式是不肯定哪一个接收者会处理请求,一般在拦截器中使用,须要对消息的处理过滤不少道时。像击鼓传花。
服务员,再炒个方便面,10个腰子,服务员就记下来交给炒菜师傅和烤串师傅,这就是命令模式。请求以命名的形式包裹在对象中
,并传给调用对象。调用对象寻找能够处理该命名的合适的对象,并把该命名传给相应的对象执行命名。而且支持可撤销操做,若是腰子好久没上来,能够通知服务员不要了。
核心
就是定义三个角色:一、received真正的命令执行对象二、Command三、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();
命令模式能够比较容易设计成命令队列,方便记录日志,支持撤销重作
迭代器提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。就是把元素之间的遍历游走的责任
交给迭代器,而不是集合对象自己,分离了集合对象的遍历行为,这样不暴露集合内部结构,又可以让外部访问集合内部数据。
核心
就是定义一个有Next,CurrentItem等方法的Iterator迭代接口,而后在子类具体迭代器ConcreateIterator类(能够实现多个迭代方式)中定义一个具体的汇集对象,而后遍历迭代器的Next方法遍历汇集对象内部数据。
在dotNET框架中已经准备好了相关接口,只须要去实现去就好。
IEumerator支持对非泛型集合的简单迭代接口,就和上面Iterator同样
public interface IEnumerator
{
object? Current { get; }
bool MoveNext();
void Reset();
}
经常使用的foeach遍历,编译器也是转化为了IEnumerator遍历。由于太经常使用,高级语言都进行了封装,本身也就不经常使用了。
中介者模式用一个中介对象来封装一系列的对象交互
。中介者使各对象不须要显示地相互引用,从而使其耦合松散,并且能够独立地改变它们之间的交互。就是提供一个中介类,处理不一样类之间的通讯。
面向抽象编程要求咱们类应该依赖于抽象
核心
就是定义中介者关联不一样的须要通讯的类,通讯类内部也须要关联具体中介者。通讯类发送信息时实际已经经过中介者来转发。
咱们看下核心的具体中介者和通讯者以及客户端调用实例
中介者模式将原来网状的结构变成星状结构,因此中介者可能会很庞大。通常应用在一组对象已定义良好可是复杂的方式进行通讯的场合。
备忘录模式保存一个对象的某个状态,以便在适当的时候恢复对象
。不少时候咱们须要记录一个对象的内部状态,这样可让用户去恢复。像玩魔塔游戏存进度同样。
结构图中Originator发起人负责建立一个备忘录,保存备忘录的内部数据。Memento备忘录包含要备份的数据,Caretaker管理者获得或设置备忘录。
若是保存所有信息,咱们可使用Clone实现,但若是只保存部分信息,就应该有一个独立的Memento备忘录类。
观察者模式也叫发布订阅模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生改变时,会通知全部观察者对象,是它们可以主动更新本身
。这也是很经常使用的,像委托事件就是这种模式
核心
是将全部通知者抽象类中用一个集合放全部观察者们。当通知者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();
}
}
}
当一个对象的改变须要通知其余对象时使用,通常这种通知可能经过异步来处理。
在状态模式中,类的行为时基于它的状态改变的。就是当一个对象的内部状态发生改变时改变它的行为
。主要解决当控制一个对象状态转换的条件表达式过于复杂时,把判断的逻辑迁移到表示不一样状态的一系列类中,达到给功能类瘦身的目的。
上面结构图中将状态单独从Context功能类中抽出来,先有一个抽象的状态类,而后具体不一样的状态能够继承实现不一样状态的Handle处理行为,最后把抽象状态聚合放到Context中去,最终调用Context的Request会根据不一样的状态触发不一样的Handle行为。
看下核心的代码。也能够在每个状态子类Handle行为中设置Context的下一个状态,下次调用Request就触发相应的状态行为。
核心
就是将状态和行为放入一个对象中。这么多种设计模式都有不少相像的地方,反正就是面向对象,分分合合,像先后端同样,各有优劣。这里就和命令模式处理的问题很像,均可以用做if分支语句的代替。经过状态模式能够很容易的增长新的状态,把状态行为封装起来,减轻了功能类。
策略模式定义一个类的行为算法能够在运行时更改, 把这些算法一个个封装起来
,并使它们能够相互替换。
和前面状态模式结构图无差异,就是用策略代替了状态,描述不一样的问题,解决方法同样。硬要找个不一样,大概就是在Context中,状态模式调用时会传递自己引用到各个子状态的以实现状态的改变,策略模式中不须要传递,只在初始化时指定策略。
核心
和前面同样,不一样策略实现同一个接口,聚合到Context类中。也是为了不多重判断,扩展性好,能够随意切换新增算法。像商场里的打折促销满减不一样活动,可能会有人想到,不一样策略的选择仍是须要判断语句,能够结合简单工厂进一步处理。追求极致那就反射喽。反射反射,程序员的快乐。
访问者模式应该是这里面最复杂的,大多数时候你并不须要使用它。由于访问者模式表示做用于某对象结构中各元素的操做,它使你在不改变元素的类的前提下定义新操做,而对象数据结构是在不变
的状况下。
不要怕这个结构图,一共就两个部分,首先提供访问者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);
}
}
}
在对象结构不多变更,须要在此对象结构上定义新的操做或者自己它就有不少不相关的操做时能够考虑使用此模式。
设计模式可能对于小白来讲高大上,其实你实际也常常会使用到,只是不知道那就是设计模式,“优秀”老是那么类似
。
不用刻意去追求设计模式,一个问题也可能有不少解决方案,往良好的设计去优化。本身用的多了,就知道什么场景使用什么设计,最终会与大神不谋而合的。
主要参考程杰的《大话设计模式》
拜了拜