我一直在博客中看到访客模式的参考,但我不得不承认,我就是不明白。我读了维基百科上关于这个模式的文章,我理解了它的机制,但我仍然不知道什么时候使用它。
作为一个最近才真正了解装饰器模式的人,现在看到它在任何地方都有使用,我希望能够真正直观地理解这个看似方便的模式。
我一直在博客中看到访客模式的参考,但我不得不承认,我就是不明白。我读了维基百科上关于这个模式的文章,我理解了它的机制,但我仍然不知道什么时候使用它。
作为一个最近才真正了解装饰器模式的人,现在看到它在任何地方都有使用,我希望能够真正直观地理解这个看似方便的模式。
当前回答
游客
Visitor允许用户在不修改类本身的情况下向类族中添加新的虚函数;相反,创建一个实现虚函数的所有适当专门化的访问者类
参观者结构:
在以下情况下使用访问者模式:
必须对结构中分组的不同类型的对象执行类似的操作 您需要执行许多不同且不相关的操作。它将操作从对象结构中分离出来 必须在不改变对象结构的情况下添加新操作 将相关操作集合到一个类中,而不是强迫您更改或派生类 向没有源或不能更改源的类库中添加函数
尽管访问者模式提供了在不改变Object中现有代码的情况下添加新操作的灵活性,但这种灵活性也带来了缺点。
如果添加了新的Visitable对象,则需要在Visitor和ConcreteVisitor类中修改代码。有一种变通方法可以解决这个问题:使用反射,这将对性能产生影响。
代码片段:
import java.util.HashMap;
interface Visitable{
void accept(Visitor visitor);
}
interface Visitor{
void logGameStatistics(Chess chess);
void logGameStatistics(Checkers checkers);
void logGameStatistics(Ludo ludo);
}
class GameVisitor implements Visitor{
public void logGameStatistics(Chess chess){
System.out.println("Logging Chess statistics: Game Completion duration, number of moves etc..");
}
public void logGameStatistics(Checkers checkers){
System.out.println("Logging Checkers statistics: Game Completion duration, remaining coins of loser");
}
public void logGameStatistics(Ludo ludo){
System.out.println("Logging Ludo statistics: Game Completion duration, remaining coins of loser");
}
}
abstract class Game{
// Add game related attributes and methods here
public Game(){
}
public void getNextMove(){};
public void makeNextMove(){}
public abstract String getName();
}
class Chess extends Game implements Visitable{
public String getName(){
return Chess.class.getName();
}
public void accept(Visitor visitor){
visitor.logGameStatistics(this);
}
}
class Checkers extends Game implements Visitable{
public String getName(){
return Checkers.class.getName();
}
public void accept(Visitor visitor){
visitor.logGameStatistics(this);
}
}
class Ludo extends Game implements Visitable{
public String getName(){
return Ludo.class.getName();
}
public void accept(Visitor visitor){
visitor.logGameStatistics(this);
}
}
public class VisitorPattern{
public static void main(String args[]){
Visitor visitor = new GameVisitor();
Visitable games[] = { new Chess(),new Checkers(), new Ludo()};
for (Visitable v : games){
v.accept(visitor);
}
}
}
解释:
Visitable (Element)是一个接口,该接口方法必须添加到一组类中。 Visitor是一个接口,它包含对可访问元素执行操作的方法。 GameVisitor是一个实现Visitor接口(ConcreteVisitor)的类。 每个可访问元素接受Visitor并调用Visitor接口的相关方法。 你可以将游戏视为元素,而将象棋、跳棋和Ludo等具体游戏视为具体元素。
在上述例子中,国际象棋、西洋跳棋和卢多是三种不同的游戏(以及可访问的职业)。在一个晴朗的日子里,我遇到了一个记录每款游戏统计数据的场景。所以无需修改单个类来实现统计功能,你可以将这一职责集中到GameVisitor类中,这样你就无需修改每个游戏的结构。
输出:
Logging Chess statistics: Game Completion duration, number of moves etc..
Logging Checkers statistics: Game Completion duration, remaining coins of loser
Logging Ludo statistics: Game Completion duration, remaining coins of loser
指
oodesign文章
sourcemaking文章
欲知详情
装饰
模式允许将行为静态或动态地添加到单个对象,而不影响来自同一类的其他对象的行为
相关文章:
IO的装饰器模式
什么时候使用装饰器模式?
其他回答
我不太熟悉来客模式。看看我做得对不对。假设你有一个动物等级
class Animal { };
class Dog: public Animal { };
class Cat: public Animal { };
(假设它是一个具有良好接口的复杂层次结构。)
现在我们想要向层次结构添加一个新操作,即我们想要每个动物发出它的声音。既然层次结构这么简单,你可以直接用多态性来实现:
class Animal
{ public: virtual void makeSound() = 0; };
class Dog : public Animal
{ public: void makeSound(); };
void Dog::makeSound()
{ std::cout << "woof!\n"; }
class Cat : public Animal
{ public: void makeSound(); };
void Cat::makeSound()
{ std::cout << "meow!\n"; }
但是按照这种方式进行,每次想要添加操作时,都必须修改到层次结构中每个类的接口。现在,假设您对原始界面感到满意,并且希望对其进行尽可能少的修改。
访问者模式允许您在合适的类中移动每个新操作,并且您只需要扩展层次结构的接口一次。我们开始吧。首先,我们定义了一个抽象操作(GoF中的“Visitor”类),它对层次结构中的每个类都有一个方法:
class Operation
{
public:
virtual void hereIsADog(Dog *d) = 0;
virtual void hereIsACat(Cat *c) = 0;
};
然后,我们修改层次结构以接受新的操作:
class Animal
{ public: virtual void letsDo(Operation *v) = 0; };
class Dog : public Animal
{ public: void letsDo(Operation *v); };
void Dog::letsDo(Operation *v)
{ v->hereIsADog(this); }
class Cat : public Animal
{ public: void letsDo(Operation *v); };
void Cat::letsDo(Operation *v)
{ v->hereIsACat(this); }
最后,我们实现了实际的操作,没有修改Cat和Dog:
class Sound : public Operation
{
public:
void hereIsADog(Dog *d);
void hereIsACat(Cat *c);
};
void Sound::hereIsADog(Dog *d)
{ std::cout << "woof!\n"; }
void Sound::hereIsACat(Cat *c)
{ std::cout << "meow!\n"; }
现在,您可以在不修改层次结构的情况下添加操作。 下面是它的工作原理:
int main()
{
Cat c;
Sound theSound;
c.letsDo(&theSound);
}
Visitor设计模式非常适用于目录树、XML结构或文档概要等“递归”结构。
Visitor对象访问递归结构中的每个节点:每个目录、每个XML标记等等。Visitor对象不遍历结构。相反,Visitor方法应用于结构的每个节点。
这是一个典型的递归节点结构。可以是目录或XML标记。 [如果你是一个Java人,想象一下有很多额外的方法来构建和维护子列表。]
class TreeNode( object ):
def __init__( self, name, *children ):
self.name= name
self.children= children
def visit( self, someVisitor ):
someVisitor.arrivedAt( self )
someVisitor.down()
for c in self.children:
c.visit( someVisitor )
someVisitor.up()
visit方法将Visitor对象应用于结构中的每个节点。在本例中,它是一个自顶向下的访问者。您可以更改visit方法的结构,以进行自底向上或其他排序。
这里有一个供访问者使用的超类。它被visit方法所使用。它“到达”结构中的每个节点。由于visit方法调用了up和down,因此访问者可以跟踪深度。
class Visitor( object ):
def __init__( self ):
self.depth= 0
def down( self ):
self.depth += 1
def up( self ):
self.depth -= 1
def arrivedAt( self, aTreeNode ):
print self.depth, aTreeNode.name
子类可以做一些事情,比如在每个级别上计算节点并积累一个节点列表,生成一个良好的路径分层节号。
这是申请表。它构建了一个树结构,someTree。它创建了一个Visitor, dumpNodes。
然后它将dumpNodes应用到树中。dumpNode对象将“访问”树中的每个节点。
someTree= TreeNode( "Top", TreeNode("c1"), TreeNode("c2"), TreeNode("c3") )
dumpNodes= Visitor()
someTree.visit( dumpNodes )
TreeNode访问算法将确保每个TreeNode都被用作Visitor的arrivedAt方法的参数。
游客
Visitor允许用户在不修改类本身的情况下向类族中添加新的虚函数;相反,创建一个实现虚函数的所有适当专门化的访问者类
参观者结构:
在以下情况下使用访问者模式:
必须对结构中分组的不同类型的对象执行类似的操作 您需要执行许多不同且不相关的操作。它将操作从对象结构中分离出来 必须在不改变对象结构的情况下添加新操作 将相关操作集合到一个类中,而不是强迫您更改或派生类 向没有源或不能更改源的类库中添加函数
尽管访问者模式提供了在不改变Object中现有代码的情况下添加新操作的灵活性,但这种灵活性也带来了缺点。
如果添加了新的Visitable对象,则需要在Visitor和ConcreteVisitor类中修改代码。有一种变通方法可以解决这个问题:使用反射,这将对性能产生影响。
代码片段:
import java.util.HashMap;
interface Visitable{
void accept(Visitor visitor);
}
interface Visitor{
void logGameStatistics(Chess chess);
void logGameStatistics(Checkers checkers);
void logGameStatistics(Ludo ludo);
}
class GameVisitor implements Visitor{
public void logGameStatistics(Chess chess){
System.out.println("Logging Chess statistics: Game Completion duration, number of moves etc..");
}
public void logGameStatistics(Checkers checkers){
System.out.println("Logging Checkers statistics: Game Completion duration, remaining coins of loser");
}
public void logGameStatistics(Ludo ludo){
System.out.println("Logging Ludo statistics: Game Completion duration, remaining coins of loser");
}
}
abstract class Game{
// Add game related attributes and methods here
public Game(){
}
public void getNextMove(){};
public void makeNextMove(){}
public abstract String getName();
}
class Chess extends Game implements Visitable{
public String getName(){
return Chess.class.getName();
}
public void accept(Visitor visitor){
visitor.logGameStatistics(this);
}
}
class Checkers extends Game implements Visitable{
public String getName(){
return Checkers.class.getName();
}
public void accept(Visitor visitor){
visitor.logGameStatistics(this);
}
}
class Ludo extends Game implements Visitable{
public String getName(){
return Ludo.class.getName();
}
public void accept(Visitor visitor){
visitor.logGameStatistics(this);
}
}
public class VisitorPattern{
public static void main(String args[]){
Visitor visitor = new GameVisitor();
Visitable games[] = { new Chess(),new Checkers(), new Ludo()};
for (Visitable v : games){
v.accept(visitor);
}
}
}
解释:
Visitable (Element)是一个接口,该接口方法必须添加到一组类中。 Visitor是一个接口,它包含对可访问元素执行操作的方法。 GameVisitor是一个实现Visitor接口(ConcreteVisitor)的类。 每个可访问元素接受Visitor并调用Visitor接口的相关方法。 你可以将游戏视为元素,而将象棋、跳棋和Ludo等具体游戏视为具体元素。
在上述例子中,国际象棋、西洋跳棋和卢多是三种不同的游戏(以及可访问的职业)。在一个晴朗的日子里,我遇到了一个记录每款游戏统计数据的场景。所以无需修改单个类来实现统计功能,你可以将这一职责集中到GameVisitor类中,这样你就无需修改每个游戏的结构。
输出:
Logging Chess statistics: Game Completion duration, number of moves etc..
Logging Checkers statistics: Game Completion duration, remaining coins of loser
Logging Ludo statistics: Game Completion duration, remaining coins of loser
指
oodesign文章
sourcemaking文章
欲知详情
装饰
模式允许将行为静态或动态地添加到单个对象,而不影响来自同一类的其他对象的行为
相关文章:
IO的装饰器模式
什么时候使用装饰器模式?
使用访问者模式至少有三个很好的理由:
减少代码的增殖,当数据结构发生变化时,代码只会略有不同。 将相同的计算应用于多个数据结构,而不改变实现计算的代码。 在不更改遗留代码的情况下向遗留库添加信息。
请看看我写的一篇关于这方面的文章。
虽然我知道如何做,何时做,但我一直不明白为什么。为了对有c++等语言背景的人有所帮助,请仔细阅读本文。
对于懒惰的人,我们使用访问者模式,因为“虽然在c++中虚函数是动态分派的,但函数重载是静态完成的”。
或者,换句话说,当你传递一个实际绑定到ApolloSpacecraft对象的飞船引用时,确保CollideWith(ApolloSpacecraft&)被调用。
class SpaceShip {};
class ApolloSpacecraft : public SpaceShip {};
class ExplodingAsteroid : public Asteroid {
public:
virtual void CollideWith(SpaceShip&) {
cout << "ExplodingAsteroid hit a SpaceShip" << endl;
}
virtual void CollideWith(ApolloSpacecraft&) {
cout << "ExplodingAsteroid hit an ApolloSpacecraft" << endl;
}
}