🛸 设计模式七大原则
🌟 一段话记住七大原则:要实现易于维护和扩展,需要遵循开闭原则,面向接口或抽象类开发(依赖倒转),基本有两种方法:组合(合成复用),继承(里氏替换)。要实现更加的高内聚与低耦合,接口要满足接口分离,类要满足单一职责,类之间要满足最少知道。
1. 开闭原则
开闭原则的定义是对扩展开放,对修改关闭。当应用程序的需求需要改变或扩展时,我们应该在不修改其源代码的情况下,也能完成相应的目的。
开闭原则的优点是:使得应用程序更易于维护和扩展。
2. 依赖倒转原则
依赖倒转原则是实现开闭原则的基础,它的意思是当我们在编写面向对象的应用程序时,我们需要针对接口或抽象类编程, 而不具体的依赖某个实现类,这样可以降低系统之间的耦合性。
依赖倒转原则的优点是: 通过抽象建立系统之间的关系,使得系统具有高度的可维护性和可扩展性。
3. 合成复用原则
合成复用原则规定,当我们需要复用一些系统的代码的时候,应该优先考虑组合或聚合的方式实现,其次再考虑使用继承的方式实现。 如果一个基类的功能太多,而你只想复用一部分功能,使用继承就意味着不需要的功能也会被添加到当前系统中来,这就造成了不必要的麻烦。 所以可以优先考虑使用组合的方式完成代码的复用。
合成复用原则的优点是: 使系统易于维护,提高代码的可读性。
4. 里氏替换原则
里氏替换原则的定义是继承必须确保基类所拥有的性质在子类中仍然成立,即在基类出现的地方,其子类一定可以出现, 子类可以扩展基类的功能,但是尽量不要重写基类的功能。
里氏替换原则的优点是: 可以规范我们在正确的地方使用继承,而不至于造成继承的使用泛滥。
5. 接口分离原则
接口分离原则描述的是当一个接口的功能和职责太多时,我们需要将这个大接口分割成若干小接口,每一个小接口只服务于其对应的客户端。但是我们也需要控制每个小接口的粒度,如果粒度太小,那么会增加许多冗余的接口,不利于维护。
接口分离原则的优点是: 避免一个接口里含有不同的职责,每个接口的职责分明,与单一职责相似,都符合高内聚与低耦合的思想。
6. 单一职责原则
单一职责原则规定一个类应该有且仅有一个能够引起它变化的原因,否则此类应该被拆分。单一职责的意思是不应该让一个类 承担太多职责,否则如果一个职责修改,其他职责可能也会跟着修改,且如果一个客户端只需要这个类的一个职责时, 那么客户端不得不承受引入其他职责的代价。
单一职责原则的优点是: 提高了代码的可读性,不至于一个类里啥元素都有,且系统之间更加的高内聚与低耦合。
7. 最少知道原则
最少知道原则又称 “迪米特法则” , 它的定义是 “只与你的朋友交谈,不与陌生人说话”, 这句话的含义是如果两个软件实体或服务之间无需直接通信,那么就不应当发生直接的相互调用,可以通过第三方实体或服务进行转发通信。 其目的是为了降低系统的耦合度。
迪米特法则的优点是: 降低系统的耦合性,减少系统之间的关联,也符合高内聚与低耦合的思想。
🚢 二十三种设计模式
设计模式是解决问题的方案,学习现有的设计模式可以做到经验复用。拥有设计模式词汇,在沟通时就能用更少的词汇来讨论,并且不需要了解底层细节
创建型模式 Creational Pattern
✨ 创建型模式对类的实例化过程进行了抽象,能够【将软件模块中对象的创建和对象的使用分离】。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。
① ⭐️ 单例模式 Singleton
① 模式动机
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或 ID(序号)生成器
② 模式定义
单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
单例模式的要点有三个:
- 某个类只能有一个实例;
- 它必须自行创建这个实例;
- 它必须自行向整个系统提供这个实例。
③ 模式结构
Singleton
:单例
私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
④ 代码实现
(1) 懒汉式(线程不安全)
以下实现中,私有静态变量 singleton
被【延迟实例化】,这样做的好处是,如果没有用到该类,那么就不会实例化 LazySingleton_Unsafe
,从而节约资源。
这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if (singleton == null)
,并且此时 singleton
为 null
,那么会有多个线程执行 singleton = new Singleton();
语句,这将导致实例化多次 singleton
。
public class LazySingleton_Unsafe {
//创建 Singleton 的一个对象
private static LazySingleton_Unsafe singleton;
// 让构造函数为 private,这样该类就不会被外部实例化
private LazySingleton_Unsafe() {}
// 获取唯一可用的对象
public static LazySingleton_Unsafe getInstance() {
if (singleton == null) {
singleton = new LazySingleton_Unsafe();
}
return singleton;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
客户端测试:
public class Client5 {
public static void main(String[] args) {
//获取唯一可用的对象
LazySingleton_Unsafe singleton1 = LazySingleton_Unsafe.getInstance();
singleton1.showMessage();
}
}
(2) 懒汉式(线程安全)
只需要对 getInstance()
方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了实例化多次 Singleton。
但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,即使 Singleton 已经被实例化了。这会让线程阻塞时间过长,因此该方法有性能问题,不推荐使用。
public class LazySingleton_Safe {
//创建 Singleton 的一个对象
private static LazySingleton_Safe singleton;
// 让构造函数为 private,这样该类就不会被外部实例化
private LazySingleton_Safe() {}
// 获取唯一可用的对象
public static synchronized LazySingleton_Safe getInstance() {
if (singleton == null) {
singleton = new LazySingleton_Safe();
}
return singleton;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
(3)饿汉式(线程安全)
线程不安全问题主要是由于 Singleton 被实例化多次,采取直接实例化 Singleton 的方式就不会产生线程不安全问题。
但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
public class HungrySingleTon_Safe {
// 创建 Singleton 的一个对象并实例化
private static HungrySingleTon_Safe singleton = new HungrySingleTon_Safe();
// 让构造函数为 private,这样该类就不会被外部实例化
private HungrySingleTon_Safe() {}
// 获取唯一可用的对象
public static HungrySingleTon_Safe getInstance() {
return singleton;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
(4)双重校验锁(线程安全)
双重检验锁:double-checked locking,DCL
singleton 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 singleton 没有被实例化时,才需要进行加锁。
双重校验锁先判断 singleton是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
public class DCLSingleton_Safe {
private volatile static DCLSingleton_Safe singleton;
private DCLSingleton_Safe() {}
public static DCLSingleton_Safe getInstance() {
if (singleton == null) {
synchronized (DCLSingleton_Safe.class) {
if (singleton == null) {
singleton = new DCLSingleton_Safe();
}
}
}
return singleton;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
❓ 可以只加一次锁吗(不加内层锁)
如果只进行一次加锁,代码如下,也就是只使用了一个 if 语句。在 singleton == null
的情况下,如果两个线程都执行了 if 语句,那么两个线程都会进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 singleton = new DCLSingleton_Safe();
这条语句,只是先后的问题,那么就会进行两次实例化。
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new DCLSingleton_Safe();
}
}
因此必须使用双重校验锁,也就是需要使用两个 if 语句:第一个 if 语句用来避免 singleton
已经被实例化之后的加锁操作,而第二个 if 语句进行了加锁,所以只能有一个线程进入,就不会出现 singleton == null
时两个线程同时进行实例化操作。
❓ 可以不使用 volatile 吗
singleton 采用 volatile
关键字修饰也是很有必要的, singleton = new DCLSingleton_Safe();
这段代码其实是分为三步执行:
- 为 singleton 分配内存空间
- 初始化 singleton
- 将 singleton 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getInstance()
后发现 singleton 不为空,因此返回 singleton ,但此时 singleton 还未被初始化。
(5)静态内部类实现(线程安全)
**当 Singleton 类被加载时,静态内部类 SingletonHolder
没有被加载进内存。**只有当调用 getInstance()
方法从而触发 SingletonHolder.INSTANCE
时 SingletonHolder
才会被加载,此时初始化 INSTANCE
实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持
public class StaticInnerClassSingleton_Safe {
private StaticInnerClassSingleton_Safe singleton;
private static class SingeltonHolder {
private static final StaticInnerClassSingleton_Safe INSTANCE = new StaticInnerClassSingleton_Safe();
}
private StaticInnerClassSingleton_Safe () { }
public static final StaticInnerClassSingleton_Safe getInstance() {
return SingeltonHolder.INSTANCE;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
(6)枚举实现(线程安全)
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum
特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
public enum Singleton {
INSTANCE;
public void method() {
}
}
⑤ 总结
-
单例模式只包含一个单例角色:在单例类的内部实现只生成一个实例,同时它提供一个静态的工厂方法,让客户可以使用它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有
-
优点:提供了对唯一实例的受控访问并可以节约系统资源;
缺点:因为缺少抽象层而难以扩展,且单例类职责过重;
-
单例模式适用情况:
- 系统只需要一个实例对象;
- 客户调用类的单个实例只允许使用一个公共访问点;
⑥ jdk示例
java.lang.Runtime#getRuntime()
java.awt.Desktop#getDesktop()
java.lang.System#getSecurityManager()
② ⭐️ 简单工厂模式 Simple Factory
① 模式动机
例如:一个按钮系统可以提供多个外观不同的按钮(如圆形按钮、矩形按钮、菱形按钮等), 这些按钮都源自同一个基类,不过在继承基类后不同的子类修改了部分属性从而使得它们可以呈现不同的外观。如果我们希望在使用这些按钮时,不需要知道这些具体按钮类的名字,只需要知道表示该按钮类的一个参数,并提供一个调用方便的方法,把该参数传入方法即可返回一个相应的按钮对象,此时,就可以使用简单工厂模式。
② 模式定义
简单工厂模式又称为 静态工厂方法(Static Factory Method)模式,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。
③ 模式结构
-
Factory:工厂角色
工厂角色负责实现创建所有实例的内部逻辑(决定调用哪个类来创建实例)
-
(Abstrat)Product:抽象产品角色
抽象产品角色是所创建的【所有对象的父类】,负责描述所有实例所共有的公共接口
-
ConcreteProduct:具体产品角色
具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。
这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。
④ 代码实现
一个抽象的接口,多个抽象接口的实现类,一个工厂类
接口(Product)
public interface Car {
void run();
void stop();
}
接口实现类(ConcreteProduct)
public class CarImpl1 implements Car {
@Override
public void run() {
System.out.println("CarImpl1 is running");
}
@Override
public void stop() {
System.out.println("CarImpl1 stop running");
}
}
public class CarImpl2 implements Car {
@Override
public void run() {
System.out.println("CarImpl2 is running");
}
@Override
public void stop() {
System.out.println("CarImpl2 stop running");
}
}
实例化接口(Factory)
public class SimpleFactory {
public Car createCar(int type) {
if (type == 1) {
return new CarImpl1();
} else if (type == 2) {
return new CarImpl2();
}
return null;
}
}
客户端
💡 以下的客户端包含了实例化的代码,这是一种错误的实现。如果在客户端中存在这种实例化代码,就需要考虑将代码放到简单工厂中。
public class Client { public static void main(String[] args) { int type = 1; Car car; if (type == 1) { car = new CarImpl1(); } else if (type == 2) { car = new CarImpl2(); } else { car = null; } // do something with the product } }
public class Client {
public static void main(String[] args) {
SimpleFactory simpleFactory = new SimpleFactory();
Car car1 = simpleFactory.createCar(1);
car1.run();
Car car2 = simpleFactory.createCar(2);
car2.stop();
}
}
⑤ 总结
-
简单工厂模式包含三个角色:
Factory 负责实现创建所有实例的内部逻辑
Product 是所创建的所有对象的父类,负责描述所有实例所共有的公共接口
ConcreteProduct 是创建目标,所有创建的对象都充当这个角色的某个具体类的实例
-
简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。
-
【优点】:实现对象的创建和对象的使用分离,将对象的创建交给专门的工厂类负责。
【缺点】:工厂类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码,而且产品较多时,工厂方法代码将会非常复杂。
-
【适用情况】:工厂类负责创建的对象比较少;客户端只知道传入工厂类的参数,对于如何创建对象不关心。
⑥ jdk示例
Calendar 类
③ ⭐️ 工厂方法模式 Factory Method
① 模式动机
使用简单工厂模式时,增加一个新的产品实现方法需要改动源代码,耦合较大,于是将工厂创建方法抽象出来,变成一个抽象工厂类,各个不同的工厂去继承并实现创建产品的具体实现方法,这样在新增一个新的产品实现方法时,只需继承抽象工厂类即可,获得产品时使用向上转型,更加符合”开闭原则”。。
② 模式定义
工厂方法模式又称为工厂模式。工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象。
💡 在简单工厂中,创建对象的是父类,而在工厂方法中,是由子类来创建对象
工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点,可以允许系统在不修改工厂角色的情况下引进新产品。
③ 模式结构
- (Abstrat)Product:抽象产品
- ConcreteProduct:具体产品
- (Abstract)Factory:抽象工厂
- ConcreteFactory:具体工厂
④ 代码实现
public abstract class Factory {
abstract public Product factoryMethod();
public void doSomething() {
Product product = factoryMethod();
// do something with the product
}
}
public class ConcreteFactory extends Factory {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
public class ConcreteFactory1 extends Factory {
public Product factoryMethod() {
return new ConcreteProduct1();
}
}
public class ConcreteFactory2 extends Factory {
public Product factoryMethod() {
return new ConcreteProduct2();
}
}
//指定工厂1创建产品
Product product = new ConcreteFactory1();
product.doSomething();
实际应用举例
某系统日志记录器要求支持多种日志记录方式,如文件记录、数据库记录等,且用户可以根据要求动态选择日志记录方式, 可使用工厂方法模式设计该系统。
⑤ 总结
-
具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品
-
【优点】:增加新的产品类时无须修改现有系统,并封装了产品对象的创建细节,系统具有良好的灵活性和可扩展性;
【缺点】:增加新产品的同时需要增加新的工厂,导致系统类的个数成对增加,在一定程度上增加了系统的复杂性。
-
【适用情况】:
- 一个类不知道它所需要的对象的类;
- 一个类通过其子类来指定创建哪个对象;
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定。
⑥ jdk示例
④ ⭐️ 抽象工厂(Abstract Factory)
① 模式动机
比如智能电视和智能路由器组合成智能家居产品族,不同工厂(小米,华为等)具有不同的针对各个抽象产品的具体实现方法
② 模式定义
抽象工厂提供接口,用于创建相关的对象家族。属于一般化的工厂方法
③ 模式结构
- (Abstrat)Product:抽象产品
- ConcreteProduct:具体产品
- (Abstract)Factory:抽象工厂
- ConcreteFactory:具体工厂
④ 代码实现
public class AbstractProductA {
}
public class AbstractProductB {
}
public class ProductA1 extends AbstractProductA {
}
public class ProductA2 extends AbstractProductA {
}
public class ProductB1 extends AbstractProductB {
}
public class ProductB2 extends AbstractProductB {
}
public abstract class AbstractFactory {
abstract AbstractProductA createProductA();
abstract AbstractProductB createProductB();
}
public class ConcreteFactory1 extends AbstractFactory {
AbstractProductA createProductA() {
return new ProductA1();
}
AbstractProductB createProductB() {
return new ProductB1();
}
}
public class ConcreteFactory2 extends AbstractFactory {
AbstractProductA createProductA() {
return new ProductA2();
}
AbstractProductB createProductB() {
return new ProductB2();
}
}
public class Client {
public static void main(String[] args) {
AbstractFactory abstractFactory = new ConcreteFactory1();
AbstractProductA productA = abstractFactory.createProductA();
AbstractProductB productB = abstractFactory.createProductB();
// do something with productA and productB
}
}
⑤ 总结
将工厂模式一般化
⑥ jdk示例
javax.xml.parsers.DocumentBuilderFactory
javax.xml.transform.TransformerFactory
javax.xml.xpath.XPathFactory
🌟 三种工厂模式区别演化
刚开始时,为了封装统一管理产品的创建,将所有创建方法写到简单工厂中,提供一个参数来确定创建产品的具体实现方法。
但是增加一个新的产品实现方法需要改动源代码,耦合较大,于是将工厂创建方法抽象出来,变成一个抽象工厂类,各个不同的工厂去继承并实现创建产品的具体实现方法,这样在新增一个新的产品实现方法时,只需继承抽象工厂类即可,获得产品时使用向上转型。
前两个模式都是一对多,多个工厂生产一个产品。当有多个产品时,为了管理多个相关的产品,抽象工厂类中会存在多个抽象创建方法用来创建不同的产品而不是只有一个,具体的工厂实现类需要去实现所有产品的创建方法
⑤ ⭐️ 建造者模式(Builder)
① 模式动机
作为装机工,他们不用管你用的 CPU 是 Intel 还是 AMD,也不管你的显卡是 2000 千大元还是白送的,都能三下五除二的装配在一起——一台 PC 就诞生了
② 模式定义
将构造复杂对象的过程和组成对象的部件解耦。
建造模式可以使得产品内部的表象独立变化。在原来的工厂方法模式中,产品内部的表象是由产品自身来决定的;而在建造模式中则是“外部化”为由建造者来负责。这样定义一个新的具体建造者角色就可以改变产品的内部表象,符合“开闭原则”。
建造模式使得客户不需要知道太多产品内部的细节。它将复杂对象的组建和表示方式封装在一个具体的建造角色中,而且由指导者来协调建造者角色来得到具体的产品实例。
每一个具体建造者角色是毫无关系的。
③ 模式结构
-
抽象建造者角色:这个角色用来规范产品对象的各个组成成分的建造。一般而言,此角色独立于应用程序的业务逻辑。
-
具体建造者角色:担任这个角色的是于应用程序紧密相关的类,它们在指导者的调用下创建产品实例。这个角色在实现抽象建造者角色提供的方法的前提下,达到完成产品组装,提供成品的功能。
-
指导者角色:调用具体建造者角色以创建产品对象。指导者并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造者对象。
-
产品角色:建造中的复杂对象。它要包含那些定义组件的类,包括将这些组件装配成产品的接口。
④ 代码实现
以下是一个简易的 StringBuilder 实现,参考了 JDK 1.8 源码。
public class AbstractStringBuilder {
protected char[] value;
protected int count;
public AbstractStringBuilder(int capacity) {
count = 0;
value = new char[capacity];
}
public AbstractStringBuilder append(char c) {
ensureCapacityInternal(count + 1);
value[count++] = c;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
}
public class StringBuilder extends AbstractStringBuilder {
public StringBuilder() {
super(16);
}
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
}
public class Client {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
final int count = 26;
for (int i = 0; i < count; i++) {
sb.append((char) ('a' + i));
}
System.out.println(sb.toString());
}
}
⑤ 总结
- 建造者模式 & 抽象工厂模式:创建模式着重于逐步将组件装配成一个成品并向外提供成品,而抽象工厂模式着重于得到产品族中相关的多个产品对象
- 建造模式可以对复杂产品的创建进行更加精细的控制。产品的组成是由指导者角色调用具体建造者角色来逐步完成的,所以比起其它创建型模式能更好的反映产品的构造过程。
⑥ jdk示例
java.lang.StringBuilder
java.nio.ByteBuffer
java.lang.StringBuffer
java.lang.Appendable
⑥ ⭐️ 原型模式 (Prototype)
① 模式动机
我们借书时,不能在上面涂画做笔记,这时可以复印下来需要记笔记的书页,进行更改。
② 模式定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式使用 clone 能够动态的抽取当前对象运行时的状态并且克隆到新的对象中,新对象就可以在此基础上进行操作而不损坏原有对象;而 new 只能得到一个刚初始化的对象,而在实际应用中,这往往是不够的。
原型模式是在内存二进制流的拷贝,要被直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点
③ 模式结构
-
抽象原型角色:实现了自己的 clone 方法,扮演这种角色的类通常是抽象类,且它具有许多具体的子类。
-
具体原型角色:被复制的对象,为抽象原型角色的具体子类。
④ 代码实现
public abstract class Prototype {
abstract Prototype myClone();
}
public class ConcretePrototype extends Prototype {
private String filed;
public ConcretePrototype(String filed) {
this.filed = filed;
}
@Override
Prototype myClone() {
return new ConcretePrototype(filed);
}
@Override
public String toString() {
return filed;
}
}
public class Client {
public static void main(String[] args) {
Prototype prototype = new ConcretePrototype("abc");
Prototype clone = prototype.myClone();
System.out.println(clone.toString());
}
}
实际例子
绩效考核软件要对今年的各种考核数据进行年度分析,而这一组数据是存放在数据库中的。一般我们会将这一组数据封装在一个类中,然后将此类的一个实例作为参数传入分析算法中进行分析,得到的分析结果返回到类中相应的变量中。假设我们决定对这组数据还要做另外一种分析以对分析结果进行比较评定。这时对封装有这组数据的类进行 clone 要比再次连接数据库得到数据好的多。
⑤ 总结
-
【优点】:具有良好的扩展性
【缺点】:每个原型必须含有 clone 方法,在已有类的基础上来添加 clone 操作是比较困难的;而且当内部包括一些不支持 copy 或者循环引用的对象时,实现就更加困难了。
⑥ jdk示例
结构型模式
① ⭐️ 代理(Proxy)
① 模式动机
客户通过代理商得到了自己想要的东西,而且还享受到了代理商额外的服务;而生产厂商通过代理商将自己的产品推广出去,而且可以将一些销售服务的任务交给代理商来完成(当然代理商要和厂商来共同分担风险,分配利润),这样自己就可以花更多的心思在产品的设计和生产上了。
② 模式定义
为其他对象提供一种代理以控制对这个对象的访问。在一些情况下客户不想或者不能直接引用一个对象,而代理对象可以在客户和目标对象之间起到中介作用,去掉客户不能看到的内容和服务或者增添客户需要的额外服务。 代理模式的主要作用是 扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些额外的操作,并且不用修改这个方法的原有代码
代理模式分为 8 种,这里将几种常见的、重要的列举如下:
- 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。比如:你可以将一个在世界某个角落一台机器通过代理假象成你局域网中的一部分。
- 虚拟(Virtual)代理:根据需要将一个资源消耗很大或者比较复杂的对象延迟的真正需要时才创建。比如:如果一个很大的图片,需要花费很长时间才能显示出来,那么当这个图片包含在文档中时,使用编辑器或浏览器打开这个文档,这个大图片可能就影响了
文档的阅读,这时需要做个图片 Proxy 来代替真正的图片。 - 保护(Protect or Access)代理:控制对一个对象的访问权限。比如:在论坛中,不同的身份登陆,拥有的权限是不同的,使用代理模式可以控制权限(当然,使用别的方式也可以实现)。
- 智能引用(Smart Reference)代理:提供比对目标对象额外的服务。比如:纪录访问的流量(这是个再简单不过的例子),提供一些友情提示等等。
③ 模式结构
- Real Subject:真实类,也就是被代理类、委托类。用来真正完成业务服务功能;
- Proxy:代理类。将自身的请求用 Real Subject 对应的功能来实现,代理类对象并不真正的去实现其业务功能;
- Subject:定义 RealSubject 和 Proxy 角色都应该实现的接口。
④ 代码实现
静态代理
// 1.定义发送短信的接口
public interface SmsService {
String send(String message);
}
// 2.创建一个委托类(Real Subject)实现这个接口
public class SmsServiceImpl implements SmsService {
@Override
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
// 3.创建一个代理类(Proxy)同样实现这个接口
// 将委托类 Real Subject 注入进代理类 Proxy,在代理类的方法中调用 Real Subject 中的对应方法。
public class SmsProxy implements SmsService {
// 将委托类注入进代理类
private final SmsService smsService;
public SmsProxy(SmsService smsService) {
this.smsService = smsService;
}
@Override
public String send(String message) {
// 调用委托类方法之前,我们可以添加自己的操作
System.out.println("before method send()");
// 调用委托类方法
smsService.send(message);
// 调用委托类方法之后,我们同样可以添加自己的操作
System.out.println("after method send()");
return null;
}
}
// 4.实现
public class Main {
public static void main(String[] args) {
SmsService smsService = new SmsServiceImpl();
SmsProxy smsProxy = new SmsProxy(smsService);
smsProxy.send("Java");
}
}
从输出结果可以看出,我们已经增强了委托类 SmsServiceImpl
的 send()
方法。
当然,从上述代码我们也能看出来,静态代理存在一定的弊端。假如说我们现在新增了一个委托类实现了 SmsService
接口,如果我们想要对这个委托类进行增强,就需要重新写一个代理类,然后注入这个新的委托类,非常不灵活。也就是说静态代理是一个委托类对应一个代理类
动态代理
从 JVM 角度来说,动态代理是在运行时动态生成 .class
字节码文件 ,并加载到 JVM 中的
代理类无非是在调用委托类方法的前后增加了一些操作。委托类的不同,也就导致代理类的不同。
那么为了做一个通用性的代理类出来,我们把调用委托类方法的这个动作抽取出来,把它封装成一个通用性的处理类,于是就有了动态代理中的 InvocationHandler
角色(处理类)。
于是,在代理类和委托类之间就多了一个处理类的角色,这个角色主要是对代理类调用委托类方法的这个动作进行统一的调用
public interface SmsService {
String send(String message);
}
public class SmsServiceImpl implements SmsService{
@Override
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
// 负责创建代理角色的类
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
private SmsService smsService;
// 设置要代理的对象
public void setSmsService(SmsService smsService){
this.smsService = smsService;
}
// 生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
smsService.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before method send()");
Object result = method.invoke(smsService, args);
System.out.println("after method send()");
return result;
}
}
public class Client {
public static void main(String[] args) {
// 真实角色
SmsServiceImpl smsServiceImpl = new SmsServiceImpl();
// 负责创建代理角色的类
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setSmsService(smsServiceImpl);
// 动态创建代理角色
SmsService smsProxy = (SmsService) pih.getProxy();
smsProxy.send("hello");
}
}
⑤ 总结
代理模式能够协调调用者和被调用者,能够在一定程度上降低系统的耦合度。代理模式中的真实主题角色可以结合组合模式来构造,这样一个代理主题角色就可以对一系列的真实主题角色有效,提高代码利用率,减少不必要子类的产生。
⑥ jdk示例
java.lang.reflect.Proxy
RMI
② ⭐️ 适配器模式 (Adapter)
① 模式动机
比如USB转HDMI的转换器,两个接口不兼容可以通过中间加一层适配器来实现兼容
② 模式定义
将一个类的接口转换成客户希望的另外一个接口。 Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
③ 模式结构
- 目标(Target)角色:定义 Client 使用的接口。
- 被适配(Adaptee)角色:这个角色有一个已存在并使用了的接口,而这个接口是需要我们适配的。
- 适配器(Adapter)角色:这个适配器模式的核心。它将被适配角色已有的接口转换为目标角色希望的接口。
④ 代码实现
鸭子(Duck)和火鸡(Turkey)拥有不同的叫声,Duck 的叫声调用 quack() 方法,而 Turkey 调用 gobble() 方法。
要求将 Turkey 的 gobble() 方法适配成 Duck 的 quack() 方法,从而让火鸡冒充鸭子!
public interface Duck {
void quack();
}
public interface Turkey {
void gobble();
}
public class WildTurkey implements Turkey {
@Override
public void gobble() {
System.out.println("gobble!");
}
}
public class TurkeyAdapter implements Duck {
Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
@Override
public void quack() {
turkey.gobble();
}
}
public class Client {
public static void main(String[] args) {
Turkey turkey = new WildTurkey();
Duck duck = new TurkeyAdapter(turkey);
duck.quack();
}
}
⑤ 总结
- 适配器模式 & 代理模式:两者的主要区别在于代理模式应用的情况是不改变接口命名的,而且是对已有接口功能的一种控制;而适配器模式则强调接口转换。
⑥ jdk示例
java.util.Arrays#asList() 此方法与 Collection.toArray() 结合,充当基于数组和基于集合的 API 之间的桥梁。
java.util.Collections#list()
java.util.Collections#enumeration()
javax.xml.bind.annotation.adapters.XMLAdapter
③ ⭐️ 外观模式 (Facade)
① 模式动机
观看电影需要操作很多电器,使用外观模式实现一键看电影功能。
② 模式定义
为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 符合最少知识原则。
③ 模式结构
- 门面角色(facade):这是门面模式的核心。它被客户角色调用,因此它熟悉子系统的功能。它内部根据客户角色已有的需求预定了几种功能组合。
- 子系统角色:实现了子系统的功能。对它而言, facade角色就和客户角色一样是未知的,它没有任何facade角色的信息和链接。
- 客户角色:调用facade角色来完成要得到的功能。
④ 代码实现
Facade 模式的一个典型应用就是进行数据库连接。一般我们在每一次对数据库进行访问,都要进行以下操作:先得到 connect 实例,然后打开 connect 获得连接,得到一个statement,执行 sql 语句进行查询,得到查询结果集。我们可以将这些步骤提取出来,封装在一个类里面。这样,每次执行数据库访问只需要将必要的参数传递到这个类中就可以了。
⑤ 总结
【优点】
- 它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。
- 它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。
⑥ jdk示例
④ ⭐️ 组合模式 (Composite)
① 模式动机
在设计中想表示对象的“部分-整体”层次结构;希望用户忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象。
② 模式定义
将对象以树形结构组织起来,以达成“部分-整体”的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。
③ 模式结构
- 抽象构件角色(Component):它为组合中的对象声明接口,也可以为共有接口实现缺省行为。
- 树叶构件角色(Leaf):在组合中表示叶节点对象——没有子节点,实现抽象构件角色声明的接口。
- 树枝构件角色(Composite):在组合中表示分支节点对象——有子节点,实现抽象构件角色声明的接口;存储子部件。
管你使用的是 Leaf 类还是 Composite 类,对于客户程序来说都是一样的——客户仅仅知道 Component 这个抽象类。而且在 Composite 类中还持有对 Component抽象类的引用,这使得 Composite 中可以包含任何 Component 抽象类的子类 Leaf。
④ 代码实现
public abstract class Component {
protected String name;
public Component(String name) {
this.name = name;
}
public void print() {
print(0);
}
abstract void print(int level);
abstract public void add(Component component);
abstract public void remove(Component component);
}
public class Composite extends Component {
private List<Component> child;
public Composite(String name) {
super(name);
child = new ArrayList<>();
}
@Override
void print(int level) {
for (int i = 0; i < level; i++) {
System.out.print("--");
}
System.out.println("Composite:" + name);
for (Component component : child) {
component.print(level + 1);
}
}
@Override
public void add(Component component) {
child.add(component);
}
@Override
public void remove(Component component) {
child.remove(component);
}
}
public class Leaf extends Component {
public Leaf(String name) {
super(name);
}
@Override
void print(int level) {
for (int i = 0; i < level; i++) {
System.out.print("--");
}
System.out.println("left:" + name);
}
@Override
public void add(Component component) {
throw new UnsupportedOperationException(); // 牺牲透明性换取单一职责原则,这样就不用考虑是叶子节点还是组合节点
}
@Override
public void remove(Component component) {
throw new UnsupportedOperationException();
}
}
public class Client {
public static void main(String[] args) {
Composite root = new Composite("root");
Component node1 = new Leaf("1");
Component node2 = new Composite("2");
Component node3 = new Leaf("3");
root.add(node1);
root.add(node2);
root.add(node3);
Component node21 = new Leaf("21");
Component node22 = new Composite("22");
node2.add(node21);
node2.add(node22);
Component node221 = new Leaf("221");
node22.add(node221);
root.print();
}
}
Composite:root
--left:1
--Composite:2
----left:21
----Composite:22
------left:221
--left:3
⑤ 总结
【优点】:
- 使客户端调用简单,客户端可以一致的使用组合结构或其中单个对象,用户就不必关心自己处理的是单个对象还是整个组合结构,这就简化了客户端代码。
- 更容易在组合体内加入对象部件. 客户端不必因为加入了新的对象部件而更改代码。这一点符合开闭原则的要求,对系统的二次开发和功能扩展很有利。
【缺点】:组合模式不容易限制组合中的构件。
⑥ jdk示例
javax.swing.JComponent#add(Component)
java.awt.Container#add(Component)
java.util.Map#putAll(Map)
java.util.List#addAll(Collection)
java.util.Set#addAll(Collection)
⑤ 桥接模式 (Bridge)
① 模式动机
系统设计中,总是充满了各种变数,这是防不慎防的。面对这样那样的变动,你只能去不停的修改设计和代码,并且要开始新的一轮测试……。那采取什么样的方式可以较好的解决变化带给系统的影响?你可以分析变化的种类,将不变的框架使用抽象类定义出来,然后再将变化的内容使用具体的子类来分别实现。
② 模式定义
将抽象部分与它的实现部分分离,使它们都可以独立地变化。这里的抽象部分和实现部分不是我们通常认为的父类与子类、接口与
实现类的关系,而是组合关系。也就是说,实现部分是被抽象部分调用,以用来完成(实现)抽象部分的功能。
③ 模式结构
- 抽象( Abstraction )角色:它定义了抽象类的接口而且维护着一个指向实现角色的引用。
- 精确抽象(RefinedAbstraction)角色:实现并扩充由抽象角色定义的接口。
- 实现(Implementor)角色:给出了实现类的接口,这里的接口与抽象角色中的接口可以不一致。
- 具体实现(ConcreteImplementor)角色:给出了实现角色定义接口的具体实现。
聚合关系就是抽象与实现的桥梁
④ 代码实现
RemoteControl 表示遥控器,指代 Abstraction。
TV 表示电视,指代 Implementor。
桥接模式将遥控器和电视分离开来,从而可以独立改变遥控器或者电视的实现。
public abstract class TV {
public abstract void on();
public abstract void off();
public abstract void tuneChannel();
}
public class Sony extends TV {
@Override
public void on() {
System.out.println("Sony.on()");
}
@Override
public void off() {
System.out.println("Sony.off()");
}
@Override
public void tuneChannel() {
System.out.println("Sony.tuneChannel()");
}
}
public class RCA extends TV {
@Override
public void on() {
System.out.println("RCA.on()");
}
@Override
public void off() {
System.out.println("RCA.off()");
}
@Override
public void tuneChannel() {
System.out.println("RCA.tuneChannel()");
}
}
public abstract class RemoteControl {
protected TV tv;
public RemoteControl(TV tv) {
this.tv = tv;
}
public abstract void on();
public abstract void off();
public abstract void tuneChannel();
}
public class ConcreteRemoteControl1 extends RemoteControl {
public ConcreteRemoteControl1(TV tv) {
super(tv);
}
@Override
public void on() {
System.out.println("ConcreteRemoteControl1.on()");
tv.on();
}
@Override
public void off() {
System.out.println("ConcreteRemoteControl1.off()");
tv.off();
}
@Override
public void tuneChannel() {
System.out.println("ConcreteRemoteControl1.tuneChannel()");
tv.tuneChannel();
}
}
public class ConcreteRemoteControl2 extends RemoteControl {
public ConcreteRemoteControl2(TV tv) {
super(tv);
}
@Override
public void on() {
System.out.println("ConcreteRemoteControl2.on()");
tv.on();
}
@Override
public void off() {
System.out.println("ConcreteRemoteControl2.off()");
tv.off();
}
@Override
public void tuneChannel() {
System.out.println("ConcreteRemoteControl2.tuneChannel()");
tv.tuneChannel();
}
}
public class Client {
public static void main(String[] args) {
RemoteControl remoteControl1 = new ConcreteRemoteControl1(new RCA());
remoteControl1.on();
remoteControl1.off();
remoteControl1.tuneChannel();
RemoteControl remoteControl2 = new ConcreteRemoteControl2(new Sony());
remoteControl2.on();
remoteControl2.off();
remoteControl2.tuneChannel();
}
}
⑤ 总结
桥梁模式使用了低耦合性的组合代替继承,使得它具备了不少好处:
- 将可能变化的部分单独封装起来,使得变化产生的影响最小,不用编译不必要的代码。
- 抽象部分和实现部分可以单独的变动,并且每一部分的扩充都不会破坏桥梁模式搭起来架子。
- 对于客户程序来说,你的实现细节是透明的。
⑥ jdk示例
AWT (It provides an abstraction layer which maps onto the native OS the windowing support.)
JDBC
⑥ ⭐️ 装饰模式 (Decorator)
① 模式动机
想要给类添加额外功能但不想让客户接触子类
② 模式定义
动态地给一个对象添加一些额外的职责。就增加功能来说, Decorator 模式相比生成子类更为灵活。
③ 模式结构
- 抽象构件角色(Component):定义一个抽象接口,以规范准备接收附加责任的对象。
- 具体构件角色(Concrete Component):这是被装饰者,定义一个将要被装饰增加功能的类。
- 装饰角色(Decorator):持有一个构件对象的实例,并定义了抽象构件定义的接口。
- 具体装饰角色(Concrete Decorator):负责给构件添加增加的功能。
④ 代码实现
设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。
下图表示在 DarkRoast 饮料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它们都继承自相同父类,都有 cost() 方法,外层类的 cost() 方法调用了内层类的 cost() 方法。
public interface Beverage {
double cost();
}
public class DarkRoast implements Beverage {
@Override
public double cost() {
return 1;
}
}
public class HouseBlend implements Beverage {
@Override
public double cost() {
return 1;
}
}
public abstract class CondimentDecorator implements Beverage {
protected Beverage beverage;
}
public class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return 1 + beverage.cost();
}
}
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return 1 + beverage.cost();
}
}
public class Client {
public static void main(String[] args) {
Beverage beverage = new HouseBlend();
beverage = new Mocha(beverage);
beverage = new Milk(beverage);
System.out.println(beverage.cost());
}
}
3.0
⑤ 总结
【优点】:在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
【缺点】:采用 Decorator 模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困难。
⑥ jdk示例
java.io.BufferedInputStream(InputStream)
java.io.DataInputStream(InputStream)
java.io.BufferedOutputStream(OutputStream)
java.util.zip.ZipOutputStream(OutputStream)
java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap
⑦ ⭐️ 享元模式 (Flyweight)
① 模式动机
通常需要在大量地方用到自己的签名,不想每次都签怎么办,刻一个印章
② 模式定义
采用一个共享类来避免大量拥有相同内容的“小类”的开销。这种开销中最常见、直观的影响就是增加了内存的损耗。享元模式以共享的方式高效的支持大量的细粒度对象,减少其带来的开销。
应该尽量将事物的共性共享, 而又保留它的个性。 为了做到这点,享元模式中区分了内蕴状态和外蕴状态。内蕴状态就是共性,外蕴状态就是个性了。
③ 模式结构
- 抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。在 Java 中可以由抽象类、接口来担当。
- 具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
- 享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键
④ 代码实现
public interface Flyweight {
void doOperation(String extrinsicState);
}
public class ConcreteFlyweight implements Flyweight {
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void doOperation(String extrinsicState) {
System.out.println("Object address: " + System.identityHashCode(this));
System.out.println("IntrinsicState: " + intrinsicState);
System.out.println("ExtrinsicState: " + extrinsicState);
}
}
public class FlyweightFactory {
private HashMap<String, Flyweight> flyweights = new HashMap<>();
Flyweight getFlyweight(String intrinsicState) {
if (!flyweights.containsKey(intrinsicState)) {
Flyweight flyweight = new ConcreteFlyweight(intrinsicState);
flyweights.put(intrinsicState, flyweight);
}
return flyweights.get(intrinsicState);
}
}
public class Client {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweight1 = factory.getFlyweight("aa");
Flyweight flyweight2 = factory.getFlyweight("aa");
flyweight1.doOperation("x");
flyweight2.doOperation("y");
}
}
Object address: 1163157884
IntrinsicState: aa
ExtrinsicState: x
Object address: 1163157884
IntrinsicState: aa
ExtrinsicState: y
⑤ 总结
【优点】:大幅度的降低内存中对象的数量
【缺点】:使得系统逻辑复杂化,而且在一定程度上外蕴状态影响了系统的速度。
【使用场景】:系统中有大量的对象,他们使系统的效率降低;这些对象的状态可以分离出所需要的内外两部分
⑥ jdk示例
Java 利用缓存来加速大量小对象的访问时间。
字符串缓冲池
java.lang.Integer#valueOf(int)
java.lang.Boolean#valueOf(boolean)
java.lang.Byte#valueOf(byte)
java.lang.Character#valueOf(char)
行为型模式
① ⭐️ 观察者模式 (Observer)
① 模式动机
② 模式定义
③ 模式结构
④ 代码实现
⑤ 总结
⑥ jdk示例
② ⭐️ 迭代器模式 (Iterator)
① 模式动机
② 模式定义
③ 模式结构
④ 代码实现
⑤ 总结
⑥ jdk示例
③ 命令模式 (Command)
① 模式动机
② 模式定义
③ 模式结构
④ 代码实现
⑤ 总结
⑥ jdk示例
④ 策略模式 (Strategy)
① 模式动机
② 模式定义
③ 模式结构
④ 代码实现
⑤ 总结
⑥ jdk示例
⑤ 职责链模式 (Chain of Responsibility)
① 模式动机
② 模式定义
③ 模式结构
④ 代码实现
⑤ 总结
⑥ jdk示例
⑥ 状态模式 (State)
① 模式动机
② 模式定义
③ 模式结构
④ 代码实现
⑤ 总结
⑥ jdk示例
⑦ 模板方法模式 (Template Method)
① 模式动机
② 模式定义
③ 模式结构
④ 代码实现
⑤ 总结
⑥ jdk示例
⑧ 中介者模式 (Mediator)
① 模式动机
② 模式定义
③ 模式结构
④ 代码实现
⑤ 总结
⑥ jdk示例
⑨ 备忘录模式 (Memento)
① 模式动机
② 模式定义
③ 模式结构
④ 代码实现
⑤ 总结
⑥ jdk示例
⑩ 访问者模式 (Visitor)
① 模式动机
② 模式定义
③ 模式结构
④ 代码实现
⑤ 总结
⑥ jdk示例
⑪ 解释器模式 (Interpreter)
① 模式动机
② 模式定义
③ 模式结构
④ 代码实现
⑤ 总结
⑥ jdk示例