First we try, then we trust

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  183 随笔 :: 111 文章 :: 3326 评论 :: 358 引用

"众口难调"出自宋·欧阳修《归田录》卷一:"补仲山之衮,虽曲尽于巧心;和傅说之羹,实难调于众口。"其原意是各人的口味不同,很难做出一种饭菜使所有的人都感到好吃。众口是否真的难调呢?其实有个不错的办法可以解决众口难调的问题,那就是吃"自助餐"。

面对众口难调的问题去吃"自助餐"已经不是什么新鲜事,承办一个几百人、几千人的会议往往采用的都是自助餐的方式,让来宾各取所需,这就是所谓的以不变应万变。用更通俗的话来说,就是"东西都在这儿,自己看着办吧"。

在程序设计中解决这种众口难调的问题用的就是Visitor模式。在这里"众口难调"是指很难设计出一组对象(一桌饭菜)符合每个调用者的需要(口味),因为你根本就无法预料到这组对象的访问者是谁,有什么样的调用请求,访问什么样的数据。那么对付众口难调的方法"以不变应万变"在Visitor模式中就是指让一桌饭菜从你面前过,自己看着办就行了。

当然这里也有个先决条件,那就是你对每样饭菜都有一定的了解,自己才能做出选择。否则可能会瞪着一盘摆满小石子的盘子不知所措。反过来说,饭菜不需要知道来这里就餐的人的口味是什么,只管放出来让客人看着办就行了。这样,顾客和饭菜之间是不均衡的。顾客必须"有备而来",但饭菜却对顾客却"一视同仁"。

using System;
using System.Collections;

abstract class Visitor
{
  
public abstract void VisitCoffee(Coffee c);
  
public abstract void VisitMeat(Meat m);
  
public abstract void VisitVegetable(Vegetable v);
}


class ZhangSan : Visitor
{
  
public override void VisitCoffee(Coffee c)
  
{
    Console.Write( 
"{0}: Take a cup of {1}, ",this, c);
    c.AddMilk();
    c.AddSugar();
    Console.WriteLine();
  }


  
public override void VisitVegetable(Vegetable v)
  
{
    Console.WriteLine( 
"{0}: Take some {1}"this, v );
  }


  
public override void VisitMeat(Meat m)
  
{
    Console.WriteLine( 
"I don't want any meat!");
  }

}


class LiSi : Visitor
{
  
public override void VisitCoffee(Coffee c)
  
{
    Console.Write( 
"{0}: Take a cup of {1}, ",this, c);
    c.AddSugar();
    Console.WriteLine();
  }


  
public override void VisitVegetable(Vegetable v)
  
{
    Console.WriteLine( 
"{0}: Take some {1}"this, v );
  }


  
public override void VisitMeat(Meat m)
  
{
    Console.WriteLine( 
"{0}: Take some {1}"this, m );
  }

}


abstract class Food
{
  
abstract public void Accept( Visitor visitor );
}


class Coffee: Food  
{
  
override public void Accept( Visitor visitor )
  
{
    visitor.VisitCoffee( 
this );
  }


  
public void AddSugar()
  
{
    Console.Write(
"add sugar. ");   
  }


  
public void AddMilk()
  
{
    Console.Write(
"add milk. ");   
  }

}


class Meat: Food  
{
  
override public void Accept( Visitor visitor )
  
{
    visitor.VisitMeat( 
this );
  }

}


class Vegetable: Food  
{
  
override public void Accept( Visitor visitor )
  
{
    visitor.VisitVegetable( 
this );
  }

}


class BuffetDinner
{
  
private ArrayList elements = new ArrayList();

  
public void Attach( Food element )
  
{
    elements.Add( element );
  }


  
public void Detach( Food element )
  
{
    elements.Remove( element );
  }


  
public void Accept( Visitor visitor )
  
{
    
foreach( Food f in elements )
      f.Accept( visitor );
  }

}


public class Client
{
  
public static void Main( string[] args )
  
{
    BuffetDinner b 
= new BuffetDinner();
    b.Attach(
new Coffee());
    b.Attach(
new Vegetable());
    b.Attach(
new Meat());

    ZhangSan z 
= new ZhangSan();
    LiSi l 
= new LiSi();

    b.Accept( z );
    Console.WriteLine(
"----------------------");
    b.Accept( l );
  }

}

但是,Visitor模式中所蕴涵的思想绝非一个众口难调的例子所能完全表达的。其中还有"条件外置"(我起的名字)的含义在里面(应当归纳到"Find what vary and encapsulate it"的范畴,是我对这句话的理解),也就是说将条件判断从一个类中抽取出来,或交由专门的对象进行处理,或通过配置文件由用户手工控制。说白了就是做成"活"的。这种"条件外置"往往离不开多态的帮忙。面向对象中多态性是说,可以将子类型对象赋值给父类型对象,如果子类型复写了父类型的某个方法,那么当调用父类型对象的此方法时,自动转而调用子类型复写后的方法。条件外置在众多的模式中都有体现:

比如在简单工厂模式中,我们需要判断类型,然后进行加工,如下:

public Light Create(string LightType)
{
   
if(LightType == "Bulb")
      
return new BulbLight();
   
else if(LightType == "Tube")
      
return new TubeLight();
   
else
      
return null;
}

我们就可以让BulbLight与TubeLight共同继承自Light,再加上一个与之相对应的工厂架构,于是条件便外置到了客户端的手中:

public static void Main()
{
   Creator c 
= new BulbCreator();
   Light l 
= c.factory();
   
   l.TurnOn();
   l.TurnOff();
   }

}

如果你想生产什么样子的灯泡就给 Creater c 赋值什么类型的工厂就行了。有人可能还会问,那什么时候使用什么类型的工厂不还是要进行条件判断吗,对了,条件外置后总还是要有人处理的,只不过不在Factory里面,也不在Light里面。你可以集中管理,也可以用配置文件,总之保证了系统绝大多数模块的稳定性。

另外,在策略模式的案例中,如果没有应用"策略模式",那么我们也必须在业务对象中使用一个长长的If结构,判断何时使用哪个策略。但是借助多态性将"条件外置"后,便将判断控制权交由其他类进行处理,使得业务对象更加稳定。

至于到底将条件放到什么地方有很多解决办法,其一是放到一个专门的类中,这便应了《Design Pattern Explained》一书中的"Find what vary and Encapsulate it."这句话,发现变化的东西并且将其封装起来。其二,就是外置成基于XML或纯文本的配置文件,随时可以方便的进行修改。如果愿意,甚至可以编写一个图形化配置工具实现这一功能。

但是条件外置带来的"负"面影响便是需要"Design to Interface(针对接口或抽象编程)"。针对抽象编程增加了系统的稳定性,提高了可扩展性,并且让用户只与抽象打交道,恰好也应用了"最小知识原则"。但这对于必须了解对象类型的系统却是个灾难。Visitor模式便是在这个灾难中成活下来的一个很好的例子。虽然还有些争议,但解决的已经是很好了。

那么在Visitor模式中又是如何将"条件外置"的呢?这个外置工作恐怕比起前两种类型要复杂一些,用到了双重分派(Double Dispatch),所以我们还要从头说起。先让我们现看看没有用Visitor模式的"众口难调"的例子。

using System;
using System.Collections;

public class Visitor
{
  
public virtual void Visit(ArrayList dinner)
  
{
    
foreach(object f in dinner)
    
{
      
if(f is Coffee)
        VisitCoffee((Coffee)f);
      
else if(f is Vegetable)
        VisitVegetable((Vegetable)f);
      
else if(f is Meat)
        VisitMeat((Meat)f);
      
else
      
{
        
// do nothing
      }

    }

  }


  
protected virtual void VisitCoffee(Coffee c){}
  
protected virtual void VisitVegetable(Vegetable v){}
  
protected virtual void VisitMeat(Meat m){}
}


public class ZhangSan : Visitor
{
  
protected override void VisitCoffee(Coffee c)
  
{
    Console.Write( 
"{0}: Take a cup of {1}, ",this, c);
    c.AddMilk();
    c.AddSugar();
    Console.WriteLine();
  }


  
protected override void VisitVegetable(Vegetable v)
  
{
    Console.WriteLine( 
"{0}: Take some {1}"this, v );
  }


  
protected override void VisitMeat(Meat m)
  
{
    Console.WriteLine( 
"I don't want any meat!");
  }

}


public class Coffee
{
  
public void AddSugar()
  
{
    Console.Write(
"add sugar. ");   
  }


  
public void AddMilk()
  
{
    Console.Write(
"add milk. ");   
  }

}


public class Meat
{
}


public class Vegetable
{
}


public class Client
{
  
private static ArrayList dinner = new ArrayList();

  
public static void Main( string[] args )
  
{
    dinner.Add(
new Coffee());
    dinner.Add(
new Vegetable());
    dinner.Add(
new Meat());

    ZhangSan z 
= new ZhangSan();

    z.Visit(dinner);
  }

}

在这个例子中,各类食物没有公共父类,所以顾客在Visit的时候,必须将各类食物视作object,然后进行类型判断,再分别进行处理,处理时还不要忘了进行类型转换。长长的If结构会给系统引入很多不必要的麻烦,降低了系统的扩展性。

下面我对它应用条件外置:为了利用多态性,我们需要给条件中出现过的咖啡、蔬菜、肉赋予一个公共的父类"食物"。但这对我们这个例子这似乎是远远不够的。因为我们的顾客要"看菜吃饭",如果利用多态性隐藏了类型信息,只针对抽象"食物"编程的化,我们便失去了"看菜吃饭"的功能,我想这是谁也不希望发生的事情。因此,我们唯一的方法就是多态后再进行"回调",让每个具体类(咖啡、蔬菜、肉)自己报上名来,并且自己主动"回调"Visitor中与类型相关的方法。这样,Visitor只需撒下天罗地网,静待食物找上门来。这便是我对双重分派的一个认识。双重分派利用多态和回调消除了条件判断(这里没有外置条件),但也引入了一些新的问题:那就是当食物扩展时(比如说加入米饭),必须修改Visitor的天罗地网以提供米饭的回调函数,如果继承自Visitor的类很多的化,这个系统就变得脆弱起来。其实这也是我在后面要讨论的问题。

Visitor模式的的脆弱在于每个Visitor必须对所有它要访问的对象有个清晰的了解,一旦增加新类型对象,导致每个Visitor都必须发生变化。另外所有Element(在这个例子中是指具体食物)在进行回调的时候,也必须清楚回调的是Visitor的什么方法,方法名是什么等等,造成类与类之间耦合过于紧密。有没有更好的办法呢?我这里说说我的思路:

我的解决办法是二次利用多态性,并辅以模板方法模式(其实算不上模板方法,充其量算个缺省实现罢了)。C#中的多态性分成两个层面,一个是我们说的继承多态性,另外一个就是方法多态性。方法多态性是指某个类可以包含多个同名方法,但签名不同。系统在进行调用时自动匹配签名最相近的一个方法。我们平时使用最多的Console.WriteLine方法便有19个签名各异的"多态"。改造后的Visitor代码如下:

public class Visitor
{
  
public virtual void Visit(Meat m)
  
this.Visit((object)m); }

  
public virtual void Visit(Vegetable v)
  
this.Visit((object)v); }

  
public virtual void Visit(Coffee c)
  
this.Visit((object)c); }

  
public void Visit(Object f)
  
{
    
// do nothing
  }

}

可见,Visit方法总共有四个不同的签名。在缺省实现中,各方法都转而调用public void Visit(Object f)方法,也就是什么都不作。当客户调用Visit方法时,会自动匹配类型。如果匹配不上的类型也会最终落到public void Visit(Object f)方法的怀抱中。在Visitor的子类中,只需实现关注的焦点就行了。例如某人只吃蔬菜不吃肉,那么他就没有必要再复写Visit(Meat m)方法了。这么做带来的另外一个好处就是,当添加新的食品时,即使不改变Visitor的代码,也不会产生任何编译或运行时的错误。如果修改,也只需修改Visitor类并提供一个缺省实现。Visitor的子类如果并不关心新增加的类,便可不做任何改动。完整的代码如下:

using System;
using System.Collections;

public class Visitor
{
  
public virtual void Visit(Meat m)
  
this.Visit((object)m); }

  
public virtual void Visit(Vegetable v)
  
this.Visit((object)v); }

  
public virtual void Visit(Coffee c)
  
this.Visit((object)c); }

  
public void Visit(Object o)
  
{
    
// do nothing
  }

}


public class ZhangSan : Visitor
{
  
public override void Visit(Coffee c)
  
{
    Console.Write( 
"{0}: Take a cup of {1}, ",this, c);
    c.AddMilk();
    c.AddSugar();
    Console.WriteLine();
  }


  
public override void Visit(Vegetable v)
  
{
    Console.WriteLine( 
"{0}: Take some {1}"this, v );
  }

}


public class LiSi : Visitor
{
  
public override void Visit(Coffee c)
  
{
    Console.Write( 
"{0}: Take a cup of {1}, ",this, c);
    c.AddSugar();
    Console.WriteLine();
  }


  
public override void Visit(Meat v)
  
{
    Console.WriteLine( 
"{0}: Take some {1}"this, v );
  }

}


public abstract class Food
{
  
public abstract void Accept( Visitor visitor );
}


public class Coffee: Food  
{
  
public override void Accept( Visitor visitor )
  
{
    visitor.Visit(
this);
  }


  
public void AddSugar()
  
{
    Console.Write(
"add sugar. ");   
  }


  
public void AddMilk()
  
{
    Console.Write(
"add milk. "); 
  }

}


public class Meat: Food  
{
  
public override void Accept( Visitor visitor )
  
{
    visitor.Visit(
this);
  }

}


public class Vegetable: Food  
{
  
public override void Accept( Visitor visitor )
  
{
    visitor.Visit(
this);
  }

}


public class BuffetDinner
{
  
private ArrayList elements = new ArrayList();

  
public void Attach( Food element )
  
{
    elements.Add( element );
  }


  
public void Detach( Food element )
  
{
    elements.Remove( element );
  }


  
public void Accept( Visitor visitor )
  
{
    
foreach( Food f in elements )
      f.Accept( visitor );
  }

}


public class Client
{
  
public static void Main( string[] args )
  
{
    BuffetDinner b 
= new BuffetDinner();
    b.Attach(
new Coffee());
    b.Attach(
new Vegetable());
    b.Attach(
new Meat());

    ZhangSan z 
= new ZhangSan();
    LiSi l 
= new LiSi();

    b.Accept( z );
    Console.WriteLine(
"----------------------");
    b.Accept( l );
  }

}

"条件外置"是我对封装变化的一个理解。现在才发现,原来设计模式中还有很多深奥的东西等待开发呢。

posted on 2004-12-27 19:36 吕震宇 阅读(8937) 评论(32)  编辑 收藏

评论

#1楼 2004-12-25 12:26 sjett
第一个看,,
 回复 引用   

#2楼 2004-12-25 13:36 wayfarer      
好,又出来一篇精彩的随笔了。

我想,只要吕兄不断地写出精品,那么某些聒噪的声音自然就会停止了。
 回复 引用 查看   

#3楼 2004-12-25 14:31 wayfarer      
怎么不发表在博客园首页呢?如果因为是草稿还需修改的原因,那没什么。否则我不认为你这样做是对的。
 回复 引用 查看   

#4楼 2004-12-25 15:27 idior      
个人也觉得是草稿,总觉得少点什么
 回复 引用 查看   

#5楼 2004-12-25 19:49 吕震宇
不好意思,因为教学用到这个例子就匆忙放上来了。但还没有写完。所以没有放到首页上去。等改好了就更新时间,然后放上去。

现在,随笔有很多内容还没有说到位。一些关键点没有说透。只是最近比较忙。我会在三五天内更新过来的。
 回复 引用   

#6楼 2004-12-26 22:40 idior      

visitor 模式主要用于为已有的类结构添加新的行为,而又尽量不对原来的类结构产生大的影响。主要是一种用于扩展的方法。当然,如果你的设计思路很清晰的话,你可以在设计初始,就考虑好类结构常做的事情,而将一些与其相关而又不常做的责任放在visitor中做,这样是对类结构的一个良好的划分,也便于以后的扩展。
(visitor的类图)

其实,在visior中最重要的是d o u b l e - d i s p a t c h的思想,即得到执行的操作不仅决定于Vi s i t o r的类型还决定于它访问的E l e m e n t的类型。
另外,本人对accept有些想法。根据抽象的原则,各个Accept方法最好能统一。由于得到执行的操作不仅决定于Vi s i t o r的类型还决定于它访问的E l e m e n t的类型。而visitor的不同能够根据多态实现运行期绑定,但是Element的不同现在采用的是静态的调用不同的方法。如下



那么能不能将其形式也统一起来呢,都变成v.visit(this)。这样就可以将Accrpt方法提到Element中,具体的Element只要继承就ok了,不需要再为每个具体的Element手工写Accept方法了。

而根据v.visit(this)中的this(即Element)的类型的不同,似乎可是实现某种程度上的动态绑定。

比如在Visitor类中,添加visit方法如下。
void visit(Element  e)
{
   if (e.GetType()==typeof(ConcreteElementA))
   {
         VisitConcreteElementA(e);
   }
}
这样不是也不错吗?希望听听你的意见.

 回复 引用 查看   

#7楼 2004-12-27 15:12 吕震宇
@idior

没想到你写的东西和我现在正在写的东西几乎完全相同,只是最后的GetType我没有用,而是采取同名Visit方法,不同的参数签名实现多态性。并提供一个Default方法。因为东西还没有写完,所以只能在回复中说个大概,等什么时候我把这篇文章写完了,再统一更新。

我的思路是建立Visitor类,并提供缺省实现:

class Visitor
{
public virtual void Visit(Meat m)
{ this.Visit((object)m); }

public virtual void Visit(Vegetable v)
{ this.Visit((object)v); }

public virtual void Visit(Coffee c)
{ this.Visit((object)c); }

public virtual void Visit(Object f)
{
// do nothing
}
}

因为Visitor的Visit方法在签名上的多态,就会自适应的找到合适的代码。没有合适的就会落入缺省实现,也就是public virtual void Visit(Object f)中,因此也不用判断类型或使用GetType了。这样,子类只要实现自己的功能就行了。比如某人不吃肉,那没他就没有必要复写Visit(Meat m)方法了。

不过这不是我在这篇文章中中心想说的东西。其实我想说的叫“条件外置”,其实是我起的名字,应当归纳到“Find what vary and encapsulate it”的范畴内。是我对这句话的一个理解。等我完全写完后就放上来,希望不会辜负大家的期望。

 回复 引用   

#8楼 2004-12-27 16:13 吕震宇
@idior

补充:如果在Visit方法中还有IF存在的化,我想我们又回到了原点。如果真是这样的化,还不如写成
public void Visti(object o)
{
if (o is ConcreteElementA)
{
VisitConcreteElementA(e);
}
}

甚至我们连Accept方法都省略了,也没有必要用Element作为A、B的父类。直接在ObjectStructure中写:

foreach(object o in lists)
Visitor.Visit(o);

其中的lists是一个对象数组,存放了多个类型各异的对象。这是我的看法,你认为呢?
 回复 引用   

#9楼 2004-12-27 16:55 idior      
@吕震宇
good 你的解决方法比我的好,.还启发了我的思维,3x (可能会写篇随笔谈谈)

对于你的第二条回复,把我弄晕了,似乎确实可以在某种场合不用accept,
让visitor主动访问element,这样是不是违背了visitor模式的初衷,将依赖关系反过来了?
你是不是想表达这种意思?这点我倒没想到.

visitor 模式主要用于为已有的类结构添加新的行为,而又尽量不对原来的类结构产生大的影响。你在这篇随笔里体现的应该不是这个思想.
我现在有点明白你"条件外置"的意思,期望你能将其完善.应该将其与oo的一些基本思想联系起来说.

以后也可以发些草稿,激发大家的思维 :)

 回复 引用 查看   

#10楼 2004-12-27 17:04 吕震宇
@idior

我的第二条回复的意思是说,如果在代码中还包含IF的化,Visitor模式便失去了作用。用Visitor模式还不如不用Visitor模式简单呢。在我更新后,你可以参考其中没有用Visitor模式的“众口难调”的那段代码,用了大量If判断。

我的"条件外置"是指去掉If条件命令,或外置或消除,将If带来的不确定性因素(比如又增加一种新食品等)外置到一个单独的对象中(封装变化),对变化进行集中处理。Visitor模式有些特别,条件外置需要额外一些技巧。内容我基本已经写完,只是现在我的网速太慢,争取在速度好点的时候放上去。

另外,我也没想到这么做会启发思维,呵呵。
 回复 引用   

#11楼 2004-12-28 08:27 minbear      
"条件外置"这个词用的好,Visitor的确是把对象type的判断给抽象了出来,外置到各具体子类中了。

记得有位大师曾经说过,好的设计模式中不存在任何的Switch,应该也是出于这方面的考虑吧~
 回复 引用 查看   

#12楼 2004-12-28 15:20 ugvanxk[未注册用户]
vistor模式像一个多对多的表,加了一个中间表
 回复 引用   

#13楼 2004-12-28 19:18 吕震宇
@ugvanxk

很有意思的比喻。一个visitor对应多个element,一个element也可以被多个visitor访问。但是中间表具体指什么呢?
 回复 引用   

#14楼 2004-12-28 20:33 ugvanxk[未注册用户]
我看delphi模式编程中讲包括
抽象访问者,具体访问者,抽象元素,具体元素,对象结构

在访问者模式中,对象机构既可以是一个聚合,也可以是一个合成模式.
我觉得对象结构就是中间表
 回复 引用   

#15楼 2004-12-28 20:41 ugvanxk[未注册用户]
对象结构就是idior 图中的那个objectstucture
 回复 引用   

#16楼[楼主] 2004-12-29 10:11 吕震宇      
@ugvanxk

"对象机构既可以是一个聚合,也可以是一个合成模式",写的非常正确。其实需要objectstructure完成一个所有包含结点的遍历,并提供给Visitor逐一访问。聚合以及合成模式都非常适合做这个工作。

我不是很赞同把objectstucture看作中间表的做法。我感觉中间表的特点是需要持有多对多双方的一个引用。在objectstructure中仅持有Element的引用。却并没有持有每个Visitor的引用,因为Visitor是不可预知的。但是这种多对多的想法到是很有创意。在和idior探讨Visitor模式的时候,我感觉Visitor模式似乎是在“踢皮球”。
 回复 引用 查看   

#17楼 2004-12-29 21:50 kc
我觉得我们首先要搞清楚visitor要解决的问题
然后我们才讨论这种设计模式的好处和坏处
还有是否我们能够用更好的方法来解决这个问题
如果能找到这样的方法,我们就有比visitor更好的模式了
不过这个恐怖比较难,哈哈
但我认为至少这样讨论了就算找不到也会加深理解,有很多好处哦
 回复 引用   

#18楼 2004-12-29 21:59 kc
另外关于idior说得这个方法
/*
那么能不能将其形式也统一起来呢,都变成v.visit(this)。这样就可以将Accrpt方法提到Element中,具体的Element只要继承就ok了,不需要再为每个具体的Element手工写Accept方法了。
而根据v.visit(this)中的this(即Element)的类型的不同,似乎可是实现某种程度上的动态绑定。

比如在Visitor类中,添加visit方法如下。
void visit(Element e)
{
if (e.GetType()==typeof(ConcreteElementA))
{
VisitConcreteElementA(e);
}
}
这样不是也不错吗?希望听听你的意见.
*/

我第一次看Visitor模式的时候也想到了用这种方法来简化代码
不过我的visit()是采用静态多态的同名函数方法,也是相当于后面吕震宇说得那个visit(Meat m),visit(Vegetable v),...
结果我发现在c#中调用的永远是Visit(Food)的
是否因为
Food::Accept(Visitor visitor)
{
visitor.Visit(this);//这里的this是一个Food类型,但实际上有可能指向其子类的实例
}
所以如果不用typeof(element)来区分,可能实现不了Food的多态

期望大家探讨一下,给出答案
 回复 引用   

#19楼 2004-12-29 22:14 kc
这是流行的错误理解。那个V,我觉得毋宁说是Visitor,还不如说是Virtual更好。这个PNP最重要的用途是允许在不改变类层次的前提下,向已经存在的类层次中增加新的虚函数。首先来看看Personnel及其派生类的Accept实现细节。

我觉得这个解释不错,哈哈
 回复 引用   

#20楼 2004-12-29 23:40 idior
kc 没想到你会耐着性子看完这么多.
你想的问题,我和震宇都讨论过了.
就目前而言,c#无法在静态多态的同名函数方法上实现我的那个想法.
确实比较郁闷,但是我们还是应该看到visitor模式有用的一面.
其实象它这么"丑"的模式在dp中还是比较少的 :P
 回复 引用   

#21楼 2004-12-30 12:02 kc
idior
其实我最想知道的是不是因为
如果Meat没有重写Accept(),那么在调用
Meat m.Accept()时候
实际上在m.Accept()里面传递的this是一个Food类型
这个原因

简单来说: 假设有如下类结构
class Visitor
{
public virtual void Visit(Food f)
{
Console.WriteLine("Food");
}
public virutal void Visit(Meat m)
{
Console.WriteLine("Meat");
}
}
class Food
{
public virtual void Accept(Visitor v)
{
v.Visit(this);
}
}
class Meat:Food
{
}

class Test
{
static void Main()
{
Meat m = new Meat();
m.Accept(new Visitor());
}
}
打印出Food
集中讨论一下Accept实现中的this类型问题
 回复 引用   

#22楼 2004-12-30 19:45 idior
我们尝试了,目前似乎找不出解决方法。
 回复 引用   

#23楼 2004-12-31 15:52 kc
讨论一下Accept实现中的this类型问题
讨论一下为什么会这样,不是找出解决方法,OK?
 回复 引用   

#24楼 2004-12-31 22:04 idior      
kc
如果你有兴趣可以加我的msn,最近我上线还蛮多的.
xuinggls [at] hotmail.com
 回复 引用 查看   

#25楼[楼主] 2004-12-31 22:13 吕震宇      
@kc

.net中有个默认规则,就是当可能出现不确定性因素(精度损失,未知类型变换等)时,必须由编程者自己承担转换带来的问题,也就是必须明确进行强制类型转换。将Food类型转换为Meat类型就是一个未知转换,有可能出现转换失败。所以Meat可以当Food来用,但Food不能直接当Meat来用。这就是所谓的里氏代换原则。

而在我和idior一番努力后,发现.net似乎不允许在运行时动态进行父类向子类转换。必须在编程时静态指定需要强制类型转换的类型。我想这是因为动态类型转换其实又会引来很多不确定因素。即便使用Convert.ChangeType,返回值依然是OBJECT类型。需要编程人员明确指明到底希望转换成什么类型,也许也是这个考虑。
 回复 引用 查看   

#26楼 2004-12-31 22:24 idior
@吕震宇
呵呵,你在啊?回的好快,解释的也很好,后来我也问过allen lee他也是这个意思.
 回复 引用   

#27楼 2005-08-17 17:12 idior      
check this.
http://idior.cnblogs.com/archive/2005/08/17/216980.html
 回复 引用 查看   

#28楼 2005-12-27 09:31 gshope[未注册用户]
"条件外置",利用.net中的反射再加上配制文件,可以轻松实现,唯一的是有一点小小的性能损失.
 回复 引用   

#29楼 2006-11-14 10:05 小峰      
学习了
 回复 引用 查看   

#30楼 2007-04-07 14:16 yunhuasheng      
verygood。
 回复 引用 查看   

#31楼 2010-04-28 20:58 simon.zhu      
引用吕震宇:@kc
<br>
<br>.net中有个默认规则,就是当可能出现不确定性因素(精度损失,未知类型变换等)时,必须由编程者自己承担转换带来的问题,也就是必须明确进行强制类型转换。将Food类型转换为Meat类型就是一个未知转换,有可能出现转换失败。所以Meat可以当Food来用,但Food不能直接当Meat来用。这就是所谓的里氏代换原则。
<br>
<br>而在我和idior一番努力后,发现.net似乎不允许在运行时动态进行父类向子类转换。必须在编程时静态指定需要强制类型转换的类型。我想这是因为动态类型转换其实又会引来很多不确定因素。即便使用Convert.ChangeType,返回值依然是OBJECT类型。需要编程人员明确指明到底希望转换成什么类型,也许也是这个考虑。

过了几年才看到,相见恨晚。
谈一下个人理解。
如果Meat没有重写Accept,那么运行时将调用Food的Accept,而Food的Accept的实现是v.Visit(Food)(this表示的对象类型),这里发生了一次子类向父类转换,而Visitor中匹配这个调用的就只有public virtual void Visit(Food f) 啦,其实这里根本没有向下转换等复杂考虑的必要。
如果Meat重写Accept,假设是同样实现的话,Meat::Accept的实现中的this就是Meat类型的,而Visitor中有public virtual void Visit(Meat m)与之精确匹配,于是得到期望的结果。
 回复 引用 查看