解锁访问者模式:Java编程的灵活之道

系列文章目录

第一章 解锁单例模式:Java世界的唯一实例之道
第二章 解锁工厂模式:工厂模式探秘
第三章 解锁代理模式:代理模式的多面解析与实战
第四章 解锁装饰器模式:代码增强的魔法宝典
第五章 解锁建造者模式:Java 编程中的对象构建秘籍
第六章 解锁原型模式:Java 中的高效对象创建之道
第七章 解锁适配器模式:代码重构与架构优化的魔法钥匙
第八章 解锁桥接模式:Java架构中的解耦神器
第九章 解锁组合模式:Java 代码中的树形结构奥秘
第十章 解锁享元模式:内存优化与性能提升的关键密码
第十一章 解锁外观模式:Java 编程中的优雅架构之道
第十二章 解锁观察者模式:Java编程中的高效事件管理之道
第十三章 解锁策略模式:Java 实战与应用全景解析
第十四章 解锁状态模式:Java 编程中的行为魔法
第十五章 解锁模板方法模式:Java 实战与应用探秘
第十六章 解锁命令模式:Java 编程中的解耦神器
第十七章 解锁迭代器模式:Java 编程的遍历神器
第十八章 解锁责任链模式:Java 实战与应用探秘
第十九章 解锁中介者模式:代码世界的“社交达人”
第二十章 解锁备忘录模式:代码世界的时光机
第二十一章 解锁访问者模式:Java编程的灵活之道
第二十二章 解锁Java解释器模式:概念、应用与实战


文章目录

  • 引言:探索访问者模式的奥秘
  • 一、访问者模式的基本概念
    • (一)定义与核心思想
    • (二)主要角色解析
  • 二、访问者模式的工作原理
    • (一)双分派机制详解
    • (二)访问者模式的执行流程
  • 三、Java 实现访问者模式的步骤与代码示例
    • (一)定义抽象访问者和具体访问者
    • (二)定义抽象元素和具体元素
    • (三)构建对象结构
    • (四)客户端调用与测试
  • 四、访问者模式的使用场景
    • (一)对象结构稳定但操作多变的场景
    • (二)需要对对象进行多种不相关操作的场景
    • (三)数据结构与操作分离的场景
  • 五、访问者模式的优缺点分析
    • (一)优点
    • (二)缺点
  • 六、总结与展望
    • (一)访问者模式的核心要点回顾
    • (二)对未来学习和应用的建议


引言:探索访问者模式的奥秘

在软件开发的广袤世界里,设计模式宛如璀璨星辰,照亮着开发者前行的道路。它们是前人智慧的结晶,是解决特定问题的通用方案。而访问者模式,作为其中独特的一员,以其别具一格的设计理念和强大的功能,在众多设计模式中独树一帜。

想象一下,你置身于一家热闹非凡的超市,购物车中装满了琳琅满目的商品,有新鲜诱人的水果、美味可口的零食、实用的生活用品等等。当你推着购物车来到收银台时,收银员需要根据不同商品的价格、折扣以及会员等级来计算最终的总价。这看似平常的购物场景,却蕴含着访问者模式的核心思想。

不同类型的商品就如同访问者模式中的具体元素,它们各自有着独特的属性和行为。而收银员则像是访问者,能够对不同的商品进行统一的访问和处理。同时,超市的购物车可以看作是对象结构,它容纳了各种商品,为访问者提供了遍历和操作这些元素的场所。

在这个场景中,我们可以发现,商品的种类可能会不断增加,新的商品可能会加入超市的货架;同时,对于商品的处理方式也可能会发生变化,比如不同的促销活动、会员制度的调整等。如果我们采用传统的编程方式,可能需要频繁地修改商品类和处理商品的代码,这无疑会增加代码的复杂度和维护成本。而访问者模式的出现,为我们提供了一种优雅的解决方案。它能够将数据结构和作用于数据结构上的操作解耦,使得我们可以在不改变商品类的前提下,灵活地定义和添加新的操作。

通过这个超市购物的例子,我们对访问者模式有了一个初步的感性认识。接下来,让我们深入探索访问者模式的概念、原理、结构、实现方式、应用场景以及优缺点,揭开它神秘的面纱,领略它在软件开发中的独特魅力和强大威力。

一、访问者模式的基本概念

(一)定义与核心思想

访问者模式(Visitor Pattern)是一种将数据操作和数据结构分离的设计模式 ,它允许在不修改现有对象结构的情况下,定义作用于这些对象的新操作。其核心思想在于把对数据结构中元素的操作封装成独立的访问者类,使得操作的变化不会影响到数据结构本身,反之亦然。这就好比一个博物馆,馆内的展品(数据结构)是固定的,但不同的导游(访问者)可以根据自己的讲解风格和重点(操作)来向游客介绍这些展品,而不会改变展品本身。

在软件开发中,当我们面对一个复杂的数据结构,并且需要对其元素执行多种不同且可能变化的操作时,访问者模式就展现出了它的强大优势。例如,在一个图形绘制系统中,存在各种形状的图形对象,如圆形、矩形、三角形等(构成数据结构)。如果我们将绘制、缩放、旋转等操作直接定义在图形对象类中,随着操作的增加和变化,图形对象类会变得臃肿不堪,代码的维护和扩展也会变得异常困难。而使用访问者模式,我们可以将这些操作分别封装在不同的访问者类中,图形对象类只需要负责提供接受访问者的方法,这样就实现了数据结构和操作的解耦,使得系统更加灵活和可维护。

(二)主要角色解析

访问者模式包含以下几个主要角色:

  1. 抽象访问者(Visitor):

抽象访问者定义了一系列访问具体元素的接口方法,这些方法的参数通常是具体元素类型。它为访问具体元素提供了统一的抽象规范,是访问者模式的核心接口之一。通过这个接口,具体访问者可以定义对不同类型元素的操作逻辑。例如:

public interface Visitor {
    void visit(ConcreteElementA elementA);
    void visit(ConcreteElementB elementB);
}

在这个示例中,Visitor接口定义了visit(ConcreteElementA elementA)和visit(ConcreteElementB elementB)两个方法,分别用于访问ConcreteElementA和ConcreteElementB类型的具体元素。这就像是一个通用的操作模板,具体的操作实现由具体访问者来完成。每个方法对应一种具体元素类型,确保了对不同元素的操作可以通过这个接口进行统一的定义和调用。

  1. 具体访问者(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变量中,从而实现了计算商品总价的功能。通过这种方式,具体访问者可以根据业务需求对不同类型的元素进行个性化的操作,而不会影响到元素本身的结构和其他操作。

  1. 抽象元素(Element):

抽象元素定义了一个接受访问者的方法accept(Visitor visitor),该方法用于接收访问者对象,使得访问者能够访问该元素。它是所有具体元素的抽象基类或接口,为具体元素提供了统一的接受访问者的规范。例如:

public interface Product {
    void accept(Visitor visitor);
}

在这个商品接口示例中,Product接口定义了accept方法,任何实现该接口的具体商品类都必须实现这个方法,以接受访问者的访问。这个方法就像是一个入口,允许访问者进入元素内部进行操作,是访问者模式中元素与访问者交互的关键桥梁。

  1. 具体元素(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)作为参数传递进去。这样,当访问者访问这些具体元素时,就可以执行在具体访问者中定义的针对该元素的操作。通过这种方式,具体元素将操作的实现委托给了访问者,实现了数据结构和操作的分离。

  1. 对象结构(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方法,让访问者对其进行访问。通过对象结构,访问者可以方便地对整个元素集合进行统一的操作,而无需关心具体元素的存储和管理细节。


二、访问者模式的工作原理

(一)双分派机制详解

在理解访问者模式的工作原理时,双分派机制是一个关键概念。为了更好地理解双分派,我们先来回顾一下静态分派和动态分派。

  1. 静态分派与动态分派结合代码实例
  • 静态分派:静态分派基于静态类型在编译期进行方法选择。方法重载是静态分派的典型应用。例如:
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”。这体现了动态分派是在运行期根据对象的实际类型来确定方法的执行版本 。

  1. 双分派在访问者模式中的实现

双分派是指在选择方法时,不仅要根据对象的实际类型,还要根据参数的实际类型来确定执行的操作。在访问者模式中,通过两次方法调用实现双分派。

以一个简单的图形绘制系统为例,假设有圆形和矩形两种图形,我们要实现一个访问者来计算它们的面积和周长:

// 抽象元素
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方法。这样,通过两次方法调用,根据元素类型和访问者类型决定了最终执行的操作,实现了双分派。

(二)访问者模式的执行流程

下面通过详细的代码跟踪,展示访问者模式从客户端创建对象结构和访问者,到元素接受访问者访问,最终执行具体操作的完整流程。
假设我们有一个电商系统,要计算购物车中不同商品的总价和折扣价。

  1. 定义抽象元素和具体元素
// 抽象元素:商品
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;
    }
}
  1. 定义抽象访问者和具体访问者
// 抽象访问者
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;
    }
}
  1. 定义对象结构
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);
        }
    }
}
  1. 客户端代码
public class EcommerceSystem {
    public static void main(String[] args) {
        // 创建对象结构:购物车
        ShoppingCart cart = new ShoppingCart();

        // 添加商品到购物车
        cart.addProduct(new Book("Design Patterns", 50));
        cart.addProduct(new ElectronicProduct("Laptop", 1000));

        // 创建访问者
        PriceCalculatorVisitor visitor = new PriceCalculatorVisitor();

        // 购物车接受访问者访问
        cart.accept(visitor);

        // 输出结果
        System.out.println("Total Price: " + visitor.getTotalPrice());
        System.out.println("Total Discounted Price: " + visitor.getTotalDiscountedPrice());
    }
}

在上述代码中,执行流程如下:

  1. 客户端创建对象结构和访问者:在main方法中,创建了ShoppingCart对象cart作为对象结构,并添加了Book和ElectronicProduct两种商品。同时,创建了PriceCalculatorVisitor对象visitor作为具体访问者。

  2. 元素接受访问者访问:调用cart.accept(visitor),ShoppingCart的accept方法遍历其内部的商品列表,依次调用每个商品的accept方法。例如,对于Book对象,调用book.accept(visitor),这是根据对象的实际类型(Book)进行的第一次分派,进入Book类的accept方法。

  3. 执行具体操作:在Book类的accept方法中,调用visitor.visit(this),这里的this指的是当前的Book对象。这是根据参数的实际类型(Book)进行的第二次分派,调用PriceCalculatorVisitor类中针对Book的visit方法,在该方法中进行书籍价格的计算和折扣处理。同理,对于ElectronicProduct对象也进行类似的操作。

  4. 获取结果:最后,通过访问者的getTotalPrice和getTotalDiscountedPrice方法获取计算结果并输出。

通过这样的执行流程,访问者模式实现了对不同类型元素的统一访问和处理,将数据结构和操作解耦,使得系统在添加新的操作或元素时更加灵活和可维护。


三、Java 实现访问者模式的步骤与代码示例

(一)定义抽象访问者和具体访问者

以电子商务系统为例,我们首先定义抽象访问者接口,它声明了访问不同类型商品的方法。然后创建具体访问者类,实现抽象访问者接口中定义的方法,完成对商品的具体操作。

// 抽象访问者接口
interface ProductVisitor {
    void visit(Book book);
    void visit(ElectronicProduct electronicProduct);
}

// 计算价格的具体访问者
class CalculatePriceVisitor implements ProductVisitor {
    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;
    }
}

// 打印商品信息的具体访问者
class PrintProductInfoVisitor implements ProductVisitor {
    @Override
    public void visit(Book book) {
        System.out.println("Book: Title - " + book.getTitle() + ", Author - " + book.getAuthor() + ", Price - " + book.getPrice());
    }

    @Override
    public void visit(ElectronicProduct electronicProduct) {
        System.out.println("Electronic Product: Model - " + electronicProduct.getModel() + ", Brand - " + electronicProduct.getBrand() + ", Price - " + electronicProduct.getPrice());
    }
}

(二)定义抽象元素和具体元素

接下来,我们定义抽象元素接口,它声明了接受访问者的方法。具体元素类实现抽象元素接口,提供接受访问者的具体实现,并包含自身的业务逻辑。

// 抽象元素接口
interface Product {
    void accept(ProductVisitor visitor);
}

// 书籍类,具体元素
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(ProductVisitor visitor) {
        visitor.visit(this);
    }

    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }

    public double getPrice() {
        return price;
    }
}

// 电子产品类,具体元素
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(ProductVisitor visitor) {
        visitor.visit(this);
    }

    public String getModel() {
        return model;
    }

    public String getBrand() {
        return brand;
    }

    public double getPrice() {
        return price;
    }
}

(三)构建对象结构

我们创建一个对象结构类,用于管理商品集合。它提供添加商品和接受访问者访问的方法,使得访问者能够遍历并操作集合中的所有商品。

import java.util.ArrayList;
import java.util.List;

// 对象结构类
class ProductList {
    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) {
        // 创建对象结构
        ProductList productList = new ProductList();

        // 添加商品到对象结构
        productList.addProduct(new Book("Effective Java", "Joshua Bloch", 50));
        productList.addProduct(new ElectronicProduct("iPhone 14", "Apple", 999));

        // 创建计算价格的访问者
        CalculatePriceVisitor priceVisitor = new CalculatePriceVisitor();
        productList.accept(priceVisitor);
        System.out.println("Total Price: " + priceVisitor.getTotalPrice());

        // 创建打印商品信息的访问者
        PrintProductInfoVisitor infoVisitor = new PrintProductInfoVisitor();
        productList.accept(infoVisitor);
    }
}

上述代码中,EcommerceSystem类作为客户端,首先创建了ProductList对象productList,并向其中添加了Book和ElectronicProduct两种商品。然后分别创建了CalculatePriceVisitor和PrintProductInfoVisitor两种访问者,通过productList.accept(visitor)方法,让访问者对商品集合中的商品进行访问和操作。CalculatePriceVisitor计算出商品的总价并输出,PrintProductInfoVisitor打印出每个商品的详细信息。通过这样的方式,展示了访问者模式在实际应用中的运行效果,实现了对不同类型商品的统一访问和多样化操作。


四、访问者模式的使用场景

(一)对象结构稳定但操作多变的场景

在图形绘制系统中,常常存在各种不同类型的图形,如圆形、矩形、三角形等。这些图形构成了一个相对稳定的对象结构。然而,随着业务需求的不断变化,可能需要对这些图形执行多种不同且经常变化的操作,如计算图形的面积、周长,绘制图形,对图形进行缩放、旋转等操作。

如果将这些操作直接定义在图形类中,随着操作种类的增加,图形类会变得越来越臃肿,代码的维护和扩展也会变得异常困难。而且,每增加一种新的操作,都需要修改所有图形类的代码,这显然违背了软件设计中的开闭原则。

使用访问者模式,我们可以将这些操作封装在不同的访问者类中。图形类只需要提供接受访问者的方法,而具体的操作实现则由访问者类来完成。这样,当需要添加新的操作时,只需要创建一个新的访问者类,而不需要修改图形类的代码。
以计算图形面积为例,我们可以定义如下代码:

// 抽象元素:图形
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 AreaVisitor implements ShapeVisitor {
    @Override
    public void visit(Circle circle) {
        double area = Math.PI * circle.getRadius() * circle.getRadius();
        System.out.println("Circle - Area: " + area);
    }

    @Override
    public void visit(Rectangle rectangle) {
        double area = rectangle.getWidth() * rectangle.getHeight();
        System.out.println("Rectangle - Area: " + area);
    }
}

// 测试代码
public class GraphicsSystem {
    public static void main(String[] args) {
        Shape circle = new Circle(5);
        Shape rectangle = new Rectangle(4, 6);

        ShapeVisitor areaVisitor = new AreaVisitor();
        circle.accept(areaVisitor); 
        rectangle.accept(areaVisitor); 
    }
}

在上述代码中,Shape是抽象元素接口,Circle和Rectangle是具体元素类,它们构成了稳定的对象结构。ShapeVisitor是抽象访问者接口,AreaVisitor是具体访问者类,用于计算图形的面积。通过访问者模式,我们可以方便地添加新的操作,如计算周长、绘制图形等,而无需修改图形类的代码。

(二)需要对对象进行多种不相关操作的场景

在员工绩效评定系统中,员工是系统中的主要对象,包括工程师、经理等不同类型的员工。CEO 和 CTO 作为系统的不同使用者,对员工有着不同的关注点和操作需求。

CEO 更关注员工的整体绩效指标(KPI)以及经理的新产品数量,以此来评估员工对公司整体业绩的贡献;而 CTO 则更关注工程师的代码量、技术能力以及经理对项目的技术管理能力,从技术角度来评估员工的工作表现。

如果将 CEO 和 CTO 对员工的评估操作直接放在员工类中,会导致员工类的职责不单一,代码混乱,而且不同的操作逻辑相互交织,难以维护和扩展。例如,当公司引入新的评估指标或改变评估方式时,需要修改员工类的代码,这可能会影响到其他与员工类相关的功能。

使用访问者模式,我们可以将 CEO 和 CTO 的评估操作分别封装在不同的访问者类中。员工类只需要提供接受访问者的方法,这样可以有效地避免操作污染对象类,使得系统的结构更加清晰,代码的维护和扩展更加容易。

以下是使用访问者模式实现员工绩效评定系统的示例代码:

// 抽象员工类
abstract class Staff {
    protected String name;
    protected int kpi;

    public Staff(String name) {
        this.name = name;
        this.kpi = (int) (Math.random() * 10); 
    }

    public abstract void accept(Visitor visitor);

    public String getName() {
        return name;
    }

    public int getKpi() {
        return kpi;
    }
}

// 工程师类
class Engineer extends Staff {
    public Engineer(String name) {
        super(name);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public int getCodeLines() {
        return (int) (Math.random() * 10000); 
    }
}

// 经理类
class Manager extends Staff {
    public Manager(String name) {
        super(name);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public int getProducts() {
        return (int) (Math.random() * 10); 
    }
}

// 抽象访问者接口
interface Visitor {
    void visit(Engineer engineer);
    void visit(Manager manager);
}

// CEO访问者类
class CEOVisitor implements Visitor {
    @Override
    public void visit(Engineer engineer) {
        System.out.println("CEO关注:【程序员[" + engineer.getName() + "]的KPI:" + engineer.getKpi() + "】");
    }

    @Override
    public void visit(Manager manager) {
        System.out.println("CEO关注:【经理[" + manager.getName() + "]的KPI:" + manager.getKpi() + ",新产品数量:" + manager.getProducts() + "】");
    }
}

// CTO访问者类
class CTOVisitor implements Visitor {
    @Override
    public void visit(Engineer engineer) {
        System.out.println("CTO关注:【程序员[" + engineer.getName() + "]的代码数量:" + engineer.getCodeLines() + "】");
    }

    @Override
    public void visit(Manager manager) {
        System.out.println("CTO关注:【产品经理[" + manager.getName() + "]的产品数量:" + manager.getProducts() + "】");
    }
}

// 员工列表类,作为对象结构
class StaffList {
    private List<Staff> staffList = new ArrayList<>();

    public void addStaff(Staff staff) {
        staffList.add(staff);
    }

    public void accept(Visitor visitor) {
        for (Staff staff : staffList) {
            staff.accept(visitor);
        }
    }
}

// 测试代码
public class PerformanceEvaluationSystem {
    public static void main(String[] args) {
        StaffList staffList = new StaffList();
        staffList.addStaff(new Engineer("张三"));
        staffList.addStaff(new Manager("李四"));

        Visitor ceoVisitor = new CEOVisitor();
        Visitor ctoVisitor = new CTOVisitor();

        staffList.accept(ceoVisitor);
        System.out.println("--------------------------");
        staffList.accept(ctoVisitor);
    }
}

在上述代码中,Staff是抽象员工类,Engineer和Manager是具体员工类,构成了对象结构。Visitor是抽象访问者接口,CEOVisitor和CTOVisitor是具体访问者类,分别实现了 CEO 和 CTO 对员工的评估操作。通过访问者模式,不同的访问者可以对员工进行不同的操作,而不会污染员工类,使得系统的扩展性和维护性得到了极大的提升。

(三)数据结构与操作分离的场景

在编译器的设计中,表达式树是一个非常重要的数据结构,它用于表示程序中的各种表达式,如算术表达式、逻辑表达式等。表达式树的节点包括操作数节点(如变量、常量)和操作符节点(如加、减、乘、除等)。

在编译器的处理过程中,需要对表达式树进行多种操作,其中类型检查是一个关键的环节。类型检查的目的是确保表达式中的操作数和操作符的类型匹配,以保证程序的正确性和安全性。例如,在一个算术表达式中,不能将字符串类型的操作数与数字类型的操作数进行加法运算。

如果将类型检查的操作直接放在表达式树的节点类中,会导致节点类的职责过重,不仅要处理表达式树的结构和数据,还要实现类型检查的逻辑。而且,随着编译器功能的扩展,可能需要对表达式树进行更多不同的操作,如代码生成、优化等,这会使节点类变得越来越复杂,难以维护和扩展。

使用访问者模式,我们可以将类型检查的操作封装在一个独立的访问者类中。表达式树的节点类只需要提供接受访问者的方法,将操作的实现委托给访问者类。这样,表达式树的数据结构和操作就实现了分离,使得代码的结构更加清晰,易于维护和扩展。

以下是一个简单的示例,展示如何使用访问者模式实现表达式树的类型检查:

// 抽象表达式节点类
abstract class Expression {
    public abstract void accept(TypeCheckerVisitor visitor);
}

// 操作数节点类
class Operand extends Expression {
    private Object value;
    private String type;

    public Operand(Object value, String type) {
        this.value = value;
        this.type = type;
    }

    @Override
    public void accept(TypeCheckerVisitor visitor) {
        visitor.visit(this);
    }

    public Object getValue() {
        return value;
    }

    public String getType() {
        return type;
    }
}

// 操作符节点类
class Operator extends Expression {
    private String operator;
    private Expression left;
    private Expression right;

    public Operator(String operator, Expression left, Expression right) {
        this.operator = operator;
        this.left = left;
        this.right = right;
    }

    @Override
    public void accept(TypeCheckerVisitor visitor) {
        visitor.visit(this);
    }

    public String getOperator() {
        return operator;
    }

    public Expression getLeft() {
        return left;
    }

    public Expression getRight() {
        return right;
    }
}

// 抽象访问者接口:类型检查访问者
interface TypeCheckerVisitor {
    void visit(Operand operand);
    void visit(Operator operator);
}

// 具体访问者:类型检查实现
class TypeChecker implements TypeCheckerVisitor {
    @Override
    public void visit(Operand operand) {
        // 操作数类型检查,这里简单打印操作数类型
        System.out.println("Operand type: " + operand.getType());
    }

    @Override
    public void visit(Operator operator) {
        operator.getLeft().accept(this);
        operator.getRight().accept(this);
        // 操作符类型检查逻辑,这里简单示例,判断左右子表达式类型是否匹配
        String leftType = ((Operand) operator.getLeft()).getType();
        String rightType = ((Operand) operator.getRight()).getType();
        if (!leftType.equals(rightType)) {
            System.out.println("Type mismatch for operator: " + operator.getOperator());
        } else {
            System.out.println("Type check passed for operator: " + operator.getOperator());
        }
    }
}

// 测试代码
public class CompilerExample {
    public static void main(String[] args) {
        Expression operand1 = new Operand(5, "int");
        Expression operand2 = new Operand(3, "int");
        Expression operator = new Operator("+", operand1, operand2);

        TypeCheckerVisitor typeChecker = new TypeChecker();
        operator.accept(typeChecker);
    }
}

在上述代码中,Expression是抽象表达式节点类,Operand和Operator是具体的表达式节点类,构成了表达式树的数据结构。TypeCheckerVisitor是抽象访问者接口,TypeChecker是具体访问者类,实现了类型检查的操作。通过访问者模式,成功地将表达式树的数据结构和类型检查操作分离,使得编译器的代码更加模块化和可维护。当需要添加新的操作时,只需要创建新的访问者类,而无需修改表达式树的节点类,体现了访问者模式在数据结构与操作分离场景中的强大优势。


五、访问者模式的优缺点分析

(一)优点

  1. 扩展性良好:在访问者模式中,增加新操作只需添加新的访问者类,无需修改元素类,符合开闭原则。以电商系统为例,假设系统中有商品类Product,具体商品如Book和ElectronicProduct。如果我们需要添加一个新的操作,比如计算商品的折扣后价格,按照传统方式,可能需要在Product及其子类中添加相应的方法,这会涉及到修改多个类的代码,且容易出错。而使用访问者模式,我们只需创建一个新的访问者类DiscountPriceVisitor,实现对不同商品计算折扣后价格的方法。这样,当有新的操作需求时,只需要添加新的访问者类,不会影响到已有的元素类和其他访问者类,大大提高了系统的扩展性。

  2. 复用性高:多个不同的操作都可以使用相同的对象结构,提高了代码复用性。例如在图形绘制系统中,对象结构包含圆形、矩形等图形元素。我们可以定义多个不同的访问者,如AreaVisitor用于计算图形面积,PerimeterVisitor用于计算图形周长,DrawVisitor用于绘制图形。这些不同的访问者都可以使用相同的图形对象结构,避免了重复构建数据结构。当系统中需要进行新的操作时,也可以基于已有的对象结构创建新的访问者,而不需要重新构建整个数据结构,从而提高了代码的复用性。

  3. 灵活性强:访问者模式使操作集合自由演化,不影响系统的数据结构,提高了系统的灵活性。在员工绩效评定系统中,员工对象结构相对稳定,但随着公司业务的发展和管理需求的变化,可能会出现新的绩效评估方式,如引入 360 度评估、关键事件评估等。使用访问者模式,我们可以轻松地添加新的访问者类来实现这些新的评估方式,而无需修改员工类的结构和代码。这使得系统能够快速适应业务的变化,具有更强的灵活性。例如,当公司决定引入 360 度评估时,我们只需创建一个新的访问者类ThreeSixtyDegreeEvaluationVisitor,实现对员工进行 360 度评估的逻辑,而不会影响到原有的员工数据结构和其他评估方式。

(二)缺点

  1. 增加新元素类困难:增加新元素类时需要在所有具体访问者类中添加相应操作,违背开闭原则。以图形系统为例,假设现有图形系统已经有圆形Circle和矩形Rectangle,并定义了多个访问者类,如AreaVisitor、PerimeterVisitor等。当需要添加一个新的图形类型,如三角形Triangle时,不仅要创建Triangle类,还需要在所有的具体访问者类中添加对Triangle的访问方法。这意味着需要修改大量已有的代码,违背了开闭原则,增加了系统的维护成本和出错的风险。如果有多个具体访问者类,这个过程会变得更加繁琐和复杂,而且容易遗漏对某些访问者类的修改。

  2. 破坏封装性:具体元素需向访问者公布细节,破坏了对象的封装性。在访问者模式中,具体元素需要实现接受访问者的方法,并在方法中调用访问者的相应操作。这就意味着具体元素需要将自身的一些内部状态或方法暴露给访问者,从而破坏了对象的封装性。例如,在一个财务系统中,账本类AccountBook包含收入和支出等详细信息,当使用访问者模式进行账目分析时,账本类需要将这些内部信息暴露给访问者,如AccountBookViewer。这可能会导致账本类的内部细节被不当访问或修改,影响代码的维护和安全性,也增加了对象之间的耦合度。

  3. 复杂性增加:在小型系统或简单结构中使用访问者模式可能带来复杂性。访问者模式涉及多个角色和类,包括抽象访问者、具体访问者、抽象元素、具体元素和对象结构。对于小型系统或简单的数据结构,引入访问者模式可能会使代码结构变得复杂,增加了代码的理解和维护难度。过多的类和接口会使代码的层次结构变得复杂,开发者需要花费更多的时间和精力来理解和管理这些类之间的关系。例如,在一个简单的学生成绩管理系统中,学生成绩记录本身结构简单,如果使用访问者模式来处理成绩计算、统计等操作,可能会因为引入过多的类和接口,使得系统变得过于复杂,反而不如直接在成绩记录类中实现相关操作来得简洁明了。


六、总结与展望

(一)访问者模式的核心要点回顾

访问者模式作为一种独特的行为型设计模式,其核心在于巧妙地将数据操作与数据结构分离开来。它通过定义抽象访问者接口,为不同类型的具体元素提供统一的访问操作定义;具体访问者实现这些操作,针对每个具体元素执行特定逻辑。抽象元素定义接受访问者的方法,具体元素实现该方法以允许访问者对其进行访问。对象结构则管理元素集合,并提供遍历元素的方式,使访问者能够对整个结构中的元素进行操作。

从优点来看,访问者模式具有出色的扩展性,增加新操作只需添加新的访问者类,无需修改元素类,严格遵循开闭原则,这使得系统在面对不断变化的业务需求时能够轻松应对。同时,它具备高度的复用性,多个不同操作可基于相同对象结构进行,避免了重复开发,提高了代码的利用率。灵活性也是其显著优势,操作集合可自由演化,而不影响系统的数据结构,让系统更加适应复杂多变的业务场景。

然而,访问者模式也并非完美无缺。增加新元素类时较为困难,需要在所有具体访问者类中添加相应操作,这不仅繁琐,还违背了开闭原则,增加了维护成本。此外,它破坏了对象的封装性,具体元素需向访问者公布细节,这可能导致对象内部状态的不当暴露。在小型系统或简单结构中,使用访问者模式可能会引入不必要的复杂性,增加代码的理解和维护难度。

在适用场景方面,当对象结构稳定但操作多变时,访问者模式能够大显身手,如在图形绘制系统中,图形结构相对固定,而对图形的操作(如计算面积、周长,绘制、缩放、旋转等)却经常变化,此时使用访问者模式可有效解耦操作与结构,方便扩展新操作。在需要对对象进行多种不相关操作的场景,如员工绩效评定系统中,CEO 和 CTO 对员工有不同的评估操作,使用访问者模式可将这些操作分别封装,避免操作污染员工类。同时,在数据结构与操作分离的场景,如编译器中对表达式树的操作,访问者模式能使操作与数据结构各自独立演化,提高系统的可维护性和扩展性。

(二)对未来学习和应用的建议

对于未来的学习和应用,建议读者在深入理解访问者模式的基础上,积极在实际项目中尝试运用。在面对复杂业务需求时,仔细分析是否适合使用访问者模式,充分发挥其优势。同时,要善于将访问者模式与其他设计模式相结合,如在组合模式中使用访问者模式,以解决更复杂的问题,提升系统的设计质量。

在学习过程中,多参考优秀的开源项目,观察其中访问者模式的应用方式和技巧,不断积累经验。通过实际项目的锻炼,加深对访问者模式的理解和掌握程度,提高自己的设计和编程能力。随着技术的不断发展和业务需求的日益复杂,持续关注设计模式的发展动态,探索访问者模式在新场景下的应用可能性,为软件开发提供更高效、灵活的解决方

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/981605.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【每天认识一个漏洞】shiro反序列化漏洞

&#x1f31d;博客主页&#xff1a;菜鸟小羊 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 以下是在实际业务中遇到的一个漏洞&#xff0c;仅用来学习&#xff0c;通过暴露的 /actuator/heapdump 端点获取 Shiro key…

【AI大模型】DeepSeek + Kimi 高效制作PPT实战详解

目录 一、前言 二、传统 PPT 制作问题 2.1 传统方式制作 PPT 2.2 AI 大模型辅助制作 PPT 2.3 适用场景对比分析 2.4 最佳实践与推荐 三、DeepSeek Kimi 高效制作PPT操作实践 3.1 Kimi 简介 3.2 DeepSeek Kimi 制作PPT优势 3.2.1 DeepSeek 优势 3.2.2 Kimi 制作PPT优…

音频3A测试--AGC(自动增益)和NS(降噪)测试

一、测试前期准备 一台电脑&#xff1a;用于作为控制播放和录制数据&#xff1b; 一台音频处理器(调音台)&#xff1a;控制每个通道播放的数据&#xff0c;如噪声、人工头、模拟设备B输入的数据、收集标准麦克风&#xff0c;设备A处理完成的数据&#xff1b; 四个高保真音响&…

zabbix配置邮件告警

目录 实现步骤&#xff1a; 实现目的&#xff1a; 1.在监控端操作&#xff1a; 2.web界面部署 ​​​​​​​实现步骤&#xff1a; 1、在 zabbix服务端配置邮件发送脚本和修改 zabbix服务端配置文件; 2、在 zabbix前端控制台进行相关设置。 实现目的&#xff1a; Zab…

PHP fastadmin 学习

安装php环境安装mysql插件 修改 php.ini下载 phpstudy、fastadmin 错误 安装FastAdmin could not find driver 参考链接 安装插件 创建1.php <? phpinfo(); ?>运行 http://127.0.0.1/1.php 查看 POD 页面访问404 伪静态 Apache <IfModule mod_rewrite.c> O…

PARETO PROMPT OPTIMIZATION

题目 帕累托提示优化 论文地址&#xff1a;https://openreview.net/forum?idHGCk5aaSvE 摘要 自然语言迅速优化或及时工程已成为一种强大的技术&#xff0c;可以解锁大型语言模型&#xff08;LLMS&#xff09;的各种任务的潜力。尽管现有方法主要集中于最大化LLM输出的单一特…

Agent智能体是什么?

文章目录 一、Agent的起源与发展1.1时间线1.2核心驱动力 二、Agent的定义与架构2.1基本定义2.2典型结构&#xff08;以GPTs为例&#xff09; 三、OpenAI的Agent演进路径3.1关键阶段3.2技术支撑3.3 GPTs生态经济模型 四、其他Agent平台对比五、Agent实践案例5.1文本处理自动化5.…

【Linux第三弹】Linux基础指令 (下)

目录 &#x1f31f;1.find指令 1.1find使用实例 ​编辑 &#x1f31f;2.which指令 &#x1f31f;3.grep指令 3.1grep使用实例 &#x1f31f; 4.zip/unzip指令 4.1 zip/unzip使用实例 &#x1f31f;5.tar指令 5.1 tar使用实例 &#x1f31f;6.完结 很庆幸走在自己…

【Laplacian边缘检测详解】

Laplacian边缘检测详解 目录 Laplacian边缘检测详解一. 定义二. 原理三. 特点四. 使用技巧五. MATLAB示例代码示例1&#xff1a;基本Laplacian边缘检测示例2&#xff1a;扩展Laplacian核的使用示例3&#xff1a;与Sobel边缘检测的比较示例4&#xff1a;检测图像中的文字边缘示例…

为什么要学习数据结构与算法

今天&#xff0c;我向大家介绍一门非常重要的课程——《数据结构与算法》。这门课不仅是计算机学科的核心&#xff0c;更是每一位开发者从“小白”迈向“高手”的必经之路。 1、为什么要学习数据结构与算法 总的来说&#xff0c;数据结构与算法是&#xff1a; 求职的“敲门砖”…

【第13节】C++设计模式(行为模式)-Template(模板)模式

一、问题的提出 Template 模式&#xff1a;算法步骤框架与细节实现的分离 假设我们正在开发一个文档处理系统&#xff0c;需要支持多种文档格式的导出&#xff08;如 PDF、Word、HTML 等&#xff09;。每种文档格式的导出过程大致相同&#xff0c;都包含以下步骤&#xff1a; …

安卓binder驱动内核日志调试打印开放及原理(第一节)

背景&#xff1a; 经常有学员朋友在做系统开发时候&#xff0c;有时候遇到binder相关的一些问题&#xff0c;这个时候可能就需要比较多的binder相关日志&#xff0c;但是正常情况下这些binder通讯的的内核日志都是没有的打印的&#xff0c;因为经常binder通讯太过于频繁&#…

uniapp 常用 UI 组件库

1. uView UI 特点&#xff1a; 组件丰富&#xff1a;提供覆盖按钮、表单、图标、表格、导航、图表等场景的内置组件。跨平台支持&#xff1a;兼容 App、H5、小程序等多端。高度可定制&#xff1a;支持主题定制&#xff0c;组件样式灵活。实用工具类&#xff1a;提供时间、数组操…

Gpt翻译完整版

上一篇文章收到了很多小伙伴的反馈&#xff0c;总结了一下主要以下几点&#xff1a; 1. 说不知道怎么调api 2. 目前只是把所有的中文变成了英文&#xff0c;如果想要做多语言还需要把这些关键字提炼出来成放到message_zh.properties和message_en.properties文件中&#xff0c…

【MATLAB例程】三维下的IMM(交互式多模型),模型使用CV(匀速)、CT(匀速转弯)和CA(匀加速),滤波使用EKF。附完整代码

本文介绍一个三维IMM(Interacting Multiple Model)算法,该算法用于目标跟踪,结合了不同运动模型(匀速、匀加速和转弯)。代码使用MATLAB编写,包含仿真、模型预测和结果可视化。订阅专栏后,可直接获得完整代码 文章目录 运行结果完整代码代码解析1. 初始化环境2. 仿真参数…

未来经济范式争夺战:AR眼镜为何成为下一代交互终端的制高点?

未来经济范式争夺战&#xff1a;AR眼镜为何成为下一代交互终端的制高点&#xff1f; 在蒸汽机轰鸣的工业革命时代&#xff0c;煤炭、铁路、电报构建了第一个现代经济范式&#xff1b;互联网时代&#xff0c;电力、光纤、物流网络重构了全球经济版图。当前&#xff0c;我们正站…

【Python爬虫】爬取公共交通路网数据

程序来自于Github&#xff0c;以下这篇博客作为完整的学习记录&#xff0c;也callback上一篇爬取公共交通站点的博文。 Bardbo/get_bus_lines_and_stations_data_from_gaode: 这个项目是基于高德开放平台和公交网获取公交线路及站点数据&#xff0c;并生成shp文件&#xff0c;…

如何将飞书多维表格与DeepSeek R1结合使用:效率提升的完美搭档

将飞书的多维表格与DeepSeek R1结合使用&#xff0c;就像为你的数据管理和分析之旅装上一台涡轮增压器。两者的合作&#xff0c;不仅仅在速度上让人耳目一新&#xff0c;更是将智能化分析带入了日常的工作场景。以下是它们如何相辅相成并改变我们工作方式的一些分享。 --- 在…

一周学会Flask3 Python Web开发-在模板中渲染WTForms表单视图函数里获取表单数据

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 为了能够在模板中渲染表单&#xff0c;我们需要把表单类实例传入模板。首先在视图函数里实例化表单类LoginForm&#xff0c;然…

阿里通义万相2.1模型在亚马逊云科技ECS容器中的私有化部署

本文将主要介绍同义万相v2.1视频生成模型的在AWS上部署的初步测试 通义万相AI模型介绍 通义万相模型是阿里云负责大规模生成式模型的团队&#xff0c;最近发布了通义万相2.1(以下称Wan 2.1)&#xff0c;这是一个“全面开源的视频基础模型套件&#xff0c;突破了视频生成的边界…