现在的位置: 首页 > 综合 > 正文

设计模式随笔-让众口不再难调

2013年12月18日 ⁄ 综合 ⁄ 共 4240字 ⁄ 字号 评论关闭

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

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

在程序设计中解决这种众口难调的问题用的就是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)

抱歉!评论已关闭.