设计模式是一套被人们反复使用的方法,基于面向对象编程.
设计原则
单一职责原则
一个类承担职责越少,复用可能性越高
开闭原则
对扩展开放,对修改关闭;
在不修改原有代码前提下扩展新的功能.
里氏代换
里氏代换原则可以通俗表述为:在软件中如果能够使用基类对象,那么一定能够使用其子类对象。
因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
感觉这就是Java中的多态.
依赖倒转
简单来说,依赖倒转原则就是指:代码要依赖于抽象的类,而不要依赖于具体的类;要针对接口或抽象类编程,而不是针对具体类编程。
实现开闭原则的关键是抽象化,并且从抽象化导出具体化实现,如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要手段。
合成复用
尽量使用对象组合,而不是继承来达到复用的目的。
接口隔离
接口隔离原则是指使用多个专门的接口,而不使用单一的总接口。
迪米特法则
简单地说,迪米特法则就是指一个软件实体应当尽可能少的与其他实体发生相互作用。
能不直接通信就不直接通信
23种设计模式
创建型设计模式
工厂方法
定义
在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
类图
评价
相比简单工厂,增加了一层具体工厂实现类,维护了开闭原则.
不过如果后期需要使用这个工厂来增加新的产品,就需要修改源代码.
抽象工厂
定义
抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。
类图
评价
其实就是抽象工厂类多了几个方法,可以再生产其他产品.
比起工厂方法确实能造更多东西,不过如果是新产品呢?
单例模式
单例模式,就是一个类只能实例化一个对象;
在多线程下,一个常见的模板是这样的
package singleton;
public class PrintSpoolerSingleton
{
private volatile static PrintSpoolerSingleton instance = null;
private PrintSpoolerSingleton()
{
}
public static PrintSpoolerSingleton getInstance() throws
{
if(instance == null){
System.out.println("创建打印池!");
synchronized (PrintSpoolerSingleton.class){
if(instance == null){
instance = new PrintSpoolerSingleton();
}
}
}
return instance;
}
}
注意的点
- 构造方法私有---这样保证外部无法通过new创造对象,保证单一;
- 实例化对象private且static---提供一个静态getInstance来获取单例对象,而静态方法里面需要静态对象;
- 饿汉式和懒汉式---上面的代码是懒汉式,饿汉式则是在初始化时就实例化好
private static PrintSpoolerSingleton instance = new PrintSpoolerSingleton();
为什么要双重判断?
你们可能想这样写
public synchronized static PrintSpoolerSingleton getInstance()
{
if(instance == null){
instance = new PrintSpoolerSingleton();
}
return instance;
}
但是这样写在并发下效率比较低;
因为每次请求都需要锁,但是我们其实只是“读”而已;
“读”的过程并不需要同步,只有在new的时候才需要,所以应该是在判断为null是再加锁;
改成下面这种
public static PrintSpoolerSingleton getInstance() throws PrintSpoolerException
{
if(instance == null){
synchronized(PrintSpoolerSingleton.class){
instance = new PrintSpoolerSingleton();
}
}
return instance;
}
然而,这种也有问题;
如果有两个线程同时判断到instance为null,那它们都会进到里面,最后出现多次实例化;
那么就写成这样
public static PrintSpoolerSingleton getInstance() throws
{
if(instance == null){
synchronized (PrintSpoolerSingleton.class){
if(instance == null){
instance = new PrintSpoolerSingleton();
}
}
}
return instance;
}
用了双重锁之后,其实还不够完善,这里涉及到JVM指定优化方面的问题;
问题出在下面这句
instance = new PrintSpoolerSingleton();
虚拟机做了三个步骤:
1.给instance分配内存
2.调用构造方法完成初始化
3.使instance对象的引用指向分配的内存空间
完成第3步时instance就不为null了,而JVM优化时使得指令可能不会按照1-2-3的顺序执行,这样就可能造成还没有真正的实例化成功,一个线程判断发现不为null,然后就返回instance了;
所以你会看到对象变量上面还有一个关键词volatile,它能防止指令重排
private volatile static PrintSpoolerSingleton instance = null;
建造者
定义
建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。
类图
评价
乍一看,有点像模板方法.只不过这种属于创建型.
像模板的话,就要求这些Builder的构建步骤大体相同
原型
定义
原型模式(Prototype Pattern):原型模式是一种对象创建型模式,用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
原型模式允许通过一个原型对象创建一个或多个同类型的其他对象,而无须知道任何创建的细节
类图
评价
有浅克隆和深克隆两种:
浅克隆在复制非primitive类型时,只是复制了引用.比如C++中,两个对象中指针指向同一块地址,那么一个修改,另一个也会发生变化.
深克隆就是完全复制了,Java中可利用序列化与反序列化实现,这时这两个对象就无任何瓜葛了.
结构型设计模式
适配器
定义
适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作。
适配器模式既可以作为类结构型模式,也可以作为对象结构型模式 定义中所提及的接口是指广义的接口,它可以表示一个方法或者方法的集合
类图
评价
欢迎补充
组合
定义
组合模式(Composite Pattern):组合多个对象形成树形结构以表示“部分-整体”的结构层次。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。
类图
评价
是一种递归的操作,operation方法调用的是子节点的operation方法,直到到达叶子节点.
应用的场景应该要符合树的结构,比如对文件夹的操作.
装饰器
定义
装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活
类图
评价
Decorator中维持了对Component的引用,所以在使用时,是先创建ConcreteComponent,在将其注入Decorator中.
这样一来,原先需要在ConcreteComponent基础上新增的子类就统一集中到Decorator下面了.
我怎么觉得和桥接模式差不多? //当然,实现方式不同
桥接
定义
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。
用抽象关联取代了传统的多层继承
将类之间的静态继承关系转换为动态的对象组合关系
类图
评价
装饰器是注入,这一种是抽象类关联了接口类.
其中,抽象类及其子类是基础,接口类是附属品.
享元
定义
享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。
系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用
由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式
类图
评价
从FlyweightFactory中获取对象,对象的内部状态是一致的,在operation时传入外部状态.
感觉有点像原型模式,快速复制,然后调整不同的地方.不过享元获取到的是同一个对象,这样就节省了点空间.
外观
定义
外观模式(Facade Pattern):外部与子系统的通信通过一个统一的外观对象进行,为子系统中的一组接口提供一个统一的入口
类图
评价
感觉内部系统越多,Facade的责任越大.
代理
定义
代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用
类图
评价
静态代理需要为每个实际类生成代理类,会让类的个数变得很多.
动态代理,熟悉Java的都很清楚,面向切面编程中也用到.
行为型设计模式
解释器
定义
解释器模式(Interpreter Pattern) :定义一个语言的文法,并且建立一个解释器来解释该语言中的句子。
此处,“语言”是指使用规定格式和语法的代码
它是一种类行为型模式
类图
评价
有终结符表达式和非终结符表达式;
非终结符表达式在解释时,是分别解释传入的两个表达式,中间再根据需要加上自己的处理逻辑;
整体设计的核心在于构建抽象语法树,比如
public void build(String statement){
String[] nodes = statement.split("and");
Stack stack = new Stack<>();
for (String s : nodes) {
if (stack.size() == 0) {
stack.add(new CommandNode(s));
continue;
}
Node left = stack.pop();
stack.add(new CompositeNode(left, new CommandNode(s)));
}
this.node = stack.pop();
}
策略
定义
策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。
类图
评价
这种模式太基础了,典型的面向基类编程.
模板
定义
模板方法模式(Template Method Pattern):定义一个操作中算法的框架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
类图
评价
在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果
职责链
定义
职责链模式(Chain of Responsibility Pattern):避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
类图
评价
就是一条链表,调用第一个节点的方法,这个方法又调用下一个节点的方法...
有点像API网关中的过滤器处理.
中介者
定义
中介者模式(Mediator Pattern)定义:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
类图
评价
乍一看,和外观模式有点像.不过外观模式处理的是外部系统和内部系统间的调度,这里处理的是每个对象间的交互.
备忘录
定义
备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
类图
评价
一般来说,Caretaker中用栈的方式存储Memento
状态
定义
状态模式(State Pattern) :允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
类图
评价
在执行方法时,往往需要把自身的引用传过去.
状态执行完后,就能通过回调的方式实现状态转换.
因此,当增加新的状态时,需要修改源代码,使得有方式过渡到新的状态.
观察者
定义
观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
类图
评价
Subject变化时调用Observer的update方法.
有循环依赖的可能:Subject触发ObserverA,ObserverA触发ObserverB,ObserverB触发Subject,这样就没完没了.
访问者
定义
访问者模式(Visitor Pattern):表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
类图
评价
其实我感觉不用Visitor都可以,只要子类继承ConcreteElement,然后重写对应的方法就可以了.
使用Visitor的好处应该是可以缩减类的数量,一个ConcreteVisitor中可以访问多个ConcreteElement.
但是如果要访问新的Element,那就要大改特改了.
命令
定义
命令模式(Command Pattern):将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
类图
评价
这个和监听器类似.Invoker.addListener(Command).
迭代
定义
迭代器模式(Iterator Pattern) :提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示。
类图
评价
迭代器在很多编程语言中都有用到,比如C++,Java.
通过自定义Iterator,可以按自己想要的方式遍历对象.
部分练习题
https://gitee.com/jayying/DesignPattern