程序员必知!访问者模式的实战应用与案例分析
  TEZNKK3IfmPf 2024年04月19日 27 0

程序员必知!访问者模式的实战应用与案例分析

访问者模式将数据结构与操作分离,允许在不改动已有类的情况下增添新操作,在电商平台案例中,商品类(如手机、电脑)可通过接受访问者对象来实现多种操作(如打折、加入购物车),避免了类臃肿,降低了耦合度,这种模式提升了代码的可扩展性与维护性,使添加新操作更为灵活。

定义

程序员必知!访问者模式的实战应用与案例分析

访问者模式是一种行为设计模式,它允许你在不修改已有类的情况下增加新的操作,访问者模式通过将数据结构与数据操作分离,来达到在不改变各类的前提下增加新的操作。

在访问者模式中,我们通常有两个层次的结构:元素(Element)结构和访问者(Visitor)结构,元素结构包括各个被访问的具体元素类,它们通常拥有一个接受访问者的方法,访问者结构则包括各个访问者类,它们实现了访问元素所需要的方法。

举一个现实业务中的例子:假设有一个电商平台,平台上有多种商品,如手机、电脑、衣服等,现在需要对这些商品进行多种操作,比如打折、加入购物车、评价等,如果我们直接在每个商品类中添加这些方法,会导致类变得臃肿且不易维护,这时,可以使用访问者模式来解决这个问题,首先定义一个商品接口,包含接受访问者的方法,然后为每个具体商品(手机、电脑、衣服)创建类,实现商品接口,接着定义一个访问者接口,包含对商品的各种操作(打折、加入购物车、评价),最后为每个具体操作创建访问者类,实现访问者接口。

当用户需要对某个商品进行操作时(比如打折),我们只需创建一个对应的访问者对象(打折访问者),然后调用商品的接受访问者方法,将访问者对象传递给商品,商品在接受到访问者对象后,会调用访问者对象的相应方法(打折方法)来完成操作。

这样,我们就实现了在不修改商品类的情况下,为商品增加了新的操作,同时访问者模式还使商品类与操作类之间的耦合度降低,提高了代码的可扩展性和可维护性。

代码案例

程序员必知!访问者模式的实战应用与案例分析

反例

下面是一个反例代码,模拟一个简单的电商系统中的商品类以及对其进行操作的方式,首先有一个Product基类和一些具体的商品类(如PhoneComputerClothes),每个商品类中都直接包含了可能对其进行的操作(如discount打折、addToCart加入购物车),如下代码:

// 商品基类  
class Product {  
    String name;  
    double price;  
  
    public Product(String name, double price) {  
         = name;  
        this.price = price;  
    }  
  
    // 显示商品信息  
    public void display() {  
        System.out.println(name + ": " + price);  
    }  
  
    // 默认打折方法(假设所有商品打折方式一样,实际上可能不同)  
    public void discount() {  
        this.price *= 0.9; // 打9折  
        System.out.println(name + " discounted. New price: " + price);  
    }  
  
    // 默认加入购物车方法  
    public void addToCart() {  
        System.out.println(name + " added to cart.");  
    }  
}  
  
// 具体的商品类 - 手机  
class Phone extends Product {  
    public Phone(String name, double price) {  
        super(name, price);  
    }  
  
    // 手机可能有特殊的打折方式,但在这个反例中我们仍然使用默认方式  
}  
  
// 具体的商品类 - 电脑  
class Computer extends Product {  
    public Computer(String name, double price) {  
        super(name, price);  
    }  
  
    // 电脑可能有特殊的打折方式,但在这个反例中我们仍然使用默认方式  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        // 创建商品对象  
        Phone phone = new Phone("iPhone 14", 8999.0);  
        Computer computer = new Computer("MacBook Pro", 15999.0);  
  
        // 显示商品信息  
        phone.display();  
        computer.display();  
  
        // 对商品进行操作  
        phone.discount(); // 打折  
        phone.addToCart(); // 加入购物车  
  
        computer.discount(); // 打折  
        // 假设我们不想将电脑加入购物车  
        // computer.addToCart(); // 这行代码被注释掉了  
    }  
}

输出结果:

iPhone 14: 8999.0  
MacBook Pro: 15999.0  
iPhone 14 discounted. New price: 8099.1  
iPhone 14 added to cart.  
MacBook Pro discounted. New price: 14399.1

在这个反例中,暴露出了几个问题:

  1. 每个商品类中都包含了操作方法(打折、加入购物车),这导致了数据和操作的紧密耦合。
  2. 如果想要为不同的商品类添加新的操作或者修改现有操作的行为,可能需要修改每个商品类的代码,这违反了开闭原则。
  3. 即使某些商品类不需要某些操作(比如在这个例子中,我们假设Computer类不应该被加入购物车),这些操作仍然存在于类中,可能会导致误用。

通过访问者模式,可以将操作逻辑从商品类中分离出来,放到单独的访问者类中,从而解决上述问题,如下正例。

正例

下面是一个使用模板方法模式的正例代码,模拟一个简单的咖啡和茶的制作过程,如下代码:

// 抽象类,表示饮品,定义模板方法  
abstract class Beverage {  
    // 模板方法,定义制作饮品的算法骨架  
    final void prepareRecipe() {  
        boilWater();  
        brew();  
        pourInCup();  
        if (wantsCondiments()) {  
            addCondiments();  
        }  
    }  
  
    // 以下是算法的步骤,由子类根据需要选择覆盖  
    void boilWater() {  
        System.out.println("Boiling water");  
    }  
  
    // 声明为抽象方法,因为具体制作饮品的方式依赖于子类  
    abstract void brew();  
  
    void pourInCup() {  
        System.out.println("Pouring into Cup");  
    }  
  
    // 钩子方法,子类可以覆盖它以改变行为  
    boolean wantsCondiments() {  
        return true;  
    }  
  
    // 这个方法也可能由子类覆盖  
    void addCondiments() {  
        System.out.println("Adding Condiments");  
    }  
}  
  
// 具体类,表示咖啡  
class Coffee extends Beverage {  
    // 实现brew方法,提供制作咖啡的具体步骤  
    @Override  
    void brew() {  
        System.out.println("Dripping Coffee through filter");  
    }  
  
    // 覆盖钩子方法,表示咖啡不需要额外添加调料  
    @Override  
    boolean wantsCondiments() {  
        return false;  
    }  
}  
  
// 具体类,表示茶  
class Tea extends Beverage {  
    // 实现brew方法,提供制作茶的具体步骤  
    @Override  
    void brew() {  
        System.out.println("Steeping the tea");  
    }  
  
    // 可以选择覆盖addCondiments方法来添加特定的调料  
    // 这里我们保持默认行为  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        // 创建咖啡对象  
        Beverage coffee = new Coffee();  
        System.out.println("Making coffee...");  
        coffee.prepareRecipe(); // 调用模板方法制作咖啡  
  
        // 创建茶对象  
        Beverage tea = new Tea();  
        System.out.println("\nMaking tea...");  
        tea.prepareRecipe(); // 调用模板方法制作茶  
    }  
}

输出结果:

Making coffee...  
Boiling water  
Dripping Coffee through filter  
Pouring into Cup  
  
Making tea...  
Boiling water  
Steeping the tea  
Pouring into Cup  
Adding Condiments

在这个例子中,Beverage 是一个抽象类,定义了一个制作饮品的模板方法 prepareRecipe,这个方法调用了其他几个方法,其中 brew 是一个抽象方法,需要子类具体实现,CoffeeTeaBeverage 的具体子类,它们分别实现了 brew 方法来提供制作咖啡和茶的具体步骤,此外,Beverage 类还提供了一个钩子方法 wantsCondiments,子类可以覆盖它来改变添加调料的行为,client代码创建了咖啡和茶的对象,并调用了它们的模板方法来制作饮品。

核心总结

程序员必知!访问者模式的实战应用与案例分析

访问者模式在日常开发中非常常见,常被用于将数据结构与数据操作分离情况,增加程序的灵活性和可扩展性,它能够在不修改已有类的情况下增加新的操作,符合开闭原则,同时,访问者模式可以将复杂的、贯穿多个类的行为集中到一个访问者类中,方便代码的管理和维护。

它的缺点也很明显,1、增加了类的数量,以及类之间的耦合度,当数据结构变化时,可能需要修改所有访问者类的接口,成本较高,2、如果频繁进行数据结构和操作的变化,访问者模式可能不是最佳选择。

个人思考

访问者模式和状态模式虽然都属于行为型设计模式,但它们在解决的问题和应用场景上有显著的不同。

访问者模式

想象一下有一个由多种不同类型的元素组成的集合,想对这些元素执行多种不同的操作,但又不希望修改这些元素的类,这时候可以用访问者模式。

访问者模式允许定义一个新的操作,只需添加一个实现访问者接口的类,而无需修改已有的元素类,这就像有一个客人(访问者)来到的家(对象结构),他可以根据自己的需要(操作)来与的家具(元素)互动,而不需要改变家具本身。

状态模式

想象一下有一个对象结构,它的行为会根据其内部状态的变化而变化,比如一个电灯开关,按下时灯会打开,再次按下时灯会关闭,这里,“打开”和“关闭”就是电灯的不同状态,而按下开关这个动作会导致电灯状态的转换以及相应的行为变化。

状态模式允许在不修改对象类的情况下,为其添加新的状态或修改现有状态的行为,可以定义一个状态接口和一系列实现该接口的具体状态类,对象会根据其当前状态委托给相应的状态对象来处理请求,从而实现行为的动态变化。

它们的区别:

  1. 关注点不同:访问者模式关注的是对复杂对象结构中的元素执行多种操作,而状态模式关注的是对象在不同状态下的行为变化。
  2. 结构不同:访问者模式中,访问者类和元素类是分开的,访问者类包含对元素类的操作;而在状态模式中,状态类通常被包含在上下文类中,上下文类根据当前状态委托给相应的状态类来处理请求。
  3. 扩展性:访问者模式在添加新的操作时比较容易,只需添加新的访问者类;而状态模式在添加新的状态时比较容易,只需添加新的状态类。但两者在添加新的元素类或状态类时都可能需要修改已有的代码。
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2024年04月19日 0

暂无评论

TEZNKK3IfmPf