首页 理论教育 深度分析Factory模式,软件开发

深度分析Factory模式,软件开发

时间:2023-11-21 理论教育 版权反馈
【摘要】:Factory是一个用得比较广泛的设计模式。不使用Factory模式的UML图如图8-7所示。从上面的分析可见,Factory模式一定会增加复杂度。从简单性的角度看,这个例子很有误用Factory模式的嫌疑,我们来一起分析一下。为了避免对迷宫类型进行像下面进行硬编码,GoF导入了Abstract Factory模式。步骤3:在你期望的地方,初始化Factory表。在这个过程中,同原来的Factory模式比较,这一方法下只增加了一个层次,并增加了一个表格结构。

深度分析Factory模式,软件开发

接下来我们看一个现实中的例子,来进一步考察这种复杂度的度量究竟有什么意义。

Factory是一个用得比较广泛的设计模式。这个模式的主要目的是把对象的创建工作隐藏在抽象的接口之后。从使用的角度看,不使用Factory模式的程序大概是这样:

Step1:Shape circle=new Circle()//Shape是Circle的虚基类

Step2:把Circle只作为Shape来使用,而不使用Circle所特有的方法。

而使用了Factory模式的程序大概是这样:

Step1:Shape circle=shapeFactory.makeCircle();//在makeCircle()中调用new Circle();

Step2:把Circle只作为Shape来使用,而不使用Circle所特有的方法。

使用Factory模式后,Circle类的具体实现将和使用Circle类的程序完全没关系,使用Circle类的地方也不知道自己使用的是Circle,它只知道这是一个Shape。

《敏捷软件开发:原则,模式与实践》中给出了使用和不使用Factory模式的UML图,我们来一起看一下。

不使用Factory模式的UML图如图8-7所示。

978-7-111-42626-4-Chapter08-8.jpg

图8-7 不使用Factory模式时的UML图

使用Factory模式的UML图如图8-8所示。

978-7-111-42626-4-Chapter08-9.jpg

图8-8 使用Factory模式时的UML图

我们来考察一下,这两种方法下复杂度的变化。

在第一种方法下:Class in Some App与3个概念交互,两层结构。

在第二种方法下,Class in Some App2与两个概念直接交互,但变成了四层结构,概念个数则由4个增加为6个。

从上面的分析可见,Factory模式一定会增加复杂度。使用这一模式的时候,事实上等于牺牲一定简单性来换取灵活性Robert C·Martin在论及Factory模式的时候也提到了这点。而我们在之前的分析中曾经提到,需求定义价值根源,而设计和编码的目的则是用最简单的手段达成目的。所以这一模式的应用原则远不是“只要有可能就用”,而应该反过来,是“只要有可能就不用”。如果确实要用,那么要对应到需求上的可见收益。这也就意味着,我们不单要关注这个设计模式可以干什么,也要关注它不可以干什么。

下面我们来看一下《设计模式》一书中关于迷宫的例子。从简单性的角度看,这个例子很有误用Factory模式的嫌疑,我们来一起分析一下。

假设说我们要创建一个迷宫,迷宫由一系列房间组成。一个房间知道它的邻居;它的邻居要么是一个房间,要么是一堵墙,或者是通往另一个房间的一扇门。这个结构并不复杂,对应的UML图如图8-9所示。

978-7-111-42626-4-Chapter08-10.jpg

图8-9 创见迷宫时的UML图

MapSite是Room、Wall、Door共同的基类,其中只有一个方法Enter()用来判定一个人会不会碰壁。为了避免对迷宫类型进行像下面进行硬编码,GoF导入了Abstract Factory模式。

978-7-111-42626-4-Chapter08-11.jpg(www.xing528.com)

导入模式后,迷宫中各种元素的类型和迷宫的结构被分离了,分离后的程序大概是这个样子:

978-7-111-42626-4-Chapter08-12.jpg

978-7-111-42626-4-Chapter08-13.jpg

下面是《设计模式》一书中提供的为创建施了魔法的迷宫而派生的Factory:

978-7-111-42626-4-Chapter08-14.jpg

这样只要用EnchantedMazeFactory来取代MazeFactory就会变更创建出的迷宫类型,而不需要调整CreateMaze()中的逻辑。

接下来我们可以考察各种元素类型进行膨胀的情形,假设说有50种不同类型的墙,那么要派生出50个子类来分别创建不同的墙—这里是关键点:类的个数会因为墙、门或者房间类型的膨胀而急速膨胀。

极端的OO爱好者会想当然地认为这是理所应当的代价,但概念分散度过大(事实上等于是不正交),必然导致理解以及维护上出现问题。同时真的就没有更简单的办法了么?真的需要为了“创建各种的对象”这样一个并不复杂的东西创建50几个类么?

下面的尝试未必全对,但至少其复杂度在解决迷宫类问题时会比较低。

首先我们需要借用一下《代码大全》中提到的表驱动法。表驱动法的原理并不复杂:建立一张表来维护某种映射关系(比如,月份和天数的映射),在程序中直接查表来锁定待找的信息(比如,直接通过月份找到该月份的天数)。表驱动的好处是可以取代复杂的if...else...结构,以及复杂的由虚函数驱动的继承层次结构。导入表驱动的思想后,我们发现创建这事其实可以很简单,它需要的根本要素只有两个:一个名字与一个函数。

我们可以先创建一张表,这张表里维护着“名字”和“创建函数”的映射。而创建的时候,所需要的参数则是一组名字,通过这组名字就可以找到具体的创建函数。这样上述创建过程可以简化为:

步骤1:声明一组函数来分别创建各个对象。

978-7-111-42626-4-Chapter08-15.jpg

步骤2:声明一个表格来存放迷宫类型名和具体创建函数的映射。

978-7-111-42626-4-Chapter08-16.jpg

步骤3:在你期望的地方,初始化Factory表。

978-7-111-42626-4-Chapter08-17.jpg

978-7-111-42626-4-Chapter08-18.jpg

步骤4:依据名字查表,进行具体迷宫的创建。

978-7-111-42626-4-Chapter08-19.jpg

在这个过程中,同原来的Factory模式比较,这一方法下只增加了一个层次,并增加了一个表格结构(其中有4个函数指针)。同时可以获得更大的灵活性,迷宫类型的组合更为随意,甚至可以用配置文件在运行时进行这种组合。想象一下如果施了魔法的迷宫与墙中有炸弹的迷宫进行组合时,原来需要怎么处理,现在可以怎么处理,对这种收益就会有更深的感触。唯一的不足就是这不是纯粹的OO,这会让狂热者愤恨,但反过来想就会发现,我们要使用OO的目的是为了获得更好的代码结构,如果说确实可以有方法更简单实用,那为什么要用纯粹的OO?

这里要强调并不是Factory模式没有价值,而是说当我们了解一种技术时知道技术本身可以干什么不过是第一步,我们也要关注这种技术不可以干什么。

因为这世界总是有些人是疯狂的。比如说,就有人曾经提倡凡是创建对象的时候就要用Factory方法,这等价于毫无收益的导入复杂度,这是疯狂的,也是错误的。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈