您的位置:首页 > 其它

Thinking In Design Pattern——工厂模式演绎

2012-08-30 18:33 309 查看

我始终认为学习设计模式需要怀着一颗敬畏的心去探索,这一系列23种设计模式并不是一蹴而就,都是前人根据自己的经验逐渐演化出来,所以才会形成非常经典的理论。学习设计模式,我想最好的方式是根据自己的经验逐渐来推导它,这样你才理解了其中奥妙,而不是靠记忆背住了它,所以,这篇博文主要分析三种工厂模式的演变过程:

来实现吧,最简单计算器

代码能否复用性

忘记面向过程吧,面向对象思想的引入

多态,简化代码大杀器

质的飞跃:简单工厂模式的运用

迷途知返:拨开云雾见工厂方法

暴走一击:抽象工厂+工厂方法


我们先举一个最平常例子——写一个最简单的计算器,我们可能非常快就完成了第一个版本:

来实现吧,简易计算器

View Code?
优秀的程序员依靠清晰的逻辑思维方能拨开云雾见晨曦,对于第一版本,有着各种各样的差强人意的设计,所以第二版本,我们有的放矢,对第一版本稍作修改。

代码能否复用性

View Code?
版本二虽然比版本一稍稍先进了,但依然是面向过程编程,看来写一个令人满意的计算器还是有点麻烦的,希望版本三引入面向对象思想,希望能够让我满意。

忘记面向过程吧,面向对象思想的引入

View Code?
版本三相对于版本二来说是质的飞跃,由面向过程编程转向了面向对象编程,但Swith里面有太多的重复代码,仅仅只有 AddCalculate calcAdd = new AddCalculate()这些实例化语句在改变。所以下一个版本就是看能否抽象出一个然后父类采用里氏替换原则来替换之前重复代码。

多态,简化代码大杀器

View Code?
这个版本在上一个版本的基础上增加了多态,简化了很多重复代码,看似完美,但仍旧有缺陷:客户端耦合度还是高,除了要认识父类外,还要认识加减乘除类,所以尽量来解耦,就像我去商店买笔,没必要知道笔是怎么制造的,也就是说客户端只要和父类耦合即可,子类没必要和客户端耦合。所以为了解耦,引入一个第三方工厂类,将和客户端耦合的加减乘除类封装到我们的工厂类中(去耦合,封装复杂度),这就是简单工厂模式,其特征:



只有一个工厂(具体的,没有抽象)

只生产一种产品(抽象的产品,基类)

这种产品可以有多种具体产品类型(派生)

注意:由于简单工厂很容易违反高内聚责任分配原则,因此一般只在很简单的情况下应用。


质的飞跃,简单工厂模式的运用





namespace 简单工厂模式
{
/// <summary>
/// 简单工厂特点:
/// 1.只有一个工厂(不是抽象)
/// 2.只有一种类型的产品(基类,与子类数量无关)
/// 3.一个工厂只生产一种类型产品
/// </summary>
class Program
{
static void Main(string[] args)
{
Console.WriteLine("请输入一个数A=");
int a = int.Parse(Console.ReadLine());
Console.WriteLine("请输入另一个数B=");
int b = int.Parse(Console.ReadLine());
Console.WriteLine("请输入运算符:");
string op = Console.ReadLine();
int result = 0;

Calculate calc=SimpleFactory.GetCalc(op);//解耦了,客户端不认识具体的类,用简单工厂类封装了这种复杂度,也就是去耦合度。这样客户端和工厂、抽象的产品耦合了。
calc.NumberA=a;
calc.NumberB=b;
result = calc.GetResult;
Console.WriteLine("{0}{1}{2}={3}", a, op, b, result);
Console.ReadLine();
}
}
/// <summary>
///创建一个简单工厂
/// </summary>
static class SimpleFactory
{
/// <summary>
/// 简单工厂封装了对象创建的复杂度
/// </summary>
/// <param name="op"></param>
/// <returns>产品的抽象</returns>
public static Calculate GetCalc(string op)
{
Calculate calc = null;

switch (op)
{
case "+":
calc = new AddCalculate();//多态
break;
case "-":
calc = new SubCalculate();
break;
case "*":
calc = new MultiCalculate();
break;
case "/":
calc = new DivisCalculate();
break;

default:
break;
}
return calc;
}
}

abstract class Calculate
{
protected int numberA;
//属性:封装字段
public int NumberA
{
get { return numberA; }
set { numberA = value; }
}
protected int numberB;
//属性:封装字段
public int NumberB
{
get { return numberB; }
set { numberB = value; }
}
/// <summary>
/// 抽象属性:由子类对象来完成计算
/// </summary>
public abstract int GetResult { get; }

}
/// <summary>
/// 加法运算类
/// </summary>
class AddCalculate : Calculate
{
public override int GetResult
{
get
{

return base.NumberA + base.NumberB;
}
}
}
/// <summary>
/// 减法运算类
/// </summary>
class SubCalculate : Calculate
{

public override int GetResult
{
get { return base.NumberA - base.numberB; }
}
}
/// <summary>
/// 乘法运算类
/// </summary>
class MultiCalculate : Calculate
{
public override int GetResult
{
get { return base.numberA * base.numberB; }
}
}
/// <summary>
/// 除法运算类
/// </summary>
class DivisCalculate : Calculate
{
public override int GetResult
{
get { return base.numberA / base.numberB; }
}
}
}




ok,到目前为止我们再和第一版的计算器比较下,原先那个版本简直是A Piece Of XXX。简单工厂其实不是一种设计模式,这不重要,重要的是你是否能理解并为自己运用。我们再回过头来,看看我们的简单计算器是否还有地方要改进,这就要引出我们下一个话题,简单工厂的局限性:

由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了(也违反了开放封闭原则,对修改封闭,对扩展开放)。

当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;

这些缺点在工厂方法模式中得到了一定的克服。

迷途知返,拨开云雾见工厂方法


什么是工厂方法——

定义一个用于创建对象的接口,让子类决定实例化哪一个类。FactoryMethod使得一个类的实例化延迟到子类。——GOF 《设计模式》





工厂方法的概念很抽象,我们对其进行解释:


工厂方法模式对简单工厂模式进行了抽象(想想看,简单工厂模式里工厂是个具体类),Factory Method模式的两种情况:

一:Creator类是一个抽象类且它不提供它所声明的工厂方法的实现,比如有一个抽象的Factory类(可以是抽象类和接口),这个类将不在负责具体的产品生产,而是只制定一些规范,具体的生产工作由其子类去完成(体现了延迟性)。在这个模式中,工厂类和产品类往往可以依次对应。即一个抽象工厂对应一种类型抽象产品,一个具体工厂对应一个具体产品(体现了类的单一职责),这个具体的工厂就负责生产对应的产品。

[b]二:Creator类是一个具体的类且它提供一个工厂方法的缺省实现。[/b]

看了上面那段话,晕了吗,还是代码解释吧(第一种情况:Creator是抽象的类,所以我的工厂类定义为是抽象类[即下面案例中:运算AbstractFactory]):





namespace 工厂方法
{

//Factory Method模式主要用于隔离类对象的使用者(Main函数)和具体类型(我在Main函数里只知道抽象的父类,也不知道具体的实现子类)之间的耦合关系(迪米特法则:用户知道越少越好,这样封装性越高)。面对一个经常变化的具体类型,紧耦合关系会导致软件的脆弱。
class Program
{
static void Main(string[] args)
{
Console.WriteLine("请输入A:");
int a = int.Parse(Console.ReadLine());
Console.WriteLine("请输入B:");
int b = int.Parse(Console.ReadLine());
Console.WriteLine("请输入运算符");

string op = Console.ReadLine();

运算AbstractFactory factory = null;
Calculate calc = null;

#region 这儿就是局部化
//注意:这里的switch不是必须的,可以利用反射技术动态创建工厂的实例
//反射详见抽象工厂
switch (op)
{
case "+":
factory = new 加法Factory();

break;
case "-":
factory = new 减法Factory();
break;
case "*":
factory = new 乘法Factory();
break;
case "/":
factory = new 除法Factory();
break;
default:
break;
}
#endregion

calc = factory.GetCalc();
calc.NumberA = a;
calc.NumberB = b;
int result = calc.GetResult;
Console.WriteLine(result);
Console.ReadKey();

}
}

//1.Factory Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略(开放封闭原则思想),较好地解决了这种紧耦合关系。(和简单工厂比较)
//2.工厂类和产品类往往可以依次对应,即一个抽象工厂对应一个抽象产品,一个具体工厂对应一个具体产品(体现了类的单一职责),这个具体的工厂就负责生产对应的产品。
abstract class 运算AbstractFactory
{

public abstract Calculate GetCalc();
}

//通过继承来扩展,而非是修改Swith语句那样去修改————开放封闭原则,对扩展开放,对修改封闭
class 加法Factory : 运算AbstractFactory
{
public override Calculate GetCalc()
{
return new AddCalculate();//这里体现了推迟.../延迟到工厂子类 子类辅助帮助父类实现具体创建哪个对象,也就是New XXCalculate()
}
}
class 减法Factory : 运算AbstractFactory
{
public override Calculate GetCalc()
{
return new SubCalculate();
}
}
class 乘法Factory : 运算AbstractFactory
{
public override Calculate GetCalc()
{
return new MultiCalculate();
}
}
class 除法Factory : 运算AbstractFactory
{
public override Calculate GetCalc()
{
return new DivisCalculate();
}
}
///////////////产品没变化//////////////

//对产品的抽象
abstract class Calculate
{
protected int numberA;
//属性:封装字段
public int NumberA
{
get { return numberA; }
set { numberA = value; }
}
protected int numberB;
//属性:封装字段
public int NumberB
{
get { return numberB; }
set { numberB = value; }
}
/// <summary>
/// 抽象属性:由子类对象来完成计算
/// </summary>
public abstract int GetResult { get; }

}
/// <summary>
/// 加法运算类
/// </summary>
class AddCalculate : Calculate
{
public override int GetResult
{
get
{

return base.NumberA + base.NumberB;
}
}
}
/// <summary>
/// 减法运算类
/// </summary>
class SubCalculate : Calculate
{

public override int GetResult
{
get { return base.NumberA - base.numberB; }
}
}
/// <summary>
/// 乘法运算类
/// </summary>
class MultiCalculate : Calculate
{
public override int GetResult
{
get { return base.numberA * base.numberB; }
}
}
/// <summary>
/// 除法运算类
/// </summary>
class DivisCalculate : Calculate
{
public override int GetResult
{
get { return base.numberA / base.numberB; }
}
}

}




第二种情况:Creator是一个具体的类且它提供一个工厂方法的缺省实现。

View Code?
OK,其实工厂方法不复杂,注意其特点:只有一种产品。


在以下情况下,适用于工厂方法模式:

1. 当一个类不知道它所必须创建的对象的类的时候。

2. 当一个类希望由它的子类来指定它所创建的对象的时候。

3. 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候(通过反射技术或者Swith语句来实现局部化变化)。

工厂方法模式是简单工厂模式的衍生,解决了许多简单工厂模式的问题。首先完全实现“开-闭 原则”,实现了可扩展。其次更复杂的层次结构,可以应用于产品结果复杂的场合。


到目前为止,我们的计算器已经完美了,计算器也将告一段落了。接下来我们再来探讨一下,如果产品总类增加,怎样来设计我们的应用程序呢?接下来我们看抽象工厂模式(抽象工厂模式针对的是多个产品等级结构)。

柳暗花明又一村:抽象工厂+工厂方法


什么是抽象工厂——

提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。 ——GOF 《设计模式》





M系列*N产品



通过这幅图也可以发现: Abstract Factory模式主要在于应对“新系列”的需求变动。其缺点在于难以应对“新对象”的需求变动(因为添加一个新的产品类型需要修改太多的地方了)

抽象工厂实现要点:



抽象工厂将产品对象的创建延迟到它的具体工厂的子类。

通常在运行时刻创建一个具体工厂类的实例(反射),这一具体工厂的创建具有特定实现的产品对象,为创建不同的产品对象,客户应使用不同的具体工厂。

把工厂作为单例,一个应用中一般每个产品系列只需一个具体工厂的实例,因此,工厂通常最好实现为一个单例模式(反射耗费性能)。

创建产品,抽象工厂仅声明一个创建产品的接口,真正创建产品是由具体工厂类创建的,最通常的一个办法是为每一个产品定义一个工厂方法(生产产品),一个具体的工厂将为每个产品重定义该工厂方法以指定产品,虽然这样的实现很简单,但它要求每个产品系列都要有一个新的具体工厂子类,即使这些产品系列的差别很小。


具体代码实现:





namespace 抽象工厂Form
{

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private Assembly assembly = null;
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "exe文件|*.exe|dll文件|*.dll";
dialog.InitialDirectory = Application.StartupPath;
if (dialog.ShowDialog()==DialogResult.OK)
{
//反射
assembly = System.Reflection.Assembly.LoadFile(dialog.FileName);
this.comboBox1.Items.Clear();
foreach (var item in assembly.GetTypes())
{
if (item.IsSubclassOf(typeof(SportShop)))
{
//将SportShop的两个子类加入下拉列表
this.comboBox1.Items.Add(item);
}
}
}
}

private void button2_Click(object sender, EventArgs e)
{
Clothes clothes = null;
Hat hat = null;
SportShop shop = null;
string typeName = ((Type)this.comboBox1.SelectedItem).FullName;
//运行时刻创建一个具体工厂类的实例,这一具体工厂的创建具有特定实现的产品对象,为创建不同的产品对象,客户应使用不同的具体工厂(通过配置文件反射)
shop = Factory.CreateShop(assembly, typeName);
clothes = shop.SellClothes();
hat = shop.SellHat();
MessageBox.Show(string.Format("衣服:{0},帽子:{1}", clothes, hat));
}

}
/// <summary>
/// 抽象的产品A
/// </summary>
abstract class Clothes
{

}
/// <summary>
/// 抽象的产品B
/// </summary>
abstract class Hat
{

}
/// <summary>
/// Nike产品A(系列1的产品A)
/// </summary>
class NikeClothes : Clothes
{
public override string ToString()
{
return "Nike衣服";
}
}
/// <summary>
/// Nike产品B(系列1的产品B)
/// </summary>
class NikeHat : Hat
{
public override string ToString()
{
return "Nike帽子";
}
}
/// <summary>
/// LiNing产品A(系列2的产品A)
/// </summary>
class LiNingClothes : Clothes
{
public override string ToString()
{
return "LiNing衣服";
}
}
/// <summary>
/// LiNing产品B(系列2的产品B)
/// </summary>
class LiNingHat : Hat
{
public override string ToString()
{
return "LiNing帽子";
}
}
/// <summary>
/// 抽象的工厂,负责规定创建产品(多种)
/// </summary>
abstract class SportShop
{
/// <summary>
/// 创建产品A
/// </summary>
/// <returns></returns>
public abstract Clothes SellClothes();
/// <summary>
/// 创建产品B
/// </summary>
/// <returns></returns>
public abstract Hat SellHat();
}
/// <summary>
/// 具体的系列1工厂
/// 把工厂作为单例,一个应用中一般每个产品系列只需一个具体工厂的实例,因此,工厂通常最好实现为一个单例模式。
/// </summary>
class NikeFactory : SportShop
{

//创建产品,抽象工厂仅声明一个创建产品的接口,真正创建产品是由具体工厂类创建的,最通常的一个办法是为每一个产品定义一个工厂方法,一个具体的工厂将为每个产品重定义该工厂方法以指定产品,虽然这样的实现很简单,但它要求每个产品系列都要有一个新的具体工厂子类,即使这些产品系列的差别很小。
public override Clothes SellClothes()//抽象工厂将产品对象的创建延迟到它的具体具体工厂子类中
{
return new NikeClothes();
}
public override Hat SellHat()
{
return new NikeHat();
}
}
/// <summary>
/// 具体的系列2的工厂
/// </summary>
class LiNingFactory : SportShop
{
public override Clothes SellClothes()
{
return new LiNingClothes();
}
public override Hat SellHat()
{
return new LiNingHat();
}
}
/// <summary>
/// Abstract Factory模式经常和Factory Method模式共同组合来应对“对象创建”的需求变化。
///这里的处理很经典: 把SportShop可以看成是产品(之前还是系列),所以这儿是工厂方法
/// </summary>
static class Factory
{
public static SportShop CreateShop(Assembly assembly,string typeName)
{
//反射,当然Swith...Case也OK
return (SportShop)assembly.CreateInstance(typeName);

//switch (typeName)
//{

//    case "Nike...":
//        return new NikeFactory();
//    case "LiNing":
//        return new LiNingFactory();
//    default:
//        break;
//}
}
}
}




对抽象工厂的总结:



如果没有应对“多系列对象构建”的需求变.化,则没有必要使用Abstract Factory模式,这时候使用简单的静态工厂完全可以。

“ 系列对象”指的是这些对象之间有相互依赖、或作用的关系,例如游戏开发场景中的“道路”与“房屋”的依赖,“道路”与“地道”的依赖。

Abstract Factory模式主要在于应对“新系列”的需求变动。其缺点在于难以应对“新对象”的需求变动。(案例图体现了)

Abstract Factory模式经常和Factory Method模式共同组合来应对“对象创建”的需求变化。(代码里体现了)


总结


简单工厂模式:一个具体工厂+一种产品

工厂方法模式:一个抽象工厂(多个具体工厂)+一种产品

抽象工厂模式:一个抽象工厂(M个系列工厂)+N种产品

千万别死记硬背设计模式,以不同的视角看同一个类,会有不同的处理,如:上述代码把SportShop可以看成是产品(之前还是系列)。

3x:)

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