设计模式随笔-从“有病”说起(工厂模式前传) (转)

吕震宇 

十年前,我有一个很有钱的朋友,他家有三辆汽车(VOLVO(沃尔沃)、BENCH(奔驰)、MAZDA(马自达)),还雇了司机为他开车。不过,这个人上车后跟司机说的话取决于他坐的车:当他坐上VOLVO后,会跟司机说“开沃尔沃车!”,坐上BENCH后他说“开奔驰车!”,坐上MAZDA后他说“开马自达车!”。

大家猜这个人怎么着?.....有病!

其实我这个朋友叫“C”。

注:我对C一直很虔诚,上大学时,C语言是我最喜爱的语言。而且它的功能要远比我在后面例子中描述的功能强大的多(毕竟还有杀手锏“指针”呢),我并不想让这段故事给C留下什么不好的印象,只是举例而已(BASIC和VFP什么的连举例资格都没有呢  )。

把上面的故事用C写下来的化就是(我在Tubro C++ 1.0下调试通过):

/****** CARTEST.C *******/

#include
<stdio.h>

void PrintHelp();
void DriveVolvo();
void DriveBench();
void DriveMazda();

main(
int argc, char *argvs[])
{
  
if(argc < 2)
  
{
    PrintHelp();
    
return;
  }

  
  
// C 先生的开车法则
  if(strcmp(argvs[1],"V")==0)
    DriveVolvo();     
//开沃尔沃车!
  else if(strcmp(argvs[1], "B")==0)
    DriveBench();     
//开奔驰车!
  else if(strcmp(argvs[1], "M")==0)
    DriveMazda();     
//开马自达车!
  else
    PrintHelp();

  
return;
}


void PrintHelp()
{
  printf(
"Please input a correct car type.(V, B, M) ");
}


void DriveVolvo()
{
  printf(
"Driving Volvo  ");
}


void DriveBench()
{
  printf(
"Driving Bench  ");
}

void DriveMazda()
{
  printf(
"Driving Mazda  ");
}

程序编译成可执行文件后,在命令提示符下输入: CARTEST V 或 CARTEST B 或 CARTEST M。程序自动完成开不同车的功能。

现在让我们看看C先生病在哪里?其实,C先生之所以“有病”,就是在他发号的施令上,实际上只要说声“开车”就行了,他却不厌其烦的在里面加上车名(DriveVolvo(); DriveBench(); DriveMazda();)。

如果用用C#改写上面的程序的话,我们可以将程序写成:

using System;

public class Client
{
  
public static void Main(string[] argvs)
  
{
    Car c;

    
if(argvs.Length < 1)
    
{
      PrintHelp();
      
return;
    }


    
// 司机将车开来
    if(argvs[0== "V")
      c 
= new Volvo();
    
else if(argvs[0== "B")
      c 
= new Bench();
    
else if(argvs[0== "M")
      c 
= new Mazda();
    
else
    
{
      PrintHelp();
      
return;
    }


    
// C#先生发号施令“开车!”
    c.Drive();
  }


  
private static void PrintHelp()
  
{
    Console.WriteLine(
"Please input a correct car type.(V, B, M)");
    Console.WriteLine(
"For example: CarTest M");
  }

}


public abstract class Car
{
  
public abstract void Drive();
}


public class Volvo : Car
{
  
public override void Drive()
  
{
    Console.WriteLine(
"Driving Volvo ");
  }

}


public class Bench : Car
{
  
public override void Drive()
  
{
    Console.WriteLine(
"Driving Bench ");
  }

}


public class Mazda : Car
{
  
public override void Drive()
  
{
    Console.WriteLine(
"Driving Mazda ");
  }

}

现在问题就出来了,这两种做法哪种更好一些呢?是不是C#将本是很简单的问题搞复杂了呢?让我们分析一下:

  1. 从代码长度来看,显然C语言的代码长度要远少于C#的代码。两程序完成的是相同的功能。
  2. 从代码结构上看,C语言的结构也要比C#清晰,属于典型的结构化程序设计。
  3. 从“有病”的角度看,显然C语言程序“有病”,而C#程序更为容易接受。

C#程序通过对车的抽象,实现只需“开车”,就可以调用任何车的开车方法。这就是我们常说的“多态性”。将多态性浓缩到两行代码上,就是(以下简称方法一):

Car c = new Bench();
c.Drive();

不要小看这两行代码,隐藏在其中的深意还需要我们好好挖掘一下。

有人可能会问,不就是开车吗,开奔驰就是开奔驰,干吗要把奔驰赋值给车,然后调用车的开车,再通过多态性转而调用奔驰的开车。如此麻烦,不如直接就调用奔驰的开车(以下简称方法二):

Bench b = new Bench();
b.Drive();

到底谁好谁坏,我们可以从两个角度来看这个问题:

一、从迪米特法则的角度来看:

(关于迪米特法则,请参考:C#设计模式(3)

迪米特法则可以简单的表述成最小知识原则,也叫做“使民无知”。一个对象应当对其它对象知道的越少越好。

如果客户在进行代码调用时,使用了方法二的方法,那么当不开奔驰转开沃尔沃时,必须将客户端所有Bench的代码改为Volvo。如果两个车都可能开的化,那么客户端不得不跟两个对象都打交道。

如果采用方法一的方法,客户只需要知道“车”就行了,反正车都可以开。至于什么车,客户并不关心,关键的是能开就行。这不但很好的应用了迪米特法则,同时也应用了里氏代换原则(参见:C#设计模式(2)):“一个子类可以替换掉父类”。这允许在客户不知情的情况下就可以代换不同类型的车。

二、从开放封闭原则的角度来看:

(关于开放封闭原则,请参考:C#设计模式(2)

开放封闭原则要求对修改封闭,对扩展开放。在上面的两个例子种,方法二没有很好遵循开放封闭原则,当添加新类型汽车后,不得不修改代码以适应这种改变。而方法一具有很强的适应性,只需要给Car对象添加一个子类就可以了,客户由于只知道有“车”,所以加一种新车后,根本不需要改变客户端代码。因此也提高了系统的可维护性。

工厂模式中之所以引入“工厂”的概念,而抛弃直接使用 new 实例化对象,其中一个根本的原因也在于此。通过对“简单工厂模式”、“工厂方法模式”以及“抽象工厂模式”的学习我们会很强的感受到这点。

posted @ 2007-10-11 13:13  智慧园区-老朱  阅读(206)  评论(0编辑  收藏  举报