概述
在软件系统中,经常面临着“某个对象”的创建工作,由于需求的变化,这个对象的具体实现经常面临着剧烈的变化,但是它却拥有比较稳定的接口。
如何隔离出这个易变对象的变化,使得系统中“其它依赖该对象的对象”不随着需求的改变而改变,这就是本章要说的Factory Method模式了。
定义
“定义创建对象的接口,让子类决定实例化哪一个类。工厂方法使得一个类的实例化延迟到其子类。”
- 最初的定义出现于《设计模式》(Addison-Wesley,1994)。
结构图
抽象产品Product(可以是接口或者抽象类)定义了工厂方法创建的对象的接口和产品的共性;ConcreteProduct实现了Product。Creator定义了返回Product类型对象的工厂方法;ConcreteCreator实现了Creator,返回具体的ConcreteProduct的实例。
从结构图可以看出,在工厂方法模式中,核心的工厂类(Creator)不再负责所有产品的创建,而是将具体创建工作交给子类(ConcreteCreator)去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不接触哪一个产品类被实例化这种细节。与直接创建新的具体产品相比,工厂方法模式让客户程序可以要求由工厂方法创建的对象拥有一组共同的行为。这样往类层次结构中引入新的具体产品时,并不需要修改客户端代码,因为返回的任何具体对象的接口都跟客户端一直在用的从前的接口相同。从结构图也可以看到,工厂方法模式中的工厂类与产品类往往具有平行的等级结构,它们之间一一对应。
示例
根据工厂方法模式的定义和结构图,现在将简单工厂模式中的示例,用工厂方法模式来实现,先看使用工厂方法模式实现的结构图:
从图中可以看到,在简单工厂模式中,由工厂类(ChartFactory)根据参数负责创建具体的产品(线形图、饼状图);而在工厂方法模式中,工厂类(Factory)只定义了一个创建产品的抽象接口,创建具体产品的工作由具体的工厂(线形图工厂、饼状图工厂)来实现。如果需要增加其他类型的图形绘制,那么使用简单工厂模式实现的话,首先需要增加一个其他图形绘制的类,例如柱状图(BarChart),然后修改工厂类(ChartFactory),在里面加分支语句来判断;使用工厂方法模式实现的话,不仅需要增加图形绘制类,还需要增加具体工厂类(BarFactory)。看到这里,可能大家会感觉到,工厂方法模式不但没有减少难度,反而增加了一些类和复杂度。这样来看,是不是没有必要使用工厂方法模式?咱们再回顾一下开篇介绍的六大设计原则,有一个原则是“开放-关闭原则”,简单工厂模式不仅对扩展开放,而且对修改也开放,违反了“开放-关闭原则”。工厂方法模式是简单工厂模式的进一步抽象,它保持了简单工厂模式的优点(去除了客户端与具体产品的依赖),而且克服了它的缺点(违反开放-关闭原则”)。它的缺点是每增加一个产品,就需要加一个产品工厂的类,增加了额外的开发工作量。理论分析就到这里,接下来看看代码:
IChart.h:
1 @protocol IChart <NSObject> 2 3 - (void)drawing;
LineChart.m(部分代码):
1 - (void)drawing 2 3 { 4 5 NSLog(@"LineChart drawing."); 6 7 }
PieChart.m(部分代码):
1 - (void)drawing 2 3 { 4 5 NSLog(@"PieChart drawing."); 6 7 }
Factory.h:
1 @protocol Factory <NSObject> 2 3 - (id<IChart>)createChart;
LineFactory.m(部分代码):
1 - (id<IChart>)createChart 2 { 3 4 return [[[LineChartalloc] init] autorelease]; 5 6 }
PieFactory. .m(部分代码):
1 - (id<IChart>)createChart 2 { 3 4 return [[[PieChartalloc] init] autorelease]; 5 6 }
客户端调用代码:
1 id<Factory> factory = [[[LineFactoryalloc] init] autorelease]; 2 3 // id<Factory> factory = [[[PieFactory alloc] init] autorelease]; 4 5 id<IChart> chart = [factory createChart]; 6 7 [chart drawing];
从调用代码可以看出,工厂方法模式从代码中消除了对应用程序特有类的耦合。代码秩序处理Product抽象接口(这里是id<IChart>),这样同一代码就可以复用。
思考
从上面的客户端调用代码看到,如果有多处调用绘图的地方,我们需要每处都进行修改,这样的话,实际上也没有达到我们的效果:应对变化,尽可能少的修改代码。那么该怎样处理这种情况呢?
下面一种方式可以做到:
1 // id<Factory> factory = [[[LineFactory alloc] init] autorelease]; 2 // id<Factory> factory = [[[PieFactory alloc] init] autorelease]; 3 id<Factory> factory = [[[NSClassFromString(@"PieFactory") alloc] init] autorelease]; 4 5 id<IChart> chart = [factory createChart]; 6 [chart drawing]; 7 8 [NSNumber numberWithBool:YES];
这样的话,我们可以将@"PieFactory"放到配置文件中,当我们需要绘制线形图的时候,只需要修改配置文件即可,客户端的所有代码都不需要改变。
何时使用工厂方法模式
- 编译时无法准确预期要创建的对象的类;
- 类想让其子类决定在运行时创建什么;
- 类有若干辅助类为其子类,而你想将返回哪个子类这一信息局部化。