您的位置:首页 > 其它

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

2008-10-21 20:44 253 查看
十年前,我有一个很有钱的朋友,他家有三辆汽车(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#将本是很简单的问题搞复杂了呢?让我们分析一下:

从代码长度来看,显然C语言的代码长度要远少于C#的代码。两程序完成的是相同的功能。
从代码结构上看,C语言的结构也要比C#清晰,属于典型的结构化程序设计。
从“有病”的角度看,显然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 实例化对象,其中一个根本的原因也在于此。通过对“简单工厂模式”、“工厂方法模式”以及“抽象工厂模式”的学习我们会很强的感受到这点。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: