写在前面
设计模式是为了实现代码高内聚和低耦合,提高复用,最后开发出可维护、可扩展的程序,但滥用设计模式会造成程序臃肿、系统复杂度高的缺点,使用时根据情况调整。
通常设计模式都是结合使用的。
其实日常开发中,许多开发人员已经不经意的在使用设计模式,这些只是总结出来的一些普遍的模式,不要死板硬套,做到用要有理由。
设计原则
- 单一职责原则(SRP):类的职责要单一,要有明显的界限,界面就是界面,业务逻辑就是业务逻辑,可以完成一类或者一种任务,不要耦合过重。
- 开放-封闭原则(OCP):类、模块、函数等,扩展总是优于修改,修改可能会影响其他的地方,设计之初就需要考虑到可能有的变化,但不可能全部考虑到,在需要变化的时候,就需要重构,抽象隔离变化,而原先的内容尽量不做修改。
- 依赖倒转原则(DIP):针对抽象、接口编程,而不是针对过程、实现,高层模块和低层模块之间应该依赖抽象,是面向对象设计最重要的原则。具体指模块之间,不应该直接调用,而是通过抽象类或者接口。
- 里氏代换原则(LSP):子类必须能够替换掉父类。意思是子类必须包含和支持父类所有的内容,这样在使用父类型时,更换子类就可以扩展。
- 最少知识原则(LKP):类成员的访问权限应当尽量的低,不公开不需要让其他类知道的字段或方法。如果两个类没有直接的关系(聚合、组合、关联),那这两个类就不应该发生直接的调用,可以通过第三方转发调用。目的是弱化类之间的耦合,修改一个类,不会影响其他有关系的类,更有利于复用。
- 合成复用原则(CARP):尽量使用合成、聚合,尽量不要使用类继承。聚合指拥有(has-a),合成指是一部分(有相同的生命周期)。
创建型模式
关注怎么创建一个对象,才能降低耦合,而使用者不用知道具体的创建方法。
1. 单例模式
保证一个类仅有一个实例,提供一个全局访问点。
c#public class Singleton{ //静态变量存储 private static Singleton instance; //私有构造 private Singleton(){} //全局入口 public static Singleton GetInstance(){ if(instance==null) instance = new Singleton(); return instance; } }
2. 原型模式
原型模式即克隆,使用重写Clone方法,来实例化不同的对象。
在C#中使用
object.MemberwiseClone()
来进行浅克隆,只是复制了值和对象的引用,深克隆需要重新实例化成员对象。直接重写clone会返回object对象,需要转换,可以进行一些封装,返回想要的抽象类型。
工厂模式
3. 简单工厂模式
一个简单工厂,一个抽象产品(含有公共成员和方法),和具体的产品。工厂根据条件来决定创造哪一种具体的产品。
优点:对于相同性质的类,抽象比较简单,调用也简单,适合产品种类较少的情况。
缺点:使用子类的特殊属性需要转换类型,增加子类需要修改工厂类,工厂类无法继承。
c#public abstract Product{ public abstract void Show(); } public class ProductA:Product{ public override void Show(){ Console.WriteLine("Product A"); } } public clsss SimpleFactory{ public static Product CreateProduct(string type){ Product p=null; switch(type){ case "a": p=new ProductA(); break; default: Console.WriteLine("No Product"); break; } return p; } } //使用 SimpleFactory.CreateProduct("a").Show();
4. 工厂方法模式
定义一个用于创建对象的接口,让子类决定实例化哪一个类。
分为一个抽象工厂,一个抽象产品,多个具体工厂,多个具体产品。
抽象工厂提供创建产品的接口,调用者通过它访问具体工厂的创建方法,具体工厂实现这个接口。
抽象产品定义了产品的公共方法和属性。
优点:新增产品,只需新增一个工厂,不会影响到其他类,主要是解耦。
缺点:产品和工厂类会越来越多,抽象产品只能创造一个产品。
c#interface IFactory{ Product CreateProduct(); } class FactoryA:IFactory{ public Product CreateProduct()=>new ProductA(); } abstract class Product{ public void Show(); } class ProductA:Product{ public override void Show(){ Console.WriteLine("Product A"); } } //使用 new FactoryA().CreateProduct().Show();
5. 抽象工厂方法
提供一个创建一系列相关或相互依赖对象的接口,而不必指定它们具体的类。
是对工厂方法模式的补充,可以使抽象工厂生产多个具体的产品。
优点:使用一个工厂就可以生产所有一系列产品,同类产品可在不同工厂生产,可以保证同一时刻都是一个工厂的产品。
缺点:添加产品时工厂类都需要修改。
使用场景:一些类型使用方法相同,但细节不同,又分为几类,不同类放进一组,希望在某刻只用其中一组的时候。
c#interface AbstractFactory{ AbstractProductA CreateProductA(); AbstractProductB CreateProductB(); } interface AbstractProductA{ void Show(); } class ProductA1:AbstractProductA{ public void Show(){ Console.WriteLine("Product A1"); } } class ProductA2:AbstractProductA{ public void Show(){ Console.WriteLine("Product A2"); } } interface AbstractProductB{ void Show(); } class ProductB1:AbstractProductB{ public void Show(){ Console.WriteLine("Product B1"); } } class ProductB2:AbstractProductB{ public void Show(){ Console.WriteLine("Product B2"); } } class FactoryA{ public AbstractProductA CreateProductA()=>new ProductA1(); public AbstractProductB CreateProductB()=>new ProductB1(); } class FactoryB{ public AbstractProductA CreateProductA()=>new ProductA2(); public AbstractProductB CreateProductB()=>new ProductB2(); } //使用 new FactoryB().CreateProductA().Show();
6. 建造者模式
将一个复杂对象的构建与它的表示分离,这样构建过程可以创建不同的表示。
优点:可以有多个创造者,相互独立。调用方不需要知道具体的组成,和创造细节,修改不影响调用。
缺点:产品部件更改,所有创造者都要改。
应用场景:创建复杂的对象,而且这些对象有几种固定的模式,比如说创建两个人物,一个胖一个瘦,身高体型参数不同,却有固定的默认值,或者一个有头发一个没头发。总之,是同一类,但细节不同,组装在一起的。
c#//复杂装配逻辑 class Director{ public Director(Builder builder){ builder.BuildPartA(); builder.BuildPartB(); } } //抽象创建者 interface Builder{ void BuildPartA(); void BuildPartB();//需要确定有哪些部分需要创建 Product GetResult();//每种创建方式都需要返回一个Product } //具体产品,有各个部件 class Product{ List<string> parts=new List<string>(); internal void Add(string part)=>parts.add(part); public void Show(){ foreach(var part in parts) Console.WriteLine(part); } } //具体创建者,实现具体的创建细节 class Builder1:Builder{ private Product product=new Product(); public void BuildPartA()=>product.Add("头"); public void BuildPartB()=>product.Add("身体"); } //使用 var builder=new Builder1(); var direcotr=new Director(builder); var product=builder.GetResult(); product.Show();
结构型模式
结构型模式关注,类或对象之间的组合方式,形成更大的结构。
7. 装饰模式
动态的给对象添加一些额外的职责。装饰类包含具体对象,执行一些动作。
优点:动态添加的内容在装饰类中,与具体对象分离,而不必为了添加特殊的内容去修改对象,适合排列组合的行为。
缺点:会产生大量的装饰类。
应用场景:动态的启用和禁止对象的功能时,对一些功能进行组合变换使用时,对类添加职责又不能继承的情况。
c#//抽象对象,如果只有一个对象,这个可以没有,直接使用具体对象 abstract class Component{ public abstract void Do(); } //具体对象 class ComponentA:Component{ public override void Do(){ Console.WriteLine("Do nothing"); } } //抽象装饰,如果只有一个对象,这个可以没有,直接使用具体装饰 abstract class Decorator:Component{ protocted Component comp; public Decorator(Component comp){ this.comp=comp; } public override void Do(){ comp?.Do(); } } //具体装饰 class DecoratorA:Decorator{ private bool done; public override void Do(){ base.Do(); done=true; Console.WriteLine("Do Decorator A"); } } //使用 var comp=new ComponentA(); var da=new DecoratorA(comp); var aa=new DecoratorB(da);//可以套娃 aa.Do();
8. 代理模式
不直接访问一个对象,而是通过一种代理以控制对这个对象的访问。
优点:用以保护真实的对象,使目标对象和调用方分离,可以添加一些额外的内容。
缺点:多了个类,多了的话会增加系统复杂度,中间隔了个代理,处理速度会比直接调用慢。
应用场景:远程代理(使用代理方法,处理远程事务,是一个转发过程),虚拟代理(创建对象开销大的时候,比如加载图片,可以使用代理获取大小等,或者先下载缩略图),安全代理(一般指权限控制),智能指引(即添加一些额外的内容,处理一些其他相关的事)
c#//定义公共接口 abstract class Subject{ public abstract void Method(); } //实际调用的实体 class SubjectA:Subject{ public override void Method(){ Console.WriteLine("实际执行的方法。"); } } //代理 class Proxy:Subject{ SubjectA subjectA; public override void Method(){ //通常会有一些其他的代码 if(subjectA==null) subjectA=new subjectA(); subjectA.Method(); } } //使用 Proxy p=new Proxy(); p.Method();
9. 适配器模式
将一个类的接口,转换成客户希望的另一个接口。
解决接口不兼容的问题,比如说一些老的代码,不好修改,通过增加适配类,以现在的方式调用它。
和代理模式有点像,只解决固定的问题。
优点:统一调用方法,不必修改现有的类,可重用。
缺点:增加系统复杂性,降低代码可读性。
应用场景:统一接口,比如老的代码或者接入第三方时,转换成自己熟悉的接口。
c#//客户希望的接口 class Want{ public vitual void DoSomething(){ Console.WriteLine("客户希望的调用。"); } } //要适配的,不便修改的那个类 class Adaptee{ public void OldMethod(){ Console.WriteLine("旧的方法"); } } //适配器 class Adapter:Want{ private Adaptee adaptee=new Adaptee(); public override void DoSomething(){ adaptee.OldMethod(); } } //使用 Want target=new Adapter(); target.DoSomething();
10. 桥接模式
将抽象部分与它的实现部分分离,使它们都可以独立变化。
抽象类和其派生类,有各自的实现。
桥接模式是避免使用继承关系,而是使用聚合,因为继承关系在编译后便不能再更改。
当一个类内部有两种或多种变化维度时,或者使用继承太复杂时,使用桥接模式。
所谓维度,比如说支付方式,有支付宝和微信,这是一个维度,支付宝和微信又都可以刷脸、密码、指纹支付,这又是一个维度。如果传统的继承方式至少需要6个类,才能完全实现,如果支付方式再多,或者支付服务提供方再多,那就需要实现更多的类。
使用桥接模式,只需要分别实现,各种支付方式和支付服务提供商,通过聚合交互使用,大大简化。
一般情况下,抽象整体,实现具体方式,抽象和实现之间是包含的关系。
c#abstract class Implementor{ public abstract void Method(); } class ImplementA:Implementor{ public override void Method(){ Console.WriteLine("实现 A"); } } class Abstraction{ protected Implementor implementor; public void SetImplementor(Implementor implementor){ this.implementor=implementor; } public virtual void Method(){ } } class AbstractionA:Abstraction{ public override void Method(){ implementor.Method(); } } //使用 var ab=new AbstractionA(); ab.SetImplementor(new ImplementA()); ab.Method();
11. 外观模式
类似于API网关,为复杂的子系统提供一致的接口,不用关系子系统,只调用这些接口就行,更容易使用。
优点:客户端不用知道子系统,降低耦合,更改子系统不会影响调用方。
缺点:增加子系统,可能需要修改外观类和客户端。
应用场景:架构层与层之间,系统复杂需要提供简单的访问接口。
c#class SubSystem1{ public void Method(){ Console.WriteLine("Subsystem 1"); } } class SubSystem2{ public void Method(){ Console.WriteLine("Subsystem 2"); } } class Facade{ SubSystem1 s1= new SubSystem1(); SubSystem2 s2= new SubSystem2(); public void MethodA(){ s1.Method(); s2.Method(); } public void MethodB(){ s2.Method(); } } //使用 Facade fd=new Facade(); fd.MethodA();
12. 享元模式
运用共享技术有效地支持大量细粒度的对象。
优点:通过共享已经存在的对象,避免更多新对象的创建,提高利用率。
缺点:一些对象状态,随环境变化,不能共享,需要特殊处理,增加程序的复杂性。
应用场景:全部创建会有大量相同或相似的对象,同一刻只使用其中部分,是一种缓存的功能。
c#//享元抽象 abstract class Flyweight{ public abstract void Operation(int n); } //享元角色 class FlyweightA:Flyweight{ public override void Operation(int n){ Console.WriteLine("FlayweightA:"+n); } } //非享元角色,只是为了说明享元不是必须的 class UnsharedFlyweight:Flyweight{ public override void Operation(int n){ Console.WriteLine("UnsharedFlyweight:"+n); } } //享元工厂 class FlyweightFactory{ private Hashtable flyweights=new Hashtable(); public FlyweightFactory(){ flyweights.Add("1",new FlyweightA()); flyweights.Add("2",new FlyweightA()); } public Flyweight GetFlyweight(string key){ return (Flyweight)flyweights[key]; } } //使用 int n=30; var factory=new FlyweightFactory(); factory.GetFlyweight("1").Operation(--n); factory.GetFlyweight("2").Operation(--n); var uf=new UnsharedFlyweight(); uf.Operation(--n);
13. 组合模式
将对象组合成树形结构以表示‘部分-整体’的层次结构,使用户对单个对象和组合对象的使用具有一致性。
就是典型的树形结构。
c#//抽象节点 abstract class Component{ protected string name; public Cmponent(string name){this.name=name;} public abstract void Add(Component c); public abstract void Remove(Component c); public abstract void Method(int c); } //树枝节点 class Composite: Component{ List<Componect> Children=new List<Componect>(); public Leaf(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 Method(int c){ Console.WriteLine("This is a leaf."); } } //树叶节点 class Leaf: Component{ public Leaf(string name):base(name){} public override void Add(Component c){}//不能添加和移除节点 public override void Remove(Component c){} public override void Method(int c){ Console.WriteLine("This is a leaf."); } } //使用 Composite root=new Composite("root"); root.Add(new Leaf("Leaf 1")); var com=new Composite("Composite 1"); com.Add(new Leaf("Leaf 2-1")); root.Add(com);
行为型模式
关注程序运行时复杂的流程控制,主要是类和其对象间的协作。
14. 模板方法模式
定义一个操作中的算法骨架,而将一些步骤延迟到子类中,从而使子类不改变算法结构,就可以修改具体步骤。
适用于,调用逻辑固定,但细节实现不同的地方。
c#abstract class AbstractClass{ public abstract void Method1(); public abstract void Method2(); public void Excute(){//逻辑骨架 Method1(); Method2(); } } class ClassA:AbstractClass{ public override void Method1(){ Console.WriteLine("方法1"); } public override void Method1(){ Console.WriteLine("方法2"); } } //使用 var ac=new ClassA(); ac.Excute();
15. 策略模式
定义一系列算法,分别封装,让他们可以相互替换,不影响使用。
可以避免使用多重条件语句,算法可重用。
缺点是策略可能会有很多。
c#//抽象策略 abstract class Strategy{ public abstract void Method(); } class StrategyA:Strategy{ public override void Method(){ Console.WriteLine("Strategy A "); } } //上下文,具体调用方 class Context{ Strategy strategy; public Context(Strategy strategy){ this.strategy=strategy; } public void Execute(){ strategy.Method(); } } //使用 Context context=new Context(new StrategyA()); context.Execute();
16. 命令模式
将一个请求封装为一个对象(即命令)。分为抽象命令和具体命令,最终执行者和调用者。
封装了命令,就容易封装成一个命令队列,做撤销、恢复会变得容易,而且最终执行者决定是否执行。
c#abstract class Command{ protected Receiver receiver; public Command(Receiver recv){this.receiver=recv;} abstract public void Execute(); } class CommandA:Command{ ... public void Execute(){receviver.DoTask();} } class Invoker{ public Command Command{get;set;} public void ExecuteTask(){Command.Execute();} } class Receiver{ public void DoAction(){Console.WriteLine("Receiver 是真正执行者。");} } //使用 var recv=new Receiver(); var c=new CommandA(recv); var inv=new Invoker(); inv.Command=c; inv.ExecuteTask();
17. 职责链模式
使多个对象都有机会处理请求,从而避免请求的发送者和接收者间的耦合,将这些对象连城链,直到有一个对象处理了请求。
缺点:请求可能到了链尾还是没有处理。
典型的应用是RoutedEvent
c#abstract class Handler{ public Handler Successor{get;set;} public abstract void Handle(int reqid); } class Handler1:Handler{ public override void Handle(int reqid){ //判断是否处理 //否则,移交给下一位 Successor.Handle(); } } var h1=new Handler1(); var h2=new Handler2(); h1.Successor=h2; h1.Handle(1);
18. 状态模式
当一个对象内在状态改变时,允许改变其行为。
抽象一个状态类,有许多具体状态类,每个状态处理完,更改为下一个状态,有点像职责链模式,但是它是非集合、无序的,然后定义一个上下文管理当前的状态。
c#abstract class State{ public abstract void Handle(Context context); } class Context{ public State State{get;set;} public void NextState(){ State.Handle(this); } } class StateA{ public override void Handle(Context context){ context.State=new StateB();//切换到另一个状态 } } //使用 Context context=new Context(); context.State=new StateA();//初始化状态 context.NextState();
19. 观察者模式
也叫订阅-发布模式,c#语言的委托和事件,就是一种自带的实现。
就不再列出代码了,用一个集合保存观察者,有消息时逐个通知,调用观察者接口的行为方法。
20. 中介者模式
用一个中介对象来封装一系列的对象交互,使得对象间不用显示的引用,解耦,还可以独立改变它们的交互。
中介需要知道所有的其他人,其他人互相不知道,都知道中介,通过中介转发消息。
c#//抽象中介 abstract class Mediator{ public abstract void Send(string message,Member member); } //抽象人员 abstract class Member{ protected Mediator mediator; public Member(Mediator m){mediator=m;} } //具体中介 class MediatorA:Mediator{ public MemberA MemberA{get;set;} public MemberB MemberB{get;set;} public override void Send(string message,Member member){ if(member==MemberA)//A 找 B memberB.Recvice(message); else memberA.Recvice(message); } } class MemberA:Member{ public MemberA(Mediator m):base(m){} public void Send(string message){ m.Send(message,this); } public void Recvice(string message){ Console.WriteLine("A got message:"+message); } } //使用 MediatorA ma=new MediatorA(); MemberA a=new MemberA(ma); MemberB b=new MemberB(ma); ma.MemberA=a; ma.MemberB=b; a.Send("hello");
21. 迭代器模式
提供一种方法顺序的访问一个聚合对象中的各个元素,而不保了对象的内部。
这个在C# 中实现
IEnumerable
接口即可,是一种仿链表结构,访问过就指到下一个元素。
22. 访问者模式
表示一个作用于某对象结构中的各元素的操作,可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
适合数据结构相对稳定,但是对数据的操作比较复杂的系统,只需增加访问者就可以增加新的操作。
缺点:增加新的数据结构困难,需要对访问者都做更改。
c#//抽象访问者 abstract class Visitor{ public abstract void VisitElementA(ElementA a);//依赖具体元素,而非抽象 public abstract void VisitElementB(ElementB b); } //具体访问者 class VisitorA:Visitor{ public override void VisitElementA(ElementA a){ Console.WriteLine("a 访问 e a"); } public override void VisitElementB(ElementABa){ Console.WriteLine("a 访问 e b"); } } //抽象访问对象 abstract class Element{ public abstract void Accept(Visitor visitor); } //具体访问对象 class ElementA:Element{ public override void Accept(Visitor visitor){ visitor.VisitElementA(this);//派发 } } //一个上层接口,管理元素,和允许访问者访问 class ObjectStructure{ private List<Element> elements=new List<Element>(); public void Attach(Element element)=>elements.Add(element);//添加支持的元素 //删除 remove public void Accept(visitor visitor){ foreach(Element e in elements) e.Accept(visitor); } } //使用 var context=new ObjectStructure(); context.Attach(new ElementA()); context.Attach(new ElementB()); var v1=new Visitor1(); context.Accept(v1);
23. 备忘录模式
在不破坏封装性的前提下,捕获一个对象的内部状态,并在
对象之外
保存这个状态,使得状态可以恢复。
c#//发起人 class Originator{ public string State{get;set;} public Memeto CreateMemento()=new Memento(state);//创建新的备忘录,保存State public void Restore(Memento m){ State=m.State;//恢复指定的状态 } } //备忘录 class Memento{ public string State{get;} public Memento(string state){this.State=state;}//保存状态 } //备忘录管理者,用于保存状态 class Caretaker{ public Memento Saved{get;set;} } //使用 Caretaker ct=new Caretaker(); Originator o=new Originator(); o.State="State1"; ct.Saved=o.CreateMemento(); o.State="State2"; o.Restore(ct.Saved);
24. 解释器模式
给分析对象定义一个语言,并定义语言的文法表示,再设计一个解释器,解读语言中的内容。
XAML就是典型的解释型语言,会转换成C#语言。再比如,Markdown语法转HTML,也是一种解释器。
解释器模式,是抽象了表达式(或语法),每一个表达式,都可能对输入有一定的处理,逐字或者逐句,进行翻译、转换。
c#//抽象表达式 abstract class AbstractExpression{ public abstract void Interpet(Context context); } //终结符表达式 class TerminalExpression:AbstractExpression{ public override void Interpet(Context context){ Console.WriteLine("终结符"); } } //非终结符表达式 class NoneTerminalExpression:AbstractExpression{ public override void Interpet(Context context){ Console.WriteLine("非终结符"); } } //保存输入和输出 class Context{ public string Input{get;set;} public string Output{get;set;} } Context context=new Context(); var list=new List<AbstractExpression>(); list.Add(new NoneTerminalExpression()); list.Add(new TerminalExpression()); foreach(var exp in list){ exp.Interpret(context); }