12. 策略模式(Strategy Pattern)
12.1 定义
Define a family of algorithms,.encapsulate each one.and make them interchangeable.(定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。)
12.2 自己的理解
策略模式就像是一个工具箱,里面有各种不同用途的工具(算法),当需要完成某项任务时,可以根据具体情况选择合适的工具来使用。在软件中,当一个类有多种不同的行为或算法,并且这些行为在运行时可以相互替换时,可以使用策略模式。将每个算法封装成一个独立的策略类,使得算法的变化不会影响到使用算法的类,提高了系统的灵活性和可维护性。
12.3 类型
行为型模式。
12.4 通用代码
- Context 封装角色:也称为上下文角色,它持有一个策略对象的引用,并负责调用策略对象的算法方法,屏蔽高层模块对策略算法的直接访问。
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy() {
strategy.algorithm();
}
}
- Strategy 抽象策略角色:定义算法家族的抽象接口,所有具体策略类都必须实现这个接口。
public interface Strategy {
void algorithm();
}
- ConcreteStrategy 具体策略角色(多个):实现抽象策略接口,包含具体的算法实现。
public class ConcreteStrategyA implements Strategy {
@Override
public void algorithm() {
System.out.println("执行策略A的算法");
}
}
public class ConcreteStrategyB implements Strategy {
@Override
public void algorithm() {
System.out.println("执行策略B的算法");
}
}
12.5 注意事项
- 策略类的数量不宜过多,如果策略类过多,可能会导致系统变得复杂,增加代码的理解和维护难度。可以考虑使用策略枚举等方式来简化策略类的定义。(具体策略数量超过4个,则需要考虑使用混合模式)
- 策略类之间应该具有良好的独立性,避免策略类之间相互依赖或耦合度过高,否则可能会影响策略模式的灵活性和可扩展性。
12.6 优缺点
- 优点:
- 实现了算法的独立封装和可替换性,使得算法的变化不会影响到使用算法的类,符合开闭原则,提高了系统的灵活性和可维护性。
- 可以方便地切换不同的算法策略,根据不同的需求选择合适的算法,提高了系统的适应性。
- 策略模式将算法的实现细节与使用算法的类分离,使得代码结构更加清晰,易于理解和调试。
- 缺点:
- 客户端需要知道所有的策略类,并根据具体情况选择合适的策略类,增加了客户端的复杂性。
- 策略类的增加可能会导致类的数量增多,尤其是在复杂的系统中,过多的类会增加代码的管理成本。
12.7 使用场景
- 排序算法切换:在数据处理中,对于数组或列表的排序,可以使用策略模式实现不同排序算法(如冒泡排序、快速排序、归并排序等)的切换。
import java.util.ArrayList;
import java.util.List;
public class SortContext {
private SortStrategy strategy;
public SortContext(SortStrategy strategy) {
this.strategy = strategy;
}
public void sort(List<Integer> data) {
strategy.sort(data);
}
}
public interface SortStrategy {
void sort(List<Integer> data);
}
public class BubbleSortStrategy implements SortStrategy {
@Override
public void sort(List<Integer> data) {
for (int i = 0; i < data.size() - 1; i++) {
for (int j = 0; j < data.size() - i - 1; j++) {
if (data.get(j) > data.get(j + 1)) {
int temp = data.get(j);
data.set(j, data.get(j + 1));
data.set(j + 1, temp);
}
}
}
}
}
public class QuickSortStrategy implements SortStrategy {
@Override
public void sort(List<Integer> data) {
quickSort(data, 0, data.size() - 1);
}
private void quickSort(List<Integer> data, int low, int high) {
if (low < high) {
int pivotIndex = partition(data, low, high);
quickSort(data, low, pivotIndex - 1);
quickSort(data, pivotIndex + 1, high);
}
}
private int partition(List<Integer> data, int low, int high) {
int pivot = data.get(high);
int i = low - 1;
for (int j = low; j < high; j++) {
if (data.get(j) <= pivot) {
i++;
int temp = data.get(i);
data.set(i, data.get(j));
data.set(j, temp);
}
}
int temp = data.get(i + 1);
data.set(i + 1, data.get(high));
data.set(high, temp);
return i + 1;
}
}
- 游戏角色移动策略:在游戏开发中,不同类型的游戏角色(如步兵、骑兵、飞行单位等)可能具有不同的移动策略,可以使用策略模式来实现不同角色的移动逻辑。
public class CharacterMovementContext {
private MovementStrategy strategy;
public CharacterMovementContext(MovementStrategy strategy) {
this.strategy = strategy;
}
public void move() {
strategy.move();
}
}
public interface MovementStrategy {
void move();
}
public class InfantryMovementStrategy implements MovementStrategy {
@Override
public void move() {
System.out.println("步兵缓慢移动");
}
}
public class CavalryMovementStrategy implements MovementStrategy {
@Override
public void move() {
System.out.println("骑兵快速移动");
}
}
public class FlyingUnitMovementStrategy implements MovementStrategy {
@Override
public void move() {
System.out.println("飞行单位自由飞行移动");
}
}
- 多个类只有在算法或行为上稍有不同的场景
- 算法需要自由切换的场景
- 需要屏蔽算法规则的场景
**注意事项:**具体策略数量超过4个,则需要考虑使用混合模式
策略模式扩展:策略枚举
public enum Calculator{
//1加法运算
ADD("+"){
public int exec(int a,int b){
return a+b;
}
},
//减法运算
SUB("-"){
public int exec(int a,int b){
return a b;
}
};
String value = "";
//定义成员值类型
private Calculator(String _value){
this.value = _value;
}
//获得枚举成员的值
public String getValue(){
return this.value;
}
//声明一个抽象函数
public abstract int exec(int a,int b);
}
定义:
- 它是一个枚举
- 它是一个浓缩了的策略模式的枚举
注意:
受枚举类型的限制,每个枚举项都是public、final、static的,扩展性受到了一定的约束,因此在系统开发中,策略枚举一般担当不经常发生变化的角色。
致命缺陷:
所有的策略都需要暴露出去,由客户端决定使用哪一个策略。
13. 适配器模式(Adapter Pattern)
13.1 定义
****Convert the interface of a class into another interface clients expectAdapter lets classes work together that couldn't otherwise because of incompatible interfaces.(将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。)
13.2 自己的理解
适配器模式就像是一个万能插头转换器,当你在国外旅行时,当地的电源插座接口(目标接口)与你带来的电器插头(源接口)不匹配,插头转换器(适配器)可以将电器插头的接口转换为当地插座的接口,使电器能够正常使用。在软件中,当需要使用一个已经存在但接口不兼容的类时,可以创建一个适配器类来将其接口转换为目标接口,使得原本不兼容的类能够在新的环境中正常工作。
13.3 类型
结构型模式。
13.4 通用代码
- 类适配器:
- Target 目标角色:定义客户端所期望的接口。
public interface Target {
void request();
}
- Adaptee 源角色:需要适配的现有类,它具有与目标接口不兼容的接口。
public class Adaptee {
public void specificRequest() {
System.out.println("执行源类的特定请求");
}
}
- Adapter 适配器角色:通过继承源类并实现目标接口,将源类的接口转换为目标接口。
public class Adapter extends Adaptee implements Target {
@Override
public void request() {
specificRequest();
}
}
- 对象适配器:与类适配器的区别在于,对象适配器使用对象的合成关系(关联关系)来实现适配,而不是继承源类。
public class ObjectAdapter implements Target {
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
13.5 注意事项
- 在使用适配器模式时,要确保适配器类能够正确地转换源类的接口,使其符合目标接口的规范,避免出现转换错误或功能异常。
- 适配器模式主要用于解决现有类的接口不兼容问题,在设计新系统时,应尽量避免出现接口不匹配的情况,以减少对适配器模式的依赖。
13.6 优缺点
- 优点:
- 可以使原本不兼容的类能够协同工作,提高了代码的复用性,避免了重复开发。
- 遵循开闭原则,在不修改现有类的基础上,通过适配器类实现了对现有类的适配,对现有系统的影响较小。
- 缺点:
- 过多地使用适配器模式可能会导致系统变得复杂,尤其是当存在多个适配器类时,代码的可读性和维护性可能会受到影响。
- 适配器类的存在可能会隐藏系统的一些复杂性,使得代码的理解和调试难度增加,尤其是在处理复杂的接口转换时。
13.7 使用场景
- 数据库驱动适配:在不同的数据库系统(如 MySQL、Oracle、SQL Server 等)中,它们的驱动接口可能存在差异。当开发一个应用程序需要切换数据库时,可以使用适配器模式创建一个统一的数据库访问接口,然后针对不同的数据库驱动编写适配器类,使得应用程序可以无缝切换数据库。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public interface DatabaseDriverAdapter {
Connection getConnection() throws SQLException;
}
public class MySQLDriverAdapter implements DatabaseDriverAdapter {
private String url;
private String username;
private String password;
public MySQLDriverAdapter(String url, String username, String password) {
this.url = url;
this.username = username;
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
}
public class OracleDriverAdapter implements DatabaseDriverAdapter {
private String url;
private String username;
private String password;
public OracleDriverAdapter(String url, String username, String password) {
this.url = url;
this.username = username;
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
}
- 第三方库接口适配:
// 假设第三方库提供的接口
public class ThirdPartyLibrary {
public void thirdPartyMethod() {
System.out.println("第三方库的原始方法");
}
}
// 目标接口
public interface TargetInterface {
void targetMethod();
}
// 适配器类
public class ThirdPartyAdapter implements TargetInterface {
private ThirdPartyLibrary thirdPartyLibrary;
public ThirdPartyAdapter(ThirdPartyLibrary thirdPartyLibrary) {
this.thirdPartyLibrary = thirdPartyLibrary;
}
@Override
public void targetMethod() {
thirdPartyLibrary.thirdPartyMethod();
}
}
- 你有动机修改一个己经投产中的接口时,适配器模式可能是最适合你的模式。比如系统扩展了,需要使用一个已有或新建立的类,但这个类又不符合系统的接口,怎么办?使用适配器模式,这也是我们例子中提到的。
注意事项:
详细设计阶段不要考虑使用适配器模式,使用主要场景为扩展应用中。
对象适配器:
对象适配器和类适配器的区别:
类适配器是类间继承,对象适配器是对象的合成关系,也可以说是类的关联关系,这是两者的根本区别。(实际项目中对象适配器使用到的场景相对比较多)。
14. 迭代器模式(Iterator Pattern)
14.1 定义
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.(它提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。)
14.2 自己的理解
迭代器模式就像一个导游,带领游客依次参观旅游景点,而游客不需要了解景点的内部布局和管理方式。在软件中,当需要遍历一个集合或容器对象,但又不想暴露其内部结构时,可以使用迭代器模式,将遍历的逻辑封装在迭代器中,使客户端可以方便地访问元素,而不影响集合对象的内部结构。
14.3 类型
行为型模式。
14.4 通用代码
- Aggregate 聚合类:定义创建迭代器的接口。
public interface Aggregate {
Iterator createIterator();
}
- ConcreteAggregate 具体聚合类:实现聚合类接口,返回具体的迭代器。
import java.util.ArrayList;
import java.util.List;
public class ConcreteAggregate implements Aggregate {
private List<Object> items = new ArrayList<>();
public void add(Object item) {
items.add(item);
}
@Override
public Iterator createIterator() {
return new ConcreteIterator(this);
}
public Object getItem(int index) {
return items.get(index);
}
public int getCount() {
return items.size();
}
}
- Iterator 迭代器:定义访问和遍历元素的接口。
public interface Iterator {
boolean hasNext();
Object next();
}
- ConcreteIterator 具体迭代器:实现迭代器接口,完成元素的遍历。
public class ConcreteIterator implements Iterator {
private ConcreteAggregate aggregate;
private int index;
public ConcreteIterator(ConcreteAggregate aggregate) {
this.aggregate = aggregate;
this.index = 0;
}
@Override
public boolean hasNext() {
return index < aggregate.getCount();
}
@Override
public Object next() {
if (hasNext()) {
return aggregate.getItem(index++);
}
return null;
}
}
14.5 注意事项
- 迭代器模式要注意迭代器的边界条件,避免越界访问,确保迭代器在遍历过程中不会出现异常。
- 迭代器的实现要考虑集合对象的修改操作,例如在迭代过程中添加或删除元素可能会导致迭代结果不一致,需要采取相应的处理措施。
- 迭代器模式已经被淘汰,java中已经把选代器运用到各个聚集类(collection)中了,使用java自带的选代器就已经满足我们的需求了.
14.6 优缺点
- 优点:
- 分离了集合对象的遍历行为和集合对象本身,符合单一职责原则,提高了集合对象的内聚性。
- 迭代器提供了统一的遍历接口,使得客户端可以以一种标准的方式遍历不同类型的集合,提高了代码的复用性和可维护性。
- 可以为不同的集合对象提供不同的迭代器,满足不同的遍历需求,提高了系统的灵活性。
- 缺点:
- 对于简单的集合对象,使用迭代器模式可能会增加系统的复杂性,增加了代码量。
- 迭代器模式可能会影响系统性能,尤其是在处理大量元素时,迭代器的创建和遍历操作可能会有一定的开销。
14.7 使用场景
- 自定义集合类的遍历:当开发一个自定义的集合类时,可以使用迭代器模式提供标准的遍历方式。
public class CustomList implements Aggregate {
private Object[] elements;
private int size;
private int capacity;
public CustomList() {
capacity = 10;
elements = new Object[capacity];
size = 0;
}
public void add(Object element) {
if (size == capacity) {
Object[] newElements = new Object[capacity * 2];
System.arraycopy(elements, 0, newElements, 0, size);
elements = newElements;
capacity *= 2;
}
elements[size++] = element;
}
@Override
public Iterator createIterator() {
return new CustomListIterator(this);
}
public Object get(int index) {
if (index < size) {
return elements[index];
}
return null;
}
public int size() {
return size;
}
}
public class CustomListIterator implements Iterator {
private CustomList list;
private int index;
public CustomListIterator(CustomList list) {
this.list = list;
this.index = 0;
}
@Override
public boolean hasNext() {
return index < list.size();
}
@Override
public Object next() {
if (hasNext()) {
return list.get(index++);
}
return null;
}
}
- 文件系统遍历:在文件系统中,遍历文件夹中的文件和子文件夹,可以使用迭代器模式将遍历逻辑封装起来,使客户端可以方便地遍历文件系统,而不影响文件系统的内部结构。
import java.io.File;
public class FileSystem implements Aggregate {
private File root;
public FileSystem(File root) {
this.root = root;
}
@Override
public Iterator createIterator() {
return new FileSystemIterator(root);
}
}
public class FileSystemIterator implements Iterator {
private File[] files;
private int index;
public FileSystemIterator(File root) {
files = root.listFiles();
index = 0;
}
@Override
public boolean hasNext() {
return index < files.length;
}
@Override
public Object next() {
if (hasNext()) {
return files[index++];
}
return null;
}
}
15. 组合模式(Composite Pattern)
15.1 定义
Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.(将对象组合成树形结构以表示“部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性。)
15.2 自己的理解
组合模式类似于文件系统,文件系统中既有文件(叶子节点)又有文件夹(树枝节点),文件夹可以包含文件和其他文件夹。用户可以像操作文件一样操作文件夹,例如复制、删除等操作,而不必区分是文件还是文件夹。在软件中,当需要表示具有层次结构的对象,并希望对单个对象和组合对象进行统一操作时,可以使用组合模式,它将对象组合成树形结构,让用户可以以统一的方式处理这些对象,简化了代码逻辑。
15.3 类型
结构型模式。
15.4 通用代码
- Component 抽象构件:为组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。
public abstract class Component {
protected String name;
public Component(String name) {
this.name = name;
}
public abstract void add(Component component);
public abstract void remove(Component component);
public abstract void display(int depth);
}
- Leaf 叶子构件:表示叶子节点,没有子节点,实现抽象构件的接口。
public class Leaf extends Component {
public Leaf(String name) {
super(name);
}
@Override
public void add(Component component) {
System.out.println("叶子节点不能添加子节点");
}
@Override
public void remove(Component component) {
System.out.println("叶子节点不能移除子节点");
}
@Override
public void display(int depth) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append("-");
}
System.out.println(sb.toString() + name);
}
}
- Composite 组合构件:表示树枝节点,可以包含子节点,实现抽象构件的接口,并维护子节点的集合。
import java.util.ArrayList;
import java.util.List;
public class Composite extends Component {
private List<Component> children = new ArrayList<>();
public Composite(String name) {
super(name);
}
@Override
public void add(Component component) {
children.add(component);
}
@Override
public void remove(Component component) {
children.remove(component);
}
@Override
public void display(int depth) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append("-");
}
System.out.println(sb.toString() + name);
for (Component child : children) {
child.display(depth + 2);
}
}
}
15.5 注意事项
- 要注意组件的层次结构和操作的一致性,确保叶子节点和组合节点对客户端操作的处理符合预期,避免出现异常情况。
- 组合模式中,如果层次结构过深或节点过多,可能会影响系统性能,需要考虑性能优化,如采用懒加载等方式。
15.6 优缺点
- 优点:
- 定义了包含基本对象和组合对象的类层次结构,使客户端可以统一处理基本对象和组合对象,简化了客户端代码。
- 可以方便地增加新的组件,符合开闭原则,易于扩展。
- 可以方便地对整个树形结构进行递归操作,如遍历、计算等。
- 缺点:
- 对于复杂的树形结构,可能会导致系统的复杂性增加,尤其是在维护层次结构和处理递归操作时。
- 由于使用了递归操作,可能会影响系统性能,尤其是在处理大量节点时。
15.7 使用场景
- 图形绘制系统:在图形绘制软件中,图形可以是基本图形(如圆形、矩形等),也可以是组合图形(如多个图形组合成的复杂图形)。可以使用组合模式对图形对象进行组合,实现统一的绘制、移动、缩放等操作。
public class Circle extends Leaf {
public Circle(String name) {
super(name);
}
}
public class Rectangle extends Leaf {
public Rectangle(String name) {
super(name);
}
}
public class ComplexShape extends Composite {
public ComplexShape(String name) {
super(name);
}
}
- 组织结构:在企业管理系统中,可以使用组合模式表示公司的组织结构,公司由部门组成,部门又可以包含子部门或员工,对公司进行统一的操作(如统计人数、计算成本等)。
public class Employee extends Leaf {
public Employee(String name) {
super(name);
}
}
public class Department extends Composite {
public Department(String name) {
super(name);
}
}
- 维护和展示部分整体关系的场景,如树形菜单、文件和文件夹管理。
- 从一个整体中能够独立出部分模块或功能的场景。
注意:
只要是树形结构,就考虑使用组合模式。
16. 观察者模式(Observer Pattern)
16.1 定义
Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and updated automatically.(定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。)
16.2 自己的理解
观察者模式就像一个订阅系统,当一个频道(主题)发布新消息时,所有订阅该频道的用户(观察者)都会收到通知。在软件中,当一个对象的状态变化需要通知其他多个对象时,可以使用观察者模式,将对象间的依赖关系解耦,提高系统的灵活性和可维护性。
16.3 类型
行为型模式。
16.4 通用代码
- Subject 主题:管理观察者,提供注册、移除和通知观察者的方法。
- 定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。
import java.util.ArrayList;
import java.util.List;
public class Subject {
private List<Observer> observers = new ArrayList<>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
private void notifyAllObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
- Observer 观察者:定义一个更新接口,当主题状态改变时,主题会调用此接口更新观察者。
- 观察者接收到消息后,即进行update(更新方法)操作,对接收到的信息进行处理
public interface Observer {
void update();
}
- ConcreteObserver 具体观察者:实现更新接口,当收到主题通知时,执行相应的操作。
- 每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑
public class ConcreteObserverA implements Observer {
private Subject subject;
public ConcreteObserverA(Subject subject) {
this.subject = subject;
subject.attach(this);
}
@Override
public void update() {
System.out.println("ConcreteObserverA收到更新通知,新状态:" + subject.getState());
}
}
public class ConcreteObserverB implements Observer {
private Subject subject;
public ConcreteObserverB(Subject subject) {
this.subject = subject;
subject.attach(this);
}
@Override
public void update() {
System.out.println("ConcreteObserverB收到更新通知,新状态:" + subject.getState());
}
}
被观察者通用代码:
public abstract class Subject{
//定义一个观察者数组
private Vector<Observer> obsVector = new Vector<Observer>();
//增加一个观察者
public void addobserver(Observer o){
this.obsVector.add(o);
}
//删除一个观察者
public void delObserver(Observer o){
this.obsVector.remove(o);
}
//通知所有观察者
public void notifyobserversO{
for(Observer o:this.obsVector){
o.update():
}
}
}
16.5 注意事项
- 要注意观察者的注册和移除,避免出现内存泄漏,例如忘记移除不再需要的观察者。
- 在多线程环境下,需要考虑线程安全,尤其是在注册、移除和通知观察者的操作中,避免出现并发问题。
16.6 优缺点
- 优点:
- 实现了对象之间的解耦,主题对象和观察者对象之间的依赖关系松散,符合迪米特法则,提高了系统的灵活性和可维护性。
- 可以方便地添加和移除观察者,符合开闭原则,对于系统的扩展非常方便。
- 可以实现广播式的通知,一个主题的变化可以通知多个观察者,提高了系统的协同性。
- 缺点:
- 如果观察者过多,通知的性能可能会受到影响,尤其是在通知逻辑复杂时。
- 当观察者和主题之间的依赖关系复杂时,可能会导致系统的调试和维护困难,因为通知链可能比较长。
16.7 使用场景
- 股票价格更新通知:在股票交易系统中,当股票价格发生变化时,需要通知所有关注该股票的用户(观察者)。
public class Stock {
private String name;
private double price;
private List<StockObserver> observers = new ArrayList<>();
public Stock(String name) {
this.name = name;
}
public void setPrice(double price) {
this.price = price;
notifyObservers();
}
public double getPrice() {
return price;
}
public void attach(StockObserver observer) {
observers.add(observer);
}
public void detach(StockObserver observer) {
observers.remove(observer);
}
private void notifyObservers() {
for (StockObserver observer : observers) {
observer.update(this);
}
}
}
public interface StockObserver {
void update(Stock stock);
}
public class UserObserver implements StockObserver {
@Override
public void update(Stock stock) {
System.out.println("用户收到股票 " + stock.getName() + " 的价格更新通知,新价格:" + stock.getPrice());
}
}
- 消息推送服务:在消息推送系统中,当有新消息时,需要将消息推送给所有订阅该消息类型的用户。
public class Message {
private String content;
private List<MessageObserver> observers = new ArrayList<>();
public Message(String content) {
this.content = content;
}
public void setContent(String content) {
this.content = content;
notifyObservers();
}
public String getContent() {
return content;
}
public void attach(MessageObserver observer) {
observers.add(observer);
}
public void detach(MessageObserver observer) {
observers.remove(observer);
}
private void notifyObservers() {
for (MessageObserver observer : observers) {
observer.update(this);
}
}
}
public interface MessageObserver {
void update(Message message);
}
public class UserMessageObserver implements MessageObserver {
@Override
public void update(Message message) {
System.out.println("用户收到消息更新通知,消息内容:" + message.getContent());
}
}
- 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系
- 事件多级触发场景,
- 跨系统的消息交换场景,如消息队列的处理机制
注意:
- 广播链的问题
在一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次(传递两次)
- 异步处理问题
观察者比较多,而且处理时间比较长,采用异步处理来考虑线程安全和队列的问题
17. 门面模式(Facade Pattern)
17.1 定义
**定义:**Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level interface that makes the subsystem easier to use.(要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。)
17.2 自己的理解
可以将门面模式理解为一个统一的服务窗口,这个窗口隐藏了背后多个服务部门(子系统)的复杂流程和操作。就像在银行办理业务,用户只需要在一个综合服务窗口(门面)提交需求,该窗口会自动协调多个内部部门(如储蓄部门、贷款部门、理财部门等)来完成各种业务操作,而用户无需直接与这些内部部门打交道。在软件系统中,它可以将多个子系统的操作整合起来,以一种简洁的方式呈现给客户端,降低客户端与子系统的耦合度,同时提高系统的易用性和可维护性。
17.3 类型
结构型模式。
17.4 通用代码
- 子系统类:代表复杂子系统中的不同部分,每个类具有各自独立的操作。
class SubSystemOne {
public void operationOne() {
System.out.println("子系统一执行操作一");
}
}
class SubSystemTwo {
public void operationTwo() {
System.out.println("子系统二执行操作二");
}
}
class SubSystemThree {
public void operationThree() {
System.out.println("子系统三执行操作三");
}
}
- 门面类:将子系统的多个操作进行封装,为客户端提供一个统一的操作接口。
class Facade {
private SubSystemOne subSystemOne = new SubSystemOne();
private SubSystemTwo subSystemTwo = new SubSystemTwo();
private SubSystemThree subSystemThree = new SubSystemThree();
public void performComplexOperation() {
subSystemOne.operationOne();
subSystemTwo.operationTwo();
subSystemThree.operationThree();
}
}
- 客户端代码示例:展示如何使用门面类来操作子系统。
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.performComplexOperation();
}
}
17.5 注意事项
- 避免臃肿:门面类的设计应该简洁明了,主要负责子系统操作的组合和调用,不应包含过多的业务逻辑细节,否则会违背单一职责原则,导致代码的可维护性降低。
- 子系统的独立性保留:虽然使用了门面类,但子系统应该仍然可以被单独使用,因为在某些特殊情况下,可能需要调用子系统的特定功能,而不是通过门面类的统一接口。
- 异常处理:在门面类的方法中要考虑对可能出现的异常进行处理,确保子系统操作出现异常时,能给客户端合理的反馈,避免程序崩溃或出现难以排查的错误。
17.6 优缺点
- 优点:
- 简化客户端使用:将复杂的子系统操作封装在一个接口后面,为客户端提供了简洁的使用方式,减少了客户端与子系统之间的耦合,使得客户端代码更简洁、易于理解和维护。
- 提高系统可维护性:通过将子系统的复杂性隐藏在门面类后面,当子系统发生变化时,只需要修改门面类中相关的操作,而不会影响到客户端代码,提高了系统的可维护性。
- 提高系统安全性:通过限制客户端对子系统的直接访问,只暴露门面类,可以更好地保护子系统内部的敏感信息和操作,防止客户端对系统的不当操作。
- 缺点:
- 单点故障风险:如果门面类出现问题,可能会影响到整个系统的正常运行,因为客户端依赖门面类来完成操作,需要确保门面类的高可用性。
- 功能扩展性限制:过度依赖门面类可能会限制子系统的功能扩展,因为客户端通常只使用门面类提供的操作,可能会忽略子系统本身的其他功能,对于一些高级用户或特殊需求,可能会造成不便。
17.7 使用场景
- 集成多个子系统的操作:当一个系统由多个子系统构成,并且客户端需要同时操作多个子系统的功能时,使用门面模式可以将这些操作集成到一个接口中,方便客户端使用。例如,在一个电商系统中,涉及商品管理子系统、订单管理子系统、用户管理子系统,可使用门面类提供诸如 “用户下单” 这样的操作,它会涉及到用户身份验证、商品库存检查、订单创建等多个子系统操作。
class ProductManagementSystem {
public void checkStock() {
System.out.println("检查商品库存");
}
}
class OrderManagementSystem {
public void createOrder() {
System.out.println("创建订单");
}
}
class UserManagementSystem {
public void authenticateUser() {
System.out.println("验证用户身份");
}
}
class EcommerceFacade {
private ProductManagementSystem productSystem = new ProductManagementSystem();
private OrderManagementSystem orderSystem = new OrderManagementSystem();
private UserManagementSystem userSystem = new UserManagementSystem();
public void userPlaceOrder() {
userSystem.authenticateUser();
productSystem.checkStock();
orderSystem.createOrder();
}
}
- 简化第三方库或遗留系统的使用:当使用复杂的第三方库或遗留系统时,这些系统可能具有复杂的接口和操作,可以使用门面模式将其封装,为开发人员提供更简洁的使用接口。
class LegacySystemA {
public void legacyOperationA() {
System.out.println("遗留系统A的操作A");
}
}
class LegacySystemB {
public void legacyOperationB() {
System.out.println("遗留系统B的操作B");
}
}
class LegacySystemFacade {
private LegacySystemA legacySystemA = new LegacySystemA();
private LegacySystemB legacySystemB = new LegacySystemB();
public void performLegacyTask() {
legacySystemA.legacyOperationA();
legacySystemB.legacyOperationB();
}
}
- 分层架构中的服务层:在分层架构中,门面模式可以作为服务层的实现方式,为表现层提供简单的服务接口,将业务逻辑层和数据访问层的操作封装起来。
class BusinessLogicLayer {
public void processBusinessLogic() {
System.out.println("处理业务逻辑");
}
}
class DataAccessLayer {
public void accessData() {
System.out.println("访问数据");
}
}
class ServiceFacade {
private BusinessLogicLayer businessLogicLayer = new BusinessLogicLayer();
private DataAccessLayer dataAccessLayer = new DataAccessLayer();
public void performService() {
businessLogicLayer.processBusinessLogic();
dataAccessLayer.accessData();
}
}
- 为一个复杂的模块或子系统提供一个供外界访问的接口
- 子系统相对独立----外界对子系统的访问只要黑箱操作即可
- 预防低水平人员带来的风险扩散
注意:
- 一个子系统可以有多个门面
- 门面不参与子系统内的业务逻辑
18. 备忘录模式(Memento Pattern)
18.1 定义
Without violating encapsulation.capture and externalize an object's internal state so that the object can be restored to this state later.(在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。)
18.2 自己的理解
备忘录模式就像一个游戏的存档功能,玩家可以在游戏的某个时刻保存游戏状态(备忘录),当需要时可以将游戏恢复到保存的状态。在软件中,当需要保存对象的某个状态,并且在之后能够将对象恢复到该状态时,可以使用备忘录模式,它可以在不破坏对象封装性的前提下实现状态的保存和恢复。
18.3 类型
行为型模式。
18.4 通用代码
- Originator 发起人:负责创建一个备忘录,用于记录当前状态,并可以使用备忘录恢复状态。
public class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public Memento createMemento() {
return new Memento(state);
}
public void restoreFromMemento(Memento memento) {
state = memento.getState();
}
}
- Memento 备忘录:存储发起人的内部状态。
public class Memento {
private final String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
- Caretaker 管理者:负责保存备忘录,但不能对备忘录的内容进行操作或检查。
public class Caretaker {
private Memento memento;
public void saveMemento(Memento memento) {
this.memento = memento;
}
public Memento retrieveMemento() {
return memento;
}
}
18.5 注意事项
- 备忘录模式需要注意备忘录对象的存储和管理,确保备忘录的安全性,避免备忘录被外部修改,破坏对象的封装性。
- 在使用备忘录恢复状态时,要确保发起人的状态能够正确恢复,避免出现状态不一致的情况。
18.6 优缺点
- 优点:
- 可以在不破坏对象封装性的前提下,实现对象状态的保存和恢复,符合对象的封装原则。
- 可以方便地实现撤销操作,对于需要撤销功能的系统(如文本编辑器、图形编辑器等)非常有用。
- 缺点:
- 备忘录对象的存储可能会占用较多的资源,尤其是当保存大量的状态时。
- 备忘录模式会增加系统的复杂性,尤其是在多状态的情况下,需要管理多个备忘录对象。
18.7 使用场景
- 文本编辑器的撤销操作:在文本编辑器中,用户输入的文本内容可以看作是一个状态,使用备忘录模式可以保存用户输入的历史状态,实现撤销操作。
public class TextEditor {
private StringBuilder text;
private TextEditorMemento memento;
public TextEditor() {
this.text = new StringBuilder();
}
public void appendText(String newText) {
text.append(newText);
}
public String getText() {
return text.toString();
}
public TextEditorMemento createMemento() {
return new TextEditorMemento(text.toString());
}
public void restoreFromMemento(TextEditorMemento memento) {
this.text = new StringBuilder(memento.getSavedText());
}
}
public class TextEditorMemento {
private final String savedText;
public TextEditorMemento(String savedText) {
this.savedText = savedText;
}
public String getSavedText() {
return savedText;
}
}
public class TextEditorHistory {
private List<TextEditorMemento> mementos = new ArrayList<>();
public void save(TextEditorMemento memento) {
mementos.add(memento);
}
public TextEditorMemento undo() {
if (mementos.size() > 0) {
return mementos.remove(mementos.size() - 1);
}
return null;
}
}
- 游戏存档和读档:在游戏开发中,玩家的游戏进度(如角色的位置、等级、道具等)可以使用备忘录模式保存,玩家可以随时读取存档,恢复游戏进度。
public class GameCharacter {
private int level;
private int health;
private int mana;
public void setLevel(int level) {
this.level = level;
}
public void setHealth(int health) {
this.health = health;
}
public void setMana(int mana) {
this.mana = mana;
}
public int getLevel() {
return level;
}
public int getHealth() {
return health;
}
public int getMana() {
return mana;
}
public GameCharacterMemento createMemento() {
return new GameCharacterMemento(level, health, mana);
}
public void restoreFromMemento(GameCharacterMemento memento) {
this.level = memento.getLevel();
this.health = memento.getHealth();
this.mana = memento.getMana();
}
}
public class GameCharacterMemento {
private final int level;
private final int health;
private final int mana;
public GameCharacterMemento(int level, int health, int mana) {
this.level = level;
this.health = health;
this.mana = mana;
}
public int getLevel() {
return level;
}
public int getHealth() {
return health;
}
public int getMana() {
return mana;
}
}
public class GameSaveManager {
private List<GameCharacterMemento> mementos = new ArrayList<>();
public void save(GameCharacterMemento memento) {
mementos.add(memento);
}
public GameCharacterMemento loadLastSave() {
if (mementos.size() > 0) {
return mementos.remove(mementos.size() - 1);
}
return null;
}
}
- 需要保存和恢复数据的相关状态场景
- 提供一个可回滚(rollback)的操作
- 需要监控的副本场景中
- 数据库连接的事务管理就是用的备忘录模式。
注意:
- 各忘录的生命期
- 备忘录的性能
- 不要在频繁建立备份的场景中使用备忘录模式(比如一个for循环中)。
clone方式备忘录:
- 发起人角色融合了发起人角色和备忘录角色,具有双重功效
多状态的备忘录模式
- 增加了一个BeanUtils类,其中backupProp是把发起人的所有属性值转换到HashMap中,方便备忘录角色存储.restoreProp方法则是把HashMap中的值返回到发起人角色中。
BeanUtil工具类代码:
public class BeanUtils{
//把bean的所有属性及数值放入到Hashmap中
public static HashMap<String,Object> backupProp(Object bean){
HashMap<String,Object> result = new HashMap<String,Object>();
try{
//获得Bean描述
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
//获得属性描述
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
//遍历所有属性
for(PropertyDescriptor des:descriptors){
//属性名称
String fieldName des.getName();
//读取属性的方法
Method getter = des.getReadMethod();
//读取属性值
Object fieldValue = getter.invoke(bean.new Object[]());
if(!fieldName.equalsIgnoreCase("class")){
result.put(fieldName,fieldValue);
}
}
}catch(Exception e){
//处理异常
}
return result:
}
}
19. 访问者模式(Visitor Pattern)
19.1 定义
Represent an operation to be performed on the elements of an object structure.Visitor lets you define a new operation without changing the classes of the elements on which it operates.(封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。)
19.2 自己的理解
访问者模式就像一个访客,它可以访问不同的元素,并对不同元素执行不同的操作,而元素对象不需要改变自身的结构和行为。在软件中,当需要对一个对象结构中的元素添加新的操作,而不修改元素类的结构时,可以使用访问者模式,将操作封装在访问者类中,通过访问者类访问元素,实现操作的添加。
19.3 类型
行为型模式。
19.4 通用代码
- Element 元素接口:定义一个接受访问者访问的接口。
- 接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。
public interface Element {
void accept(Visitor visitor);
}
- ConcreteElement 具体元素:实现元素接口,接受访问者访问。
- 实现accept方法,通常是visitor.visit(this),基本上都形成了一种模式了。
public class ConcreteElementA implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationA() {
System.out.println("具体元素A的操作");
}
}
public class ConcreteElementB implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationB() {
System.out.println("具体元素B的操作");
}
}
- Visitor 访问者接口:声明对元素的访问操作。
- 抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法的参数定义哪些对象是可以被访问的
public interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
- ConcreteVisitor 具体访问者:实现访问者接口,实现对元素的具体操作。
- 它影响访问者访问到一个类后该怎么干,要做什么事情。
public class ConcreteVisitor1 implements Visitor {
@Override
public void visit(ConcreteElementA element) {
System.out.println("具体访问者1访问具体元素A");
element.operationA();
}
@Override
public void visit(ConcreteElementB element) {
System.out.println("具体访问者1访问具体元素B");
element.operationB();
}
}
public class ConcreteVisitor2 implements Visitor {
@Override
public void visit(ConcreteElementA element) {
System.out.println("具体访问者2访问具体元素A");
element.operationA();
}
@Override
public void visit(ConcreteElementB element) {
System.out.println("具体访问者2访问具体元素B");
element.operationB();
}
}
- ObjectStructure 对象结构:包含元素集合,并提供遍历元素的方法。
- 元素产生者,一般容纳在多个不同类、不同接口的容器,如List、Set、Map等,在项目中,一般很少抽象出这个角色。
import java.util.ArrayList;
import java.util.List;
public class ObjectStructure {
private List<Element> elements = new ArrayList<>();
public void add(Element element) {
elements.add(element);
}
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
}
19.5 注意事项
- 访问者模式会使元素和访问者之间产生依赖,当元素结构发生变化时,可能需要修改访问者的代码,需要考虑两者之间的耦合度。
- 访问者模式适用于对象结构相对稳定,而操作经常变化的情况,如果对象结构频繁变化,可能会导致大量的修改工作。
19.6 优缺点
- 优点:
- 可以在不修改元素类的情况下添加新的操作,符合开闭原则,提高了系统的可扩展性。
- 将元素的操作分离到访问者类中,使元素的操作更具灵活性,并且可以集中处理不同元素的操作。
- 缺点:
- 增加了系统的复杂性,尤其是元素和访问者之间的依赖关系,增加了代码的理解和维护难度。
- 访问者模式可能会破坏封装性,因为访问者需要访问元素的内部细节。
19.7 使用场景
- 报表生成:在报表系统中,报表由不同的元素(如表格、图表、文本等)组成,不同的访问者(如 HTML 报表生成器、PDF 报表生成器)可以访问这些元素并生成不同格式的报表。
public class Table implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void displayTable() {
System.out.println("显示表格");
}
}
public class Chart implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void displayChart() {
System.out.println("显示图表");
}
}
public class HTMLVisitor implements Visitor {
@Override
public void visit(Table table) {
System.out.println("生成 HTML 表格");
table.displayTable();
}
@Override
public void visit(Chart chart) {
System.out.println("生成 HTML 图表");
chart.displayChart();
}
}
public class PDFVisitor implements Visitor {
@Override
public void visit(Table table) {
System.out.println("生成 PDF 表格");
table.displayTable();
}
@Override
public void visit(Chart chart) {
System.out.println("生成 PDF 图表");
chart.displayChart();
}
}
- 编译器设计:在编译器中,对抽象语法树的元素(如表达式、语句等)进行不同的操作,如代码生成、类型检查等,可以使用访问者模式。
public class Expression implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void evaluate() {
System.out.println("计算表达式");
}
}
public class Statement implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void execute() {
System.out.println("执行语句");
}
}
public class CodeGeneratorVisitor implements Visitor {
@Override
public void visit(Expression expression) {
System.out.println("为表达式生成代码");
expression.evaluate();
}
@Override
public void visit(Statement statement) {
System.out.println("为语句生成代码");
statement.execute();
}
}
public class TypeCheckerVisitor implements Visitor {
@Override
public void visit(Expression expression) {
System.out.println("对表达式进行类型检查");
expression.evaluate();
}
@Override
public void visit(Statement statement) {
System.out.println("对语句进行类型检查");
statement.execute();
}
}
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作,也就说是用迭代器模式己经不能胜任的情景。
- 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。
20. 状态模式(State Pattern)(复杂)
20.1 定义
Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.(当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。)
20.2 自己的理解
状态模式就像一个自动售货机,它有不同的状态(如准备、出货、找零等),当处于不同状态时,它的行为也会相应改变,例如在准备状态可以接收硬币,在出货状态会输出商品。在软件中,当一个对象的行为取决于其内部状态,并且状态变化频繁时,可以使用状态模式,将状态封装成独立的状态类,使得对象可以根据状态的改变而改变行为,避免了大量的条件判断语句。
20.3 类型
行为型模式。
20.4 通用代码
- Context 上下文:维护一个具体状态对象,并将请求委托给状态对象处理。
- 定义客户端需要的接口,并且负责具体状态的切换。
public class Context {
private State state;
public Context(State state) {
this.state = state;
}
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle(this);
}
}
- State 状态接口:定义一个接口,用于封装与上下文相关的操作。
- 接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换。
public interface State {
void handle(Context context);
}
- ConcreteState 具体状态类:实现状态接口,处理与状态相关的操作。
- 每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理,通俗地说,就是本状态下要做的事情,以及本状态如何过渡到其他状态。
public class ConcreteStateA implements State {
@Override
public void handle(Context context) {
System.out.println("处理状态A的操作");
context.setState(new ConcreteStateB());
}
}
public class ConcreteStateB implements State {
@Override
public void handle(Context context) {
System.out.println("处理状态B的操作");
context.setState(new ConcreteStateA());
}
}
20.5 注意事项
- 状态模式需要准确地划分状态和状态之间的转换条件,避免状态转换混乱,确保状态的转换逻辑清晰明确。
- 状态类的设计要考虑状态之间的独立性,避免状态类之间相互依赖或耦合度过高,影响系统的可维护性。
20.6 优缺点
- 优点:
- 实现了状态和行为的封装,将状态相关的行为封装在状态类中,避免了大量的条件判断语句,提高了代码的可读性和可维护性。
- 符合开闭原则,当添加新的状态时,只需要添加新的状态类,而不需要修改现有代码,便于系统的扩展。
- 可以方便地实现状态的切换和状态行为的变化,提高了系统的灵活性。
- 缺点:
- 会增加系统中类的数量,尤其是在状态较多时,会增加代码的复杂性和管理成本。
- 状态模式的设计需要对状态和状态转换有清晰的理解,否则可能会导致状态管理混乱,影响系统的性能和可维护性。
20.7 使用场景
- 订单状态管理:在电商系统中,订单有不同的状态(如待支付、已支付、已发货、已完成等),根据订单的不同状态,可以执行不同的操作。
public class Order {
private OrderState state;
public Order() {
this.state = new OrderStatePending();
}
public void setState(OrderState state) {
this.state = state;
}
public void process() {
state.process(this);
}
}
public interface OrderState {
void process(Order order);
}
public class OrderStatePending implements OrderState {
@Override
public void process(Order order) {
System.out.println("订单处于待支付状态,等待用户支付");
order.setState(new OrderStatePaid());
}
}
public class OrderStatePaid implements OrderState {
@Override
public void process(Order order) {
System.out.println("订单已支付,准备发货");
order.setState(new OrderStateShipped());
}
}
public class OrderStateShipped implements OrderState {
@Override
public void process(Order order) {
System.out.println("订单已发货,等待用户确认收货");
order.setState(new OrderStateCompleted());
}
}
public class OrderStateCompleted implements OrderState {
@Override
public void process(Order order) {
System.out.println("订单已完成");
}
}
- 游戏角色状态控制:在游戏中,游戏角色有不同的状态(如正常、受伤、死亡等),根据角色的状态,其行为会有所不同,如移动速度、攻击能力等。
public class GameCharacter {
private CharacterState state;
public GameCharacter() {
this.state = new CharacterStateNormal();
}
public void setState(CharacterState state) {
this.state = state;
}
public void performAction() {
state.performAction(this);
}
}
public interface CharacterState {
void performAction(GameCharacter character);
}
public class CharacterStateNormal implements CharacterState {
@Override
public void performAction(GameCharacter character) {
System.out.println("角色正常,执行正常行动");
// 受到攻击等情况,可能切换状态
character.setState(new CharacterStateInjured());
}
}
public class CharacterStateInjured implements CharacterState {
@Override
public void performAction(GameCharacter character) {
System.out.println("角色受伤,行动受限");
// 进一步受伤或恢复,可能切换状态
}
}
public class CharacterStateDead implements CharacterState {
@Override
public void performAction(GameCharacter character) {
System.out.println("角色死亡,无法行动");
}
}
- 行为随状态改变而改变的场景
- 这也是状态模式的根本出发点,例如权限设计,人员的状态不同即使执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式。
- 条件、分支判断语句的替代者
注意:
状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说在行为受状态约束的情况下可以使用状态模式,而且使用时对象的状态最好不要超过5个。
21. 解释器模式(Interpreter Pattern)(少用)
21.1 定义
Given a language,define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.(给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。)
21.2 自己的理解
解释器模式就像一个翻译器,将一种语言的语句翻译成另一种语言或操作。在软件中,当需要实现一个简单的语言或规则系统,并且需要解释执行该语言或规则时,可以使用解释器模式,将语言的解释逻辑封装在解释器中,将语言的元素和解释规则进行抽象和组合,实现对语言的解释执行。
21.3 类型
行为型模式。
21.4 通用代码
- AbstractExpression 抽象表达式:声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。
public abstract class AbstractExpression {
public abstract void interpret(Context context);
}
- TerminalExpression 终结符表达式:实现与文法中的终结符相关联的解释操作。
public class TerminalExpression extends AbstractExpression {
@Override
public void interpret(Context context) {
// 终结符的解释逻辑
}
}
- NonterminalExpression 非终结符表达式:为文法中的非终结符实现解释操作,通常包含一个或多个子表达式。
public class NonterminalExpression extends AbstractExpression {
private AbstractExpression left;
private AbstractExpression right;
public NonterminalExpression(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public void interpret(Context context) {
// 非终结符的解释逻辑,通常需要调用子表达式的解释操作
left.interpret(context);
right.interpret(context);
}
}
- Context 上下文:包含解释器之外的一些全局信息。
public class Context {
// 上下文信息,如变量存储、环境设置等
}
21.5 注意事项
- 解释器模式对于简单的语言或规则系统比较适用,对于复杂的语言,使用解释器模式可能会导致系统过于复杂,难以维护和扩展。
- 在设计解释器模式时,要考虑表达式的组合和递归调用,确保解释器能够正确处理各种表达式的嵌套和组合。
21.6 优缺点
- 优点:
- 可以方便地实现一个简单的语言或规则系统,将语言的解释逻辑封装在解释器中,提高了代码的可维护性和可扩展性。
- 可以通过组合和扩展解释器类,实现复杂的语言结构,符合开闭原则。
- 缺点:
- 对于复杂的语言,解释器模式会导致类的数量增加,系统变得复杂,性能可能会受到影响。
- 解释器模式的设计需要对语言的语法和语义有深入的理解,对于开发人员的要求较高。
21.7 使用场景
- 简单的表达式求值:对于简单的数学表达式(如加减乘除运算),可以使用解释器模式进行解释和求值。
public class NumberExpression extends AbstractExpression {
private int value;
public NumberExpression(int value) {
this.value = value;
}
@Override
public void interpret(Context context) {
context.setResult(value);
}
}
public class AddExpression extends AbstractExpression {
private AbstractExpression left;
private AbstractExpression right;
public AddExpression(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public void interpret(Context context) {
left.interpret(context);
int leftValue = context.getResult();
right.interpret(context);
int rightValue = context.getResult();
context.setResult(leftValue + rightValue);
}
}
public class SubtractExpression extends AbstractExpression {
private AbstractExpression left;
private AbstractExpression right;
public SubtractExpression(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public void interpret(Context context) {
left.interpret(context);
int leftValue = context.getResult();
right.interpret(context);
int rightValue = context.getResult();
context.setResult(leftValue - rightValue);
}
}
public class Context {
private int result;
public int getResult() {
return result;
}
public void setResult(int result) {
this.result = result;
}
}
- 自定义规则引擎:在一些业务系统中,需要根据自定义的规则(如优惠规则、权限规则等)进行判断和处理,可以使用解释器模式实现规则的解释和执行。
public RuleExpression(String rule) {
this.rule = rule;
}
@Override
public void interpret(Context context) {
// 这里可以根据规则字符串进行解析和处理,例如根据规则字符串的内容判断权限、计算优惠等
// 以下是一个简单示例,根据规则字符串进行权限判断
if (rule.contains("admin")) {
context.setHasPermission(true);
} else {
context.setHasPermission(false);
}
}
}
public class Context {
private boolean hasPermission;
public boolean hasPermission() {
return hasPermission;
}
public void setHasPermission(boolean hasPermission) {
this.hasPermission = hasPermission;
}
}
- 重复发生的问题可以使用解释器模式
- 一个简单语法需要解释的场景
注意:
尽量不要在重要的模块中使用解释器模式,否则维护会是一个很大的问题。在项目中可以使用shell、JRuby、Groovy等脚本语言来代替解释器模式,弥补Java编译型语言的不足。
22. 享元模式(Flyweight Pattern)
22.1 定义
Use sharing to support large numbers of fine-grained objects efficiently.(使用共享对象可有效地支持大量的细粒度的对象。)
22.2 自己的理解
享元模式就像一个共享资源池,当需要创建大量相似的对象时,将这些对象存储在池中,当再次需要相同对象时,直接从池中获取,而不是重新创建,从而节省内存和资源。例如,在一个文档编辑软件中,字母、数字、标点符号等字符对象可能会大量出现,使用享元模式可以将这些字符对象存储在一个池子里,需要时复用,而不是为每个字符创建新对象。
对象的信息分为两个部分:内部状态(intrinsic)与外部状态(extrinsic)。
- 内部状态
内部状态是对象可共享出来的信息,存储在享元对象内部并且不会随环境改变而改变.
- 外部状态
外部状态是对象得以依赖的一个标记,是随环境改变而改变的、不可以共享的状态
22.3 类型
结构型模式。
22.4 通用代码
- Flyweight 抽象享元角色:定义享元对象的接口或抽象类。
public interface Flyweight {
void operation(String extrinsicState);
}
- ConcreteFlyweight 具体享元角色:实现抽象享元角色,存储内部状态,外部状态通过方法参数传递。
public class ConcreteFlyweight implements Flyweight {
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String extrinsicState) {
System.out.println("内部状态:" + intrinsicState + ",外部状态:" + extrinsicState);
}
}
- FlyweightFactory 享元工厂角色:负责创建和管理享元对象,使用一个池存储享元对象。
import java.util.HashMap;
import java.util.Map;
public class FlyweightFactory {
private Map<String, Flyweight> flyweightPool = new HashMap<>();
public Flyweight getFlyweight(String key) {
if (flyweightPool.containsKey(key)) {
return flyweightPool.get(key);
} else {
Flyweight flyweight = new ConcreteFlyweight(key);
flyweightPool.put(key, flyweight);
return flyweight;
}
}
}
22.5 注意事项
- 享元模式的关键是区分内部状态和外部状态,内部状态存储在享元对象中,外部状态通过方法参数传递,避免将过多的状态存储在享元对象中,导致对象不能共享。
- 享元工厂的设计要考虑线程安全,在多线程环境下,对享元池的操作可能会引发并发问题。
- 享元模式是线程不安全的,只有依靠经验,在需要的地方考虑一下线程安全,在大部分场景下不用考虑。对象池中的享元对象尽量多,多到足够满足为止,
- 性能安全:外部状态最好以java的基本类型作为标志,如String,int,可以提高效率。
22.6 优缺点
- 优点:
- 可以大幅度减少内存中对象的数量,节省系统资源,提高系统性能,尤其是在处理大量相似对象时。
- 享元模式将对象的共享部分提取出来,提高了对象的复用性,符合内存优化的原则。
- 缺点:
- 享元模式会使系统更加复杂,尤其是在区分内部状态和外部状态时,需要精心设计。
- 享元工厂的管理可能会带来额外的复杂性,如对象的创建、存储和检索等操作。
22.7 使用场景
- 文本编辑器:在文本编辑器中,对于重复出现的字符,可以使用享元模式共享字符对象,减少内存占用。
public class CharacterFlyweightFactory {
private Map<Character, Flyweight> characterPool = new HashMap<>();
public Flyweight getCharacter(char c) {
if (characterPool.containsKey(c)) {
return characterPool.get(c);
} else {
Flyweight flyweight = new ConcreteFlyweight(String.valueOf(c));
characterPool.put(c, flyweight);
return flyweight;
}
}
}
- 游戏中的粒子系统:在游戏开发中,大量的粒子(如雪花、雨滴等)具有相似的属性和行为,可以使用享元模式共享粒子对象,提高性能。
public class ParticleFlyweightFactory {
private Map<String, Flyweight> particlePool = new HashMap<>();
public Flyweight getParticle(String particleType) {
if (particlePool.containsKey(particleType)) {
return particlePool.get(particleType);
} else {
Flyweight flyweight = new ConcreteFlyweight(particleType);
particlePool.put(particleType, flyweight);
return flyweight;
}
}
}
- 系统中存在大量的相似对象。
- 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份.
- 需要缓冲池的场景
23. 桥接模式(Bridge Pattern)
23.1 定义
Decouple an abstraction from its implementation so that the two can vary independent心y.(将抽象和实现解耦,使得两者可以独立地变化。)
23.2 自己的理解
桥接模式就像一个桥梁,将抽象部分(如形状)和实现部分(如颜色)连接起来,使得抽象部分和实现部分可以独立地扩展和变化,而不会相互影响。例如,在绘制不同颜色的形状时,形状可以有圆形、矩形等,颜色可以有红色、蓝色等,使用桥接模式可以将形状和颜色分离,使得可以方便地添加新的形状或颜色,而不会对已有的部分产生影响。
23.3 类型
结构型模式。
23.4 通用代码
- Abstraction 抽象类:定义抽象部分的接口,包含一个对实现部分的引用。
public abstract class Abstraction {
protected Implementor implementor;
public Abstraction(Implementor implementor) {
this.implementor = implementor;
}
public abstract void operation();
}
- RefinedAbstraction 扩充抽象类:扩充抽象类的功能,实现更复杂的操作。
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
@Override
public void operation() {
implementor.operationImpl();
System.out.println("RefinedAbstraction的额外操作");
}
}
- Implementor 实现类接口:定义实现部分的接口。
public interface Implementor {
void operationImpl();
}
- ConcreteImplementorA 具体实现类 A:实现实现类接口。
public class ConcreteImplementorA implements Implementor {
@Override
public void operationImpl() {
System.out.println("具体实现类A的实现操作");
}
}
- ConcreteImplementorB 具体实现类 B:实现实现类接口。
public class ConcreteImplementorB implements Implementor {
@Override
public void operationImpl() {
System.out.println("具体实现类B的实现操作");
}
}
23.5 注意事项
- 桥接模式的关键在于将抽象部分和实现部分分离,并且通过组合的方式将它们连接起来,这样可以使抽象和实现部分独立地扩展,避免了使用继承导致的类爆炸问题。
- 在设计时,要准确地划分抽象部分和实现部分,确保它们之间的关联是合理的,并且要注意它们之间的交互逻辑,避免出现不匹配的情况。
23.6 优缺点
- 优点:
- 分离了抽象部分和实现部分,使它们可以独立地变化,符合开闭原则,提高了系统的可扩展性。
- 实现了抽象和实现的解耦,抽象部分和实现部分可以分别扩展,不会相互影响,提高了代码的灵活性。
- 对于多维度的变化,桥接模式可以很好地应对,避免了使用继承带来的复杂的类层次结构。
- 缺点:
- 增加了系统的复杂度,需要理解抽象部分和实现部分的分离和组合关系,对于开发人员的设计能力要求较高。
- 由于分离和组合的设计,代码的理解和维护成本可能会有所增加,尤其是在大型系统中,需要更多的设计和架构知识。
23.7 使用场景
- 不同平台的图形绘制:在跨平台开发中,图形绘制的抽象部分(如绘制形状)可以与具体的实现部分(如在 Windows 平台绘制或在 Linux 平台绘制)分离,使用桥接模式可以方便地切换平台,而不影响图形绘制的抽象逻辑。
public interface DrawingAPI {
void drawCircle(double x, double y, double radius);
void drawRectangle(double x, double y, double width, double height);
}
public class WindowsDrawingAPI implements DrawingAPI {
@Override
public void drawCircle(double x, double y, double radius) {
System.out.println("在Windows平台上绘制圆形,圆心坐标:(" + x + ", " + y + "),半径:" + radius);
}
@Override
public void drawRectangle(double x, double y, double width, double height) {
System.out.println("在Windows平台上绘制矩形,左上角坐标:(" + x + ", " + y + "),宽:" + width + ",高:" + height);
}
}
public class LinuxDrawingAPI implements DrawingAPI {
@Override
public void drawCircle(double x, double y, double radius) {
System.out.println("在Linux平台上绘制圆形,圆心坐标:(" + x + ", " + y + "),半径:" + radius);
}
@Override
public void drawRectangle(double x, double y, double width, double height) {
System.out.println("在Linux平台上绘制矩形,左上角坐标:(" + x + ", " + y + "),宽:" + width + ",高:" + height);
}
}
public abstract class Shape {
protected DrawingAPI drawingAPI;
public Shape(DrawingAPI drawingAPI) {
this.drawingAPI = drawingAPI;
}
public abstract void draw();
}
public class Circle extends Shape {
private double x;
private double y;
private double radius;
public Circle(double x, double y, double radius, DrawingAPI drawingAPI) {
super(drawingAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawingAPI.drawCircle(x, y, radius);
}
}
public class Rectangle extends Shape {
private double x;
private double y;
private double width;
private double height;
public Rectangle(double x, double y, double width, double height, DrawingAPI drawingAPI) {
super(drawingAPI);
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public void draw() {
drawingAPI.drawRectangle(x, y, width, height);
}
}
- 设备驱动程序开发:对于不同类型的设备(如打印机、扫描仪等),设备的抽象操作(如打印、扫描)可以与具体的设备驱动实现分离,使用桥接模式可以方便地添加新的设备类型或驱动实现。
public interface DeviceDriver {
void performOperation();
}
public class PrinterDriver implements DeviceDriver {
@Override
public void performOperation() {
System.out.println("打印机驱动执行操作");
}
}
public class ScannerDriver implements DeviceDriver {
@Override
public void performOperation() {
System.out.println("扫描仪驱动执行操作");
}
}
public abstract class Device {
protected DeviceDriver deviceDriver;
public Device(DeviceDriver deviceDriver) {
this.deviceDriver = deviceDriver;
}
public abstract void operate();
}
public class Printer extends Device {
public Printer(DeviceDriver deviceDriver) {
super(deviceDriver);
}
@Override
public void operate() {
deviceDriver.performOperation();
System.out.println("打印机进行打印操作");
}
}
public class Scanner extends Device {
public Scanner(DeviceDriver deviceDriver) {
super(deviceDriver);
}
@Override
public void operate() {
deviceDriver.performOperation();
System.out.println("扫描仪进行扫描操作");
}
}
- 不希望或不适用使用继承的场景
- 接口或抽象类不稳定的场景
- 重用性要求较高的场景
注意:
发现类的继承有N层时,可以考虑使用桥梁模式。桥梁模式主要考虑如何拆分抽象和实现。