终于可以闲会了,继续研究设计模式,这次是Visitor ,访问者模式。照例先看李建忠老师的视频,然后看GOF的原文,从不明白的郁闷,到反复看了之后的恍然,然后又有了点疑问。
先说我的理解,访问者模式有2个参与者,一个访问者,一个被访问者。访问者是一组操作的集合,对他所知的被访问者进行操作。
访问者模式有2个重点,1 已知被访问者,就是说访问者只能访问有限的对象,如果所访问的对象增加了,那么就要修改访问者了,2 被访问者必须有个统一的被访问接口。
看GOF画的结构图,Visitor类就是访问者,Visitor类拥有几个方法是固定的,不同的方法访问不同的被访问者,我说的一个重点就是在这里体现,如果不幸有第三个对象需要访问,那么就只能修改Visitor类了,李建忠老师在视频里也说这是访问者模式的缺陷。而Element类就是被访问者了,它只有一个方法,接受Visitor类型参数的Accept方法,也就是所有被访问者统一的被访问接口,当然,实际上它还有自己的其他方法。
接着看,问题来了,这个ObjectStructure干什么的?GOF原图上干干净净的,李老师的视频也没提它,我就找了Terrylee版的C#设计模式来看,Terrylee对ObjectStructure的描述是一个统一调用被访问者的容器,语言不好描述,贴原码如下:
// "ObjectStructure"
class ObjectStructure
{
private ArrayList elements = new ArrayList();

public void Attach(Element element)
{
elements.Add(element);
}
public void Detach(Element element)
{
elements.Remove(element);
}
public void Accept(Visitor visitor)
{
foreach (Element e in elements)
{
e.Accept(visitor);
}
}
}
个人觉得,这样理解恐怕不是很正确,我觉得这个ObjectStructure应该是一个相当自由的类,Visitor模式的变化就体现在这里,就是说如果增加了新的行为(给Visitor增加了新的子类),那么就应当在这里调用,而调用的方式不应当是这样固定的,应该是自由组合的。
//客户程序,变,如果增加新的Visitor,可以在这里体现
public class ObjectStructure
{
public void Process()
{
//访问者总数是不固定的,而使用也是需要则生成,不需要不生成
//在一个类里,不一定都用到,也可能不会只用一个
ConcreteVisitorA va = new ConcreteVisitorA();
ConcreteVisitorB vb = new ConcreteVisitorB();

//被访问者总数是固定的,总是这些
//在一个类里,不一定都用到,也可能不会只用一个
ConcreteElementA ca = new ConcreteElementA();
ConcreteElementB cb = new ConcreteElementB();

//对于放问者和被访问者的组合,应当是自由的,任意组合的

ca.Accept(va);
cb.Accept(va);

//


ca.Accept(vb);
cb.Accept(vb);
//

}
}
public class ConcreteVisitorC : Visitor
{
ConcreteVisitorB vb = new ConcreteVisitorB();

public override void Visit(ConcreteElementA ca)
{
vb.Visit(ca);
}

public override void Visit(ConcreteElementB cb)
{
vb.Visit(cb);
}

public override void Visit(ConcreteElementC cc)
{
//新的行为
}
}
2 可以提供默认解决方案,如无必要,不必重写所有基类方法,例如上面那个ConcreteVisitorC,如果不想改变对以前的Element的操作,那么只重写新增加的方法就可以了。
先说我的理解,访问者模式有2个参与者,一个访问者,一个被访问者。访问者是一组操作的集合,对他所知的被访问者进行操作。
访问者模式有2个重点,1 已知被访问者,就是说访问者只能访问有限的对象,如果所访问的对象增加了,那么就要修改访问者了,2 被访问者必须有个统一的被访问接口。
看GOF画的结构图,Visitor类就是访问者,Visitor类拥有几个方法是固定的,不同的方法访问不同的被访问者,我说的一个重点就是在这里体现,如果不幸有第三个对象需要访问,那么就只能修改Visitor类了,李建忠老师在视频里也说这是访问者模式的缺陷。而Element类就是被访问者了,它只有一个方法,接受Visitor类型参数的Accept方法,也就是所有被访问者统一的被访问接口,当然,实际上它还有自己的其他方法。
接着看,问题来了,这个ObjectStructure干什么的?GOF原图上干干净净的,李老师的视频也没提它,我就找了Terrylee版的C#设计模式来看,Terrylee对ObjectStructure的描述是一个统一调用被访问者的容器,语言不好描述,贴原码如下:
// "ObjectStructure"
class ObjectStructure
{
private ArrayList elements = new ArrayList(); 
public void Attach(Element element)
{
elements.Add(element);
}
public void Detach(Element element)
{
elements.Remove(element);
}
public void Accept(Visitor visitor)
{
foreach (Element e in elements)
{
e.Accept(visitor);
}
}
} 个人觉得,这样理解恐怕不是很正确,我觉得这个ObjectStructure应该是一个相当自由的类,Visitor模式的变化就体现在这里,就是说如果增加了新的行为(给Visitor增加了新的子类),那么就应当在这里调用,而调用的方式不应当是这样固定的,应该是自由组合的。
//客户程序,变,如果增加新的Visitor,可以在这里体现
public class ObjectStructure
{
public void Process()
{
//访问者总数是不固定的,而使用也是需要则生成,不需要不生成
//在一个类里,不一定都用到,也可能不会只用一个
ConcreteVisitorA va = new ConcreteVisitorA();
ConcreteVisitorB vb = new ConcreteVisitorB();
//被访问者总数是固定的,总是这些
//在一个类里,不一定都用到,也可能不会只用一个
ConcreteElementA ca = new ConcreteElementA();
ConcreteElementB cb = new ConcreteElementB();
//对于放问者和被访问者的组合,应当是自由的,任意组合的
ca.Accept(va);
cb.Accept(va);
//


ca.Accept(vb);
cb.Accept(vb);
//

}
}
正是因为这个ObjectStructure这么自由,所以GOF的图中没有给出任何相关结构,呵呵,胡乱猜想,大家别笑。
回头再来看Visitor的意图:
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
适用性:
1 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
2 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
3 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
可能我比较笨吧,所以这段话是在把李老师的视频看了2遍,GOF的书也看了2遍之后才明白的。
最后还有个疑问,在GOF的书中,Visitor的方法用不同的方法签名,李老师的视频里用重载代替,我也觉得重载更OO一些,但为什么不使用虚方法而使用抽象方法呢?
我的看法是,在这样的意图下,虚方法显然更灵活些,1 如果我们真要给Visitor增加新的方法,不用去改以前的子类,改改基类定义,再增加新的子类就可以了,例如这样:
public class ConcreteVisitorC : Visitor
{
ConcreteVisitorB vb = new ConcreteVisitorB();
public override void Visit(ConcreteElementA ca)
{
vb.Visit(ca);
}
public override void Visit(ConcreteElementB cb)
{
vb.Visit(cb);
}
public override void Visit(ConcreteElementC cc)
{
//新的行为
}
}2 可以提供默认解决方案,如无必要,不必重写所有基类方法,例如上面那个ConcreteVisitorC,如果不想改变对以前的Element的操作,那么只重写新增加的方法就可以了。

浙公网安备 33010602011771号