Java Design Pattern [Java23种设计模式]
设计模式的主要目的是为了代码的扩展性和灵活性,具体操作起来就是解耦,耦合度降低,修改代码对已有代码的修改就会变少。Java有23种设计模式,可以大致分为三类。
创建型模式:单例模式[Singleton Pattern],工厂模式[Factory Pattern],抽象工厂模式[Abstract Factory Pattern],建造者模式[Builder Pattern],原型模式[Prototype Pattern]
创建型模式主要负责创建对象。
结构型模式:适配器模式[Adapter Pattern],组合模式[Composite Pattern],代理模式[Proxy Pattern],享元模式[Flyweight Pattern],外表模式[Facade Pattern],桥接模式[Bridge Pattern],装饰者模式[Decorator Pattern]
结构模型的作用是从程序结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。
行为型模式:模板方法模式[Template Method Pattern],中介者模式[Mediator Pattern],责任链模式[Chain of Responsibility Pattern],观察着模式[Observer Pattern],策略模式[Strategy Pattern],命令模式[Command Pattern],状态模式[State Pattern],访问者模式[Visitor Pattern],解释器模式[Interpreter Pattern],迭代器模式[Iterator Pattern],备忘录模式[Memento Pattern]
行为型模式关注系统中对象之间的相互交互,研究系统在运行时对象之间的相互通信和协作,进一步明确对象的职责。
Singleton Pattern
单例模式的核心作用是保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
单例模式中,创建实例的步骤要求线程安全。主要的实现方式包括饿汉式,懒汉式,静态内部类式,枚举单例式,双重检测锁模式。其中双重检测锁模式本身存在问题使用不多,所以暂时忽略。
饿汉式
package lr.singleton;
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry() {
}
public static SingletonHungry getInstance() {
return instance;
}
}
因为饿汉式有一个静态成员变量,在类加载到静态区的时候,就会执行new SingletonHungry()
,这种静态加载是线程安全的,所以getInstance()
不需要使用synchronized
关键字。
饿汉式是线程安全,调用效率高,不能延时加载。不能延时加载一定程度上会浪费内存资源。
懒汉式
package lr.singleton;
public class SingletonLazy {
private static SingletonLazy instance = null;
private SingletonLazy() {
}
public static synchronized SingletonLazy getInstance() {
if(instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
饿汉式声明instance的时候并没有实例化,而是赋值null,直到调用getInstance()
方法。这样的机制,降低了资源的浪费,但是产生了延迟,因为多个线程调用的时候,线程需要等待。
饿汉式是线程安全,调用效率不高,可以延时加载
静态内部类式
package lr.singleton;
public class SingletonStaticInner {
private SingletonStaticInner() {
}
public static SingletonStaticInner getInstance() {
return SingletonClassInstance.instance;
}
private static class SingletonClassInstance {
private static final SingletonStaticInner instance = new SingletonStaticInner();
}
}
成员内部类可以无条件访问外部类的所有成员属性和成员方法[包括private成员和静态成员],所以内部类可以调用私有的构造函数。根据Java的内存管理机制,类加载的时候,成员内部类并不会被加载,直到getInstance()
方法被调用,才会加载成员内部类,这就实现了延迟加载,并且内部类的静态成员变量加载是线程安全的,所以也不担心同步问题。
所以,内部静态类方式是线程安全,调用效率高,可以延时加载。很多开源框架都采用静态内部类方式实现单例。
枚举单例式
package lr.singleton;
public enum SingletonEnum {
INSTANCE;
public void singletonOperation() {
}
}
枚举类方式是线程安全,调用效率高,不能延时加载
懒汉,饿汉以及静态内部类都可以通过反射或反序列化的方式进行破解,也就是说可以获得不同的实例,但是可以通过构造器抛出异常和添加回调函数readResolve方法来解决
如果应用场景是占用资源少,不需要延时加载,枚举式好于饿汉式。
如果应用场景是占用资源大,需要延时加载,静态内部类好于懒汉式。
Factory Pattern
简单工厂模式又称静态工厂模式,就是工厂类一般使用静态方法,通过接受参数来构建并返回不同的对象实例。
但是,这种模式,必须通过修改已有代码的方式对于增加新功能。
//Without Factory Pattern
public class Client {
public static void main(String[] args) {
Car c1 = new Audi();
Car c2 = new Benz();
c1.run();
c2.run();
}
}
//With Factory Pattern
public class ClientFactory {
public static void main(String[] args) {
Car c1 = CarFactory.createCar("Audi");
Car c2 = CarFactory.createCar("Benz");
c1.run();
c2.run();
}
}
另一种工厂方法模式可以避免这种问题,主要的做法是创建一个新的接口,每次增加新的功能只要添加新的类来实现接口就可以了,不用修改已有的代码。但是在实际项目开发中还是简单工厂模式使用更广泛,因为工厂方法模式需要更多的类和接口来实现。
public interface CarFactory {
public Car createCar();
}
public class BenzFactory implements CarFactory {
public Car createCar() {
return new Benz();
}
}
public class Client {
public static void main(String[] args) {
Car c1 = new AudiFactory().createCar();
c1.run();
}
}
Abstract Factory Pattern
抽象工厂的核心思想是保证在已有源码不修改的情况下,添加新的一类功能或者产品[是一类不是一个]。
本质来说在工厂方法模式之上再建一层。就是最顶端是一个抽象工厂类,这个类被实现的时候,其中可以再调其他的对应的类,当然这些类也对应着不同的接口。
一般大项目会用到抽象工厂。
Builder Pattern
建造者模式本质是分离了对象子组件的单独构造[构造由builder负责]和装配[装配由Director负责]。从而可以构建出复杂的对象,这个模式使用于某个对象的构建过程复杂的情况下使用。
由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象,相同的构建器,不同的构建顺序也会做出不同的对象。这就实现了构建方法和装配方法的解耦,从而达到更好地代码复用。
Prototype Pattern [Clone]
原型模式的应用场景是在使用new产生一个对象的时候需要非常繁琐的数据准备或者访问权限,使用原型模式直接clone对象更有效率。Clone时以某个对象为原型,复制出新的对象,但是new方法的新对象会用默认值,clone的则和原对象一模一样。
这里还要注意深clone和浅clone,就像c语言的深拷贝和浅拷贝。如果clone的Java对象的成员变量是一个对象引用[这里存的是对应对象的地址],那clone的对象也将获得这个地址,如果原对象发生改变,那么原型和clone的对象的对应成员变量也会都改变,这就是浅clone。深clone指的是,对于每一个成员变量,只要它是对象引用,创建的时候也逐一clone。
原型模式很少单独出现,通常和工厂模式搭配,通过clone的方法创建一个对象,然后由工厂方式提供给调用者。
Adapter Pattern
适配器模式可将两个不相关的对象通过Adapter方式连接在一起,也是一个类可以调用另一个类的方法,但两个类之间并没有直接引用[降低了耦合性]。
在旧系统升级的时候大量使用适配器
Composite Pattern
组合模式用于把部分和整体的关系用树型结构来表示,从而使客户端可以使用统一的方式处理部分对象和整体对象。所以遇到树形结构可以考虑组合模式。
组合模式核心角色
抽象构件[component]:定义了叶子和容器构件的共同点
叶子[leaf]:无子节点
容器[composite]:有容器特征,可以包含子节点
组合模式中的元素继承了统一的接口,所以他们的处理方式都是一样的。
Proxy Pattern
代理模式非常常用,其核心作用是通过代理,控制对对象的访问。可以详细控制访问某个[某类]对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理。
代理模式中有抽象角色,真实角色,代理角色。抽象角色定义代理角色和真实角色对外的公开方法。真实角色实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用[真实角色关注业务逻辑]。代理角色也实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并附加自己的操作,所以将统一的流程放到代理角色中处理。
静态代理指的是我们自己定义代理类,动态代理指的是系统自动生成代理类[比如JDK的Proxy类]。动态代理使用的更多。
Flyweight Pattern
享元模式以共享的方式高效的支持大量细粒度对象的重用。
如果有很多个完全相同或者相似的对象,可以通过享元模式节省内存。
Facade Pattern
外观模式,核心原理就是封装,实践迪米特法则,尽量降低对象和对象之间的复杂交互。
Bridge Pattern
桥接模式主要用于处理多重分类问题。这种问题往往有扩展性问题,添加新类或者新item时候,需要修改代码。
桥接模式可以将不同维度的类连接起来。所以在存在多个变化维度的场景可以考虑桥接模式。
通过桥接模式,可以达到一个类似多重继承的效果。
Decorator Pattern
装饰者模式[也叫wrapper包装模式]的主要功能是动态的为一个对象增加新的功能。
装饰者模式是一种用于代替继承的技术,无须通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。
Component 抽象构件
ConcreteComponent 具体构件,真实对象。
Decorator装饰角色
ConcreteDecorator具体装饰角色
Template Method Pattern
模板方法模式核心是模板,这是一种编程经常使用的模式。它定义了一个操作中的算法骨架,将某些步骤延迟到子类实现。这样,新的子类可以在不改变一个算法结构的前提下重新定义该算法的某些特定步骤。
处理某个流程的代码都已经具备,但是其中某个节点的代码暂时不能确定。因此,我们采用工厂方法模式,将这个节点的代码实现转移给子类完成,即:处理步骤父类定义好,具体实现延迟到子类定义。
Mediator Pattern
中介者模式核心作用是,如果一个系统中对象之间的联系呈现为网状结构,对象之间存在大量多对多关系,将导致关系及其复杂,这些对象成为同事对象。
我们可以引入一个中介者对象,使各个同事对象只跟中介者对象打交道,将复杂的网络结构化解为星型结构,所以中介者是核心角色。
如果应用场景没有复杂的关系,使用中介者模式反而会画蛇添足。
Chain of Responsibility Pattern
责任链模式是将能够处理同一类请求的对象连成一条链,所提交的请求沿着链传递,链上的对象逐个判断是否有能力处理该请求,如果能处理则处理,如果不能处理则传递给链上的下一个对象。
常用场景包括,异常处理,拦截器,过滤器等等都是责任链模式。
Observer Pattern
观察者模式非常重要,是实现系统广播功能的核心。
把多个订阅者,客户称之为观察者。需要同步给多个订阅者的数据封装到对象中,称为目标。
观察者主要用于1:N的通知,当一个对象[目标对象Subject]的状态变化时,他需要及时告知一系列对象[Observer],令他们做出响应。
通知观察者的方式:
Push-每次都会把通知以广播方式发送给所有观察者,所有观察者只能被动接受。
Pull-观察者只要发现有情况即可,至于什么时候获取内容,获取什么内容,都可以自主决定。
JDK提供了java.util.Observable
和java.util.Observer
来帮助实现观察者模式。
Strategy Pattern
策略模式很常见,主要功能是根据不同对象指定不同策略。相当于把大量if else
语句转换成该设计模式。
策略模式对应于解决某一个问题的一个算法族,允许用户从该算法族中任选一个算法解决某一问题,同时可以方便的更换算法或者增加新的算法。并且由客户端决定调用哪个算法。
本质:分离算法,选择实现
Command Pattern
命令模式是将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数;堆请求排队或者请求记录日志,以及支持可撤销的操作。也成之为动作Action模式,事务transaction模式。
State Pattern
状态模式使用场景是,不同的模式对应不同的行为,将模式和行为封装到一起。
Visitor Pattern
访问者模式使用很少,其核心目的是对于存储在一个集合中的对象,他们可能具有不同的类型[即使有一个公共的接口],对于该集合中的对象,可以接受一类成为访问者的对象来访问,不同的访问者其访问模式也有所不同。
Interpreter Pattern
解释器模式并不常用,它是用于描述如何构成一个简单的语言解释器的模式,主要用于使用面向对象语言开发的编译器和解释器设计。如果不是在开发语言的情况下尽量不要使用,后期维护比较麻烦。
Iterator Pattern
迭器模式其实就是JDK自带的迭代器的另一种实现,提供一种可以遍历聚合对象的方式,又称为游标cursor模式。
聚合对象:存储数据[一个容器对象]
迭代器:遍历数据
Memento Pattern
备忘录模式核心是保存某个对象内部状态的拷贝,这样以后就可以将该对象恢复到原先的状态。
应用场景,比如Word突然关闭后,再此打开,部分文档依然有机会恢复到以前的文档。管理系统中,文件发送撤回功能。
Originator源发器类[负责保存某个对象内部的状态]
Memento备忘录类[负责保存拷贝]
CareTake[负责保存管理多个备忘录类]