- 1 创建型(ConstructionalPatterns)
- 2 结构型(StructuralPatterns)
- 3 行为型(BehavioralPatterns)
- 3.1 观察者模式(ObserverPattern)
- 3.2 模板方法模式(TemplateMethodPattern)
- 3.3 策略模式(StrategyPattern)
- 3.4 责任链模式(ChainOfResponsibilityPattern)
- 3.5 状态模式(StatePattern)
- 3.6 迭代器模式(IteratorPattern)
- 3.7 访问者模式(VisitorPattern)—不常用
- 3.8 备忘录模式(MementoPattern)
- 3.9 命令模式(CommandPattern)—不常用
- 3.10 解释器模式(InterpreterPattern)
- 3.11 中介模式(MediatorPattern)
- 4 设计模式使用
设计模式要干的事情就是解耦。 创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型 模式是将不同的行为代码解耦。
借助设计模式,我们利用更好的代码结构,将一大坨代码拆分成职责更单一的小类,让其满足开闭原则、高内聚松耦合等特性,以此来控制代码的复杂性,提高代码的可扩展性。
常用:单例、工厂、建造者、代理、装饰器、适配器、观察者、模板、策略、职责链、迭代器
1 创建型(ConstructionalPatterns)
将对象的创建与使用分离。 这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
1.1 单例模式(SingletonPattern)
1.1.1定义
一个类只允许创建一个对 象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例 模式
1.1.2 应用场景
- 从业务概念上,有些数据在系统中只应该保存一份,就比较适合设计为单例类。如系统的配置信息类。
-
1.1.3 代码实现
含有一个该类的静态变量来保存这个唯一的实例;
- 构造器私有化,确保唯一;
- 对外提供获取该实例对象的方式(饿汉式一开始就初始化实例,懒汉式调用方法再初始化实例);
/*
在类初始化的时候直接创建这个对象,当实例对象初始化需要通过配置文件加载参数是也可以使用静态代码块来初始化对象
*/
public class Singleton01 {
private static Singleton01 instance = new Singleton01();
private Singleton01(){ }
public static Singleton01 getInstance(){
return instance;
}
}
public class Singleton02 {
private static volatile Singleton02 instance ;
private Singleton02(){ }
//普通的懒汉式在调用getInstance方法时再创建对象,线程不安全
public static Singleton02 getInstance(){
if (instance == null){
instance = new Singleton02();
}
return instance;
}
//使用同步方法加锁保证线程安全
public static synchronized Singleton02 getInstance2(){
if (instance == null){
instance = new Singleton02();
}
return instance;
}
//双重判断,比第二种方法提高效率
public static Singleton02 getInstance3(){
if (instance == null){
synchronized (Singleton02.class){
if (instance == null){
instance = new Singleton02();
}
}
}
return instance;
}
}
/*
只用调用的时候才会加载
*/
public class Singleton03 {
private Singleton03(){ }
public static class Singleton04_Inner{
private static final Singleton03 INSTANCE = new Singleton03();
}
public static Singleton03 getInstance(){
return Singleton03_Inner.INSTANCE;
}
}
//枚举形式是最简单的饿汉式
public enum Singleton04 {
INSTANCE;
}
1.1.4 缺点
- 单例对 OOP 特性的支持不友好
- 单例会隐藏类之间的依赖关系
- 单例对代码的扩展性不友好
- 单例对代码的可测试性不友好
- 单例不支持有参数的构造函数
如果单例类并没有后续扩展的需求,并且不依赖外部系统,那设计成单例类就没有太大问题。 单例的解决方案: 比如,通过工厂模式、IOC 容器。
1.1.5 唯一性
经典的单例模式是“进程唯一”的,“线程唯一”可以使用通过 ThreadLocal 来实现。 “集群唯一”进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对 象,然后再使用,使用完成之后还需要再存储回外部共享存储区。 为了保证任何时刻,在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁(分布式锁),避免其他进程再将其获取。在进程使用完这个对象之后,还需要显式地将对象从内存中删除,并且释放对对象的加锁。
1.2 工厂模式(FactoryPattern)
1.2.1 工厂模式定义
不暴露创建对象的具体逻辑,而是将将逻辑封装在一个函数中,实例化对象时不使用new而使用该方法创建对象,该方法通过传入不同的入参而制造出特定的产品。
一般细分为 简单工厂、工厂方法 、 抽象工厂
- 简单工厂模式,通过if else判断去实例化不同的具体产品类,它的缺点是增加新产品时会违背“开闭原则”。
- 工厂方法模式,定义一个用于创建对象的接口,让子类决定将哪一个产品实例化。工厂方法模式让一个类的实例化延迟到工厂类的子类。
- 抽象工厂方式,创建一系列相关或者相互依赖的类的工厂接口,他包含多个产品类的工厂方法,及一个工厂类能够创建多个产品。
1.2.2 应用场景
在日常开发中,凡是需要生成复杂对象的地方,都可以尝试考虑使用工厂模式来代替。(因为类的构造过于复杂,如果直接在其他业务类内使用,则两者的耦合过重,后续业务更改,就需要在任何引用该类的源代码内进行更改)。
1.2.3 简单工厂代码实现
- Product(抽象产品角色):所有具体产品的父类
- ConcreteProduct(具体产品角色) : 继承自抽象产品类的具体实现类
- Factory(工厂角色):实现产品创建逻辑的类
interface Shape {
void draw();
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("画一个长方形");
}
}
public class ShapeFactory {
public static Chart getChart(String shapeType) {
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}
在上面的代码实现中,我们每次调用 ShapeFactory的 getChart() 的时 候,都要创建一个新的 Shape。实际上,如果 Shape可以复用,为了节省内存和对象创建 的时间,我们可以将 Shape事先创建好缓存起来。当调用 getChart() 函数的时候,我们从缓存中取出 Shape对象直接使用。 这有点类似单例模式和简单工厂模式的结合 。
1.2.4 工厂方法代码实现
- Product(抽象产品):所有具体产品的父类
- ConcreteProduct(具体产品):实现抽象产品类的具体的类,与具体工厂一一对应
- Factory(抽象工厂):提供具体工厂所需要实现的接口集合,供客户端调用
- ConcreteFactory(具体工厂):每一个具体的工厂生产一种具体的产品
public interface Car {
String getCarBrand();
}
public class BMWCar implements Car {
private String brand;
@Override
public String getCarBrand() {
return "宝马";
}
}
public interface CarFactory {
Car getCar();
}
public class BMWCarFactory implements CarFactory{
@Override
public Car getCar() {
return new BMWCar();
}
}
和简单工厂比较,从具体的工厂类通过if else来判断来生产对象,转变为多态形式由工厂类的子类进行生产。
1.2.5 抽象工厂代码实现
- AbstractProduct(抽象产品):所有具体产品的父类,这里指单一产品的父类而不是一个产品族的共同父类
- ConcreteProduct(具体产品):具体的产品
- AbstractFactory(抽象工厂):包含多个创建同一产品族中产品的抽象工厂方法的抽象类,一个抽象工厂定义了一个输出的产品族
- ConcreteFactory(具体工厂):抽象工厂的实现
public interface Button {
void display();
}
// 省略ComboBox、TextField
public class GreenButton implements Button {
@Override
public void display() {
System.out.println("按钮显示绿色");
}
}
//省略 GreenComboBox、GreenTextField
public interface SkinFactory {
Button createButton();
ComboBox createComboBox();
TextField createTextField();
}
public class GreenSkinFactory implements SkinFactory {
@Override
public Button createButton() {
System.out.println("创建绿色按钮");
return new SpringButton();
}
@Override
public ComboBox createComboBox() {
System.out.println("创建绿色勾选框");
return new SpringComboBox();
}
@Override
public TextField createTextField() {
System.out.println("创建绿色文本域");
return new SpringTextField();
}
}
和工厂方法比较, 让一个工厂类负责创建多个不同类型的对象(Button、ComboBox、TextField),而不是原来的一个对象, 可以有效地减少工厂类的个数 。
1.2.6 工厂模式与 DI 容器
DI 容器底层最基本的设计思路就是基于工厂模式的 , 一个工厂类只负责某个类对象或者某一组相关类对象(继承自同一抽 象类或者接口的子类)的创建,而 DI 容器负责的是整个应用中所有类对象的创建。 除此之外,DI 容器负责的事情要比单纯的工厂模式要多。比如,它还包括配置的解析、对象生命周期的管理。
实现一个简单的DI容器思路
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml")
applicationContext.getBean();
//执行入口就是一组暴露给外部使用的接口和类
public class ClassPathXmlApplicationContext implements ApplicationContext {
private BeansFactory beansFactory;
private BeanConfigParser beanConfigParser;
public ClassPathXmlApplicationContext(String configLocation) {
this.beansFactory = new BeansFactory();
this.beanConfigParser = new XmlBeanConfigParser();
loadBeanDefinitions(configLocation);
}
private void loadBeanDefinitions(String configLocation) {
//1.根据文件名读取配置文件(省略)
...
//2.解析配置文件为统一的 BeanDefinition 格式
List<BeanDefinition> beanDefinitions = beanConfigParser.parse(in);
//3.BeansFactory 根据 BeanDefinition 来创建对象
beansFactory.addBeanDefinitions(beanDefinitions);
}
@Override
public Object getBean(String beanId) {
return beansFactory.getBean(beanId);
}
}
public class BeansFactory {
private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap();
private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap();
public void addBeanDefinitions(List<BeanDefinition> beanDefinitionList) {
for (BeanDefinition beanDefinition : beanDefinitionList) {
this.beanDefinitions.putIfAbsent(beanDefinition.getId(), beanDefinition)
}
for (BeanDefinition beanDefinition : beanDefinitionList) {
if (beanDefinition.isLazyInit() == false && beanDefinition.isSingleton()){
createBean(beanDefinition);
}
}
}
public Object getBean(String beanId) {
BeanDefinition beanDefinition = beanDefinitions.get(beanId);
if (beanDefinition == null) {
throw new NoSuchBeanDefinitionException("Bean is not defined: " + beanId)
}
return createBean(beanDefinition);
}
protected Object createBean(BeanDefinition beanDefinition) {
//通过反射来创建beanDefinitions中的对象
//单例对象会存放到singletonObjects中
}
}
1.3 建造者(BuliderPattern)
1.3.1 定义
指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。
1.3.2 应用场景
如果一个类中有很多属性, 类的属性之间有一定的依赖关系或者约束条件 ,或者希望创建不可变对象不提供set方法,就可以要考虑使用建造者模式 。
1.1.3 代码实现
public class ResourcePoolConfig {
private String name;
private int maxTotal;
private int maxIdle;
private int minIdle;
private ResourcePoolConfig(Builder builder) {
this.name = builder.name;
this.maxTotal = builder.maxTotal;
this.maxIdle = builder.maxIdle;
this.minIdle = builder.minIdle;
}
//我们将Builder类设计成了ResourcePoolConfig的内部类。
//我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。
public static class Builder {
private static final int DEFAULT_MAX_TOTAL = 8;
private static final int DEFAULT_MAX_IDLE = 8;
private static final int DEFAULT_MIN_IDLE = 0;
private String name;
private int maxTotal = DEFAULT_MAX_TOTAL;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
public ResourcePoolConfig build() {
// 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
...
return new ResourcePoolConfig(this);
}
//只列出setName,其他属性同理
public Builder setName(String name) {}
}
}
与工厂模式的区别,工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子 类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对 象,通过设置不同的可选参数,“定制化”地创建不同的对象。
1.4 原型式(PrototypePattern) —不常用
1.4.1 定义
如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同), 在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新 对象,以达到节省创建时间的目的 。
1.4.2 应用场景
创建对象包含的申请内存、给成员变量赋值这一过程,本身并不会花费太多时间, 或者说对于大部分业务系统来说,这点时间完全是可以忽略的。应用一个复杂的模式,只得 到一点点的性能提升,这就是所谓的过度设计 。只有在 创建对象包含的申请内存、给成员变量赋值这一过程,本身并不会花费太多时间, 或者说对于大部分业务系统来说,这点时间完全是可以忽略的。应用一个复杂的模式,只得 到一点点的性能提升,这就是所谓的过度设计 。
1.4.3 代码实现
浅拷贝:当对象被复制时,只复制对象本身与值类型的成员变量,引用类型的成员变量没有被复制
深拷贝:当对象被复制时,对象本身、值类型成员变量、引用类型成员变量都会被复制,原型对象与复制对象完全独立
//java实现Cloneable接口即可实现浅拷贝
class Realizetype implements Cloneable {
public Object clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (Realizetype) super.clone();
}
}
深拷贝的实现方式,也可以通过实现Cloneable接口并重写clone()进行深拷贝,但是对象层次较多,每层对象都需要实现接口重写方法;另一种方式是通过对象序列化实现深拷贝,注意被transient修饰的属性无法被拷贝。
2 结构型(StructuralPatterns)
结构型模式描述如何将类或对象按某种布局组成更大的结构。 它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。 由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
2.1 代理模式(ProxyPattern)
1.1.1定义
不改变原始类 (或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能
1.1.2 应用场景
对代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知
- 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
-
1.1.3 静态代理代码实现
抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
public interface Searcher {
String search(String userName, String keyWord);
}
public class RealSearcher implements Searcher {
@Override
public String search(String userName, String keyWord) {
return userName + keyWord;
}
}
public class ProxySearcher implements Searcher {
public static long count = 0L;
private RealSearcher realSearcher;
@Override
public String search(String userName, String keyWord) {
//代码增强
return realSearcher.search(userName,keyWord);
}
}
参照基于接口而非实现编程的设计思想,将原始类对象替换为代理类对象的时候,为了让代 码改动尽量少,在刚刚的代理模式的代码实现中,代理类和原始类需要实现相同的接口。
但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的(比如它来自一个 第三方的类库),对于这种外部类的扩展,我们一般都是采用继承的方式。这里也不例外。我们让代理类继承 原始类,然后扩展附加功能。
1.1.4 动态代理
(1)JDK动态代理
基于接口实现,要求委托类要是接口的实现。
Java的 java.lang.reflect 包下提供了 Proxy 类和一个InvocationHandler接口。
public interface Sleep {
void goToSleep();
}
public class SleepImpl implements Sleep {
@Override
public void goToSleep() {
System.out.println("睡觉");
}
}
public class MyInvocationHandler<T> implements InvocationHandler {
/**
* 被代理的目标对象
*/
private T target;
public MyInvocationHandler(T target) {
this.target = target;
}
/**
* 调用委托类的实际方法及执行增强逻辑
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强逻辑
System.out.println("前增强");
// 调用委托类的实际方法
Object result = method.invoke(target, args);
// 增强逻辑
System.out.println("后增强");
// 结果返回
return result;
}
}
// 实例化委托类
Sleep sleep = new SleepImpl();
// 生成代理对象
Sleep sleepProxy= (Sleep) Proxy.newProxyInstance(sleep.getClass().getClassLoader(),sleep.getClass().getInterfaces(),new MyInvocationHandler<Sleep>(sleep));
// 执行代理类增强后的方法
sleepProxy.goToSleep();
(2)CGLib动态代理
public class Sleep {
public void goToSleep() {
System.out.println("sleeping");
}
public class CGLibDynamicProxy implements MethodInterceptor {
/**
* 调用委托类的实际方法及执行增强逻辑
*/
@Override
public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 增强逻辑
System.out.println("前增强");
//调用委托类(父类中)的方法
Object result = methodProxy.invokeSuper(obj, objects);
// 增强逻辑
System.out.println("后增强");
// 返回执行结果
return result;
}
}
// 生成Sleep类的代理对象
Sleep sleepProxy= (Sleep) Enhancer.create(Sleep.class,new CGLibDynamicProxy());
// 执行代理对象增强后的方法
sleepProxy.goToSleep();
2.2 桥接模式(BridgePattern)—不常用
2.2.1 定义
一个类存在两个(或多个) 独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。通过组合关系来替代继承关系,避免继承层次的指数级爆炸。
2.2.2 应用场景
如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系
如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系
一个类存在两个独立变化的维度,且这两个维度都需要进行扩展(如,图形有颜色和形状的变化,手机有品牌和软件的变化)
2.2.3 代码实现
- Abstraction(抽象类):用于定义抽象类的接口,一般为抽象类而不是接口,其中维持一个Implementor的引用
- RefinedAbstraction(扩充抽象类):继承或者实现抽象类,通常情况下为具体类而不是抽象类,实现抽象类中定义的抽象业务方法,
在具体业务方法中可以调用Implementor中定义的业务方法 - Implementor(实现类接口):定义实现类的接口,一般而言,Implementor接口仅提供基本操作,并交由子类去实现
- ConcreteImplementor (具体实现类):实现Implementor中定义的基本操作方法
public abstract class Shape {
Color color;
public void setColor(Color color) {
this.color = color;
}
public abstract void draw();
}
public class Circle extends Shape{
public void draw() {
color.bepaint("圆形");
}
}
//其他形状一样
public class AdapteeA {
public void play() {
System.out.println("aaa");
}
}
public class White implements Color{
public void bepaint(String shape) {
System.out.println("白色的" + shape);
}
}
//其他颜色一样
public class Client {
public static void main(String[] args) {
//白色
Color white = new White();
//正方形
Shape square = new Square();
//白色的正方形
square.setColor(white);
square.draw();
//长方形
Shape rectange = new Rectangle();
rectange.setColor(white);
rectange.draw();
}
}
2.3 装饰模式(DecoratorPattern)
2.3.1 定义
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
2.3.2 应用场景
- 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
装饰器模式在 Java 语言中的最著名的应用莫过于 Java I/O 标准库的设计了。例如,InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。
2.3.3 代码实现
- Component(抽象构件):具体构件与抽象装饰类的共同父类,声明具体构件中实现的业务方法,它的出现能够让客户端一致的透明的对待装饰前和装饰后的类
- ConcreteComponent(具体构件):抽象构件的子类,实现具体的业务方法
- Decorator(抽象装饰类):抽象构件的子类,内部维持一个抽象构件的引用,通过该引用调用具体构件的业务方法
- ConcreteDecorator(具体装饰类):抽象装饰类的实现类,声明并实现各种装饰方法实现对具体构件的装饰
public abstract class Component {
/**
* 需要装饰的方法
*/
public abstract void display();
}
public class Window extends Component {
@Override
public void display() {
System.out.println("显示窗口");
}
}
//和具体构建继承同一个抽象类
public class ComponentDecorator extends Component {
private Component component;
@Override
public void display() {
component.display();
}
}
public class BlackBorderDecorator extends ComponentDecorator {
public BlackBorderDecorator(Component component) {
super(component);
}
@Override
public void display() {
super.display();
System.out.println("显示黑色边框");
}
}
public class ScrollBarDecorator extends ComponentDecorator {
public ScrollBarDecorator(Component component) {
super(component);
}
@Override
public void display() {
super.display();
System.out.println("显示滚动条");
}
}
public class Client {
public static void main(String[] args) {
// 声明具体构件
Component window = new Window();
// 装饰具体构件,这里相当于将装饰后的构件重新变为具体构件,方便再次装饰
ScrollBarDecorator decoratorSB = new ScrollBarDecorator(window);
// 对装饰后的构件进行再一次的装饰
BlackBorderDecorator decoratorBB = new BlackBorderDecorator(decoratorSB);
// 先调用具体构件的放
decoratorBB.display();
}
}
装饰器模式所包含的 4 个角色不是任何时候都要存在的,在有些应用环境下模式是可以简化的。
装饰器类和原始类继承同样的父类,这样我们可以对原始类“嵌套”多个装饰器类。
装饰器类是对功能的增强, 同样是组合关系的设计模式,代理模式中,代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功能。
// 代理模式的代码结构(下面的接口也可以替换成抽象类)
public interface IA {
void f();
}
public class A impelements IA {
public void f() { //... }
}
public class AProxy impements IA {
private IA a;
public AProxy(IA a) {
this.a = a;
}
public void f() {
// 新添加的代理逻辑
a.f();
// 新添加的代理逻辑
}
}
// 装饰器模式的代码结构(下面的接口也可以替换成抽象类)
public interface IA {
void f();
}
public class A impelements IA {
public void f() { //... }
}
public class ADecorator impements IA {
private IA a;
public ADecorator(IA a) {
this.a = a;
}
public void f() {
// 功能增强代码
a.f();
// 功能增强代码
}
}
2.4 适配器模式(AdapterPattern)
2.4.1 定义
将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类 可以一起工作 (如, USB 转接头 )。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高。
2.4.2 应用场景
- 封装有缺陷的接口设计
- 统一多个类的接口设计
- 替换依赖的外部系统
- 兼容老版本接口
-
2.4.3 代码实现
ITarget(目标抽象类):定义客户所需接口,可以是接口、抽象类或者具体类
- Adaptee(适配者):定义存在的接口被适配器适配
- Adaptor(适配器):继承实现ITarget关联Adaptee,完成接口的转换
public interface ITarget {
void f1();
void f2();
void fc();
}
public class Adaptee {
public void fa() { //... }
public void fb() { //... }
public void fc() { //... }
}
public class Adaptor extends Adaptee implements ITarget {
public void f1() {
super.fa();
}
public void f2() {
//...重新实现f2()...
}
// 这里fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点
}
public interface ITarget {
void f1();
void f2();
void fc();
}
public class Adaptee {
public void fa() { //... }
public void fb() { //... }
public void fc() { //... }
}
public class Adaptor implements ITarget {
private Adaptee adaptee;
public Adaptor(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void f1() {
adaptee.fa(); //委托给Adaptee
}
public void f2() {
//...重新实现f2()...
}
public void fc() {
adaptee.fc();
}
}
代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是 控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相 对独立地加以改变。
装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持 多个装饰器的嵌套使用。
适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理 模式、装饰器模式提供的都是跟原始类相同的接口。
2.5 外观模式(FacadePattern)
2.5.1 定义
一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。
2.5.2 应用场景
- 解决易用性问题 ,
解决性能问题(分布式事务问题 ), 将多个接口调用替换为一个门面接口调用,减少网络通信成本,提高App客户端的响应速度。
2.5.3 代码实现
Facade(外观角色):客户端调用子系统功能的入口,正常情况下,它将所有客户端发过来的请求委托给相应的子系统处理
- SubSystem(子系统):可以是一个类、一组类、一个模块或者是一个系统,可以被客户端直接调用或者被外观类调用。子系统并不知道外观的存在,对于它而言外观也是客户端
public class Facade {
private SubSystemA sa;
private SubSystemB sb;
private SubSystemC sc;
public Facade() {
sa = new SubSystemA();
sb = new SubSystemB();
sc = new SubSystemC();
}
public void show() {
sa.show();
sb.show();
sc.show();
}
}
public class SubSystemA {
public void show() {
System.out.println("aaa");
}
}
public class SubSystemB {
public void show() {
System.out.println("bbb");
}
}
public class SubSystemC {
public void show() {
System.out.println("ccc");
}
}
尽量保持接口的可复用性,但针对特殊情况,允许提供冗余的门面接口, 来提供更易用的接口 。
2.6 组合模式(CompositePattern)—不常用
2.6.1 定义
将一组对象组织成树形结构,将单个对象和组合对象都看做树中的节点,以统一处理逻辑,并且它利用树形结构的特点,递归地处理每个子树,依次简化代码实现 。 说是对业务场景的一种数据结构和算法的抽象。
2.6.2 应用场景
2.6.3 代码实现
- Component(抽象构件):叶子构件与容器构件共同继承的父类或者是共同实现的接口,该角色中包含所有子类共有方法的声明和实现,在抽象构件中定义了管理子构件的方法,新增构件、删除构件、获取构件。
- Composite(容器构件):表示容器节点,包含子节点,子节点可以是容器节点也可以是叶子节点,其提供一个集合来对子节点进行维护,以迭代的方式对子节点进行处理。
- Leaf(叶子构件):表示叶子节点,没有子节点,对于继承父类的管理子节点的方法以抛出异常的方式处理。
public abstract class HumanResource {
protected long id;
protected double salary;
public HumanResource(long id) {
this.id = id;
}
public long getId() {
return id;
}
//计算出部门的薪资成本(隶属于这个部门的所有员工的薪资和)
public abstract double calculateSalary();
}
public class Department extends HumanResource {
private List<HumanResource> subNodes = new ArrayList<>();
public Department(long id) {
super(id);
}
@Override
public double calculateSalary() {
double totalSalary = 0;
for (HumanResource hr : subNodes) {
totalSalary += hr.calculateSalary();
}
this.salary = totalSalary;
return totalSalary;
}
public void addSubNode(HumanResource hr) {
subNodes.add(hr);
}
}
public class Employee extends HumanResource {
public Employee(long id, double salary) {
super(id);
this.salary = salary;
}
@Override
public double calculateSalary() {
return salary;
}
}
public class Demo {
private static final long ORGANIZATION_ROOT_ID = 1001;
private DepartmentRepo departmentRepo; // 依赖注入
private EmployeeRepo employeeRepo; // 依赖注入
public void buildOrganization() {
Department rootDepartment = new Department(ORGANIZATION_ROOT_ID);
buildOrganization(rootDepartment);
}
private void buildOrganization(Department department) {
List<Long> subDepartmentIds = departmentRepo.getSubDepartmentIds(department.getSubDepartmentIds())
for (Long subDepartmentId : subDepartmentIds) {
Department subDepartment = new Department(subDepartmentId);
department.addSubNode(subDepartment);
buildOrganization(subDepartment);
}
List<Long> employeeIds = employeeRepo.getDepartmentEmployeeIds(department.getEmployeeIds())
for (Long employeeId : employeeIds) {
double salary = employeeRepo.getEmployeeSalary(employeeId);
department.addSubNode(new Employee(employeeId, salary));
}
}
}
2.7 享元模式(FlyweightPattern)—不常用
2.7.1 定义
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
2.7.2 应用场景
当一个系统中存在大量重复对象的时候,如果这些重复的对象是不可变对象 , 就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。
2.7.3 代码实现
- Flyweight(抽象享元类):通常是一个接口或者是抽象类,在抽象享元类中声明了共享享元类的公共方法,通过这些方法可以访问享元类的内部数据(内部状态),也可以设置享元类的外部数据(外部状态)
- Concrete Flyweight(共享具体享元类):实现抽象享元类,其实例称为享元对象,一般为单例模式,提供唯一的享元对象
- Flyweight Factory(享元工厂类):创建并管理享元对象,以内部的键值对结构存储享元对象
public interface ChessPieces {
void operation();
}
public class ChessPieceUnit implements ChessPieces{
private int id;
private String text;
private Color color;
public ChessPieceUnit(int id, String text, Color color) {
this.id = id;
this.text = text;
this.color = color;
}
public static enum Color {
RED, BLACK
}
// ...省略其他属性和getter方法...
@Override
public void operation() {
//...省略操作方法
}
}
public class ChessPieceUnitFactory {
private static final Map<Integer, ChessPieceUnit> pieces = new HashMap<>();
static {
pieces.put(1, new ChessPieceUnit(1, "車", ChessPieceUnit.Color.BLACK));
pieces.put(2, new ChessPieceUnit(2,"馬", ChessPieceUnit.Color.BLACK));
//...省略摆放其他棋子的代码...
}
public static ChessPieceUnit getChessPiece(int chessPieceId) {
return pieces.get(chessPieceId);
}
}
主要是通过工厂模式,在工厂类中,通过一个 Map 来缓存已经创建过的享元对象,来达到复用的目的 。
享元模式是为了对象的“共享使用”,节省内存 。
单(多)例模式是为了保证对象全局唯一(几) 。
缓存是为了提高访问效率,而非复用。
池化技术中的“复用”理解为“重复使用”,主要是为了节省时间。
2.7.4 在JDK中的实现
Java Integer -128 到 127 之间的整型对象会被事先创建好,缓存在 IntegerCache 类 。 这里的IntegerCache 类就是享元工厂类,事先创建好的整型对象就是享元对象 。
Java String 字符串常量池并非事先创建好需要共享的对象,而是在程序的运行期间,根据需要来创建和缓存字符串常量 。
3 行为型(BehavioralPatterns)
用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间的交互,共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
3.1 观察者模式(ObserverPattern)
3.1.1 定义
在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依 赖的对象都会自动收到通知
3.1.2 应用场景
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
- 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
- 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
3.1.3 代码实现
- Subject(抽象目标):被观察的对象,内部定义一个观察者集合,并提供管理方法,定义通知方法notify()。
- ConcreteSubject(具体目标类):继承目标类,实现notify中具体的方法,可省略。
- Observer(观察者):对目标类做出的变化进行反应,一般为接口,声明更新方法update()。
- ConcreteObserver(具体观察者):继承观察者,一般持有指向具体目标类的引用,实现update方法,调用具体目标类完成具体的业务操作。
public abstract class Subject {
protected List<Observer> observers = new ArrayList<>();
//增加观察者方法
public void add(Observer observer) {
observers.add(observer);
}
//删除观察者方法
public void remove(Observer observer) {
observers.remove(observer);
}
//通知观察者方法
public abstract void notify();
}
public class ConcreteSubject extends Subject {
@Override
public void notify(Object args) {
System.out.println("目标类更改了状态");
for (Observer observer : this.observers) {
observer.update();
}
}
}
interface Observer {
void response(); //反应
}
class ConcreteObserver implements Observer {
public void response() {
System.out.println("具体观察者作出反应!");
}
}
目标类和观察者进行抽象以便后期对程序进行扩展,目标类持有观察者的引用,实现自身变化时对观察者更新方法的调用。
3.1.3 观察者不同实现方式
不同的应用场景和需求下,这个模式也有截然不同的实现方式,有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。
如果接口是一个调用比较频繁的接口,对性能非常敏感,希望接口的响应时间尽可能短,那我们可以将同步阻塞的实现方式改为异步非阻塞的实现方式,以此来减少响应时间。
启动一个新的线程来执行观 察者的处理函数 。
3.2 模板方法模式(TemplateMethodPattern)
3.2.1定义
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。 模板模式主要是用来解决复用和扩展两个问题。
3.2.2 应用场景
- 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
- 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
- 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展
3.2.3 代码实现
- Abstract Class(抽象类):抽象类,提供默认实现的具体方法与供子类实现的抽象方法。
- Concrete Class(具体子类):实现父类声明的抽象方法,重写父类的逻辑。
public abstract class AbstractClass {
public final void templateMethod() {
//...
method1();
//...
method2();
//...
}
protected abstract void method1();
protected abstract void method2();
}
public class ConcreteClass extends AbstractClass {
@Override
protected void method1() {
//...
}
@Override
protected void method2() {
//...
}
}
public class Client {
public static void main(String[] args) {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();
}
}
抽象类提供模板方法(包含基本方法的执行顺序,可被重写),里边包含具体方法(子类的通用方法),抽象方法(供子类重写的方法),钩子方法(供子类重写控制模板方法辑的方法),子类重写父类,使类具有不同的功能。
3.2.4 回调
回调也能起到跟模板模式相同的作用 。 A 类事先注册某个函数 F 到 B 类,A 类在调用 B 类的 P 函数的时候,B 类反过来调用 A 类注册给它的 F 函数。这里的 F 函数就是“回调函数”。A 调用 B,B 反过来又调用 A,这种调用机制就叫作“回调”。
public interface ICallback {
void methodToCallback();
}
public class BClass {
public void process(ICallback callback) {
//...
callback.methodToCallback();
//...
}
}
public class AClass {
public static void main(String[] args) {
BClass b = new BClass();
b.process(new ICallback() { //回调对象
@Override
public void methodToCallback() {
System.out.println("Call back me.");
}
});
}
}
回调基于组合关系来实现,模板模式基于继承关系来实现,回调比模板模式更加灵活。同步回调跟模板模式几乎一致。它们都是在一个大的算法骨架中,自 由替换其中的某个步骤,起到代码复用和扩展的目的。
3.3 策略模式(StrategyPattern)
3.3.1定义
定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端 。
3.3.2 应用场景
最常见的应用场景是,利用它来避免冗长的 if-else 或 switch 分支判断。
它也可以像模板模式那样,提供框架的扩展点等
3.3.3 代码实现
- Strategy(抽象策略类):声明策略方法。
- Concrete Strategy(具体策略类):实现策略方法。
public interface Strategy {
void algorithmInterface();
}
public class ConcreteStrategyA implements Strategy {
@Override
public void algorithmInterface() {
System.out.println("AAA");
}
}
public class ConcreteStrategyB implements Strategy {
@Override
public void algorithmInterface() {
System.out.println("BBB");
}
}
public class Client {
public static void main(String[] args) {
Strategy strategy = new ConcreteStrategyA();
strategy.doSomething();
strategy = new ConcreteStrategyB();
strategy.doSomething();
}
}
策略的创建,可以交给对应的工厂类创建。
策略的的使用,上面的代码是在代码中指定使用哪种策略并不能发挥策略模式的优势 ,根据配置、用户输入、计算结果等这些不确定因素,动态决定使用哪种策略会更好。
3.4 责任链模式(ChainOfResponsibilityPattern)
3.4.1 定义
多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请 求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再 传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责 。
3.4.2 使用场景
- 多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。
- 可动态指定一组对象处理请求,或添加新的处理者。
- 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求
3.4.3 代码实现
- Handler(抽象处理者):所有具体处理者的父类,一般定义为抽象类,定义了一个统一的处理入口,以及维持了一个抽象处理者类型对象的引用,用于形成链式处理者
- ConcreteHandler(具体处理者):继承抽象处理者,实现统一的处理入口,将自己无法处理的请求转发给下一个处理者
- HandlerChain ( 处理器链 ):组装责任链
public interface IHandler {
boolean handle();
}
public class HandlerA implements IHandler {
@Override
public boolean handle() {
boolean handled = false;
//...
return handled;
}
}
public class HandlerB implements IHandler {
@Override
public boolean handle() {
boolean handled = false;
//...
return handled;
}
}
public class HandlerChain {
private List<IHandler> handlers = new ArrayList<>();
public void addHandler(IHandler handler) {
this.handlers.add(handler);
}
public void handle() {
for (IHandler handler : handlers) {
boolean handled = handler.handle();
if (handled) {
break;
}
}
}
}
public class Application {
public static void main(String[] args) {
HandlerChain chain = new HandlerChain();
chain.addHandler(new HandlerA());
chain.addHandler(new HandlerB());
chain.handle();
}
}
3.4.4 代码实战
Servlet Filter 和 Spring Interceptor 都用来实现对 HTTP 请求进行拦截处理。
Servlet Filter 是 Servlet 规范的一部分,实现依赖于 Web 容器。 Spring Interceptor 是 Spring MVC 框架的一部分,由 Spring MVC 框架来提供实现。职责链模式常用在框架开发中,用来实现框架的过滤器、拦截器功能,让框架的使用者在不 需要修改框架源码的情况下,添加新的过滤拦截功能。
3.5 状态模式(StatePattern)
3.5.1 定义
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
状态机有 3 个组成部分:状态(State)、事件(Event)、动作(Action)。其中,事件也称为转 移条件(Transition Condition)。 事件触发状态的转移及动作的执行。不过,动作不是必 须的,也可能只转移状态,不执行任何动作。
3.5.1 使用场景
3.5.2 代码实现
- Context(状态机):将自身的状态与行为分离出去,封装成状态类,持有抽象状态类的引用,根据自身属性变换更换具体状态类。
- State(抽象状态类):定义一个接口,用以封装环境对象中的特定状态所对应的事件
- Concrete State(具体状态类):实现抽象状态所对应的事件,并且在需要的情况下进行状态切换。
public interface IMario {
State getName();
//以下是定义的事件
void obtainMushRoom();
void obtainCape();
void obtainFireFlower();
void meetMonster();
}
public class SmallMario implements IMario {
private MarioStateMachine stateMachine;
public SmallMario(MarioStateMachine stateMachine) {
this.stateMachine = stateMachine;
}
@Override
public State getName() {
return State.SMALL;
}
@Override
public void obtainMushRoom() {
stateMachine.setCurrentState(new SuperMario(stateMachine));
stateMachine.setScore(stateMachine.getScore() + 100);
}
@Override
public void obtainCape() {
stateMachine.setCurrentState(new CapeMario(stateMachine));
stateMachine.setScore(stateMachine.getScore() + 200);
}
@Override
public void obtainFireFlower() {
stateMachine.setCurrentState(new FireMario(stateMachine));
stateMachine.setScore(stateMachine.getScore() + 300);
}
@Override
public void meetMonster() {
// do nothing...
}
}
//省略SuperMario、CapeMario、FireMario状态
public class MarioStateMachine {
private int score;
private IMario currentState; // 不再使用枚举来表示状
public MarioStateMachine() {
this.score = 0;
this.currentState = new SmallMario(this);
}
public void obtainMushRoom() {
this.currentState.obtainMushRoom();
}
public void obtainCape() {
this.currentState.obtainCape();
}
public void obtainFireFlower() {
this.currentState.obtainFireFlower();
}
public void meetMonster() {
this.currentState.meetMonster();
}
public int getScore() {
return this.score;
}
public State getCurrentState() {
return this.currentState.getName();
}
public void setScore(int score) {
this.score = score;
}
public void setCurrentState(IMario currentState) {
this.currentState = currentState;
}
}
第一种实现方式叫分支逻辑法。利用 if-else 或者 switch-case 分支逻辑,参照状态转移 图,将每一个状态转移原模原样地直译成代码。简单的状态机来说,这种实现方式最简 单、最直接,是首选。
第二种实现方式叫查表法。对于状态很多、状态转移比较复杂的状态机来说,查表法比较合适。
通过二维数组来表示状态转移图,能极大地提高代码的可读性和可维护性。
第三种实现方式叫状态模式。对于状态并不多、状态转移也比较简单,但事件触发执行的动 作包含的业务逻辑可能比较复杂的状态机来说,我们首选这种实现方式。
3.6 迭代器模式(IteratorPattern)
3.6.1 定义
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
3.6.2 应用场景
3.6.3 代码实现
- Iterator(抽象迭代器):它定义了访问和遍历元素的接口,声明了用于遍历数据元素的方法。
- ConcreteIterator(具体迭代器):实现抽象迭代器,实现了遍历元素数据的方法,通过内部游标(非负整数)的方式记录访问的位置。
- Aggregate(抽象聚合类):存储和管理元素对象,声明了createIterator ()方法用于创建一个迭代器对象,充当抽象迭代器工厂角色。
- ConcreteAggregate(具体聚合类):实现createIterator ()方法,返回一个与之对应的具体迭代器角色。
public interface Iterator<E> {
boolean hasNext();
void next();
E currentItem();
}
public class ArrayIterator<E> implements Iterator<E> {
private int cursor;
private ArrayList<E> arrayList;
public ArrayIterator(ArrayList<E> arrayList) {
this.cursor = 0;
this.arrayList = arrayList;
}
@Override
public boolean hasNext() {
return cursor != arrayList.size(); //注意这里,cursor在指向最后一个元素的时候,ha
@Override
public void next() {
cursor++;
}
@Override
public E currentItem() {
if (cursor >= arrayList.size()) {
throw new NoSuchElementException();
}
return arrayList.get(cursor);
}
}
public interface List<E> {
Iterator iterator();
//...省略其他接口函数...
}
public class ArrayList<E> implements List<E> {
//...
public Iterator iterator() {
return new ArrayIterator(this);
}
//...省略其他代码
}
3.7 访问者模式(VisitorPattern)—不常用
3.7.1 定义
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
3.7.2 应用场景
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
3.7.3 代码实现
- Visitor(抽象访问者):内部声明一个对每一个具体元素进行访问的操作。
- Concrete Visitor(具体访问者):继承抽象访问者,实现对所有具体元素的一种访问操作。
- Element(抽象元素):声明accept()方法,以抽象访问者作为参数,供访问者访问。
- Concrete Element(具体元素):实现accept()方法,调用访问者的访问方法以完成访问者对自身的访问。
- Object Structure(对象结构):存储并管理元素,提供一个入口,使所有元素被访问。
public interface Visitor {
void visit(PdfFile pdfFile);
void visit(PPTFile pdfFile);
void visit(WordFile pdfFile);
}
public class Extractor implements Visitor {
@Override
public void visit(PPTFile pptFile) {
//...
System.out.println("Extract PPT.");
}
@Override
public void visit(PdfFile pdfFile) {
//...
System.out.println("Extract PDF.");
}
@Override
public void visit(WordFile wordFile) {
//...
System.out.println("Extract WORD.");
}
}
public abstract class ResourceFile {
protected String filePath;
public ResourceFile(String filePath) {
this.filePath = filePath;
}
abstract public void accept(Visitor vistor);
}
public class PdfFile extends ResourceFile {
public PdfFile(String filePath) {
super(filePath);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
//...
}
//...PPTFile、WordFile跟PdfFile类似,这里就省略了...
public class ToolApplication {
public static void main(String[] args) {
Extractor extractor = new Extractor();
List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]);
for (ResourceFile resourceFile : resourceFiles) {
resourceFile.accept(extractor);
}
Compressor compressor = new Compressor();
for(ResourceFile resourceFile : resourceFiles) {
resourceFile.accept(compressor);
}
private static List<ResourceFile> listAllResourceFiles(String resourceDirecto)
List<ResourceFile> resourceFiles = new ArrayList<>();
//...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile)
resourceFiles.add(new PdfFile("a.pdf"));
resourceFiles.add(new WordFile("b.word"));
resourceFiles.add(new PPTFile("c.ppt"));
return resourceFiles;
}
}
3.7.4 其他
支持双分派的语言不需要访问者模式 ,java不支持分派
访问者模式难以理解,应用场景有限,不是特别必需 ,以上例子可以使用工厂模式替换
3.8 备忘录模式(MementoPattern)
3.8.1 定义
在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外 保存这个状态,以便之后恢复对象为先前的状态
3.8.2 应用场景
- 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
- 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。
3.8.3 代码实现
- Originator(原发器):需要被记录状态的类
- Memento(备忘录):记录原发器状态的类,内部含有原发器的部分属性或者全部属性,不能被其他类所修改
- Caretaker(管理者):管理备忘录
public class InputText {
private StringBuilder text = new StringBuilder();
public String getText() {
return text.toString();
}
public void append(String input) {
text.append(input);
}
public Snapshot createSnapshot() {
return new Snapshot(text.toString());
}
public void restoreSnapshot(Snapshot snapshot) {
this.text.replace(0, this.text.length(), snapshot.getText());
}
}
public class Snapshot {
private String text;
public Snapshot(String text) {
this.text = text;
}
public String getText() {
return this.text;
}
}
public class SnapshotHolder {
private Stack<Snapshot> snapshots = new Stack<>();
public Snapshot popSnapshot() {
return snapshots.pop();
}
public void pushSnapshot(Snapshot snapshot) {
snapshots.push(snapshot);
}
}
public class ApplicationMain {
public static void main(String[] args) {
InputText inputText = new InputText();
SnapshotHolder snapshotsHolder = new SnapshotHolder();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String input = scanner.next();
if (input.equals(":list")) {
System.out.println(inputText.toString());
} else if (input.equals(":undo")) {
Snapshot snapshot = snapshotsHolder.popSnapshot();
inputText.restoreSnapshot(snapshot);
} else {
snapshotsHolder.pushSnapshot(inputText.createSnapshot());
inputText.append(input);
}
}
}
}
3.9 命令模式(CommandPattern)—不常用
3.9.1 定义
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
3.9.2 应用场景
- Command(抽象命令类):所有具体命令类的父类,一般定义为抽象类或者接口,声明执行请求的execute()方法等。
- Concrete Command(具体命令类):继承抽象命令类,持有接收者的引用,在execute()等方法中调用接收者的方法已完成业务的处理。
3.9.3 代码实现
public interface Command {
void execute();
}
public class GotDiamondCommand implements Command {
// 省略成员变量
public GotDiamondCommand(/*数据*/) {
//...
}
@Override
public void execute() {
// 执行相应的逻辑
}
}
//GotStartCommand/HitObstacleCommand/ArchiveCommand类省略
public class GameApplication {
private static final int MAX_HANDLED_REQ_COUNT_PER_LOOP = 100;
private Queue<Command> queue = new LinkedList<>();
public void mainloop() {
while (true) {
List<Request> requests = new ArrayList<>();
//省略从epoll或者select中获取数据,并封装成Request的逻辑,
//注意设置超时时间,如果很长时间没有接收到请求,就继续下面的逻辑处理。
for (Request request : requests) {
Event event = request.getEvent();
Command command = null;
if (event.equals(Event.GOT_DIAMOND)) {
command = new GotDiamondCommand(/*数据*/);
} else if (event.equals(Event.GOT_STAR)) {
command = new GotStartCommand(/*数据*/);
} else if (event.equals(Event.HIT_OBSTACLE)) {
command = new HitObstacleCommand(/*数据*/);
} else if (event.equals(Event.ARCHIVE)) {
command = new ArchiveCommand(/*数据*/);
} // ...一堆else if...
queue.add(command);
}
int handledCount = 0;
while (handledCount < MAX_HANDLED_REQ_COUNT_PER_LOOP) {
if (queue.isEmpty()) {
break;
}
Command command = queue.poll();
command.execute();
}
}
}
}
在策略模式中,不同的策略具有相同的目的、不同的实现、互相之间可以替换。比如, BubbleSort、SelectionSort 都是为了实现排序的,只不过一个是用冒泡排序算法来实现 的,另一个是用选择排序算法来实现的。而在命令模式中,不同的命令具有不同的目的,对 应不同的处理逻辑,并且互相之间不可替换。
3.10 解释器模式(InterpreterPattern)
3.10.1 定义
解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解 释器用来处理这个语法 。
3.10.2 应用场景
3.10.3 代码实现
- AbstractExpression(抽象表达式):终结符表达式与非终结符表达式的共同父类,声明了抽象的解释行为。
- TerminalException(终结符表达式):抽象表达式的子类,包含文法中终结符的解释操作。
- NonterminalException(非终结符表达式):抽象表达式的子类,实现了文法中非终结符的解释操作,内部包含非终结符表达式或者终结符表达式。
- Context(环境类):上下文环境
interface AbstractExpression {
public void interpret(String info); //解释方法
}
class TerminalExpression implements AbstractExpression {
public void interpret(String info) {
//对终结符表达式的处理
}
}
class NonterminalExpression implements AbstractExpression {
private AbstractExpression exp1;
private AbstractExpression exp2;
public void interpret(String info) {
//非对终结符表达式的处理
}
}
class Context {
private AbstractExpression exp;
public Context() {
//数据初始化
}
public void operation(String info) {
//调用相关表达式类的解释方法
}
}
对代码的复杂性,解释器模式也不例外。它的代码实现的核心思想,就是将语法解析的工作 拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分一些小的 独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析
3.11 中介模式(MediatorPattern)
3.11.1 定义
中介模式定义了一个单独的(中介)对象,来封装一组对象之间的交互。 将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。 提供一个中介者完成对一系列操作的封装,降低对象间的系统耦合度。中介者模式又称为调停者模式。
3.11.2 应用场景
3.11.3 代码实现
- Mediator(抽象中介者):声明具体中介者公共的方法,被抽象同事类所引用,供具体同事类适用
- ConcreteMediator(具体中介者):实现抽象中介者公共的方法,引用具体同事类完成一系列操作的封装
- Colleague(抽象同事类):声明公共方法,引用抽象中介者,供子类调用
- ConcreteColleague(具体同事类):实现公共方法,编写具体业务方法供具体中介者调用或者调用中介者中的方法。
abstract class Mediator {
public abstract void register(Colleague colleague);
public abstract void relay(Colleague cl);
}
//具体中介者
class ConcreteMediator extends Mediator {
private List<Colleague> colleagues = new ArrayList<Colleague>();
public void register(Colleague colleague) {
if (!colleagues.contains(colleague)) {
colleagues.add(colleague);
colleague.setMedium(this);
}
}
public void relay(Colleague cl) {
for (Colleague ob : colleagues) {
if (!ob.equals(cl)) {
((Colleague) ob).receive();
}
}
}
}
abstract class Colleague {
protected Mediator mediator;
public void setMedium(Mediator mediator) {
this.mediator = mediator;
}
public abstract void receive();
public abstract void send();
}
class ConcreteColleague1 extends Colleague {
public void receive() {
System.out.println("具体同事类1收到请求。");
}
public void send() {
System.out.println("具体同事类1发出请求。");
mediator.relay(this); //请中介者转发
}
}
中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系 (或者依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟 n 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代 码的复杂度,提高了代码的可读性和可维护性。
观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的, 一个参与者只有一个身份,要么是观察者,要么是被观察者。而在中介模式的应用场景中, 参与者之间的交互关系错综复杂,既可以是消息的发送者、也可以同时是消息的接收者。