23种设计模式全解析

总体来说设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

其实还有两类:并发型模式和线程池模式。

设计模式的六大原则(化简它)

总原则:开闭原则(Open Close Principle)

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。

1、单一职责原则

不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。

2、里氏替换原则(Liskov Substitution Principle)

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科

历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。

3、依赖倒转原则(Dependence Inversion Principle)

这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。

5、迪米特法则(最少知道原则)(Demeter Principle)

就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。

6、合成复用原则(Composite Reuse Principle)

原则是尽量首先使用合成/聚合的方式,而不是使用继承。

基本原则就是用新代码写新需求,而不用改动旧代码。

第二个原则是意义相近的代码写在一块,改的时候好改。

1 工厂模式

1.1 简单工厂模式(静态工厂模式)

优点就是获得一个对象的时候,只用一个函数+一个enum即可,我不用知晓每一个类名(用enum的flag即可),而且一个enum也能起到聚合作用,不然代码哪都是就不知道有没有实现过这个功能了,是给出一堆类合适,还是给出一个函数+enum合适呢,隐藏了具体类名

会把这个对象的诞生过程死死捆绑在我们的代码里,也就是说,功能代码塞进了需求代码里,对于一些庞大复杂的系统来说,过多的实例化逻辑于宿主类中会给其后期的维护与扩展带来很多麻烦。

而事实是我们根本不关心到底使用哪个对象;怎样生产出它的实例;制造过程是怎样,我们只在乎谁能给我产品来完成我的任务。

另外一个好处是,如果构造函数参数比较多,但又比较稳定,这时候包成一个工厂函数也合适,固化参数,少些代码,前提是固化参数没有修改的可能

另外一个好处是,构造过程相似,可以把构造函数抽出来,少些代码,前提是构造过程必须相似

另一个好处是,你用一个具体类在很多地方new了,但是你预测某一天,你要都改成另一个类,这时候相比于全局替换,多封装一个函数更方便

你要是说,如果代码会改呢?那你为什么不说代码可能永远都很简单,根本用不着设计模式呢?使用设计模式要预估未来,并使用最恰当的模式,即便不使用模式

1.2 工厂方法模式

继承简单工厂,这样不同工厂可分别交给不同人实现,自己仍只要调用接口即可。

比如说一个存储软件,它能存mysql,maria,acess等,并且以后仍可能增加,那么基于用新代码实现新需求的基本原则,这样在增删查改的时候,我就用相应增删查改的工厂函数,来具现是哪个db的增删查改,不然的话,一般我就用普通的具体Sql类来初始化,这里的问题就是

关键就是,用工厂类代替工厂函数,这样的好处是

工厂函数解决的是new爆炸(产品种类太多)的问题,工厂类解决的是new太复杂(产品创建太复杂)的问题,因为这样工厂函数就会变得过于复杂(构造实例太复杂,工厂函数就会变得很长很难读懂,需要再次抽出子函数,这其实就是工厂模式了,当然,函数都可以用类实现,因为java里面类是一等公民,工厂函数实际上用类实现比较好,因为构造过程的复杂用类属性保存比较好)

但是,简单工厂是用一个函数+enum实现简化过多的new(即便new不同的),这个即便用类实现也可以被看作是简化的,但是工厂模式的话,用类实现的话,就显得没有简化。。。工厂模式的关键在于构造过程的简化,简单工厂的关键在于构造数量的简化

new爆炸+复杂用抽象工厂

记住一点,设计模式的本质是为了解决复杂性问题,代码的复杂性源于现实的复杂性

工厂模式是为了解决创建实例的复杂性,创建实例的复杂性会影响简单工厂函数的复杂性,创建实例的多性并不会影响简单函数的复杂性,一旦确定用函数实现,拓展可能会很难,因为需要直接修改,因此用类实现工厂模式是正解,因为可拓展,当然,拓展到什么程度源于自己的预测,对代码复杂度的预测也源于自己的预测,预测的本质就是在于用最恰当的方法解决恰当的问题,盲目留拓展只是说明你预测的不准

还是说工厂模式,构造太复杂,所以用工厂类代替原本构建过程,这就是一种优化,定义一个工厂接口,利用多态,每个工厂实现类返回目标类/接口的一种特定子类实例。工厂方法解决了简单工厂可能过于复杂的问题,使得工厂本身更符合开闭原则单一职责原则

1.3 抽象工厂模式

代码中有很多直接调用工厂类的,没有调用接口这不好,我们用一个AbstractFactroy来生成其他工厂,然后用其他工厂生产产品。这样就实现了面向接口编程。

工厂类随着目标类数量一起爆炸增长,因为这是简单工厂要做的事情,到工厂模式,这里反而退化了,抽象工厂解决的就是又多又复杂的问题

产品种类太多的,往往可以用二维划分来降维,这样从o(n2)复杂度能降到o(n)复杂度,这能使得产品种类被很好的划分,划分后的产品种类能降一大截,就是如此

2 单例模式

这个模式有什么用呢?感觉没什么用啊。。。单例模式可以分为两种:预加载和懒加载

2.1 预加载

就是还没有使用该单例对象,但是,该单例对象就已经被加载到内存了。会造成内存的浪费。但是比较快。保证线程的安全。

2.2 懒加载

为了避免内存的浪费,我们可以采用懒加载,即用到该单例对象的时候再创建。但是无法保证线程的安全。首先,if判断以及其内存执行代码是非原子性的。其次,new Singleton()无法保证执行的顺序性。但是感觉自己用不到了,例如开辟内存和内存规格化可能被编译器混乱排序。。。不满足原子性或者顺序性,线程肯定是不安全的,这是基本的常识

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

工厂模式的拓展,我们知道工厂模式是因为实例构建困难导致的,如果这个构建过程是由很多相似部分构成,那么能不能抽出来一个构造类呢?这就是建造者模式,导演类负责组装,构造者类负责构建一部分和整体,就是如此

4 原型模式

就是拷贝构造,让一个实例当原型,跟拷贝构造函数一毛一样,还区分深拷贝和浅拷贝

B、结构模式(7种)

适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

5 适配器模式

例如USB转VBA,USB有ppt播放,VBA有projection播放,而这个adapter呢,就是将USB转成VBA,就可以用projection播放了。Adapter继承自USB,实现了VBA接口,就是这样的:

public class AdapterUSB2VGA extends USBImpl implements VGA {
       @Override
       public void projection() {
              super.showPPT();
       }
}
还是看代码好一些,USB的projection实际上调用了showPPT。其实就是个转换器小黑盒。

对象适配器模式

对象适配器和类适配器使用了不同的方法实现适配,对象适配器使用组合,类适配器使用继承。

public class AdapterUSB2VGA implements VGA {
       USB u = new USBImpl();
       @Override
       public void projection() {
              u.showPPT();
       }
}

还是说,用处不大啊。反正都是适配器了,不是说用处不大,而是现阶段没这需求。

5.3 接口适配器模式

当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。这个可能只有Java有用了意义不明

类适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。

对象适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。

接口适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。

6 装饰者模式

定义:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性。

继承是上下级的关系,而装饰是平级的关系,在类内添加一个引用对象,方便进行组合。根据装饰者模式的理念,我们可以在任何时候,实现新的装饰者增加新的行为。如果是用继承,每当需要增加新的行为时,就要修改原程序了。例如给n种咖啡添加m种调料,总不能用继承m*n吧,对吧。如果仅仅变成添加一个函数的话,当饮料或调料种类变多的情况下,又该怎么做呢?违反了 开放关闭原则(类应该对扩展开放,对修改关闭。)

原来调料单独一个类,而且更大,包裹住了咖啡,就是如此。调用就是调用调料的cost函数,明白了。 

因此饮料和调料是具有相同的超类的,这样才能层层嵌套。

对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。举个例子,实际上装饰者和被装饰者都有相同的函数,但是是继承关系。并且上下层是有随便配对的需求的。比如一个变量,我给它修饰上访问权限,也不好,这个例子不好说。订单系统嘛,就是如此了,电影票订座也不好,除非说座位与服务都要算钱,对吧。关键就是修饰者与被修饰者具有相同的

7 代理模式

例如客户买车,客户仅仅想买一辆车,其余可以交给中介做,代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。

假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了(在类里添加上过多多余代码,找车源代码,做质量检测代码等)。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。

例如非侵入式日志监听,人到了结婚年龄,父母总是迫不及待的开始到处为自己的子女相亲。用father类里添加son类指针(就是C++吧),包裹住son,两个类都有findlover,对吧,代理相当于代替实体做一些事情。

7.3 动态代理

代理类不用再实现接口了。但是,要求被代理对象必须有接口。这个是进阶吧,不用管。

8 外观模式

隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口。简单来说,该模式就是把一些复杂的流程封装成一个接口供给外部用户更简单的使用。

跟中介模式(代理模式)感觉差不多,只不过这不是相同的接口了,而是特点就是更方便的接口。中介是复杂的接口,为什么叫外观模式呢?为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

  • 客户端不与系统耦合,外观类与系统耦合

9 桥接模式

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

感觉很多模式的区别很细微啊。。。如果某个类存在两个独立变化的维度,可以运用桥接模式将这两个维度分离出来,使两者可以独立扩展,让系统更加符合“单一职责原则”。 

10 组合模式

把一个对象变成一组组合的对象。

C、关系模式(11种)

开闭原则:一直都会可用,而不至于重构时什么都不能用,扩展开放,修改封闭,但很难被完全实现。

单一职责原则:这个很简单

里是替换原则:不能依赖太多

依赖倒置原则:不要直接依赖,最好面向接口编程。基于接口进行交互,接口是不会修改的。

接口隔离原则:接口尽可能小,不要实现不需要的方法。

迪米特法则:尽量解耦合

工厂模式:例如创建一个窗口。但是用electron了。例如图像

一个通用结果原型接口Shape

一个具体产品,继承自Shape,例如circle,Rect等

一个构造工厂,ShapeFactory,里面有Shape getShape(string type),这样。

我们明确地计划不同条件下创建不同实例时。主要解决接口选择的问题。让其子类实现工厂接口,返回的也是一个抽象的产品(Shape)。

缺点:每次增加一个产品时,都需要增加一个具体类和修改对象实现工厂,使得系统中类的个数成倍增加(尤其是不同属性可以相互组合,就会变成幂次倍),在一定程度上增加了系统的复杂度。

抽象工厂:例如创建填充颜色(只有红黄蓝)和图形形状(只有矩形,圆形,三角形),实际上是颜色和大小,这个是某个对象的属性而已,是工厂模式隐藏的。

是对象相互依赖,而不是对象属性相互依赖,一次生成一堆的对象,这些对象还有关系。例如更换主题,变则整套都变了。同时创建按钮皮肤,标题栏皮肤,这样。是针对多个产品。

例如窗口换肤功能,

抽象工厂为AbstractStyleFactory,是一个接口,里面有getButtonStyle,getTitleStyle

几个实例工厂,继承了抽象工厂,例如ButtonStyleFactory,TitleStyleFactory,在这些工厂中,只实现对应的接口,其他的返回null。

一个工厂创造器/生成器类StyleFactoryProducer,用来制造实体工厂,当然返回的是抽象工厂接口。返回DarkStyleFactory什么的,也不是很好啊。直接抽象工厂就好了。

意义不大啊,如果换肤的话,还不如封装几个一套的工厂呢。在electron中,就是设置stype而已。有多少皮肤就有多少工厂。

如果新增一个滚动条呢?两个方法其实都不行。用第一种方法的话,第一种方法根本不行。就不能设置皮肤。

        SkinFactory skinFactory = new BlueSkinFactory(); // 用一个抽象工厂实现一个具体工厂
        Background background = skinFactory.createBackground();//以后需要换皮肤只需改这一句,返回BlueBackground
        Button button = skinFactory.createButton(); // 返回BlueButton
        QQFrame frame = skinFactory.createFrame(); // 返回BlueFrame
        background.showBgPic(); // 一般用于初始化过程而不是及时修改过程。把信息存入数据库,重启时更新。
        button.onclick();
        frame.printColor();

观察者模式

就是订阅,发信息,类似于中断,但怎么用呢?只能说,就是订阅系统,或者天气预报系统而已。如果普通软件的话,恐怕没有用。

责任链模式

例如钱财审批,不同金额由不同人处理,这就是责任链模式,这个也是很难集成的。例如订单的依次处理,比如各种优惠券,减免什么的。主要就是走一个流程。请求必须经过一系列检查后才能由订购系统来处理。

命令模式

例如工具栏上的保存,热键的保存,菜单栏的保存,简单的方法是各自发送各自的命令给服务,命令模式就是从中间添加一层saveobject,各个GUI都是负责激活这个save object,然后用这个save object来实现与服务的交互,这样就复用了代码。这个命令甚至还可以是一个队列,那么就可以有历史记录。

迭代器模式

是一种行为设计模式, 让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。不然就会暴露内层结构,也不利于泛化。

而且说,如果今天是数组结构,明天改成树结构,那么使用代码也需要更改,如果封装成迭代器,使用代码就不需要更改了。

中介者模式

例如表单提交,按钮按下后,按钮代码包含其他控件(输入框)实例,如果其他控件有增删或者替换,就会修改按钮代码,如果抽象成一个类,各个输入框改动这个类,按钮也操作这个类,这样就实现了解耦,这就是中介者模式。

备忘录(Memento)模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。

普通情况下先创建一个备忘录实例,然后有什么动作时,在相应动作代码里引用这个实例,而且还会直接破坏数据类的私有性,现在呢,是每个想要保存的类自己实现自己的存储代码,想存储时引用这个备忘录实例,关键就是在于不破坏私有性。这时候除了能读取自己的信息,其他信息的确也读取不了。

观察者模式

例如Android里面的按钮,实际上是类似中断的感觉了。观察者模式(Observer),又叫发布-订阅模式(Publish/Subscribe),定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。ANdroid的里面是一个接口,也就是这样了,感觉并不是很相同,或者你不知道其代码长什么样子。

状态模式

假如你有一个 文档Document类。 文档可能会处于 草稿Draft 、 ​ 审阅中Moderation和 已发布Published三种状态中的一种。 文档的 publish发布方法在不同状态下的行为略有不同,说白了就是状态机。

状态模式建议为对象的所有可能状态新建一个类, 然后将所有状态的对应行为抽取到这些类中。原始对象被称为上下文 (context), 它并不会自行实现所有行为, 而是会保存一个指向表示当前状态的状态对象的引用, 且将所有与状态相关的工作委派给该状态对象。

在状态模式中, 特定状态知道其他所有状态的存在, 且能触发从一个状态到另一个状态的转换; 策略则几乎完全不知道其他策略的存在。

策略模式

是状态模式的进阶,想要多少个状态就要多少个状态,不断往上添加即可,而原对象被抽象成为上下文,就是这样。实际上, 上下文并不十分了解策略, 它会通过同样的通用接口与所有策略进行交互, 而该接口只需暴露一个方法来触发所选策略中封装的算法即可。例如导游的规划路线方案,我只想要一个路线而已。

模板方法模式

方法如果有重复的,可以拆分这个方法成为几个小方法,而原方法则变成模板方法,或者说,调用这些小方法,达到重用代码的目的。

  • 抽象步骤必须由各个子类来实现
  • 可选步骤已有一些默认实现, 但仍可在需要时进行重写

模板方法可用于建造大量房屋。 标准房屋建造方案中可提供几个扩展点, 允许潜在房屋业主调整成品房屋的部分细节。

每个建造步骤 (例如打地基、 建造框架、 建造墙壁和安装水电管线等) 都能进行微调, 这使得成品房屋会略有不同。

访问者模式

访问者模式是最难的,也是一般用不到的,就像卖保险的拿不同类型的保险去不同的房屋。

下面是结构模式了

适配器,这个很简单了,例如json2xml

桥接,把两个不同维度的类变成一个类包含一个类参数,这样可以避免类爆炸。例如把颜色放到shape里面。例如GUI与api,拆分是关键

组合模式

例如套装购买,不可能一次计算其促销,定价,而是应该封装成一个类,然后让它自己完成。多用在有树状结构的。

装饰模式

由同一个基类构造了QQ message,微信 message,邮箱message,然后再用一个类包括他们,这就是装饰。

外观模式,门面模式,则是封装,将复杂的类封装成一个实用,简单的类。

享元模式,将大家重复的部分拿出来,从而节省内存。

代理模式,代理类控制原类,就是这样。因为原类可能需要特别方法启动,或者需要大量资源,就是这样。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值