设计模式——19. 访问者模式

1. 说明

访问者模式(Visitor Pattern)是一种行为型设计模式,它允许你在不改变元素类(被访问者)的前提下,定义对元素的新操作(访问者),并将这些操作封装到独立的访问者类中。这样,你可以在不修改被访问者的类的情况下,通过不同的访问者来执行不同的操作。
访问者模式的主要目的是将数据结构和数据操作分离,使得可以在不修改数据结构的情况下,添加新的操作。这对于复杂的数据结构和多种不同的操作场景非常有用。它提供了一种可扩展性强的方式来处理各种数据结构和操作的组合。
访问者模式通常包含以下几个关键角色:

  1. 访问者(Visitor):定义了访问被访问者元素的方法,通常为每一种被访问的元素类型定义一个访问方法。
  2. 具体访问者(Concrete Visitor):实现了访问者接口中定义的方法,即针对每种被访问的元素类型提供了具体的操作逻辑。
  3. 被访问者(Element):定义了一个接受访问者的方法,通常是 accept(Visitor visitor),用于接收访问者的访问。
  4. 具体被访问者(Concrete Element):实现了被访问者接口中的 accept 方法,将自身传递给访问者,使访问者能够访问自身。
  5. 结构对象(Object Structure):包含了多个被访问者,可以遍历这些被访问者,并将访问者应用到它们身上。

访问者模式的优点包括了支持新的操作扩展而无需修改元素类,以及将相关操作封装在独立的访问者类中,使代码更加清晰和易于维护。然而,它也会导致访问者类的数量增加,增加了系统的复杂性。因此,访问者模式适用于那些需要频繁添加新操作而不希望修改现有类的情况。

2. 使用的场景

访问者模式在以下情况下通常很有用:

  1. 需要对复杂对象结构进行操作和遍历:当你有一个复杂的对象结构,其中包含不同类型的元素,并且你需要对这些元素执行各种不同的操作,访问者模式可以将操作和元素解耦,使得操作能够轻松应用于整个对象结构。
  2. 不希望修改现有元素类:如果你不想修改已经存在的元素类,但需要为这些类添加新的操作,访问者模式是一种有效的方式。你可以通过创建新的访问者类来添加新的操作,而无需修改已存在的元素类。
  3. 支持多种不同的操作:当你需要为对象结构添加多个不同的操作,这些操作可能是不断变化和扩展的,访问者模式可以让你更容易地管理和扩展这些操作。
  4. 数据结构和算法分离:访问者模式将数据结构和数据操作分离开来,使得数据结构可以专注于数据的表示,而操作可以专注于数据的处理。这样有助于保持代码的清晰和可维护性。
  5. 分布式操作:当你需要在对象结构的元素上执行分布在多个类中的操作时,访问者模式可以用于将这些操作组织起来,并在需要时轻松添加新的操作。

总之,访问者模式适用于需要对复杂对象结构进行多种不同操作,且这些操作可能会不断变化和扩展的情况。它提供了一种灵活的方式来处理这些情况,同时保持代码的可维护性和可扩展性。

3. 应用例子

下面是一个使用Python实现的访问者模式的简单例子,我们将创建一个动物园的模拟,其中有不同种类的动物,我们将使用访问者模式来访问并执行不同的操作:

# 抽象访问者
class Visitor:
    def visit_dog(self, dog):
        pass

    def visit_cat(self, cat):
        pass


# 具体访问者
class AnimalSoundVisitor(Visitor):
    def visit_dog(self, dog):
        print(f"The dog makes a sound: {dog.sound}")

    def visit_cat(self, cat):
        print(f"The cat makes a sound: {cat.sound}")


# 抽象被访问者
class Animal:
    def accept(self, visitor):
        pass


# 具体被访问者
class Dog(Animal):
    def __init__(self):
        self.sound = "Woof!"

    def accept(self, visitor):
        visitor.visit_dog(self)


class Cat(Animal):
    def __init__(self):
        self.sound = "Meow!"

    def accept(self, visitor):
        visitor.visit_cat(self)


# 对象结构
class Zoo:
    def __init__(self):
        self.animals = []

    def add_animal(self, animal):
        self.animals.append(animal)

    def perform_operation(self, visitor):
        for animal in self.animals:
            animal.accept(visitor)


if __name__ == "__main__":
    zoo = Zoo()
    zoo.add_animal(Dog())
    zoo.add_animal(Cat())

    sound_visitor = AnimalSoundVisitor()
    zoo.perform_operation(sound_visitor)

在这个示例中,我们创建了两个具体被访问者类 Dog 和 Cat,它们都实现了 accept 方法,以便接受访问者的访问。我们还创建了一个具体访问者类 AnimalSoundVisitor,它实现了对不同动物对象的访问操作。
Zoo 类是对象结构,它包含了多个被访问者动物对象,并提供了 perform_operation 方法来接受访问者的访问。

当我们运行这个示例时,AnimalSoundVisitor 将访问动物对象并执行不同的操作,输出每种动物的声音。这展示了如何使用访问者模式来在不修改动物类的情况下添加新的操作。

4. 实现要素

访问者模式的实现要素包括以下几个关键组件:

  1. 抽象访问者(Visitor):定义了访问被访问者元素的方法,通常为每一种被访问的元素类型定义一个访问方法。
  2. 具体访问者(Concrete Visitor):实现了访问者接口中定义的方法,即针对每种被访问的元素类型提供了具体的操作逻辑。
  3. 抽象被访问者(Element):定义了一个接受访问者的方法,通常是 accept(Visitor visitor),用于接收访问者的访问。
  4. 具体被访问者(Concrete Element):实现了被访问者接口中的 accept 方法,将自身传递给访问者,使访问者能够访问自身。
  5. 对象结构(Object Structure):包含了多个被访问者,可以遍历这些被访问者,并将访问者应用到它们身上。

5. Java/golang/javascrip/C++ 等语言实现方式

5.1 Java实现

上述例子用Java语言实现示例如下:

// 抽象访问者
interface Visitor {
    void visit(Dog dog);
    void visit(Cat cat);
}

// 具体访问者
class AnimalSoundVisitor implements Visitor {
    @Override
    public void visit(Dog dog) {
        System.out.println("The dog makes a sound: " + dog.getSound());
    }

    @Override
    public void visit(Cat cat) {
        System.out.println("The cat makes a sound: " + cat.getSound());
    }
}

// 抽象被访问者
interface Animal {
    void accept(Visitor visitor);
}

// 具体被访问者
class Dog implements Animal {
    private String sound = "Woof!";

    public String getSound() {
        return sound;
    }

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

class Cat implements Animal {
    private String sound = "Meow!";

    public String getSound() {
        return sound;
    }

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

// 对象结构
class Zoo {
    private List<Animal> animals = new ArrayList<>();

    public void addAnimal(Animal animal) {
        animals.add(animal);
    }

    public void performOperation(Visitor visitor) {
        for (Animal animal : animals) {
            animal.accept(visitor);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Zoo zoo = new Zoo();
        zoo.addAnimal(new Dog());
        zoo.addAnimal(new Cat());

        AnimalSoundVisitor soundVisitor = new AnimalSoundVisitor();
        zoo.performOperation(soundVisitor);
    }
}

在上述示例中,我们使用Java编程语言实现了访问者模式。类似于Python示例,我们定义了抽象访问者接口(Visitor)以及具体访问者类(AnimalSoundVisitor),并实现了访问不同动物对象的方法。

具体的被访问者类(Dog 和 Cat)实现了accept方法,以接受访问者的访问。Zoo 类是对象结构,包含多个被访问者动物对象,并提供了 performOperation 方法来接受访问者的访问。
在 main 方法中,我们创建了动物对象,将它们添加到动物园中,并创建了一个 AnimalSoundVisitor 实例来执行访问操作。运行程序后,将输出不同动物的声音。这演示了如何使用访问者模式来添加新的操作而无需修改现有动物类。

5.2 Golang实现

上述例子用golang实现示例如下:

package main

import "fmt"

// 抽象访问者
type Visitor interface {
        VisitDog(dog *Dog)
        VisitCat(cat *Cat)
}

// 具体访问者
type AnimalSoundVisitor struct{}

func (v *AnimalSoundVisitor) VisitDog(dog *Dog) {
        fmt.Printf("The dog makes a sound: %s\n", dog.GetSound())
}

func (v *AnimalSoundVisitor) VisitCat(cat *Cat) {
        fmt.Printf("The cat makes a sound: %s\n", cat.GetSound())
}

// 抽象被访问者
type Animal interface {
        Accept(visitor Visitor)
}

// 具体被访问者
type Dog struct {
        sound string
}

func NewDog() *Dog {
        return &Dog{sound: "Woof!"}
}

func (d *Dog) GetSound() string {
        return d.sound
}

func (d *Dog) Accept(visitor Visitor) {
        visitor.VisitDog(d)
}

type Cat struct {
        sound string
}

func NewCat() *Cat {
        return &Cat{sound: "Meow!"}
}

func (c *Cat) GetSound() string {
        return c.sound
}

func (c *Cat) Accept(visitor Visitor) {
        visitor.VisitCat(c)
}

// 对象结构
type Zoo struct {
        animals []Animal
}

func (z *Zoo) AddAnimal(animal Animal) {
        z.animals = append(z.animals, animal)
}

func (z *Zoo) PerformOperation(visitor Visitor) {
        for _, animal := range z.animals {
                animal.Accept(visitor)
        }
}

func main() {
        zoo := &Zoo{}
        zoo.AddAnimal(NewDog())
        zoo.AddAnimal(NewCat())

        soundVisitor := &AnimalSoundVisitor{}
        zoo.PerformOperation(soundVisitor)
}

在这个示例中,我们使用Go语言实现了访问者模式。与前面的示例类似,我们定义了抽象访问者接口(Visitor),具体访问者类(AnimalSoundVisitor),以及抽象被访问者接口(Animal)和具体被访问者类(Dog 和 Cat)。然后,我们创建了对象结构 Zoo,将动物对象添加到动物园中,并使用 PerformOperation 方法执行访问操作。

在 main 函数中,我们创建了动物对象,将它们添加到动物园中,并创建了一个 AnimalSoundVisitor 实例来执行访问操作。运行程序后,将输出不同动物的声音。这演示了如何使用访问者模式来添加新的操作而无需修改现有动物类。

5.3 Javascript实现

上述例子用javascript实现示例如下:

// 抽象访问者
class Visitor {
  visitDog(dog) {}
  visitCat(cat) {}
}

// 具体访问者
class AnimalSoundVisitor extends Visitor {
  visitDog(dog) {
    console.log(`The dog makes a sound: ${dog.getSound()}`);
  }

  visitCat(cat) {
    console.log(`The cat makes a sound: ${cat.getSound()}`);
  }
}

// 抽象被访问者
class Animal {
  accept(visitor) {}
}

// 具体被访问者
class Dog extends Animal {
  constructor() {
    super();
    this.sound = "Woof!";
  }

  getSound() {
    return this.sound;
  }

  accept(visitor) {
    visitor.visitDog(this);
  }
}

class Cat extends Animal {
  constructor() {
    super();
    this.sound = "Meow!";
  }

  getSound() {
    return this.sound;
  }

  accept(visitor) {
    visitor.visitCat(this);
  }
}

// 对象结构
class Zoo {
  constructor() {
    this.animals = [];
  }

  addAnimal(animal) {
    this.animals.push(animal);
  }

  performOperation(visitor) {
    for (const animal of this.animals) {
      animal.accept(visitor);
    }
  }
}

// 创建动物园和动物
const zoo = new Zoo();
zoo.addAnimal(new Dog());
zoo.addAnimal(new Cat());

const soundVisitor = new AnimalSoundVisitor();
zoo.performOperation(soundVisitor);

在这个示例中,我们使用JavaScript语言实现了访问者模式。我们定义了抽象访问者类 Visitor 和具体访问者类 AnimalSoundVisitor,以及抽象被访问者类 Animal 和具体被访问者类 Dog 和 Cat。然后,我们创建了对象结构 Zoo,将动物对象添加到动物园中,并使用 performOperation 方法执行访问操作。

在示例的最后部分,我们创建了动物园和动物对象,并执行了访问操作,输出不同动物的声音。这演示了如何使用访问者模式来添加新的操作而无需修改现有动物类。

5.4 C++实现

上述例子用C++实现如下:

#include <iostream>
#include <vector>

// 前置声明
class Dog;
class Cat;

// 抽象访问者
class Visitor {
public:
    virtual void visit(Dog* dog) = 0;
    virtual void visit(Cat* cat) = 0;
};

// 具体访问者
class AnimalSoundVisitor : public Visitor {
public:
    void visit(Dog* dog) override {
        std::cout << "The dog makes a sound: " << dog->getSound() << std::endl;
    }

    void visit(Cat* cat) override {
        std::cout << "The cat makes a sound: " << cat->getSound() << std::endl;
    }
};

// 抽象被访问者
class Animal {
public:
    virtual void accept(Visitor* visitor) = 0;
};

// 具体被访问者
class Dog : public Animal {
private:
    std::string sound = "Woof!";

public:
    std::string getSound() {
        return sound;
    }

    void accept(Visitor* visitor) override {
        visitor->visit(this);
    }
};

class Cat : public Animal {
private:
    std::string sound = "Meow!";

public:
    std::string getSound() {
        return sound;
    }

    void accept(Visitor* visitor) override {
        visitor->visit(this);
    }
};

// 对象结构
class Zoo {
private:
    std::vector<Animal*> animals;

public:
    void addAnimal(Animal* animal) {
        animals.push_back(animal);
    }

    void performOperation(Visitor* visitor) {
        for (Animal* animal : animals) {
            animal->accept(visitor);
        }
    }
};

int main() {
    Zoo zoo;
    zoo.addAnimal(new Dog());
    zoo.addAnimal(new Cat());

    AnimalSoundVisitor soundVisitor;
    zoo.performOperation(&soundVisitor);

    return 0;
}

在这个示例中,我们使用C++语言实现了访问者模式。我们定义了抽象访问者类 Visitor 和具体访问者类 AnimalSoundVisitor,以及抽象被访问者类 Animal 和具体被访问者类 Dog 和 Cat。然后,我们创建了对象结构 Zoo,将动物对象添加到动物园中,并使用 performOperation 方法执行访问操作。

在 main 函数中,我们创建了动物园和动物对象,并执行了访问操作,输出不同动物的声音。这演示了如何使用访问者模式来添加新的操作而无需修改现有动物类。

6. 练习题

考虑一个图书馆管理系统,其中包括不同类型的图书(例如,小说、非小说、杂志等)。每种类型的图书都有不同的属性和操作。使用访问者模式来实现一个报表生成器,该报表生成器可以根据不同类型的图书生成不同的报表。

要求:

  1. 定义一个抽象访问者类 Visitor,其中包括用于访问不同类型的图书的抽象方法(例如,visitNovel、visitMagazine等)。
  2. 定义具体访问者类 ReportGenerator,该类实现了抽象访问者类中的方法,用于生成不同类型图书的报表。
  3. 定义一个抽象图书类 Book,其中包括图书的通用属性和一个抽象方法 accept,用于接受访问者的访问。
  4. 定义具体图书类,如 Novel、NonFiction、Magazine,它们继承自抽象图书类,并实现了 accept 方法,以便接受 ReportGenerator 访问。
  5. 创建一个图书馆类,该类包含图书的集合,并提供方法来添加不同类型的图书。
  6. 在主程序中,创建图书馆对象,添加不同类型的图书,并使用 ReportGenerator 来生成报表。

你可以使用 C++、Java、Python 或任何其他编程语言来实现这个练习。
你可以在评论区里或者私信我回复您的答案,这样我或者大家都能帮你解答,期待着你的回复~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

guohuang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值