设计模式概要

设计模式简单理解小结

Posted by Jingming on May 13, 2017

0. 面向对象的目标

  1. 可扩展性

容易添加新的功能。

  1. 灵活性

代码修改平稳的发生,修改一个方法不会影响另一个方法。

  1. 可替换性

可插入性,将一个同样接口的类加入进来,不影响原来代码。

  1. 高内聚、低耦合

模块内部之间关系紧密,也就是不可分割;低耦合指模块间关系弱。

1. 设计模式分类:

  1. 创建型模式

解决对象创建问题。创建型模式包括:工厂(简单工厂、抽象工厂)、单例、原型、建造者。

  1. 结构型模式

解决对象的结构设计问题例如依赖问题。结构型模式包括:外观、适配器、代理、装饰、桥接、组合、享元。

  1. 行为型模式

解决对象的行为问题。行为型模式包括:模版方法、观察者、状态、策略、职责链、命令、访问者、中介者、备忘录、迭代器、解释器。

2. 工厂模式(Factory Pattern)

工厂模式是解决根据类生成对象时候由于类种类比较琐碎、比较多造成的依赖多的麻烦。也就是说解决了一系列相互依赖对象的创建工作。

工厂模式的方法就是:有一个工厂,只要你告诉工厂该对象的名字(字符串),那么工厂就会造一个该对象给你。这样好处是不需要知道对象的创建细节。

具体实现:

简单工厂模式:简单工厂使用到了类似switch case的这种创建结构,坏处是违反了系统对修改封闭的原则,也就是说,新增产品的时候,工厂类必须进行修改。具体应用:JDBC。

工厂方法模式:使用的结构不再是switch case,而是抽象出产品的接口标准,这样可以通过不同的工厂对象来生成的相应产品,前提是这些工厂实现了这些接口。这种方法下,工厂生产代码不是全部浓缩在一个类里了。是简单工厂的延伸,区别就在于对工厂进行抽象并让子类去完成具体工作。举例:生产汉堡,有各个牌子的工厂,工厂自己实现生产汉堡的接口。

抽象工厂模式:工厂方法的延伸。工厂类可以创建复杂的有依赖关系的对象,也就是一次能创建一组有关系的对象。和工厂方法模式相比,代码上的区别就是,工厂本身也抽象出了一种抽象工厂,也就是具体工厂本身要实现抽象工厂的接口,然后通过这些抽象工厂接口去使用工厂。举例:生产汉堡的工厂,可以抽象出快餐店这个抽象类,该抽象类不仅生产汉堡,也生产薯条等其他产品。

3. 单例模式(Singleton Pattern)

实现单例的三要素:

(1)保证唯一:则需要将各种拷贝构造函数和赋值运算符重载放入private,并在private声明一个static的对象(或者对象的指针)。

(2)提供全局的访问唯一对象的方式:getInstance()方法。

(3)只有一次定义唯一对象:在全局区定义。如果没有这次的全局区定义,则会报链接错误。

4. 状态模式(State Pattern)

类会含有状态,这样,只要运行时该类的对象的状态改变,那么他的行为自然发生改变,看起来好像我们更换了对象的类型。

5. 策略模式(Strategy Pattern)

可以是算法和对象分开。

类中某些方法会根据传入对象的不同,而进行不同的处理。与状态模式相比,更加轻量化,状态模式是整个类的切换,而策略模式只是类中一个方法的切换。

策略模式实现:context对象,里面包含策略类的抽象引用,然后调用其中的算法。抽象策略类由多个子策略类来实现算法。

6. 适配器模式 (Adapter Pattern)

适配器模式是两个不兼容接口之间的桥梁,例如读卡器,网卡适配器。对应设计模式,就是使得不兼容的类或者对象之间可以一起工作。

角色:Target是所期待得到的接口;Adapee是需要适配的接口,Adapter是转换器。

例如:Target接口类想要调用Adaptee的request方法,但是Adaptee没有该方法。适配的方法就是建立一个中间类Adapter,里面包含了一个Adaptee的实例,并提供request接口。中间类同时implements Target类,这样生成适配器类对象后,可以使用Target来调用request接口。 举例:笔记本的启动需要传入5V电压接口,但是实际上传入的是适配器对象,适配器对象同时实现了220V和5V接口(或者是对象适配器中的对象组合方式),在其中的5V接口中实现220V+转化逻辑。

适用环境:系统使用现有的类,而这些类不符合要求。建立一个重复使用的类,与以后建立的类一起合作。

运用实例:jdbc。jdbc向上层提供数据库访问接口,其实就是在原来不同数据库的操作接口的基础上,封装了一层统一的适配接口。 使用情况:内部结构少用适配器,有问题最好采用重构;使用外部库的时候可以考虑使用适配器维持系统内部接口一致。

7. 代理模式(Proxy Pattern)

https://zh.wikipedia.org/wiki/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F

有些对象不方便被另一个对象直接调用,而是需要一种中介。例如房屋的主人并不自己租房,而是通过中介来租房,中介可以帮助房主管理房子;例如老板开会的时候,可以让助理(代理) 去发送通知、布置会场等。

代理模式是一个对象调用另一个对象的时候,并不直接调用,而是通过第三方的对象来调用。

这样的好处是,第三方对象(也称代理对象)可以实现一些额外的处理逻辑,例如日志、缓存、访问控制、远程访问控制等。

具体实现:subject类、realSubject类、Proxy类。

realSubject类,就是真正的服务拥有者,例如”房东”。

subject抽象类,就是指代理的接口类,里面的内容是房东可以开放给代理的接口,例如出租这个接口方法。

Proxy类,代理类,里面需要实现subject提供的接口,然后实现的时候,加入自己的服务逻辑。

静态代理:代理对象中使用的类都是程序员先写好的。

动态代理:代理对象中使用的类对象是根据反射机制生成的,也就是工厂模式。运用实例:spring aop。 动态代理实现:使用Proxy类的newProxyInstance方法,传入要被代理的类类型(接口类型)以及实现代理逻辑的proxyHandler(传入具体被代理对象),返回一个被代理类的对象,通过接口调用。

PS. 代理屏蔽了真正的服务提供者,中介也可以先从产品拥有者进货,然后卖给消费者。

8. 原型模式 (Prototype Pattern)

目的是解决某些结构复杂对象的创建工作。JAVA中的可以通过重写clone()方法来拷贝自身来创建新的对象,其中一种使用方式就是实现深拷贝。

9. 建造者模式(Builder Pattern)

目的是在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。

https://blog.csdn.net/carson_ho/article/details/54910597

https://www.jianshu.com/p/e2a2fe3555b9

可以解决构造函数参数种类多样,不好设计的痛点。

具体实现:在被构造类A中建立final静态内部类,静态内部类的build方法负责调用A的构造方法传入一个builder对象(Builder类的this引用)返回一个A的对象。 静态类的好处是该类对象只有一个实例,final表示该类不能被继承,内部类好处是方便的让外部类、内部类互相知道类型,静态加内部的好处是内部类不依赖外部类对象,且静态内部类 并不会随着外部类的载入而被载入,只有当其内部方法被调用时才会装入JVM,这样节省了空间。

使用Builder的类不对外提供setter方法,构造方法,并使得属性是final的,这样防止对象处于不一致状态。也就是说builder生成的对象是不可变对象。

参考:https://blog.csdn.net/hikvision_java_gyh/article/details/8963562 https://blog.csdn.net/yaomingyang/article/details/79363631 https://stackoverflow.com/questions/30396203/how-to-modify-the-already-built-object-in-java-when-the-object-is-built-using-b

10. 外观模式 (Facade Pattern)

https://blog.csdn.net/ruizeng88/article/details/6606438

外观模式,就是抽象出高层接口,具体实现留给子层次。看起来和适配器模式区别不大,但是外观模式更加注重细节隐藏,而适配器模式更注重可移植。Facade模式里面可以由实现者来定义新的接口,而adapter只是为了满足现有接口进行封装。

个人理解:Facade模式像是在定义标准,抽象出新接口,不关心具体实现,而且很权威。

11. 观察者模式 (Observer Pattern)

https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/observer.html

通知代替轮询。

一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。也就是一种一对多的依赖关系。也叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。 被观察的目标有观察者的对象列表,一旦观察目标发生变化,将执行notify方法通知所有观察者。 例子:订阅频道、明星信息、气象。一旦有新消息自动推送,而不是每次自己去主动获取,好处是自己去获取经常会落空,也就是发现没有新消息。

实现:被观察的主题(抽象类),它使用集合保存所有的观察者,可以新增删除,使用notify方法通知所有观察者(调用观察者的更新方法)。

观察者(抽象类),提供更新方法给被观察主题回调。

被观察主题(实现类),它实现了被观察主题抽象类,并且有状态属性,在状态属性变化时候(例如set值)调用通知方法。

观察者(实现类),它实现了观察者抽象类,实现更新方法,它还可以通过保存被观察主题(实现类)的引用来知道目前的状态,以便相应更新。

推模式:观察者的更新方法里面并不传入被观察者对象(主题对象),而是传入被观察者对象提供的推送信息。缺点:推送信息类型种类不够会造成冗余信息接收。 拉模式:观察者的更新方法里面传入被观察者对象(主题对象)。缺点:耦合性大。

reactive programming:流对象就是目标对象,然后观察者对象可以接受流对象的信息,并做相应处理,其中处理方法使用回调,也就是自己设计。

12. 委托模式 (Delegation Pattern)

https://zh.wikipedia.org/wiki/%E5%A7%94%E6%89%98%E6%A8%A1%E5%BC%8F

委托模式就是说,接收请求的对象将请求委托给另一个对象来处理。也就是说通过一种方式,一个对象可以切换自己的委托者(依赖多个委托者的组合),从而达到不同效果,也就是使用组合来代替继承。

与代理模式的区别:代理模式的接口和接收请求的对象是一致的,但是代理模式自己内部封装了逻辑,接收请求者完全不知道代理者干了什么,而委托模式下,通过切换委托者,我们大致能知道他干了什么。

13. 装饰器模式(Decorator Pattern)

增加一个修饰类包裹原来的类,包裹的方式一般是通过在将原来的对象作为修饰类的构造函数的参数。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。修饰类必须和原来的类有相同的接口。

14. 对象池模式 (Pool Object Pattern)

设计一个池类来对于一组对象进行管理,目的是重用这些对象。例如,池的内部分为两个组,一个组表示对象正在使用,另一组表示对象未在使用。这样,当有使用者来要求使用对象的时候,那么就可以在未使用组里拿出一个对象,供使用者使用,并加入到正在使用组,等到用户使用完该对象后,可以通知池对象,池对象就会对使用完的对象进行检查,并进行一些例如还原性的处理后,从正在使用组拿出并加入到未使用的组里面,供后来的使用者来继续申请使用。

实例:

(1)游戏中的子弹。游戏中的子弹对象显然不应该被频繁的创造和释放,可以建立池对象对子弹对象的属性进行管理,例如在子弹消失的时候,可以将子弹位置进行还原达到“回收”效果。

(2)数据库连接池。程序与数据库建立连接和释放连接都是有很大的开销的,如果简单的一次读一点数据,就创建关闭连接,再读一点就再创建释放一次,显然不可取。当然,相比于子弹这种简单的例子,数据库的连接池更加复杂庞大,例如管理的对象是按粒度分层管理的,第一层就是连接,一个连接有对应了多个的语句,也就是第二层,然后每个语句对应了不同的查询结果,作为第三层。数据库连接池系统保证了这些new出来的对象被充分的复用。