本篇文章是对于23种设计模式的一个全面的总结,受限于文章篇幅无法对每个设计模式做到全面的解析,但几乎每个设计模式都提供了案例和类图结构,非常适合快速复习和在学习设计模式之前的全预习把握。
💡文章的 pdf + markdown 版本可通过链接获取:设计模式
文章目录
- 单例模式(Singleton Pattern)
- 简单工厂模式(Simple Factory Pattern)
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 装饰器模式(Decorator Pattern)
- 适配器模式(Adaptor Pattern)
- 观察者模式(Observer Pattern)
- 外观模式(Facade Pattern)
- 状态模式(State Pattern)
- 策略模式(Strategy Pattern)
- 代理模式(Proxy Pattern)
- 责任链模式(Chain Of Responsibility Pattern)
- 模板方法模式(Template Method Pattern)
- 享元模式(FlyWeight Pattern)
- 命令模式(CommandPattern)
- 原型模式(Prototype Pattern)
- 备忘录模式(Memento Pattern)
- 迭代器模式(Iterator Pattern)
- 组合模式(Composite Pattern)
- 桥接模式(Bridge Pattern)
- 中介者模式(Mediator Pattern)
- 访问者模式(Visitor Pattern)
- 解释器模式(Interpreter Pattern)
单例模式(Singleton Pattern)
确保一个类只有一个实例,而且自行实例化并且向整个系统提供这个实例,也就表示它的构造方法不对外公开,而是通过暴露一个 static 的 getInstance 方法来进行实例对象的获取。
根据类的实例化时机可以分为饿汉式和懒汉式。
- 饿汉式即在类初始化的时候就进行实例化
class SignalPattern {private final static SignalPattern signalPattern = new SignalPattern();private SignalPattern() {}public static SignalPattern getInstance() {return signalPattern;}
}
- 懒汉式则是在类第一次被使用的时候才进行实例化
class SignalPattern {private static SignalPattern signalPattern;private SignalPattern() {}// 加锁保证时候实现一次public synchronized static SignalPattern getInstance() {if (signalPattern == null) {signalPattern = new SignalPattern();}return signalPattern;}
}
观察上面的懒汉式获取实例的方法中,如果我们将锁加到方法体的外面,在多线程的情况下,线程获取对象实例的时候必须等待其他线程获取完成,这个过程是非常冗余的,所以可以考虑将锁放到方法体中,来减小锁的控制范围,来构造一个双重检查锁。
比如上面的方法中,就可以将锁放置到 if
语句内,最终的效果是这样的:
class SignalPattern {private volatile static SignalPattern signalPattern;private SignalPattern() {}public static SignalPattern getInstance() {if (signalPattern == null) {synchronized (SignalPattern.class) {if (signalPattern == null) {signalPattern = new SignalPattern();}}}return signalPattern;}
}
里面进行了两次判断,这也就是为什么它被称为双重检查锁;此时就只有第一次获取到实例为空的时候才会使用到锁;但熟悉双重检查锁的朋友一定知道双重检查锁必须配合 volatile 关键字来使用,这是为什么呢?
为了提升程序运行的性能,编译器和处理器会选择对要执行的指令进行重排序,比如上面的 new SignalPattern()
构造方法其实是由三个指令构成的:1)分配内存 2)初始化对象 3)指向刚刚分配的地址;指令重排序可能会打乱上面的顺序,使得执行的情况可能会变成这样 1 → 3 → 2,这样的重排在单线程的情况下是没有任何影响的,但是在多线程的情况下就会产生影响,甚至导致严重的安全性问题。
比如在上面的实例获取方法中,如果 3 在 2 之前先执行,即使对象没有初始化完成,此时其他线程同样会判断 signalPattern
是非空的,从而拿到一个不完整的错误对象,此时就出现了问题;那么应该如何避免这种情况呢?就是通过 volatile 关键字,volatile 关键字可以保证:当写 volatile 关键字修饰的变量,可以确保 volatile 写之前的操作不会被编译器重排序到 volatile 写之后。
通过这个关键字就能保证 volatile 关键字修饰的变量写入顺序的正确性,从而避免了双重检查锁由于指令重排序导致的安全问题。
单例模式可以保证项目中使用的是一个实例,从而避免了重复的实例化导致的性能开销,但同样的,它的问题也是由于只有一个实例,所以一个线程去操控它的变量的时候,其他线程也可以同时操作,所以单例模式的表现形式应该是无状态的,以工具类的形式呈现。
在 Spring 框架中,默认情况下,Spring 容器使用单例模式(Singleton)来管理 bean 的依赖。这意味着,对于每个定义的 bean,Spring 容器会创建并维护一个单一的实例,并在每次需要该 bean 时返回这个实例。
与单例相对,Spring 也支持原型作用域,这意味着每次请求该 bean 时,都会创建一个新的实例,可以通过注解的方式修改
@Scope("prototype")
@Component
public class MyClass {// ...
}
简单工厂模式(Simple Factory Pattern)
有称为静态工厂方法,它属于类创建型模式,在简单工厂模式中,可以根据参数的不同返回不同的类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,创建的实例通常都拥有一个共同的父类。
比如常用的日期格式化类 DateFormat 中就使用了工厂模式来管理类的创建:
public class SimpleFactory {// 管理 Product 的创建public static Product getProduct(String type) {switch (type) {case "A": return new ProductA();case "B": return new ProductB();}throw new RuntimeException("type error");}public static void main(String[] args) {Product a = getProduct("A");a.print();}
}
abstract class Product{public abstract void print();
}
class ProductA extends Product{@Overridepublic void print() {System.out.println("A");}
}
class ProductB extends Product{@Overridepublic void print() {System.out.println("B");}
}
工厂类的优点是使用者不用关心类是如何创建的,这个过程交给工厂类负责,缺点就是工厂类不够灵活,新增或者删除其创建的实例都需要去修改代码。
工厂模式(Factory Pattern)
先来回顾一下简单工厂模式的缺点,简单工厂模式需要知道类的构造细节,所以在对类进行修改或者新增的时候,势必会导致简单工厂类业务代码的修改,这就违背了软件设计原则的开闭原则,即一个软件的实体,如 类、模块 等,应该对扩展开放,对修改关闭;导致了无法灵活的拓展。
在工厂模式中,核心工厂变为了一个抽象的接口,它不再负责实例的创建,而是定义创建实例需要实现的方法,交给它的子类去完成创建,也就是子工厂。
现在就可以给出工厂模式的定义了,也就是定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使得类的实例化延迟到了它的子类;最终的类图结构是这样的:
在 JDK 中也大量使用到了工厂模式,比如我们常见的集合类,里面定义了一个 iterator 方法,用于创建迭代器,而这个创建就是延迟到子类中去实现的。
抽象工厂模式(Abstract Factory Pattern)
由于工厂模式在父工厂中定义了一个抽象的实例化方法,这也就导致了通过工厂模式只能创建一类的实例,这是工厂模式的限制;而抽象工厂类将解决了这个问题。
也就是提供多个不同的接口来实现不同大类的创建。
但这样将引起了一个问题,当我们去新增工厂能够创建的类的时候,又必须去修改抽象的父工厂,又回到了之前简单工厂的问题,即违反了开闭原则。
装饰器模式(Decorator Pattern)
动态的给对象添加一些额外的功能,就添加功能来说,装饰模式比生成子类更加灵活。
它的类图结构是这样的
实际的实现类 ConcreteComponent 和 抽象的装饰器类 都需要实现 Component 接口
抽象装饰器类又引用了装饰器接口的实现类来辅助实现接口
具体装饰器 ConcreteDecorator 继承自抽象装饰器类,可以添加多种功能。
来看一个具体的案例
public class DecoratorPattern {public static void main(String[] args) {PhonePro phonePro = new PhonePro(new Phone1());phonePro.call();phonePro.takePhoto(); // 拓展的新功能}
}
// 接口
interface Phone { void call();}
// 最初的实现类
class Phone1 implements Phone {@Overridepublic void call() {System.out.println("通话中......");}
}
// 抽象装饰器类
abstract class PhoneDecorator implements Phone {private final Phone phone;PhoneDecorator(Phone phone) {this.phone = phone;}@Overridepublic void call() {phone.call();}
}
// 装饰器类
class PhonePro extends PhoneDecorator {PhonePro(Phone phone) {super(phone);}public void takePhoto() {System.out.println("拍照中");}
}
在上面的案例中,我们使用了装饰器模式在原有手机功能的基础上拓展了拍照功能。
适配器模式(Adaptor Pattern)
将一个类的接口变化成客户端期待的另一种接口,从而是的原本因为接口不匹配而无法在一起工作的两个类能够在一起工作。
中文使用者和英文使用者交流的时候因为语言不通需要一个翻译官(适配器),翻译官将中文使用者的话翻译(translate)成英文来让英文使用者能够听懂(使得接口匹配),能够完成工作,写出代码就是这样的:
public class AdapterPattern {public static void main(String[] args) {// 正常使用Adapter adapter = new Adapter(new Speaker());System.out.println(adapter.translate());}
}
// 中文
class Speaker {public String speak() {return "你好";}
}
interface Translator {String translate();
}
// 翻译者
class Adapter implements Translator {private final Speaker speaker;public Adapter(Speaker speaker) {this.speaker = speaker;}@Overridepublic String translate() {return speaker.speak() + "->" + "hello";}
}
观察者模式(Observer Pattern)
定义了对象之间的一种一对多的关系,是的每当一个对象状态发生改变的时候,其相关的依赖对象都得到通知并且自动被更新。
public class ObserverPattern {public static void main(String[] args) {Factory factory = new Factory();UserImpl user1 = new UserImpl("张三");UserImpl user2 = new UserImpl("李四");factory.getOrder(user1);factory.getOrder(user2);factory.notifyUser();}
}
interface Producer {/*** 处理订单*/void getOrder(User user);/*** 提醒取货*/void notifyUser();
}
// 工厂
class Factory implements Producer {private static final List<User> orders = new ArrayList<>();@Overridepublic synchronized void getOrder(User user) {orders.add(user);}@Overridepublic synchronized void notifyUser() {for (User user : orders) {user.get();}}
}
interface User{void get();
}
// 取货用户
class UserImpl implements User{String name;UserImpl(String name) {this.name = name;}@Overridepublic void get() {System.out.println(this.name + "取货");}
}
上面的 factory 被称为主题对象,user 就是观察者,主题对象中获取观察者,并且通过调用观察者的方法来通知观察者。
外观模式(Facade Pattern)
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行,外观模式提供一个更高层次的接口,是的子系统更加容易使用。
比如当你需要请假的时候,需要校医院出示证明、辅导员签字、学院审核;但是经过学校的整改以后,这些这些步骤可以交给学院的医生进行,医生协助你完整整个流程,将复杂的细节隐藏起来。这个医生就是那个更高层次的接口,负责外部(请假学生)与内部(学校)的通信。
public class FacadePattern {public static void main(String[] args) {CollegeDoctor collegeDoctors = new CollegeDoctor();System.out.println(collegeDoctors.leave());}
}
class Hospital {static boolean hospitalProve() {System.out.println("医院证明");return true;}
}
class Counselor {static boolean CounselorSignature() {System.out.println("辅导员签字");return true;}
}
class College {static boolean CollegeReview() {System.out.println("学院审核");return true;}
}
interface Facade {boolean leave();
}
class CollegeDoctor implements Facade {@Overridepublic boolean leave() {boolean a = Hospital.hospitalProve();boolean b = Counselor.CounselorSignature();boolean c = College.CollegeReview();return a && b && c;}
}
状态模式(State Pattern)
允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。
public class StatePattern {public static void main(String[] args) {Worker worker = new Worker();worker.work();worker.changeState(new Happy());worker.work();}
}
class State {void handle() {System.out.println("工作");};
}
class Happy extends State {@Overridevoid handle() {System.out.println("高兴的工作");}
}
class Sad extends State {@Overridevoid handle() {System.out.println("伤心的工作");}
}
class Mad extends State {@Overridevoid handle() {System.out.println("愤怒的工作");}
}
class Calm extends State {@Overridevoid handle() {System.out.println("平静的工作");}
}
class Worker {private State state = new State();public void changeState(State state) {this.state = state;}public void work() {state.handle();}
}
在上面的案例中,工人工作有四种状态,如果按照一般的方式,需要写四个 if-else 分支语句,上面通过定义 State 和它的实现字类,在其内部实现工作的具体流程。
策略模式(Strategy Pattern)
定义一组算法,将每个算法都封装起来,并且使得它们之间可以相互转化,策略模式算法独立于使用它的客户而变化,称之为政策(Policy)模式。
策略模式的类图和上面的状态模式几乎完全相同,但是与状态模式关注状态不同,策略模式关注的是某个行为的实现方式。
比如上面状态模式的案例中,由于状态不同,可能会导致一系列方法执行的不通,除了工作行为还可能影响休息、吃饭等行为;但是策略模式比起你此时的状态,更关心的是你某个行为的实现方式,比如你如何工作,如何休息。
举一个策略模式的案例,比如说商场的打折活动,根据不同的打折策略计算出来的价值也不同,此时就可以定义多种策略,通过在计算的时候传入不同的策略对象来实现最终结果的计算。
比如在线程池的构造方法中,有这样的构造方法:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
其中的参数 handler 就是一种拒绝策略,
public interface RejectedExecutionHandler {void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
传入的是该接口的实现类,子类中实现了具体的拒绝策略。
代理模式(Proxy Pattern)
为其他对象提供一种这个对象的代理,以控制对这个对象的访问。
代理模式中其他对象无法直接访问这个对象,必须通过代理的方式来访问,这样就控制了访问的途径;比如我们日常生活中使用的网络代理就是这种模式。因为有一些服务器我们无法直接访问,所以可以通过代理服务器替我们转发请求,并且将返回内容再传输给我们的方式来实现访问。
public class ProxyPattern {public static void main(String[] args) {RealProjectProxy realProjectProxy = new RealProjectProxy();System.out.println(realProjectProxy.getImage());}}
interface Subject {String getImage();
}
class RealSubject implements Subject {@Overridepublic String getImage() {return "小猫图片";}
}
class RealProjectProxy implements Subject{private static final Subject subject = new RealSubject();private void connect() {System.out.println("建立连接");}private void log() {System.out.println("记录日志");}@Overridepublic String getImage() {connect();String image = subject.getImage();log();return image;}
}
在上面的案例中,我们需要去 RealSubject 中去获取图片,此时通过代理类去代理访问,并且建立链接和记录日志。
代理模式和装饰器模式可能会有些类似,但它们的关注点是不通的,代理模式关注的是控制对于某个对象的访问,而装饰器模式则是更注重与对对象功能的拓展。
责任链模式(Chain Of Responsibility Pattern)
是一种请求处理的模式,它让多个处理器都有机会处理这个请求,知道某个请求处理成功为止,责任连模式把多个处理器串成串,然后让请求在链上传递。
比如在公司需要请假的话,如果是小假期可以让开发经理直接审批,但是如果我们想要请示更长的假期,可能就需要部门经理来审批;再长的话可能就需要涉及到总经理。
对于请求者不用关心其中的处理,只需要将请求交给处理的开端,就可以得到最终的结果。
public class ChainOfResponsibilityPattern {public static void main(String[] args) {Handler1 handler1 = new Handler1();handler1.handle(10);}
}
abstract class Handler {protected Handler nextHandler;protected void setNextHandler(Handler handler) {this.nextHandler = handler;}public abstract void handle(int day);
}
class Handler1 extends Handler {Handler1() {setNextHandler(new Handler2());}@Overridepublic void handle(int day) {if (day <= 3) {System.out.println("1 处理成功");} else {nextHandler.handle(day);}}
}
class Handler2 extends Handler {Handler2() {setNextHandler(new Handler3());}@Overridepublic void handle(int day) {if (day <= 10) {System.out.println("2 处理成功");} else {nextHandler.handle(day);}}
}
class Handler3 extends Handler {@Overridepublic void handle(int day) {if (day <= 10) {System.out.println("3 处理成功");} else {System.out.println("请求失败");}}
}
这里我们对上面的案例来进行一个实现,里面的 handle 方法接受一个请假天数作为参数,然后将请求放到责任链中去处理,最终得到处理结果。
模板方法模式(Template Method Pattern)
定义一个操作中的方法的框架,而将一些步骤延迟到子类中去实现,使得子类可以在不改变一个算法的结构既可以定义某些特定的步骤。
jdk 中的 AQS 抽象类(AbstractQueuedSynchronizer)及其子类锁的实现就使用到了模板方法模式,在 AQS 中并没有提供 tryAcquire 等方法,而是开放到子类中去实现,从而实现各式各样的锁
protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();}
享元模式(FlyWeight Pattern)
运用共享技术有效地支持大量细类度的对象。
享元模式是为了解决公共资源的共享问题,当一个公共资源不需要考虑外部状态(如使用者是谁)的时候,通过单例模式就可以实现共享;但是如果公共资源需要针对外部状态做出改变的时候,单例模式就无法解决了,此时就可能需要创建大量的类,这样就造成了资源的浪费。
此时就可以使用到享元模式,享元模式的逻辑是首先有一个公共的享元类,享元类中提供一个方法来接收外部的参数(状态),然后根据不同的状态去执行逻辑,这样就避免了公共的实例对象被过多的创建。
享元模式的类图结构是这样的,首先有一个抽象的享元类,里面定义了享元的执行逻辑,通过继承这个类来实现各种的功能,享元类的创建委托给享元工厂完成,享元工厂负责返回、创建和管理具体的享元类。
享元模式被大量的用于池技术中,比如线程池、连接池等;以线程池距离,它维护了线程的创建和销毁,并且传入不通的任务来使得线程能够处理不同的外部请求。
public class FlyWeightPattern {public static void main(String[] args) {FlyWeightFactory flyWeightFactory = new FlyWeightFactory();SharedBike bike1 = flyWeightFactory.getBike("张三");SharedBike bike2 = flyWeightFactory.getBike("李四");flyWeightFactory.getBike("王五");bike1.back();SharedBike bike4 = flyWeightFactory.getBike("王五");}
}
interface Bike {void ride(String name);
}
class SharedBike implements Bike {/*** 车辆状态,0 空闲,1 使用中*/public int state = 0;public int getState() {return state;}@Overridepublic synchronized void ride(String name) {state = 1;System.out.println(name + "骑行中");}public synchronized void back() {state = 0;}}
class FlyWeightFactory {public static final List<SharedBike> sharedBikePool = new ArrayList<>();static {sharedBikePool.add(new SharedBike());sharedBikePool.add(new SharedBike());}public SharedBike getBike(String name) {for (SharedBike bike : sharedBikePool) {if (bike.getState() == 0) {bike.ride(name);return bike;}}System.out.println("无空闲车辆");return null;}
}
上面展示了一个共享单车案例,用享元模式来实现了资源的共享。
命令模式(CommandPattern)
命令模式是一种行为设计模式,它可以将请求转换为一个包含与请求相关的所有信息的独立对象。这个转换让你能够根据不同的请求将方法参数化,延迟请求执行或者将其放入队列中,且能实现可撤销的操作。
对命令模式最常见的实现就是服务器的 MVC 架构,MVC 架构的三层为模型(model)、视图(View)和控制器(Controller),通过 MVC 架构能够将视图层和业务层分割开来,那为什么要将它们分割开呢?
先来看看如果要讲它们写在一起会有什么后果,比如我们设计一个按钮实现刷新操作,按钮是一个视图的展示,将刷新的代码写到这个 Button 中,但如果别的地方也用到了刷新,比如点按 F5,此时就需要将刚刚的刷新代码复制过去;而这种直接复制往往就是我们最忌讳的事情,因为它会引发一系列的难以维护的问题。
此时就应该将视图对象和业务逻辑分隔开,使得复制方法得以复用
最终抽象出这样的类图结构
这好像也是定义了多种策略,可能入上面的策略模式混淆,但策略模式关注的是对象中某个方法的执行算法,而命令模式则更加强调命令的复用和与对象的绑定。
原型模式(Prototype Pattern)
用原型实例指定要创建对象的种类,并通过拷贝这些原型的属性来创建新的对象。通过拷贝自身的属性来创建一个新对象的过程叫作原型模式;也被称为克隆模式。
原型模式的是为了解决对象复制的问题的,如果我们获取到了一个对象,想要复制一个和它完全相同的对象,我们可以通过循环复制其中的属性的方式来构建新的对象,但这必须要求这个类提供了全部属性的访问和修改权限,且有时我们获取的运行类型和编译类型不相同的时候,我们甚至都无法知道对象中有哪些属性,复制更是无稽之谈了。
此时就需要寻求原本类的帮助,使得被复制的类实现一个 clone 方法,返回一个复制后的类。Java 中就提供了这样一个 Clonable 接口,继承了这个接口的类需要实现 clone 方法来返回克隆对象。
public interface Cloneable {}
接口中没有定义任何的方法,因为公共父类 Object 中已经实现了 protected 修饰的 clone 方法:
protected native Object clone() throws CloneNotSupportedException;
public class PrototypePattern {public static void main(String[] args) throws CloneNotSupportedException {A a = new A();A clone = a.clone();}
}
class A implements Cloneable {int a = 10;@Overridepublic A clone() throws CloneNotSupportedException {return (A)super.clone();}
}
备忘录模式(Memento Pattern)
备忘录模式是一种行为设计模式,它允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。
比如我们要实现一个文本编辑器,编辑器最重要的功能就是撤销模式;撤销模式的原理就是在用户修改的时候去保存上一个状态的快照,当用户撤销的时候,通过读取快照来使得文本恢复到之前的状态,而要获取快照,和上面的克隆相同,也需要去获取类的全部属性,此时也会遇到和克隆相同的问题:没有权限访问,此时同样需要委托需要做备忘录的类来实现备忘录功能。
首先需要定义一个快照类,里面存有所有的属性,然后这个快照类还存有一个额外内容,比如快照名称、时间戳等;然后建立一个快照栈结构来存储快照。
当存储快照的时候,在类内部构建一个快照对象然后将其存入到快照栈中,需要获取的时候从栈中弹出这个对象就能实现撤销恢复的操作。
public class MementoPattern {public static void main(String[] args) {Document document = new Document();document.change("1");document.change("12");document.change("123");document.print();document.resume();document.print();}
}
class Document {private String text;private final History history = new History();public void change(String text) {// 修改之前记录上个状态history.record(new BackUp(this.text));this.text = text;}public void resume() {text = history.getLastVersion().text;}public void print() {System.out.println(text);}
}
interface Memento{}
class BackUp implements Memento {public String text;public BackUp(String text) {this.text = text;}
}
class History {private final Stack<BackUp> backUpStack = new Stack<>();public void record(BackUp memento) {backUpStack.push(memento);}public BackUp getLastVersion() {return backUpStack.pop();}
}
上面的案例中构造了一个文本对象,并且在存储记录之前保存快照来实现撤销功能。
迭代器模式(Iterator Pattern)
迭代器模式是一种行为设计模式,能够在不暴露集合底层表现形式的情况下遍历集合中的所有元素。
迭代器模式大家一定都不陌生,Collection 接口就继承了 Iterable 接口,Iterable 接口中就定义了迭代器的获取和迭代等逻辑。
public interface Collection<E> extends Iterable<E>
我们可以通过获取迭代器的方式在不需要了解集合内容的情况下完成遍历。
public class IteratorPattern {public static void main(String[] args) {ArrayList<Integer> integers = new ArrayList<>();integers.add(1);integers.add(2);integers.add(3);Iterator<Integer> iterator = integers.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}}
}
增强 for 循环底层使用的也是这样的方式。
组合模式(Composite Pattern)
组合模式是一种结构性设计模式,可以使用它将对象组合成树形结构,并且能够像使用独立对象那样去使用它们。
这其实就类似于二叉树的后续遍历,叶子节点就是下面的 Leaf,而树枝节点就是 Composite,当我们需要统计叶子节点的总和的时候,其实就是层层递归的:
上面的案例中,展示了获取叶子节点的 val 之和的方式,树枝节点就是 Composite,它将所有的任务委派给了子元素,然后将子元素的结果整合作为本层结果返回给自己的上一层,最终在根节点得到最终的结果。
桥接模式(Bridge Pattern)
桥接模式是一种结构型设计模式,可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构,从而能在开发时分别使用。桥接模式将抽象和实现进行解耦,使得两者可以独立的变化
比如一个苹果类,我们将其按照品质分为两个类(优质和普通),这就需要两个实现子类,此时在通过颜色将其分类(红色和黄色),子类的个数通过组合变成了四个,此时再按照产地进分类…… 子类的个数指数级别的增长。
此时就可以使用桥接模式,让苹果还是只有两个子类,优质和普通,再通过引用颜色类的方式构造不同的分支;它的类图结构是这样的:
中介者模式(Mediator Pattern)
中介者模式是一种行为设计模式,能让你减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互,迫使它们通过一个中介者对象进行合作。
访问者模式(Visitor Pattern)
访问者模式用于封装一些作用于某周数据结构中各元素的操作,它可以在不改变数据结构的前提下,定义作用于这些元素的新的操作。
比如此时我们有一个机器人,想要让它的更新功能,最简单的做法肯定不是去替换它的硬件,而是去修改硬件的指令集。此时就可以通过传入一个软件包的方式来实现 Robot 的更新,此时就需要有一个访问者去访问机器人的硬件并且给它们传入新的指令。
首先定义了一个 Visitor 接口,里面定义了两个方法 visitCpu 和 visitDisk 来访问 Robot 中的硬件属性,所以需要获取到这个属性;在硬件接口中定义一个 accept 方法,在这个方法中会调用 Visitor 接口中的 visit 方法来将自己传入(或者自己某个属性的指针传入),然后在 visit 方法中进行修改,最终实现功能。
public class VisitorPattern {public static void main(String[] args) {Robot robot = new Robot();robot.run();SoftwarePackage softwarePackage = new SoftwarePackage();robot.accept(softwarePackage);robot.run();}
}
interface HardWares {void accept(Visitor visitor);
}
interface Visitor{void visitCpu(Cpu cpu);void visitDisk(Disk disk);
}
class SoftwarePackage implements Visitor {@Overridepublic void visitCpu(Cpu cpu) {cpu.command = "int a = 2;";}@Overridepublic void visitDisk(Disk disk) {disk.command = "int a = 3;";}
}
class Robot {Cpu cpu = new Cpu("int a = 1;");Disk disk = new Disk("int a = 2;");public void run() {System.out.println(cpu.command + "\n" + disk.command);}public void accept(Visitor visitor) {cpu.accept(visitor);disk.accept(visitor);}
}
class Cpu implements HardWares{public String command;Cpu(String command) {this.command = command;}@Overridepublic void accept(Visitor visitor) {visitor.visitCpu(this);}
}
class Disk implements HardWares{public String command;Disk(String command) {this.command = command;}@Overridepublic void accept(Visitor visitor) {visitor.visitDisk(this);}
}
解释器模式(Interpreter Pattern)
定义一个语言的文法,并且创建一个解释器来解释这个方法中的句子,这里的语言指的是使用规定格式和语法的代码。解释器模式是一种类行为模式。
解释器模式的类图结构是这样的,首先定义文法 Context,将其传入 Expression 解析器中执行解析,这个解析器根据是否终止分为 TerminalExpression 和 NoTerminalExpression,当内容进入第一个解析器的时候,它会不断调用其他对应的解析器,直到最终返回结果。
比如这里我们构造一个加法解析器,当遇到等号的时候输出结果,等号解析器就是终止解析器;首先定义一个 Context 类,里面含有指令和存储的中间值 value,提供两个方法去获取下一条指令;然后定义了解析器接口 Expression 抽象类,在抽象类中实现了获取下一个值和获取下一个符号的方法,并且声明了抽象方法 interpret 来执行实际的解析任务。
然后创建了加法执行器循环的处理语句,并且在遇到等号处理器的时候结束。
public class InterpreterPattern {public static void main(String[] args) {Context context = new Context("1+2+3=");AddExpression addExpression = new AddExpression();addExpression.interpret(context);}
}
class Context {String command;long value;int index = 0;Context(String command) {this.command = command;}
}
abstract class Expression {abstract void interpret(Context context);Integer findNextNum(Context context) {return (int) context.command.charAt(context.index++);}String findNextOpr(Context context) {return String.valueOf(context.command.charAt(context.index++));}
}
class AddExpression extends Expression {@Overridevoid interpret(Context context) {Integer nextNum = findNextNum(context);String nextOpr = findNextOpr(context);context.value += nextNum;switch (nextOpr) {case "+":this.interpret(context);return;case "=": new EqualExpression().interpret(context);}}
}
class EqualExpression extends Expression {@Overridevoid interpret(Context context) {System.out.println("res = " + context.value);}
}