您的位置:首页 > 其它

浅谈设计模式(二)

2010-04-08 20:22 288 查看

二 创建-最常用之模式应用-工厂模式

不知道怎么起了个这样混乱的标题。先解释一下标题的意思,这部分要阐述的内容是和创建相关的模式。和创建有关的模式可能是最常被用到的,而其中首当其冲的就是工厂模式。故此起了这么一个标题。下面开始说具体的内容。

工厂方法

初接触设计模式的读者可能会奇怪为什么出来个“工厂方法”,不是说模式么?没错,就是工厂方法,工厂模式在工厂方法这部分内容后面。
工厂方法是把创建成员之职责交予拥有该成员之类本身。即是说由该类负责成员之创建过程。此类方法并未清晰体现模式解耦之思想,只是沾了“工厂”的光,所以准确说来,工厂方法并未以模式记述。还记得前述设计模式需把握思想的部分举的关于如何出行的示例吧(从A到B采取何种方式的例子,见第一部分)? 注意看下面摘取的这部分伪码:

Class GoingToB
{
交通工具 vehicle; // 这里使用接口

Going()
{
vehicle = ChooseVehicle();
vehicle.go();
}

交通工具 ChooseVehicle()
{
if ( 天晴 )
{
return new bike;
}

if ( 下雨 )
{
return new taxi;
}
……
}
}

这里的ChooseVehicle()方法即是工厂方法的一个体现,为了体现其创建功能之价值,将方法改为CreateVehicle(),上述示例修改为:

Class GoingToB
{
交通工具 vehicle; // 这里使用接口

Going()
{
vehicle = CreateVehicle();
vehicle.go();
}

交通工具 CreateVehicle() // 通常这里应该是私有方法
{
……
}
}

如果该类成员本身并不包含多种可变派生类型,并明确指出预见之结果并未体现将来可能发生变化,则使用工厂方法将创建过程封装在私有方法中,就是一个中规中举的做法了。

工厂模式

该模式的目的在于使成员的创建过程与成员的引用过程解耦。在可预见成员的派生类型可能发生变化,或以当前需求来观察并不能展现类型不变的稳固特征的情况下,都应该只操纵接口来进行成员方法之调用,而不应该持有具体类型的实现。

以比格的比萨饼来做实例说明,现根据用户喜好,提供不同口味的比萨饼。比如,目前有至尊薄批、田园风光、香辣精选等口味,我们不知道顾客会点哪种口味,并且为了顺应时尚潮流,经常会添加新口味的比萨。所以我们肯定不会希望把制作出一张比萨的过程和贩售过程搅在一起,所以比格店的运作过程应像下面这样 (我们假设每个用户只点一份比萨饼):

class BigPizza
{
……
Order( 顾客 customer ) // 顾客点餐流程
{
比萨饼 pizza = ProcessOrderList( customer.getOrderList() ); // 处理订单
customer.SetPizza( pizza ) // 给顾客比萨
}
比萨饼 ProcessOderList( 订单 orderList )
{
// cook 比萨饼并返回之
return 厨师.CookByOrderList( orderList );
}
}

就这么简单?对,就这么简单。当然你还需要不同口味的比萨,像下面这样

//接口或抽象类
Interface 比萨接口
{
}

Class 至尊薄批 extends 比萨接口
{
}

Class 田园风光 extends 比萨接口
{
}

Class 香辣精选 extends 比萨接口
{
}

好了,不同口味的比萨有了,那么顾客点比萨的时候应该做什么呢?

Class 顾客
{
OrderList myOrderList;
比萨接口 myPizza;

SetPizza( 比萨接口 pizza)
{
this.myPizza = pizza;
}

OrderPizza()
{
// 注意,很多应用中,包含该成员的class并不需要知道其成员具体类型。
// 这部分工作应该由工厂代劳,不过这里如果顾客不知道自己想点什么比萨,
// 就太说不过去了,所以让用户有机会填写订单来决定要什么类型的比萨。
myOrderList = CreateOrderList(); // 读配置文件,bla-bla-bla,who knows
BigPizza.Order(this); // 如果不是静态方法,则这里需要一个BigPizza成员
}
}

这里的BigPizza就可称为工厂类,现在你可以把BigPizza替换成Factory,并把Order()方法替换成CreateXXX(),更普通化一点的应用用伪码描述像下面这样:

Class A
{
// 注意接口的使用
B b_obj;
A( B b ) // 不一定在构造时进行实例化
{
this.b_obj = b;
}
}

// 注意接口的使用
Interface B
{
}

Class Factory
{
B CreateB();
}

那么在实例化A的对象的时候,你就可以这样

new A( Factory.CreateB() );

始终贯彻的思想是,不要关心成员的具体类型。这部分工作已经交给工厂来做了。下面做点小小的改进:

Class Factory
{
// 这里应该是私有成员,并且根据不用语言,应使其具有初始化后不可修改的性质
static B b = null;
Factory( String configFilePath )
{
// 这里根据配置文件使b有具体值
}
B getB()
{
return b;
}
}

于是在需要一个A类实例时这样做

new A( Factory.CreateB(”配置文件路径”) );

这里的b只有一份,用于某共享对象资源,这个是单例模式的一个体现,不过这里并不典型,也不完整,只是顺便提及,具体的讲解会在详细谈该模式时才做。
好了,最后让我们看一看类图应该是什么样子的:



工厂模式关键特征:
意图: 需要为特定的客户(或情况)提供特定的对象。
问题: 需要针对不同情况实例化特定对象。
解决方案: 将对象的实例化规则和过程从使用这些对象的客户对象中抽取出来。
参与者与协作者:Factory为如何创建对象提供接口。
效果: 这个模式将“使用哪个对象”的规则于“如何使用该对象”的逻辑分离开来。
实现: 定义一个特定的类专事创建工作,为抽象类型提供具体类型的实例。

抽象工厂

接下来要说的是抽象工厂模式。见名知意,抽象工厂即意味着工厂类本身亦被抽象。为何会这样,何时会用到工厂模式?让我们从比格比萨店继续说起。

比格比萨店采用了新的营销策略,现在比萨将以套餐形式出售。于是目前比萨饼的接口被放入了套餐类中,并产生出多种套餐。

Class 套餐
{
比萨饼 pizza;
饮料 drink;
}

Now what? 顾客类变成下面这样了

Class 顾客
{
OrderList myOrderList;
比萨 myPizza;
饮料 myDrink;
SetPizza( 比萨 pizza )
{
this.myPizza = pizza;
}
SetDrink( 饮料 drink )
{
this.myDrink = drink;
}
OrderCombo()
{
// 注意,很多应用中,包含该成员的class并不需要知道其成员具体类型。
// 这部分工作应该由工厂代劳,不过这里如果顾客不知道自己想点什么比萨,
// 就太说不过去了,所以让用户有机会填写订单来决定要什么类型的比萨。
myOrderList = CreateOrderList(); // 读配置文件,bla-bla-bla,who knows
BigPizza.Order(this); // 如果不是静态方法,则这里需要一个BigPizza成员
}
}

让我们把比格比萨类改动一下,现在叫比格套餐工厂

Class BigPizza
{
……
Order( 顾客 customer ) // 顾客点餐流程
{
比萨饼 pizza = CreatePizza( customer.getOrderList().getPizzaName() ); // 这里修改了
饮料 drink = CreateDrink( customer.getOrderList().getDrinkName() ); // 这里增加了饮料
customer.SetPizza( pizza ) // 给顾客比萨
customer.SetDrink( drink ) // 给顾客饮料
}
}

嗯?我们看到什么了?两个工厂方法?等等,由于套餐的组合将是固定的,我们接下来把这部分创建工作交给一个新的类来做,首先我们将这里抽象出一个接口

Interface ComboFactory // 套餐工厂接口
{
套餐 createCombo();
}

Class 至尊套餐工厂
{
套餐 createCombo()
{
套餐 combo = new 套餐();
combo.SetPizza( new 至尊薄批() );
combo.SetDrink( new 冰茶() );
return combo;
}
}

Class 田园风光工厂
{
套餐 createCombo()
{
套餐 combo = new 套餐();
combo.SetPizza( new 田园风光() );
combo.SetDrink( new 果汁() );
return combo;
}
}
……
// 这个类当然也跟着做出改变
Class BigPizza
{
ComboFactory bigFactory;
……

Order( 顾客 customer ) // 顾客点餐流程
{
// 在这里返回合适的套餐工厂
bigFactory = InitComboFactory( customer.getOrderList().getCombName() );
套餐 combo = bigFactory.createCombo();
customer.SetPizza( combo.getPizza() ) // 给顾客比萨
customer.SetDrink( combo.getDrink() ) // 给顾客饮料
}
}

事实上,这个例子有点杀鸡用牛刀的意思。主要是因为创建过程很简单,所以这里采用这个模式意义并无想象中大。但是如果创建过程较复杂,效果就不一样了。
下面看看最常见意义上的抽象工厂模式类图



然后是Abstract Factory模式关键特征:(摘抄自《设计模式解析》)

意图: 需要为特定的用户(或情况)提供对象组。
问题: 需要实例化一组相关的对象。
解决方案: 协调对象的创建。提供一种方式,将如何执行对象实例化的规则从使用这些对象的客户对象中提取出来。
参与者与协作者:AbstratFactory为如何创建对象组的每个成员定义接口。一般每个组都由独立的ConcreteFactory进行创建。
效果: 这个模式将“使用哪些对象”的规则与“如何使用这些对象”的逻辑分离开来。
实现: 定义一个抽象类来指定创建哪些对象。然后为每个组实现一个具体类。可以用表或文件完成同样的任务。
创建者模式

builder模式也是用来创建的,这里直接用它和抽象工厂模式的差异来描述好了。抽象工厂针对的情况是,客户需要一系列相关的对象,就是说,从客 户类角度来看,抽象工厂最终提供给它的是一些“产品”对象。而builder模式的不同之处在于,最终客户得到的是一个“产品”对象,但这个产品对象包含 了许多成员,并由builder负责这些成员的创建和组装这一复杂过程,而最终返回的只是一个表层对象。

还是拿比萨来举例,消费者点了一份香辣精选套餐,我们刚刚用抽象工厂完成了这件事情,先回忆一下抽象工厂怎么做的,看看类图。好,接下来要说的是,抽象工 厂在一个问题上卡壳了,就是当它准备create张香辣精选的比萨饼的时候。它不知道应该如何制作面饼;如何烘焙;在哪里加奶油和番茄酱,各加多少;要不 要芝麻?放不放胡萝卜丁?这显然不是抽象工厂能处理的复杂情况。于是抽象工厂喊来了builder。



Builder:为创建Product对象的各个部件指定抽象接口。
ConcreteBuilder: 实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,并提供一个检索产品的接口。
Director:构造一个使用Builer接口的对象。
Product: 表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程,包含定义组成部件的类,以及将这些部件装配成最终产品的接口。

类大概像这样

Interface Builder
{
void buildPartA();
void buildPartB();
void buildPartC();
//返回最后组装成品
//组装过程不在这里进行,而是在Director类中进行,可以在Director中任意调配build哪些part
//从而实现了过程和部件的解耦
Product getResult();
}

Class ConcreteBuilder implements Builder
{
Part partA, partB;

public void buildPartA()
{
//这里是具体如何构建partA的代码
};

public void buildPartB()
{
//这里是具体如何构建partB的代码
};

public void buildPartC()
{
//这里是具体如何构建partB的代码
};

public Product getResult()
{
//返回最后组装成品结果
};
}

public class Director
{
private Builder builder;

public Director( Builder builder )
{
this.builder = builder;
}

// 创建工作在这里完成的将部件partA partB组成复杂对象
public void construct()
{
builder.buildPartA();
builder.buildPartB();
}
}

然后,builder模式像这样工作

ConcreteBuilder builder = new ConcreteBuilder();
Director director = new Director( builder );
director.construct();
Product product = builder.getResult();

To be continued…
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: