简单工厂模式与工厂方法模式

1:简单工厂模式

UML图:
简单工厂模式UML图

现实生活中例子:
每次参加不同的聚会或者与不同的人见面,可能穿的衣服是不一样的,比如,你今天上午要与你的一个新客户见面,你可能会对你的老婆说:老婆,给拿件商务装(参数),我要去见我的一个客户,你老婆(工厂类)接到你的请求(商务装参数)后,从衣柜中取出一件商务装(具体产品),交给你。整个过程就完成了。


分析:
你可能根据不同的条件,要的衣服是不一样的,但要的衣服都是已经在你的衣柜中存在的。并且,每件上衣它们都属于同一种抽象,即它们可以从一个抽象类或接口中继承,这此衣服各自都有一定特征,这些都是条件。然后你要的时候,就可以向你老婆说一种特征,她就会根据这个特征为你服务了。这就是典型的简单工厂模式的应用。

抽象产品类代码


1 /**/ /// <summary>
2 /// 抽象产品类:上衣
3 /// </summary>

4 public interface ICoat
5 {
6 void GetYourCoat();
7 }

非常简单,是吧?这里我只是举一个仅仅能说明问题的例子,在具体的项目中,可能是很复杂的哦。。

具体产品类代码

1 namespace SimpleFactory
2 {
3 /**/ /// <summary>
4 /// 具体产品类:商务上衣
5 /// </summary>

6 public class BusinessCoat:ICoat
7 {
8 public void GetYourCoat()
9 {
10 Console.WriteLine( " 商务上衣 " );
11 }

12 }

13
14 /**/ /// <summary>
15 /// 具体产品类:时尚上衣
16 /// </summary>

17 public class FashionCoat : ICoat
18 {
19 /**/ /// <summary>
20 /// 实现ICoat中定义的方法
21 /// </summary>
22 /// <returns></returns>

23 public void GetYourCoat()
24 {
25 Console.WriteLine( " 时尚上衣 " );
26 }

27 }

28 }


简单工厂模式中最核心的部分:工厂类

1 namespace SimpleFactory
2 {
3 /**/ /// <summary>
4 /// 简单工厂模式中的核心部分:工厂类
5 /// </summary>

6 public class Factory
7 {
8 public ICoat CreateCoat( string styleName)
9 {
10 switch (styleName.Trim().ToLower())
11 {
12 case " business " :
13 return new BusinessCoat();
14 case " fashion " :
15 return new FashionCoat();
16 default :
17 throw new Exception( " 还没有你要的那种衣服 " );
18 }

19 }

20 }

21 }


再看一下客户在调用的时候的代码

1 /**/ /// <summary>
2 /// 客户类
3 /// </summary>

4 class Client
5 {
6 static void Main( string [] args)
7 {
8 ICoat food;
9 try
10 {
11 Factory factory = new Factory();
12
13 Console.Write( " 我要的是时尚上衣\t " );
14 food = factory.CreateCoat( " fashion " );
15 food.GetYourCoat();
16
17 }

18 catch (Exception ex)
19 {
20 Console.WriteLine(ex.Message);
21 }

22 }

23 }
1.1.2 简单工厂模式的引进(一般模式)
比如有一个农场,生产各种水果,有苹果(Apple)、草莓(Strawberry)、葡萄(Grape);农场的园丁(FruitGardener)要根据客户的需求,提供相应的水果。下面看看是如何用简单工厂模式实现这个过程的,如下图:
点击在新窗口查看全图
园丁是工厂。

例化延迟到子类。

现在看这句话可能有些不明白,我们一会再来分析一下。先来看看工厂模式的大体结构。如下图:

我们还是用实例化汽车的例子来解释。对于客户端程序( ClientApp )如果想要一个汽车的对象,需要调用生产这个汽车的 Factory 的对象。当然,这个类继承自一个 AbstractFactory 基类。而这个 Factory 类就是《设计模式》中提到的“子类”,它来决定实例化那个类。

下面我们来具体实现一下代码,首先,我们需要两个基类,一个是 Car 的,一个是 Factory 的。 Factory 类型的作用是构建 Car 的对象。代码如下:

public abstract class AbstractCar

{

public abstract string Run();

public abstract string Stop();

public abstract string Accelerate();

public abstract string Decelerate();

}

public abstract class AbstractFactory

{

public abstract AbstractCar CreateCar();

}

下面,我们来做一个BMW的实现代码:

public class BMWCar:AbstractCar

{

public override string Run()

{

return "BMW Run";

}

public override string Stop()

{

return "BMW Stop";

}

public override string Accelerate()

{

return "BMW Accelerate";

}

public override string Decelerate()

{

return "BMW Decalerate";

}

}

public class BMWFactory:AbstractFactory

{

public override AbstractCar CreateCar()

{

return new BMWCar();

}

}

这样我们就可以在客户端程序得到一个BMW的实例,并使用它的方法:

class Class1

{

/// <summary>

/// 应用程序的主入口点。

/// </summary>

[STAThread]

static void Main(string[] args)

{

AbstractCar car = CreateCarFunc(new BMWFactory ());

Console.Write(car.Run() + "\n");

Console.Write(car.Stop() + "\n");

Console.Write(car.Accelerate() + "\n");

Console.Write(car.Decelerate() + "\n");

Console.Read();

}

public static AbstractCar CreateCarFunc(AbstractFactory factory)

{

return factory.CreateCar();

}

}

在客户端程序中,我们AbstractFactory的对象来得到Car的对象

结果如下:

BMW Run

BMW Stop

BMW Accelerate

BMW Decalerate

如果我们需求变了,现在要BORA的对象,那末,我们首先要对程序作一下扩展,先加入BOAR的Car类和Factory类,代码如下:

public class BORACar:AbstractCar

{

public override string Run()

{

return "BORA Run";

}

public override string Stop()

{

return "BORA Stop";

}

public override string Accelerate()

{

return "BORA Accelerate";

}

public override string Decelerate()

{

return "BORA Decelerate";

}

}

public class BORAFactory:AbstractFactory

{

public override AbstractCar CreateCar()

{

return new BORACar();

}

}

在客户端程序中,我们只要稍作修改,将BMWFactory的实例化变为BORAFactory的实例化就可以,代码如下:

class Class1

{

/// <summary>

/// 应用程序的主入口点。

/// </summary>

[STAThread]

static void Main(string[] args)

{

AbstractCar car = CreateCarFunc(new BORAFactory());

Console.Write(car.Run() + "\n");

Console.Write(car.Stop() + "\n");

Console.Write(car.Accelerate() + "\n");

Console.Write(car.Decelerate() + "\n");

Console.Read();

}

public static AbstractCar CreateCarFunc(AbstractFactory factory)

{

return factory.CreateCar();

}

}

得到的结果是:

BORA Run

BORA Stop

BORA Accelerate

BORA Decelerate

Factory Method的几个要点:

1、Factory Method模式主要用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系会导致软件的脆弱。

2、Factory Method模式通过面向对象的手法,将所要创建的对象工作延迟到子类,从而实现一种扩展的策略,较好的解决了这种紧耦合关系。



1.简单工厂模式:

PersionFactory.java

package com.test.designModel.factory;

public class PersionFactory {
/**
* 通过不同的参数,从工厂里获取不同的实现子类。
*
* @param classType 子类获取参数
* @return PersionIn.class
*/
public PersionIn getPersion(String classType){
if("ch".equals(classType)){
return new Chinese();
}else{
return new American();
}
}
}

PersionIn.java

public interface PersionIn {
public String sayHello(String name);
public String sayGoodbye(String name);
}

Chinese.java

public class Chinese implements PersionIn {

public String sayHello(String name) {
return "您好,"+name;
}

public String sayGoodbye(String name) {
return "再见,"+name;
}

American .java

public class American implements PersionIn {

public String sayHello(String name) {
return "Hello,"+name;
}

public String sayGoodbye(String name) {
return "GoodBye,"+name;
}

测试方法:

public static void main(String[] args) {
PersionFactory factory = new PersionFactory();
PersionIn persion = factory.getPersion("ch");
String result = persion.sayHello("张三");
System.out.println("result:"+result);
result = persion.sayGoodbye("李四");
System.out.println("result:"+result);


persion = factory.getPersion("am");
String result1 = persion.sayHello("LiLee");
System.out.println("result:"+result1);
result1 = persion.sayGoodbye("HanMeiMei");
System.out.println("result:"+result1);

}

执行结果:

result:您好,张三
result:再见,李四
result:Hello,LiLee
result:GoodBye,HanMeiMei

总结:简单工厂,就是能根据不同的参数,从工厂里获取不同的实现子类实例。

简单工厂模式与工厂方法模式
在OO设计领域,我们知道前人总结了不少的经验,许多的经验在现代软件工程过程中已经被认为是原则来遵守。下面笔者摘抄几项下文涉及到的OO原则的定义。

OCP(开闭原则,Open-Closed Principle):一个软件的实体应当对扩展开放,对修改关闭。我的理解是,对于一个已有的软件,如果需要扩展,应当在不需修改已有代码的基础上进行。

DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程,不要针对实现编程。我的理解是,对于不同层次的编程,高层次暴露给低层次的应当只是接口,而不是它的具体类。

LoD (迪米特法则,Law of Demeter):只与你直接的朋友通信,而避免和陌生人通信。众所周知类(或模块)之间的通信越少,耦合度就越低,从而更有利于我们对软件的宏观管理。老子论“圣人之治”有相同的思想,《老子》云:“是以圣人之治,虚其心,实其腹,弱其志,常使民无知无欲。”,又云:“小国寡民,邻国相望,鸡犬之声相闻,民至老死,不相往来。”。佩服我们的老祖宗,N千年前就想到了西方N千年后才想到的东西,同时也佩服《java与模式》的作者阎宏,可以用中国传统哲学思想这么生动的说明这一软件设计原则。

简单工厂模式及实例
简单工厂模式又叫静态工厂模式,顾名思义,它是用来实例化目标类的静态类。下面我主要通过一个简单的实例说明简单工厂及其优点。

比如有个国家的运动员协会,他们是负责登记与注册职业运动员的(就好像我们国家的体育总局,呵呵,无论足球篮球还是乒乓球的运动员都必须在这里注册才能拿到我们国家职业运动员牌照)。一家体育俱乐部(比如篮球的广东宏远,足球的深圳健力宝)想获得球员为自己俱乐部效力,就必须通过这个运动员协会。

根据DIP我们可以设计一个“运动员”接口,“足球运动员”和“篮球运动员”(还有其他运动员)都实现“运动员”这个接口。而“运动员协会”就是一个简单工厂类,它负责实例化“运动员”。我们这里的“俱乐部”就是一个客户端(Client),不同的“俱乐部”就是不同的客户端。具体如下图表示:


对于不同的俱乐部对象(无论是八一还是深圳健力宝),他们都是面向“运动员”接口编程,而不用管是“足球运动员”还是“篮球运动员”,也就是说实现了“运动员”接口的具体类“足球运动员”无需暴露给客户端。这也满足了DIP。但具体的俱乐部(比如足球的深圳健力宝)如何确保自己获取的是自己想要的运动员(健力宝俱乐部需要的当然是足球运动员)呢?这就需要“运动员协会”这一工厂类了。俱乐部通过调用“运动员协会”的具体方法,返回不同的实例。这同时也满足了 LoD,也就是“深圳健力宝足球俱乐部”对象不直接与“足球运动员:李毅”对象通信,而是通过他们共同的“朋友”——“国家体育总局”通信。

下面给出各个类的程序,会有助于读者更好的了解笔者之前的介绍。


Code: [Copy to clipboard]
运动员.java
public interface 运动员 {
public void 跑();
public void 跳();
}

足球运动员.java
public class 足球运动员 implements 运动员 {

public void 跑(){
//跑啊跑
}

public void 跳(){
//跳啊跳
}
}

篮球运动员.java
public class 篮球运动员 implements 运动员 {

public void 跑(){
//do nothing
}

public void 跳(){
//do nothing
}
}

体育协会.java
public class 体育协会 {

public static 运动员 注册足球运动员(){
return new 足球运动员();
}

public static 运动员 注册篮球运动员(){
return new 篮球运动员();
}

}

俱乐部.java
public class 俱乐部 {
private 运动员 守门员;
private 运动员 后卫;
private 运动员 前锋;

public void test() {
this.前锋 = 体育协会.注册足球运动员();
this.后卫 = 体育协会.注册足球运动员();
this.守门员 = 体育协会.注册足球运动员();

守门员.跑();
后卫.跳();
}
}

以上就是简单工厂模式的一个简单实例,读者应该想象不用接口不用工厂而把具体类暴露给客户端的那种混乱情形吧(就好像没了体育总局,各个俱乐部在市场上自己胡乱的寻找仔细需要的运动员),简单工厂就解决了这种混乱。

我们用OCP看看简单工厂,会发现如果要对系统进行扩展的话治需要增加实现产品接口的产品类(上例表现为“足球运动员”,“篮球运动员”类,比如要增加个 “乒乓球运动员”类),而无需对原有的产品类进行修改。这咋一看好像满足OCP,但是实际上还是需要修改代码的——对,就是修改工厂类。上例中如果增加 “乒乓球运动员”产品类,就必须相应的修改“体育协会”工厂类,增加个“注册乒乓球运动员”方法。所以可以看出,简单工厂模式是不满足OCP的。

工厂方法模式及其实例
谈 了简单工厂模式,下面继续谈谈工厂方法模式。前一节的最末点明了简单工厂模式最大的缺点——不完全满足OCP。 为了解决这一缺点,设计师们提出了工厂方法模式。工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目或者一个独立模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。下面我们通过修改上一节的实例来介绍工厂方法模式。

我们在不改变产品类(“足球运动员”类和“篮球运动员”类)的情况下,修改下工厂类的结构,如下图所示:


相关代码如下:


Code: [Copy to clipboard]
运动员.java
public interface 运动员 {
public void 跑();
public void 跳();
}

足球运动员.java
public class 足球运动员 implements 运动员 {

public void 跑(){
//跑啊跑
}

public void 跳(){
//跳啊跳
}
}

篮球运动员.java
public class 篮球运动员 implements 运动员 {

public void 跑(){
//do nothing
}

public void 跳(){
//do nothing
}
}

体育协会.java
public interface 体育协会 {
public 运动员 注册();
}

足球协会.java
public class 足球协会 implements 体育协会 {
public 运动员 注册(){
return new 足球运动员();
}
}

篮球协会.java
public class 篮球协会 implements 体育协会 {
public 运动员 注册(){
return new 篮球运动员();
}
}

俱乐部.java
public class 俱乐部 {
private 运动员 守门员;
private 运动员 后卫;
private 运动员 前锋;

public void test() {
体育协会 中国足协 = new 足球协会();

this.前锋 = 中国足协.注册();
this.后卫 = 中国足协.注册();

守门员.跑();
后卫.跳();
}
}

很明显可以看到,“体育协会”工厂类变成了“体育协会”接口,而实现此接口的分别是“足球协会”“篮球协会”等等具体的工厂类。

这样做有什么好处呢?很明显,这样做就完全OCP了。如果需要再加入(或扩展)产品类(比如加多个“乒乓球运动员”)的话就不再需要修改工厂类了,而只需相应的再添加一个实现了工厂接口(“体育协会”接口)的具体工厂类。

从以上对两种模式的介绍可以了解到,工厂方法模式是为了克服简单工厂模式的缺点(主要是为了满足OCP)而设计出来的。但是,工厂方法模式就一定比简单工厂模式好呢?笔者的答案是不一定。下面笔者将详细比较两种模式。

1. 结构复杂度
从这个角度比较,显然简单工厂模式要占优。简单工厂模式只需一个工厂类,而工厂方法模式的工厂类随着产品类个数增加而增加,这无疑会使类的个数越来越多,从而增加了结构的复杂程度。

2.代码复杂度
代码复杂度和结构复杂度是一对矛盾,既然简单工厂模式在结构方面相对简洁,那么它在代码方面肯定是比工厂方法模式复杂的了。简单工厂模式的工厂类随着产品类的增加需要增加很多方法(或代码),而工厂方法模式每个具体工厂类只完成单一任务,代码简洁。

3.客户端编程难度
工厂方法模式虽然在工厂类结构中引入了接口从而满足了OCP,但是在客户端编码中需要对工厂类进行实例化。而简单工厂模式的工厂类是个静态类,在客户端无需实例化,这无疑是个吸引人的优点

4.管理上的难度
这是个关键的问题。
我们先谈扩展。众所周知,工厂方法模式完全满足OCP,即它有非常良好的扩展性。那是否就说明了简单工厂模式就没有扩展性呢?答案是否定的。简单工厂模式同样具备良好的扩展性——扩展的时候仅需要修改少量的代码(修改工厂类的代码)就可以满足扩展性的要求了。尽管这没有完全满足OCP,但笔者认为不需要太拘泥于设计理论,要知道,sun提供的java官方工具包中也有想到多没有满足OCP的例子啊(java.util.Calendar这个抽象类就不满足 OCP,具体原因大家可以分析下)。
然后我们从维护性的角度分析下。假如某个具体产品类需要进行一定的修改,很可能需要修改对应的工厂类。当同时需要修改多个产品类的时候,对工厂类的修改会变得相当麻烦(对号入座已经是个问题了)。反而简单工厂没有这些麻烦,当多个产品类需要修改是,简单工厂模式仍然仅仅需要修改唯一的工厂类(无论怎样都能改到满足要求吧?大不了把这个类重写)。

由以上的分析,笔者认为简单工厂模式更好用更方便些。当然这只是 笔者的个人看法而已,毕竟公认的,工厂方法模式比简单工厂模式更“先进”。但有时过于先进的东西未必适合自己,这个见仁见智吧。

抽象工厂模式中的有以下的四种角色:
抽象工厂( Abstract Factory )角色:担任这个角色的是工厂方法模式的核心,它是与应用系统商业逻辑无关的。
具体工厂( Concrete Factory )角色:这个角色直接在客户端的调用下创建产品的实例。这个角色含有选择合适的产品对象的逻辑,而这个逻辑是与应用系统的商业逻辑紧密相关的。
抽象产品( Abstract Product )角色:担任这个角色的类是工厂方法模式所创建的对象的父类,或它们共同拥有的接口。
具体产品( Concrete Product )角色:这个角色用以代表具体的产品。
抽象工厂模式就相当于创建实例对象的 new ,由于经常要根据类生成实例对象,抽象工厂模式也是用来创建实例对象的,所以在需要新的事例对象时便可以考虑是否使用工厂模式。虽然这样做可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。
举例来说:生产餐具和相应食物的工厂,有两个车间,其中一个车间用以生产餐具,一个车间用以生产相应的食物。
当消费者消费时,只需要向相应的具体工厂请求具体餐具和具体食物便可以使用餐具消费食物。
使用 UML 图表示以上的描述如下:
1 抽象工厂与具体工厂
2 抽象餐具与具体餐具(生产车间)
3 抽象食物与具体食物
每个具体工厂生产出来的具体产品根据不同工厂的不同各不相同,但是客户使用产品的方法是一致的。比如客户在得到餐具和食物之后,两者的搭配是正确的(使用汤匙喝牛奶,使用刀子切面包)。
在本例子中有 3 个具体工厂 AKetchen BKetchen BKetchen ,分别生产牛奶和汤匙、面包和刀、肉和叉子。牛奶、面包和肉都实现了食物接口。汤匙、刀和叉子都实现了餐具接口。
抽象工厂的接口定义如下所示;
package abstractFactory;
public interface KetchenFactory{
public Food getFood();
public TableWare getTableWare();
}
抽象餐具的接口定义如下所示:
package abstractFactory;
public interface TableWare{
public String getTool();
}
抽象事物的接口定义如下所示:
package abstractFactory;
public interface Food{
public String getEatable();
}
而具体的实现也非常简单,以 AKetchen 为例子
具体工厂 AKetchen 的定义如下所示;
package abstractFactory;
public class AKetchen implements KetchenFactory{
public Food getFood(){
return new Milk();
}
public TableWare getTableWare(){
return new Spoon();
}
}
具体餐具 (spoon) 的定义如下所示:
package abstractFactory;
public class Spoon implements TableWare{
public String getTool() {
return "spoon" ;
}
}
具体食物 (milk) 的定义如下所示:
package abstractFactory;
public class Milk implements Food{
public String getEatable(){
return "milk" ;
}
}
客户端的定义如下:
package abstractFactory;
public class Client{
public void eat(KetchenFactory k){
System. out .println( "A person eat " +k.getFood().getEatable()
+ " with " +k.getTableWare().getTool()+ "!" );
}
public static void main(String[] args){
Client client= new Client();
KetchenFactory kf = new AKetchen();
client.eat(kf);
kf= new BKetchen();
client.eat(kf);
kf= new CKetchen();
client.eat(kf);
}
}
小结:抽象工厂模式特别适合于这样的一种产品结构:产品分为几个系列,在每个系列中,产品的布局都是要同的,在一个系列中某个位置的产品,在另一个系列中一定有一个对应的产品。这样的产品结构是存在的,这几个系列中同一位置的产品可能是互斥的,它们是针对不同客户的解决方案,每个客户都只择其一。
优点:消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。
缺点:当具体产品修改时,相应的工厂类也要做相应的修改。

工厂方法模式:
一个抽象产品类,可以派生出多个具体产品类。   
一个抽象工厂类,可以派生出多个具体工厂类。   
每个具体工厂类只能创建一个具体产品类的实例。

抽象工厂模式:
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。   
一个抽象工厂类,可以派生出多个具体工厂类。   
每个具体工厂类可以创建多个具体产品类的实例。   
    
区别:
工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。   
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个

猜你喜欢

转载自rainyear.iteye.com/blog/1668353