系列文章目录
第一章 解锁单例模式:Java世界的唯一实例之道
第二章 解锁工厂模式:工厂模式探秘
第三章 解锁设计模式:代理模式的多面解析与实战
第四章 解锁装饰器模式:代码增强的魔法宝典
第五章 解锁建造者模式:Java 编程中的对象构建秘籍
第六章 解锁原型模式:Java 中的高效对象创建之道
第七章 解锁适配器模式:代码重构与架构优化的魔法钥匙
第八章 解锁桥接模式:Java架构中的解耦神器
第九章 解锁组合模式:Java 代码中的树形结构奥秘
第十章 解锁享元模式:内存优化与性能提升的关键密码
第十一章 解锁外观模式:Java 编程中的优雅架构之道
第十二章 解锁观察者模式:Java编程中的高效事件管理之道
第十三章 解锁策略模式:Java 实战与应用全景解析
第十四章 解锁状态模式:Java 编程中的行为魔法
第十五章 解锁模板方法模式:Java 实战与应用探秘
第十六章 解锁命令模式:Java 编程中的解耦神器
第十七章 解锁迭代器模式:Java 编程的遍历神器
第十八章 解锁责任链模式:Java 实战与应用探秘
第十九章 解锁中介者模式:代码世界的“社交达人”
第二十章 解锁备忘录模式:代码世界的时光机
第二十一章 解锁访问者模式:Java编程的灵活之道
第二十二章 解锁Java解释器模式:概念、应用与实战
文章目录
- 引言:探索访问者模式的奥秘
- 一、访问者模式的基本概念
- (一)定义与核心思想
- (二)主要角色解析
- 二、访问者模式的工作原理
- (一)双分派机制详解
- (二)访问者模式的执行流程
- 三、Java 实现访问者模式的步骤与代码示例
- (一)定义抽象访问者和具体访问者
- (二)定义抽象元素和具体元素
- (三)构建对象结构
- (四)客户端调用与测试
- 四、访问者模式的使用场景
- (一)对象结构稳定但操作多变的场景
- (二)需要对对象进行多种不相关操作的场景
- (三)数据结构与操作分离的场景
- 五、访问者模式的优缺点分析
- (一)优点
- (二)缺点
- 六、总结与展望
- (一)访问者模式的核心要点回顾
- (二)对未来学习和应用的建议
引言:探索访问者模式的奥秘
在软件开发的广袤世界里,设计模式宛如璀璨星辰,照亮着开发者前行的道路。它们是前人智慧的结晶,是解决特定问题的通用方案。而访问者模式,作为其中独特的一员,以其别具一格的设计理念和强大的功能,在众多设计模式中独树一帜。
想象一下,你置身于一家热闹非凡的超市,购物车中装满了琳琅满目的商品,有新鲜诱人的水果、美味可口的零食、实用的生活用品等等。当你推着购物车来到收银台时,收银员需要根据不同商品的价格、折扣以及会员等级来计算最终的总价。这看似平常的购物场景,却蕴含着访问者模式的核心思想。
不同类型的商品就如同访问者模式中的具体元素,它们各自有着独特的属性和行为。而收银员则像是访问者,能够对不同的商品进行统一的访问和处理。同时,超市的购物车可以看作是对象结构,它容纳了各种商品,为访问者提供了遍历和操作这些元素的场所。
在这个场景中,我们可以发现,商品的种类可能会不断增加,新的商品可能会加入超市的货架;同时,对于商品的处理方式也可能会发生变化,比如不同的促销活动、会员制度的调整等。如果我们采用传统的编程方式,可能需要频繁地修改商品类和处理商品的代码,这无疑会增加代码的复杂度和维护成本。而访问者模式的出现,为我们提供了一种优雅的解决方案。它能够将数据结构和作用于数据结构上的操作解耦,使得我们可以在不改变商品类的前提下,灵活地定义和添加新的操作。
通过这个超市购物的例子,我们对访问者模式有了一个初步的感性认识。接下来,让我们深入探索访问者模式的概念、原理、结构、实现方式、应用场景以及优缺点,揭开它神秘的面纱,领略它在软件开发中的独特魅力和强大威力。
一、访问者模式的基本概念
(一)定义与核心思想
访问者模式(Visitor Pattern)是一种将数据操作和数据结构分离的设计模式 ,它允许在不修改现有对象结构的情况下,定义作用于这些对象的新操作。其核心思想在于把对数据结构中元素的操作封装成独立的访问者类,使得操作的变化不会影响到数据结构本身,反之亦然。这就好比一个博物馆,馆内的展品(数据结构)是固定的,但不同的导游(访问者)可以根据自己的讲解风格和重点(操作)来向游客介绍这些展品,而不会改变展品本身。
在软件开发中,当我们面对一个复杂的数据结构,并且需要对其元素执行多种不同且可能变化的操作时,访问者模式就展现出了它的强大优势。例如,在一个图形绘制系统中,存在各种形状的图形对象,如圆形、矩形、三角形等(构成数据结构)。如果我们将绘制、缩放、旋转等操作直接定义在图形对象类中,随着操作的增加和变化,图形对象类会变得臃肿不堪,代码的维护和扩展也会变得异常困难。而使用访问者模式,我们可以将这些操作分别封装在不同的访问者类中,图形对象类只需要负责提供接受访问者的方法,这样就实现了数据结构和操作的解耦,使得系统更加灵活和可维护。
(二)主要角色解析
访问者模式包含以下几个主要角色:
- 抽象访问者(Visitor):
抽象访问者定义了一系列访问具体元素的接口方法,这些方法的参数通常是具体元素类型。它为访问具体元素提供了统一的抽象规范,是访问者模式的核心接口之一。通过这个接口,具体访问者可以定义对不同类型元素的操作逻辑。例如:
public interface Visitor {
void visit(ConcreteElementA elementA);
void visit(ConcreteElementB elementB);
}
在这个示例中,Visitor接口定义了visit(ConcreteElementA elementA)和visit(ConcreteElementB elementB)两个方法,分别用于访问ConcreteElementA和ConcreteElementB类型的具体元素。这就像是一个通用的操作模板,具体的操作实现由具体访问者来完成。每个方法对应一种具体元素类型,确保了对不同元素的操作可以通过这个接口进行统一的定义和调用。
- 具体访问者(ConcreteVisitor):
具体访问者实现了抽象访问者接口中定义的方法,针对不同的具体元素执行特定的操作。它是访问者模式中真正实现操作逻辑的部分。例如,在一个电商系统中,我们可以定义一个计算商品总价的访问者:
public class TotalPriceVisitor implements Visitor {
private double totalPrice = 0;
@Override
public void visit(Book book) {
totalPrice += book.getPrice();
}
@Override
public void visit(ElectronicProduct electronicProduct) {
totalPrice += electronicProduct.getPrice();
}
public double getTotalPrice() {
return totalPrice;
}
}
在这个例子中,TotalPriceVisitor实现了Visitor接口,并重写了visit(Book book)和visit(ElectronicProduct electronicProduct)方法。在visit方法中,根据不同的商品类型(书籍和电子产品),将它们的价格累加到totalPrice变量中,从而实现了计算商品总价的功能。通过这种方式,具体访问者可以根据业务需求对不同类型的元素进行个性化的操作,而不会影响到元素本身的结构和其他操作。
- 抽象元素(Element):
抽象元素定义了一个接受访问者的方法accept(Visitor visitor),该方法用于接收访问者对象,使得访问者能够访问该元素。它是所有具体元素的抽象基类或接口,为具体元素提供了统一的接受访问者的规范。例如:
public interface Product {
void accept(Visitor visitor);
}
在这个商品接口示例中,Product接口定义了accept方法,任何实现该接口的具体商品类都必须实现这个方法,以接受访问者的访问。这个方法就像是一个入口,允许访问者进入元素内部进行操作,是访问者模式中元素与访问者交互的关键桥梁。
- 具体元素(ConcreteElement):
具体元素实现了抽象元素中定义的接受访问者的方法,在实现中通常会调用访问者的相应访问方法,将自身作为参数传递给访问者,从而完成对自身的访问操作。例如:
public class Book implements Product {
private String title;
private String author;
private double price;
public Book(String title, String author, double price) {
this.title = title;
this.author = author;
this.price = price;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public double getPrice() {
return price;
}
}
public class ElectronicProduct implements Product {
private String model;
private String brand;
private double price;
public ElectronicProduct(String model, String brand, double price) {
this.model = model;
this.brand = brand;
this.price = price;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public double getPrice() {
return price;
}
}
在上述代码中,Book和ElectronicProduct类分别实现了Product接口的accept方法。在accept方法中,调用了访问者的visit方法,并将自身(this)作为参数传递进去。这样,当访问者访问这些具体元素时,就可以执行在具体访问者中定义的针对该元素的操作。通过这种方式,具体元素将操作的实现委托给了访问者,实现了数据结构和操作的分离。
- 对象结构(ObjectStructure):
对象结构是一个包含元素集合的容器,它负责管理元素对象,并提供遍历这些元素的方法,以便访问者能够对集合中的所有元素进行访问。例如:
import java.util.ArrayList;
import java.util.List;
public class ProductList {
private List<Product> products = new ArrayList<>();
public void addProduct(Product product) {
products.add(product);
}
public void removeProduct(Product product) {
products.remove(product);
}
public void accept(Visitor visitor) {
for (Product product : products) {
product.accept(visitor);
}
}
}
在这个商品集合类的例子中,ProductList类维护了一个Product类型的列表products,并提供了addProduct和removeProduct方法来管理商品元素。accept方法则遍历列表中的所有商品元素,并调用每个元素的accept方法,让访问者对其进行访问。通过对象结构,访问者可以方便地对整个元素集合进行统一的操作,而无需关心具体元素的存储和管理细节。
二、访问者模式的工作原理
(一)双分派机制详解
在理解访问者模式的工作原理时,双分派机制是一个关键概念。为了更好地理解双分派,我们先来回顾一下静态分派和动态分派。
- 静态分派与动态分派结合代码实例
- 静态分派:静态分派基于静态类型在编译期进行方法选择。方法重载是静态分派的典型应用。例如:
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class AnimalProcessor {
public void process(Animal animal) {
System.out.println("Processing animal");
}
public void process(Dog dog) {
System.out.println("Processing dog");
}
public void process(Cat cat) {
System.out.println("Processing cat");
}
}
public class StaticDispatchExample {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
AnimalProcessor processor = new AnimalProcessor();
processor.process(dog);
processor.process(cat);
}
}
在这个例子中,dog和cat的静态类型都是Animal,尽管它们的实际类型分别是Dog和Cat。在编译期,编译器根据参数的静态类型Animal来选择process(Animal animal)方法,所以输出结果都是 “Processing animal” 。这表明静态分派是根据变量的静态类型来确定调用哪个重载方法的,它发生在编译期。
- 动态分派:动态分派基于对象实际类型在运行期置换方法,方法重写是动态分派的典型表现。例如:
class Animal {
public void makeSound() {
System.out.println("Animal makes sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat meows");
}
}
public class DynamicDispatchExample {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
dog.makeSound();
cat.makeSound();
}
}
在这个示例中,dog和cat的静态类型都是Animal,但实际类型分别是Dog和Cat。在运行时,JVM 根据对象的实际类型来决定调用哪个重写后的makeSound方法。所以,dog.makeSound()会输出 “Dog barks”,cat.makeSound()会输出 “Cat meows”。这体现了动态分派是在运行期根据对象的实际类型来确定方法的执行版本 。
- 双分派在访问者模式中的实现
双分派是指在选择方法时,不仅要根据对象的实际类型,还要根据参数的实际类型来确定执行的操作。在访问者模式中,通过两次方法调用实现双分派。
以一个简单的图形绘制系统为例,假设有圆形和矩形两种图形,我们要实现一个访问者来计算它们的面积和周长:
// 抽象元素
interface Shape {
void accept(ShapeVisitor visitor);
}
// 具体元素:圆形
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public void accept(ShapeVisitor visitor) {
visitor.visit(this);
}
public double getRadius() {
return radius;
}
}
// 具体元素:矩形
class Rectangle implements Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public void accept(ShapeVisitor visitor) {
visitor.visit(this);
}
public double getWidth() {
return width;
}
public double getHeight() {
return height;
}
}
// 抽象访问者
interface ShapeVisitor {
void visit(Circle circle);
void visit(Rectangle rectangle);
}
// 具体访问者:计算面积和周长
class AreaAndPerimeterVisitor implements ShapeVisitor {
@Override
public void visit(Circle circle) {
double area = Math.PI * circle.getRadius() * circle.getRadius();
double perimeter = 2 * Math.PI * circle.getRadius();
System.out.println("Circle - Area: " + area + ", Perimeter: " + perimeter);
}
@Override
public void visit(Rectangle rectangle) {
double area = rectangle.getWidth() * rectangle.getHeight();
double perimeter = 2 * (rectangle.getWidth() + rectangle.getHeight());
System.out.println("Rectangle - Area: " + area + ", Perimeter: " + perimeter);
}
}
// 测试
public class VisitorPatternDemo {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(4, 6);
ShapeVisitor visitor = new AreaAndPerimeterVisitor();
circle.accept(visitor);
rectangle.accept(visitor);
}
}
在这个例子中,首先circle.accept(visitor)和rectangle.accept(visitor)这一步是根据对象的实际类型(Circle和Rectangle)进行的第一次分派,调用了具体元素类中实现的accept方法。在accept方法中,visitor.visit(this)又根据参数this(即具体的Circle或Rectangle对象)的实际类型进行了第二次分派,调用了ShapeVisitor接口中对应的visit方法。这样,通过两次方法调用,根据元素类型和访问者类型决定了最终执行的操作,实现了双分派。
(二)访问者模式的执行流程
下面通过详细的代码跟踪,展示访问者模式从客户端创建对象结构和访问者,到元素接受访问者访问,最终执行具体操作的完整流程。
假设我们有一个电商系统,要计算购物车中不同商品的总价和折扣价。
- 定义抽象元素和具体元素
// 抽象元素:商品
interface Product {
void accept(ProductVisitor visitor);
}
// 具体元素:书籍
class Book implements Product {
private String title;
private double price;
public Book(String title, double price) {
this.title = title;
this.price = price;
}
@Override
public void accept(ProductVisitor visitor) {
visitor.visit(this);
}
public double getPrice() {
return price;
}
}
// 具体元素:电子产品
class ElectronicProduct implements Product {
private String model;
private double price;
public ElectronicProduct(String model, double price) {
this.model = model;
this.price = price;
}
@Override
public void accept(ProductVisitor visitor) {
visitor.visit(this);
}
public double getPrice() {
return price;
}
}
- 定义抽象访问者和具体访问者
// 抽象访问者
interface ProductVisitor {
void visit(Book book);
void visit(ElectronicProduct electronicProduct);
}
// 具体访问者:计算总价和折扣价
class PriceCalculatorVisitor implements ProductVisitor {
private double totalPrice = 0;
private double totalDiscountedPrice = 0;
@Override
public void visit(Book book) {
totalPrice += book.getPrice();
// 假设书籍打9折
totalDiscountedPrice += book.getPrice() * 0.9;
}
@Override
public void visit(ElectronicProduct electronicProduct) {
totalPrice += electronicProduct.getPrice();
// 假设电子产品打8折
totalDiscountedPrice += electronicProduct.getPrice() * 0.8;
}
public double getTotalPrice() {
return totalPrice;
}
public double getTotalDiscountedPrice() {
return totalDiscountedPrice;
}
}
- 定义对象结构
import java.util.ArrayList;
import java.util.List;
// 对象结构:购物车
class ShoppingCart {
private List<Product> products = new ArrayList<>();
public void addProduct(Product product) {
products.add(product);
}
public void accept(ProductVisitor visitor) {
for (Product product : products) {
product.accept(visitor);
}
}
}
- 客户端代码
public class EcommerceSystem {
public static void main(String[] args) {
// 创建对象结构:购物车
ShoppingCart cart = new ShoppingCart();
// 添加商品到购物车
cart.addProduct(new Book("Design Patterns", 50));
cart