designPattern
# 一、初识
设计原则
找出应用中可能需要变化的地方,独立出来不与不需要变化的代码混在一起
针对**’接口’**编程,不针对实现
// 针对实现编程: Dog d = new dog(); d.bark(); // 针对接口编程 Animal animal = new Dog(); animal.makeSound();
多用组合,少用继承
- 将两个类结合起来使用就是组合
为了交互对象之间的松耦合设计而努力
六大原则
- 单一职责原则:一个类应该只有一个变化的原因
- 开闭原则:类、函数等对于扩展是开放的,但对于修改应该是关闭的
- 里氏替换原则:继承必须确保超类所拥有的性质在子类中依然成立
- 迪米特法则:降低类之间的耦合,相互之间减少依赖关系
- 接口隔离原则:将大接口拆分为更小更具体的接口,让接口中只包含子类感兴趣的方法
- 依赖倒置原则:抽象编程
# 二、 创建型模式
简介:提供创建对象的机制, 能够提升已有代码的灵活性和可复⽤性
# 工厂方法模式
简介:在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型
# 适用场景
- 当你在编写代码的过程中,如果无法预知对象确切类别及其依赖关系时,可使用工厂方法
- 如果你希望用户能扩展你软件库或框架的内部组件,可使用工厂方法
- 如果你希望复用现有对象来节省系统资源,而不是每次都重新创建对象, 可使用工厂方法
# 抽象工厂模式
简介:创建一系列相关的对象, 而无需指定其具体类
# 适用场景
- 如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息,或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂
# UML
# 优点
- 可以避免客户端和具体产品代码的耦合
- 单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护
- 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码
# 缺点
- 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂
# 建造者模式
简介:将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示
# 优点
- 生成不同形式的产品时, 你可以复用相同的制造代码
- 单一职责原则。 你可以将复杂构造代码从产品的业务逻辑中分离出来
# 缺点
- 由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加
# 原型模式
简介:复制或克隆一个已有对象,而又无需使你的代码依赖这个对象所属的类
注意:引用类型数据的复制要基于 深拷贝
,否则会影响到被拷贝的 原型
# 适用场景
- 需要复制一个对象,而又不希望改变原有对象的时候可以考虑使用原型模式来实现
# 实现方式
- 创建原型接口, 并在其中声明
克隆
方法。 - 原型类必须另行定义一个以该类对象为参数的构造函数。 构造函数必须复制参数对象中的所有成员变量值到新建实体中。 如果你需要修改子类, 则必须调用父类构造函数, 让父类复制其私有成员变量值
# 优点
- 你可以克隆预生成原型, 避免反复运行初始化代码
- 你可以克隆对象, 而无需与它们所属的具体类相耦合
# 缺点
- 克隆包含循环引用的复杂对象可能会非常麻烦
# 单例模式
简介:保证一个类只有一个实例, 并提供一个访问该实例的全局节点
# 适用场景
如果程序中的某个类对于所有客户端只有一个可用的实例,可以使用单例模式
如果你需要更加严格地控制全局变量,可以使用单例模式
public class Singleton {
private static volatile Singleton SINGLETON;
private Singleton() {
}
public static Singleton getInstance() {
if (SINGLETON == null) {
synchronized (Singleton.class) {
if (SINGLETON == null) {
SINGLETON = new Singleton();
}
}
}
return SINGLETON;
}
}
# 优点
- 可以保证一个类只有一个实例
- 仅在首次请求单例对象时对其进行初始化
# 缺点
- 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多
- 违反了单一职责原则
- 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象
# 三、 结构性模式
简介:将对象和类组装成较⼤的结构, 并同时保持结构的灵活和⾼效
# 适配器模式
简介:使接口不兼容的对象能够相互合作
当一个方法的入参是一个对象,而返回值是另一个对象时,这个方法就是一个适配器模式
# 适用场景
- 当你希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器类
- 需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性
# 优点
- 单一职责原则。你可以将接口或数据转换代码从程序主要业务逻辑中分离
- 开闭原则。 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器
# 缺点
- 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单
# 桥接模式
简介:将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用
# 适用场景
- 如果你想要拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类), 可以使用桥接模式
- 如果你希望在几个独立维度上扩展一个类, 可使用该模式
- 如果你需要在运行时切换不同实现方法,可使用桥接模式
# 优点
- 将一个具有多重功能的系统进行解耦,拆分成相互独立的层次,从而提高系统的可扩展性
- 开闭原则,不管新增控制层或实现层的代码都可以很容易实现,而且它们不会相互影响
- 单一原则,控制层专注于控制层的逻辑处理,实现层专注于实现层的逻辑处理业务系统在调用时只与控制层交互,不用关心实现层的具体逻辑
# 缺点
- 对高内聚的类使用该模式可能会让代码更加复杂
# 组合模式
简介:你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们
# 适用场景
- 如果你需要实现树状对象结构, 可以使用组合模式
- 如果你希望客户端代码以相同方式处理简单和复杂元素, 可以使用该模式
# 实现方式
- 定义一个接口,并在接口中定义要实现的功能
- 叶子节点实现这个接口,并重写接口中的方法
- 树枝节点中有一个集合或者数组,可以对接口对象进行管理。同时,树枝节点还要实现这个接口,在重写接口的方法时可以循环集合或数组得到接口对象,并对其进行调用
# UML
public class Test {
public static void main(String[] args) {
Employee employeeA = new Employee(24);
Employee employeeB = new Employee(25);
Employee employeeC = new Employee(22);
Employee employeeD = new Employee(20);
Department department1 = new Department();
Department department2 = new Department();
Department department3 = new Department();
department1.add(employeeA);
department1.add(employeeB);
department2.add(employeeC);
department2.add(department3);
department3.add(employeeD);
System.out.println("部门1员工总年龄:"+ department1.getAge());// 49
System.out.println("部门2员工总年龄:"+ department2.getAge());// 42
System.out.println("部门3员工总年龄:"+ department3.getAge());// 20
}
}
# 优点
- 你可以利用多态和递归机制更方便地使用复杂树结构
- 开闭原则。 无需更改现有代码, 你就可以在应用中添加新元素, 使其成为对象树的一部分
# 缺点
- 对于功能差异较大的类, 提供公共接口或许会有困难。 在特定情况下, 你需要过度一般化组件接口, 使其变得令人难以理解
# 装饰器模式
简介:通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为
# 适用场景
- 如果你希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为
- 如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以使用该模式
# UML
public class Test {
public static void main(String[] args) {
OriginalText originalText = new OriginalText();
originalText.setText("hello");
System.out.println(originalText.getText());
// span装饰
SpanDecorator spanDecorator = new SpanDecorator();
// 设置需要装饰的对象
spanDecorator.setDecorator(originalText);
// 开始装饰
spanDecorator.BeginDecorator();
System.out.println(originalText.getText());
// 加粗装饰
BoldDecorator boldDecorator = new BoldDecorator();
boldDecorator.setDecorator(originalText);
boldDecorator.BeginDecorator();
System.out.println(originalText.getText());
}
}
/*
hello
<span>hello</span>
<b><span>hello</span></b>
*/
# 优点
- 你无需创建新子类即可扩展对象的行为
- 你可以在运行时添加或删除对象的功能
- 你可以用多个装饰封装对象来组合几种行为
- 单一职责原则。 你可以将实现了许多不同行为的一个大类拆分为多个较小的类
# 缺点
- 在封装器栈中删除特定封装器比较困难
- 实现行为不受装饰栈顺序影响的装饰比较困难
- 各层的初始化配置代码看上去可能会很糟糕
# 外观模式
简介:几个方法中共用的逻辑抽象成一个独立的方法,那么这个方法就可以理解成外观模式
# 适用场景
当调用者需要调用多个子系统来完成自己的逻辑时,可以考虑使用外观模式
比如日志处理框架
SLF4J
,它对调用者提供接口。logback
、log4j
等各种日志框架作为子系统去实现这些接口需要为一个复杂的子系统提供一系列逻辑支持的时候,可以考虑使用外观模式
# 实现方式
- 抽象各个子系统的业务逻辑
- 将抽象过的业务逻辑封装到门面类中
- 调用者使用门面类中的方法完成自己的逻辑
# 优点
- 各个子系统提供统一的入口,调用者使用起来很简单
- 各个子系统和调用者解耦,扩展性会更好。比如,想要增加一个子系统时,只需要按照外观模式的规范进行开发,调用者和外观类都不用修改
# 缺点
- 如果设计不合理,增加新的子系统时可能需要修改外观类或调用者的源代码,违背了_开闭原则_
# 亨元模式
简介:摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象
在享元模式中,可以被共享的成员变量称为内在状态;不能被共享的成员变量称为外在状态
# 适用场景
- 仅在程序必须支持大量对象且没有足够的内存容量时使用享元模式
# 优点
- 如果程序中有很多相似对象, 那么你将可以节省大量内存
# 缺点
- 你可能需要牺牲执行速度来换取内存, 因为他人每次调用享元方法时都需要重新计算部分情景数据
- 代码会变得更加复杂。 团队中的新成员总是会问: “为什么要像这样拆分一个实体的状态?”
# 代理模式
简介:提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理
# 适用场景
- 需要在原有功能的前后添加自定义业务逻辑时
- 在需要对已有功能增加业务逻辑,而又无法拿到源码时可以考虑使用代理模式
- 在需要对一个很重的对象进行生命周期管理时,可以使用代理模式,比如数据库对象、Spring容器对象
# 实现方式
静态代理
- 新建一个代理类实现接口,并把接口作为代理类的成员变量
- 在代理类中提供一个带参数的构造器,并对成员变量进行初始化
- 重写接口类的方法,添加自定义业务逻辑,并通过成员变量调用父类的方法
JDK动态代理
新建一个代理类接口
新建一个代理类并实现InvocationHandler接口
// WeChatPay为被代理类 Payment为代理类接口 xxxProxyHandle为代理类 WeChatPay weChatPay = new WeChatPay(); // 先记录日志后校验密码 Payment payment = (Payment) Proxy.newProxyInstance(WeChatPay.class.getClassLoader(), new Class[]{Payment.class}, new PwdCheckProxyHandle(weChatPay)); Payment payment1 = (Payment) Proxy.newProxyInstance(WeChatPay.class.getClassLoader(), new Class[]{Payment.class}, new LogProxyHandle(payment)); payment1.pay(); System.out.println("------------------"); // 先校验密码后记录日志 Payment payment2 = (Payment) Proxy.newProxyInstance(WeChatPay.class.getClassLoader(), new Class[]{Payment.class}, new LogProxyHandle(weChatPay)); Payment payment3 = (Payment) Proxy.newProxyInstance(WeChatPay.class.getClassLoader(), new Class[]{Payment.class}, new PwdCheckProxyHandle(payment2)); payment3.pay();
CGLIB动态代理
查找目标类上的所有非final 的public类型的方法定义
将这些方法的定义转换成字节码
将组成的字节码转换成相应的代理的class对象
实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求
Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(WeChatPay.class); enhancer.setCallback(new LogInterceptor()); WeChatPay o = (WeChatPay)enhancer.create();
# UML
- 静态代理
public class Test {
public static void main(String[] args) {
WeChatPay weChatPay = new WeChatPay();
LogPayment logPayment = new LogPayment();
PwdCheck pwdCheck = new PwdCheck();
// 先记录日志后校验密码
logPayment.setProxy(pwdCheck);
pwdCheck.setProxy(weChatPay);
logPayment.pay();
System.out.println("------------------");
// 先校验密码后记录日志
pwdCheck.setProxy(logPayment);
logPayment.setProxy(weChatPay);
pwdCheck.pay();
}
}
# 优点
- 可以在调用原对象的前后,添加自定义的业务逻辑
- 降低耦合度,代理模式把原对象和调用者解耦, 使原对象更加专注自己本身的业务逻辑,非自身的逻辑可以交给代理对象处理
# 缺点
- 增加了代理类,方法调用链路变长,会增加响应时间
- 代码结构会变得相对复杂,增加理解成本
# 四、行为模式
简介:负责对象间的⾼效沟通和职责委派
# 责任链模式
简介:请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者
# 适用场景
- 当程序需要使用不同方式处理不同种类请求, 而且请求类型和顺序预先未知时,可以使用责任链模式
- 当必须按顺序执行多个处理者时, 可以使用该模式
- 如果所需处理者及其顺序必须在运行时进行改变, 可以使用责任链模式
# UML
public class Test {
public static void main(String[] args) {
Request request = new Request();
request.frequentOk = true;
request.loggedOn = false;
Handle handle = new RequestFrequentHandle(new LoginHandle(null));
boolean process = handle.process(request);
if (process) {
System.out.println("登录成功");
}
System.out.println("登录失败");
}
}
/**
访问频率正常
未登录
登录失败
*/
# 优点
- 可以控制请求处理的顺序
- 单一职责原则。 你可对发起操作和执行操作的类进行解耦
- 开闭原则。 你可以在不更改现有代码的情况下在程序中新增处理者
# 缺点
- 部分请求可能未被处理
# 命令模式
简介:将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作
# 实现方式
- 声明仅有一个执行方法的命令接口。
- 抽取请求并使之成为实现命令接口的具体命令类。 每个类都必须有一组成员变量来保存请求参数和对于实际接收者对象的引用。 所有这些变量的数值都必须通过命令构造函数进行初始化。
- 找到担任发送者职责的类。 在这些类中添加保存命令的成员变量。 发送者只能通过命令接口与其命令进行交互。 发送者自身通常并不创建命令对象, 而是通过客户端代码获取。
- 修改发送者使其执行命令, 而非直接将请求发送给接收者。
- 客户端必须按照以下顺序来初始化对象:
- 创建接收者。
- 创建命令, 如有需要可将其关联至接收者。
- 创建发送者并将其与特定命令关联
# 优点
- 单一职责原则。 你可以解耦触发和执行操作的类
- 开闭原则。 你可以在不修改已有客户端代码的情况下在程序中创建新的命令
- 你可以将一组简单命令组合成一个复杂命令
# 缺点
- 代码可能会变得更加复杂, 因为你在发送者和接收者之间增加了一个全新的层次
# 迭代器模式
简介:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示
# 优点
- 单一职责原则。 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理
- 开闭原则。 你可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码
- 你可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态
# 缺点
- 如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正
- 对于某些特殊集合, 使用迭代器可能比直接遍历的效率低
# 中介者模式
简介:减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作
# 适用场景
- 当一些对象和其他对象紧密耦合以致难以对其进行修改时
- 当组件因过于依赖其他组件而无法在不同应用中复用时
- 如果为了能在不同情景下复用一些基本行为, 导致你需要被迫创建大量组件子类时
# UML
public class Test {
public static void main(String[] args) {
Mediator md = new ConcreteMediator();
Colleague c1, c2;
c1 = new ConcreteColleague1();
c2 = new ConcreteColleague2();
md.register(c1);
md.register(c2);
c1.send();
System.out.println("-------------");
c2.send();
}
}
/*
具体同事类1发出请求。
具体同事类2收到请求。
-------------
具体同事类2发出请求。
具体同事类1收到请求。
*/
# 优点
- 单一职责原则。 你可以将多个组件间的交流抽取到同一位置, 使其更易于理解和维护
- 开闭原则。 你无需修改实际组件就能增加新的中介者
- 你可以更方便地复用各个组件
- 你可以减轻应用中多个组件间的耦合情况
# 缺点
- 一段时间后, 中介者可能会演化成为上帝对象 (opens new window)
# 备忘录模式
简介:允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态
# 适用场景
- 当你需要创建对象状态快照来恢复其之前的状态时
- 当直接访问对象的成员变量将导致封装被突破时
# UML
Originator(发起人):负责创建一个备忘录Memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复状态。Originator可根据需求决定Memento存储Originator的哪些内部状态。
Memento(备忘录):负责存储Originator对象的内部状态,并可防止Originator以外的其他对象访问备忘录Memento。备忘录有两个接口,Caretaker只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。Originator能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。
Caretaker(管理者):负责保存好备忘录Memento,不能对备忘录的内容进行操作或检查。
public class Test {
public static void main(String[] args) {
// 初始
Originator originator = new Originator();
originator.setState("On");
originator.show();// state = On
// 备份
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento());
originator.setState("Off");
originator.show();// state = Off
// 恢复
originator.setMemento(caretaker.getMemento());
originator.show();// state = On
}
}
# 优点
- 你可以在不破坏对象封装情况的前提下创建对象状态快照
# 缺点
- 如果客户端过于频繁地创建备忘录, 程序将消耗大量内存
- 负责人必须完整跟踪原发器的生命周期, 这样才能销毁弃用的备忘录
# 观察者模式
简介:定义了对象之间的一对多的依赖,当一个对象改变状态,所以依赖者都会收到通知并更新
# 适用场景
当一个对象状态的改变需要改变其他对象, 或实际对象是事先未知的或动态变化的时, 可使用观察者模式
当应用中的一些对象必须观察其他对象时, 可使用该模式。 但仅能在有限时间内或特定情况下使用
# UML
public class Test {
public static void main(String[] args) {
Subject subject = new Subject();
subject.addObserver(new Observer1());
subject.addObserver(new Observer2());
subject.publish();
// 发布者事件改变后
System.out.println("-----------------------");
subject.a = "vvv";
subject.publish();
}
}
/**
观察者1开始做事
观察者2开始做事
-----------------------
观察者1开始做事
*/
# 优点
- 开闭原则。 你无需修改发布者代码就能引入新的订阅者类 (如果是发布者接口则可轻松引入发布者类)
# 缺点
- 订阅者的通知顺序是随机的
# 状态模式
简介:在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样
策略模式与状态模式区别:
策略模式,将不同的算法封装成不同的策略,然后在具体的策略中实现具体的行为,但是测试本身是被动被选择的,容器选择策略,调用过程发生在容器中,策略本身是入参;
状态模式,将不同状态对应的行为封装,然后由具体的状态操作容器,整个过程更像是状态主动发起的,由状态执行其自己的方法,入参是容器
# 适用场景
- 行为随状态改变而改变的场景
# UML
# 优点
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
# 缺点
- 状态模式的使用必然会增加系统类和对象的个数
- 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码
# 策略模式
简介:定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。找出负责用许多不同方式完成特定任务的类, 然后将其中的算法抽取到一组被称为策略的独立类
# 适用场景
- 当你想使用对象中各种不同的算法变体,并希望能在运行时切换算法时,可使用策略模式
- 当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式
# UML
public class Test {
public static void main(String[] args) {
Random random = new Random();
int i = random.nextInt(3);
int a = 10;
int b = 5;
Context context = new Context();
// 根据不同值选择不同策略
if (i == 0) {
context.setStrategy(new ConcreteStrategyAdd());
} else if (i == 1) {
context.setStrategy(new ConcreteStrategySubtract());
} else if (i ==2) {
context.setStrategy((v1,v2) -> v1 * v2);
}
int result = context.executeStrategy(a, b);
System.out.println(result);
}
}
# 优点
- 可以使用组合来代替继承
- 开闭原则。 你无需对上下文进行修改就能够引入新的策略
# 缺点
- 客户端必须知晓策略间的不同——它需要选择合适的策略
- 如果你的算法极少发生改变, 那么没有任何理由引入新的类和接口。 使用该模式只会让程序过于复杂
# 模板方法模式
简介:超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤
# 适用场景
- 当你只希望客户端扩展某个特定算法步骤, 而不是整个算法或其结构时,可使用模板方法模式
# UML
public class Test {
public static void main(String[] args) {
Template template = new A();
template.execute(template);
System.out.println("----------");
template = new B();
template.execute(template);
}
}
/**
A执行步骤1
A执行步骤2
----------
B执行步骤1
B执行步骤2
*/
# 优点
- 你可仅允许客户端重写一个大型算法中的特定部分, 使得算法其他部分修改对其所造成的影响减小
- 你可将重复代码提取到一个超类中
# 缺点
- 模板方法中的步骤越多, 其维护工作就可能会越困难
# 访问者模式
简介:将算法与其所作用的对象隔离开来
与策略模式区别:
策略模式于一个类,在运行时的不同情况执行不同策略,一对多
访问者模式关注的是多个类,不同访问者访问这些类的时候会有不同的操作。多对多
# 适用场景
- 可使用访问者模式来清理辅助行为的业务逻辑
- 类结构固定时
# UML
**Visitor:**接口或者抽象类,定义了对每个 Element 访问的行为,它的参数就是被访问的元素,它的方法个数理论上与元素的个数是一样的,因此,访问者模式要求元素的类型要稳定,如果经常添加、移除元素类,必然会导致频繁地修改 Visitor 接口,如果出现这种情况,则说明不适合使用访问者模式。
**ConcreteVisitor:**具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。
**ComputerPart:**元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。
**Keyboard、CPU:**具体的元素类,它提供接受访问的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
public class Test {
public static void main(String[] args) {
CPU cpu = new CPU();
cpu.price = new BigDecimal("300");
Keyboard keyboard = new Keyboard();
keyboard.price = new BigDecimal("100");
StudentVisitor studentVisitor = new StudentVisitor();
CompanyVisitor companyVisitor = new CompanyVisitor();
// 7折
cpu.accept(studentVisitor);
// 8折
keyboard.accept(studentVisitor);
System.out.println("学生的CPU折后价格: " + cpu.price);// 学生的CPU折后价格: 210.0
System.out.println("学生的键盘折后价格: " + keyboard.price);// 学生的键盘折后价格: 80.0
cpu.price = new BigDecimal("300");
keyboard.price = new BigDecimal("100");
// 半价
cpu.accept(companyVisitor);
// 半价
keyboard.accept(companyVisitor);
System.out.println("企业的CPU折后价格: " + cpu.price);// 企业的CPU折后价格: 150.0
System.out.println("企业的键盘折后价格: "+ keyboard.price);// 企业的键盘折后价格: 50.0
}
}
# 优点
- 开闭原则。 你可以引入在不同类对象上执行的新行为, 且无需对这些类做出修改
- 单一职责原则。 可将同一行为的不同版本移到同一个类中
# 缺点
- 每次在元素层次结构中添加或移除一个类时, 你都要更新所有的访问者
- 在访问者同某个元素进行交互时, 它们可能没有访问元素私有成员变量和方法的必要权限