[其它] 设计模式总结

GoF提出的设计模式一共有23种,这篇文章将简单地介绍这23种模式,以及一些编程的原则,可能会带一点个人理解。

设计原则

首先不得不提的就是一些OO的设计原则:
1.封装变化
2.合成复用原则(Composite Reuse Principle, CRP) : 多用组合,少用继承
3.针对接口编程,不针对实现编程
4.为交互对象之间的松耦合设计而努力
5.开闭原则(Open-Closed Principle, OCP) : 类应该对扩展开放,对修改关闭
6.依赖倒置原则(Dependence Inversion Principle) : 依赖抽象,不要依赖具体类
7.迪米特法则(Law of Demeter) : 又叫最少知识原则(Least Knowledge Principle 简写LKP),只和朋友交谈
8.好莱坞法则(Hollywood principle) : don‘t call us, we‘ll call you. 别找我,我会找你(抽象不应该依赖细节,细节应该依赖于抽象,不要让低层依赖高层)
9.单一职责原则(SRP:Single responsibility principle) : 类应该只有一个改变的理由(一个类最好只负责一件事)
10.接口隔离原则(Interface Segregation Principle, ISP) : 使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口
11.里氏代换原则(Liskov Substitution Principle, LSP) : 任何基类可以出现的地方,子类一定可以出现

然后有一些能够帮助我们的代码变得更干净的建议(可能也参杂了点儿自己的看法,不见得正确):
1.变量不可以持有具体类的引用
这样的话我们可以尽可能地保持抽象,尽可能地针对接口而不是具体实现来编程。如果要new的话,就会有具体类的引用,可以用工厂模式来避免。
2.不要让类派生自具体类
如果派生自具体类,就会依赖具体类,最好派生自抽象
3.不要覆盖基类中已经实现的方法
继承的本来目的就是为了在子类中共享,如果去覆盖它,就违反了继承的初衷了,虽然不一定要严格遵守,比如100个子类有两三个覆盖了父类,那么可以不用过于纠结,但是如果有二三十个都覆盖了父类的方法,那么其实这里用继承已经不是一种优雅的做法了,这时就可以考虑用组合代替继承
4.在一个对象的方法内,我们只应该调用(1)该对象本身(2)被当作方法的参数传递进来的对象(3)此方法创建或者实例化的对象(4)对象的任何组件的方法
这个其实就是迪米特法则,我们如果调用了陌生人的方法,那这个陌生人如果之后有变动,这一段代码也会受影响,为了解除这个耦合,我们应该让朋友来处理这件事。
5.能设置成private的变量全部设成private,让外界通过一个public方法来访问
如果外部能够直接接触这个类里的一些成员,那其实是做了自己不该做的事情,一个类的成员应该尽量由这个类自己管,别人管的越多,耦合越严重。

当然这些原则是不用严格遵守的,否则可能给我们带来更多的麻烦,包括下面的设计模式也是这样,应该在需要使用的时候使用,不应该强行用。


23种设计模式


策略模式

策略模式是把一个类中可能变化的部分封装出去,这个封装出去的部分称为策略,策略有一个接口,然后有一些具体的类实现这个接口,原来的context类引用这个策略接口,实际运行的时候我们把具体的类绑到context上,也可以在运行时改变这个类。例如有的游戏中有副职业,比如铁匠、烹饪、制作药剂之类的,主职业是通过继承来实现的,副职业如果不用多继承的话,更好的做法就是用策略模式,把副职业封装出去。

在这里插入图片描述


观察者模式

观察者模式分Subject和Observer两个接口,subject可以注册或者移除一个observer,然后可以在某些特定的时候(一般是发生改变的时候)通知所有的observer,即调用所有observer的update方法,这个模式经常和迭代器模式一起用。
在这里插入图片描述


装饰者模式

装饰者模式有一个component接口,然后被装饰者和装饰者都扩展自这个接口,装饰者内声明了一个被装饰者component,也就是说这种模式可以一层一层地包装一个类,在装饰时加入新的行为,用户只用component这个接口,和具体的装饰者和被装饰者解耦,甚至可能完全不知道自己在和被装饰的类打交道。装饰者模式和策略模式一样,都可以在运行时引入新的行为,装饰者模式是通过装饰来加入新的行为。

在这里插入图片描述


简单工厂、工厂方法模式、抽象工厂模式

简单工厂不是一种设计模式,而是一种编程习惯,简单工厂就是在我们要用new创建一些实例的地方,把这些new的过程封装到一个单独的Factory类的一个方法里,好处是可以统一地管理。

工厂方法模式图如下,这种做法是将要创建对象的类和具体创建对象的代码解耦,具体做法就是定义一个创建对象的抽象方法,让子类来实现具体的创建对象的方法,而创建者只调用抽象的方法,将类的实例化延迟到具体的子类。用这种方法创建对象也应该返回接口,而在creator中只针对这个接口编程。
在这里插入图片描述抽象工厂模式如下图,这种模式首先有一个工厂接口,然后有 很多个 产品接口,工厂接口里定义了创建每个产品接口的方法(返回每个对应的产品接口),然后抽象工厂的子类实现具体的这些方法,创建具体的产品子类后返回。这个模式的功能也是将client和具体的要创建的产品类解耦,和工厂方法不同的是,和具体的产品类耦合的不再是客户类的子类,而是具体的工厂类,而client和具体的工厂类是耦合的。
然后这里应该这样理解抽象工厂,之所以有多个具体的工厂、多个产品,我们可以假设,如果只有一种产品而且确定以后不会增加,用抽象工厂是没有意义的,因为client总是要和一种具体的工厂类耦合,如果只有一种产品的话不如干脆让client和这种具体的产品耦合了,抽象工厂的意义之一就是将多种具体的产品类的耦合变成一种具体的工厂类的耦合。然后如果确定只有一个具体的工厂且不会增加,那么抽象工厂模式就退化成简单工厂了,也没必要再使用抽象的工厂接口,这里之所以要这个接口,是为了应付多种产品的多种组合的可能性。总而言之,就是抽象工厂模式适合用在多种产品的多种组合的情况下,将client和具体的产品类的组合解耦。
例子:副本刷怪,一个副本有四个难度,每个难度有刷怪列表,就可以用有简单、普通、困难、精英四个工厂的抽象工厂模式来创建怪物实例。
在这里插入图片描述
如果用抽象工厂模式的话,最好确定产品类不会变化,因为每次要加入新的产品,就要修改抽象工厂的接口,这就会造成相当麻烦的后果,要多种产品组合,其实工厂方法也可以用一个方法创建多个实例的组合,但是因为返回只能返回一个,可以改成传引用,但是要加新的产品的话也得在工厂方法的签名和每个子类里面也加上。所以其实这两种工厂模式很像,只是一个封装成当前类下的方法,一个封装成另一个工厂类里的方法。


单件模式

单件模式是将一个类的构造方法声明成private的,在类里面声明一个对自己的引用,然后用一个public的getInstance方法来获取对象,如果对自己的引用为null就创建一个新的,不为null就返回这个引用。可以运行时再创建实例(延迟实例化),也可以在程序开始的时候就创建实例(急切实例化)。
如果涉及到多线程,必须要小心,单例对象可能会被实例化多次,如果单例对象要维护一些状态成员的话这会导致一些错误,一定要记得在创建时加线程锁。
有的时候,如果单例包含的东西很简单,可以考虑用静态方法及成员代替。
在这里插入图片描述


命令模式

命令模式是对“调用”(method invocation)的封装,这种模式将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,可以支持撤销等操作。
命令模式可以将client和receiver解耦,本来client是直接调用receiver的action方法,使用命令模式后,我们可以建立一个command接口,让command的具体类来调用receiver中的具体的方法,也可以是一连串方法,当然,我们也可以实时地改变invoker持有的命,即invoker有个setCommand方法。实际使用时,client负责创建一个ConcreteCommand,设置接收者,然后让invoker来调用command的execute方法,来完成对接收者的方法的调用。
在这里插入图片描述


适配器模式

适配器就是一个透明的中间键,将一个接口适配到另一个接口,adapter要实现被适配的接口,然后保存一个要适配的接口的引用,然后将每个被适配接口的方法用这个要适配的对象的方法来实现,客户在用这个适配好的接口的时候用的是被适配的接口,所以可以完全不知晓适配器的存在。
在这里插入图片描述

门面模式(外观模式)

有的时候要调用太多对象的太多方法,我们可以通过一个单独的外观类来完成这个访问,所有的事情交给这个facade类处理,facade类存所有用到的类的引用,然后按需求调用这些类的方法。

在这里插入图片描述

模板方法模式

模板方法就是我们说的继承,父类中实现一些共有的方法,然后设下一些抽象的方法让子类去实现,还可以设置一些钩子方法(hook),即提供默认实现的方法,但是子类可以去覆盖。
在这里插入图片描述

迭代器模式

迭代器模式有一个iterator接口, 其中声明了一些迭代需要的方法,然后再具体的迭代器类中实现这些方法,这个具体的迭代器类里有对应聚类的引用,然后在聚类接口中声明一个CreateIterator方法,并在具体的聚类中实现它,实现的过程就是创建一个对应的具体的迭代器然后在迭代器中保存这个聚类的引用。之后我们就可以用迭代器接口来做遍历等操作了。
在这里插入图片描述


组合模式

组合模式定义了一个组件接口,然后下面有具体的叶节点类和组合类,叶节点没有一些get child之类的操作可以选择返回null或者抛出错误,然后对client来讲只需要把这些一致地当作是component就行了,遍历等操作一般都是递归地实现的。一般来说网站的界面等树状结构的就可以用组合模式。
在这里插入图片描述

状态模式

状态模式定义了一个状态接口,接口里面声明了客户context的所有可能的操作,实现的具体状态类中要对这些操作进行具体的处理,具体类中还要保存对context的引用,并且调用context的setState方法来改变context当前所处的状态。context中存一个状态接口的引用,保存当前状态。
所有适合用状态机描述的地方都可以考虑用状态模式。
在这里插入图片描述


代理模式

代理模式是指我们在访问一个对象的时候不直接访问,而是通过一个代理来访问,访问的时候我们不知道我们访问的是代理还是对象本身,也就是说代理的存在可以是透明的。这种模式里首先我们需要一个subject接口,真正的subject和代理都实现了这个接口,但是RealSubject中的方法才是做实事的方法,代理中的方法只是对RealSubject中的方法进行调用,在这个基础上还可以加上一些代理自己的功能扩展,比如访问控制等。
这个模式可以实现将被访问的subject和访问者之间的解耦,还可以在中间的代理层上引入新的功能,比如加入访问控制的话就可以称之为是保护代理。
在这里插入图片描述


桥梁模式(桥接模式)

桥梁模式可以将抽象化和实现化解耦,其中抽象和实现可以是两个层次,比如毛笔,抽象化就是笔的粗细,实现化就是笔的颜色。在桥梁模式中,我们声明两个接口,一个abstraction接口和一个implementor接口,abstraction中保留对implementor接口的引用,然后两个接口分别被具体类实现,就实现了两个层次的解耦,而两个层次的关系就可以称为是桥梁。
这个模式可以理解成一种策略模式,也就是把一个层次的东西分出去,并保留对其抽象的引用。
在这里插入图片描述


建造者模式(生成器模式)

建造者模式适合用在构造比较复杂的对象的情景中,首先我们有一个builder接口,声明了构造零件的方法,然后具体的建造者来实现这些构造的方法,然后将结果保存到builder对产品的引用中,然后不是由builder来调用创建的过程,而是由director,director依次调用builder->BuildPart()然后返回builder中的产品引用。这样的好处就是我们可以用一个director指导多个生产线,比如不同的builder中制造出来的零件可能不同。
在这里插入图片描述


责任链模式

责任链是一个链,比如邮件处理,把到来的任务在一个链中按顺序传递,如果一个handler处理不了,就传给下一个handler,这样迭代地处理到来的任务,可以用来比如邮件过滤等地程序中。
在这里插入图片描述


享元模式(绳量模式)

享元模式可以理解成“共享单元”,有的时候一个应用中我们有大量同一个类型的实例,例如场景管理、池之类的应用就适合用这种模式。首先我们有一个flyweight超类或者接口,然后实际的flyweight类或者不共享的flyweight类继承或实现这个flyweight超类或者接口,然后flyweight中包含了内部属性和外部属性,外部属性指的是受外部影响改变的,不共享的属性,内部属性是指保存在享元内部的,共享的属性,然后我们需要一个FlyweightFactory类来创建flyweight,一般来说我们先查找现有的池,如果存在了同样key的享元就直接返回,不存在就创建一个,加入池中再返回这个flyweight。client通过调用FlyweightFactory的方法来创建或者获取flyweight。
在这里插入图片描述


解释器模式

解释器模式适合用在我们需要实现一个简单的语言的时候,如果简单比效率更重要,就可以用这种模式,如果语法规则足够复杂的时候,使用解析器/编译器的产生器可能更合适。
在这里插入图片描述


中介者模式

中介者模式适合用在对象之间关系非常复杂的时候,许多对象之间要直接交流,就会导致互相耦合程度非常高,这时候就可以考虑加入一个中介者,所有的对象只和中介者打交道,将对象之间彼此解耦。
在这里插入图片描述


备忘录模式

备忘录模式就是用一个类来负责保存当前的状态,游戏中的存档就是用这种模式。
在这里插入图片描述


原型模式

原型模式适合用在我们需要动态地创建许多复杂的实例的时候,我们可以考虑用原型模式,比如游戏中创建一个怪物的时候可以考虑从一个现有的怪物实例上面复制得到。首先我们要有一个prototype接口或者超类,然后实际的类实现超类或者接口里的clone方法,这个方法返回一个相同类型的对象。这里要提到两个概念,浅克隆和深克隆,浅克隆是创建一个对象然后把要克隆的对象的所有值直接赋给克隆出来的对象,深克隆就是把被克隆的对象里的所有对象全部都创建一个新的。
在这里插入图片描述

访问者模式

访问者模式就是有的时候客户可能需要访问很多类的很多成员或者方法,这个时候我们可以考虑把客户和被访问的对象解耦,我们可以创建一个访问者,让访问者来完成访问工作,而客户只与访问者打交道。
在这里插入图片描述


复合模式 MVC

许多设计模式可以一起使用,或者相互配合达到更好的效果,MVC就是一种非常成功的复合模式。
MVC全名是Model View Controller,这里借用一下百科的图片
在这里插入图片描述

控制器取得输入,然后将用户的输入传递给模型,模型管理所有的状态、数据,然后将数据发送给视图,视图根据模型传来的数据发生改变。
MVC包含了许多设计模式的思想,虽然可能不完全是严谨地符合。首先视图可以看作一个对象,而控制器可以看作策略,这里使用了策略模式。然后,每当模型发生改变的时候,会通知视图发生改变,这里用了观察者模式。然后一般视图是用嵌套的方法实现的,也就是用了组合模式。

猜你喜欢

转载自blog.csdn.net/weixin_43675955/article/details/88268401