设计模式笔记
声明 : 未整理完成,仅仅记录下
快速回忆
- 结构型 : 适桥组装外享代
类型 | 设计模式 | 英文 | key(理解的关键点) |
创建型 | 单例 | Singleton Pattern | - |
工厂方法 | Factory Method Pattern | ||
抽象工厂 | Abstract Factory Pattern | ||
建造者 | Builder Pattern | append() / 构造字符串 | |
原型 | Prototype Pattern | clone / 拷贝构造 | |
结构型 | 适配器 | Adapter Pattern | 改变接口 |
桥接器 | Bridge Pattern | JDBC驱动接口 | |
组合 | Composite Pattern | ||
装饰器 | Decorator Pattern | 包装成一个子对象(不改变接口) | |
外观 | Facade Pattern | 子系统对外的统一入口 | |
享元 | Flyweight Pattern | JAVA中字符串常量池 | |
代理 | Proxy Pattern | Spring AOP | |
行为型 | 责任链 | Chain of Responsibility Pattern | Servlet中的FilterChain |
命令 | Command Pattern | ||
解释器 | Interpreter Pattern | ||
迭代器 | Iterator Pattern | ||
中介者 | Mediator Pattern | 一个中介对象来封装一系列对象之间的交互,(一个系统内的)多个对象间通过中介交互 | |
备忘录 | Memento Pattern | ||
观察者 | Observer Pattern | (观察者)订阅 | |
状态模式 | State Pattern | ||
策略 | Strategy Pattern | 策略替换 | |
模板方法 | Template Method Pattern | servelet重写doGet和doPost | |
访问者 | Visitor Pattern | Double Dispatch |
混淆区分
个别难理解的
1. 访问者模式
想象一个场景:你是一家公司的CEO,你的公司有两个稳定的部门:工程师部 (Engineer) 和经理部 (Manager)。这就是你的“对象结构”,它很稳定,不常变化。
现在,你想让不同的“外部人员”来访问你的公司,并对不同部门的人做不同的事:
一位访客是财务总监 (CFOVisitor):他来找工程师核算工资,找经理审批预算。
另一位访客是CTO (CTOVisitor):他来找工程师review代码,找经理制定技术路线。
如果不使用访问者模式,代码会怎样?
你可能会在 Engineer 和 Manager 类里不停地加方法:calculateSalary(), reviewCode(), approveBudget()… 每来一个新访客,你就要修改所有部门的类!这违反了“对修改关闭,对扩展开放”的原则。
访问者模式的核心思想就是:
允许你在不修改现有部门(元素)类的情况下,为它们定义新的操作(访客)。
它将数据结构(公司部门)和数据操作(访客要做的事)清晰地分离开。
代码模拟:一步步实现访问者模式
让我们用代码来演绎上面这个例子。
第1步:定义“元素”接口 - 公司员工
所有可以被访问的“部门”都必须实现这个接口。它只有一个核心方法 accept(Visitor visitor)。
// 元素接口:声明了一个接受访问者的方法
public interface Employee {void accept(Visitor visitor); // 每个员工都“接受”一个访客
}
第2步:实现具体的“元素” - 具体部门
// 具体元素:工程师
public class Engineer implements Employee {private String name;private double salary;public Engineer(String name, double salary) {this.name = name;this.salary = salary;}// 最重要的方法!这里实现了“双分派”@Overridepublic void accept(Visitor visitor) {// 1. 第一步:根据`this`,确定自己是`Engineer`类型。// 2. 第二步:调用访问者的`visit(this)`方法,把自己的具体类型(`Engineer`)传递过去。visitor.visit(this);}// 工程师特有的方法,比如写代码public void code() {System.out.println(name + " is coding...");}public double getSalary() { return salary; }
}// 具体元素:经理
public class Manager implements Employee {private String name;private double budget;public Manager(String name, double budget) {this.name = name;this.budget = budget;}@Overridepublic void accept(Visitor visitor) {// 同样,把`Manager`这个具体类型传递过去visitor.visit(this);}// 经理特有的方法,比如开会public void meeting() {System.out.println(name + " is in a meeting...");}public double getBudget() { return budget; }
}
第3步:定义“访问者”接口
为每一种“元素”都声明一个访问方法。这样,一个新的访问者就必须知道如何对待所有类型的员工。
// 访问者接口:为每种元素都声明一个访问方法
public interface Visitor {void visit(Engineer engineer); // 重载方法void visit(Manager manager); // 重载方法
}
第4步:实现具体的“访问者” - 具体访客
这才是模式威力的体现!添加新操作变得无比简单。
// 具体访问者:财务总监(负责核算工资和预算)
public class CFOVisitor implements Visitor {@Overridepublic void visit(Engineer engineer) {// 财务总监面对工程师:核算工资double salary = engineer.getSalary();System.out.println("Engineer " + engineer.name + "'s salary is: $" + salary);}@Overridepublic void visit(Manager manager) {// 财务总监面对经理:审批预算double budget = manager.getBudget();System.out.println("Manager " + manager.name + "'s budget is: $" + budget);}
}// 具体访问者:CTO(负责技术工作)
public class CTOVisitor implements Visitor {@Overridepublic void visit(Engineer engineer) {// CTO面对工程师:Review代码System.out.println("CTO is reviewing " + engineer.name + "'s code.");engineer.code(); // 可以让工程师演示一下}@Overridepublic void visit(Manager manager) {// CTO面对经理:讨论技术路线System.out.println("CTO is discussing tech roadmap with Manager " + manager.name);manager.meeting();}
}
第5步:客户端使用
现在,我们可以看到访问者模式是如何工作的:
public class CompanyReport {public static void main(String[] args) {// 1. 创建公司员工(稳定的数据结构)List<Employee> employees = Arrays.asList(new Engineer("Alice", 120000),new Manager("Bob", 500000),new Engineer("Charlie", 110000));// 2. 创建不同的访问者(易变的操作)Visitor cfo = new CFOVisitor();Visitor cto = new CTOVisitor();System.out.println("=== CFO's Visit ===");// 3. 让每个员工接受CFO的访问for (Employee employee : employees) {employee.accept(cfo); // 关键行!}System.out.println("\n=== CTO's Visit ===");// 4. 让每个员工接受CTO的访问for (Employee employee : employees) {employee.accept(cto); // 关键行!}}
}
- 输出结果:
text
=== CFO's Visit ===
Engineer Alice's salary is: $120000.0
Manager Bob's budget is: $500000.0
Engineer Charlie's salary is: $110000.0=== CTO's Visit ===
CTO is reviewing Alice's code.
Alice is coding...
CTO is discussing tech roadmap with Manager Bob
Bob is in a meeting...
CTO is reviewing Charlie's code.
Charlie is coding...
- 核心机制:“双分派” (Double Dispatch)
- 这是理解访问者模式的关键。在上面的代码中,最关键的一行是:
employee.accept(visitor);
这行代码完成了两次动态分配(分派):
-
第一次分派(根据 employee 的实际类型):employee 可能是 Engineer 或 Manager。JVM在运行时才能确定它的具体类型,从而决定调用 Engineer.accept() 还是 Manager.accept()。
-
第二次分派(根据 visitor 的实际类型):在 accept 方法内部,会调用 visitor.visit(this)。这里的 this 是非常明确的(例如,在 Engineer.accept() 里,this 就是 Engineer 类型)。JVM此时又能根据 visitor 的具体类型(是 CFOVisitor 还是 CTOVisitor)和 visit 方法的参数类型(Engineer),决定最终调用 CFOVisitor.visit(Engineer e) 还是 CTOVisitor.visit(Engineer e)。
通过这两步,最终将“操作”绑定到了“具体的元素类型”和“具体的访问者类型”上。 这就是“双分派”。