工厂模式(Factory)
先来明确一个问题,那就是有的时候,实例化这个活动不应该总是公开的进行,
也就是不要公开的使用 new 操作符,因为,这样容易造成耦合问题。
我们不应该针对实现编程,但是当我们在使用 new 的时候,便是针对实现编程,
而如果您要实例化的话,必须要使用 new 这个关键字,
很明显,这是一个矛盾问题!!!
当然这里的矛盾并不是说不能够使用 new ,而是更好的使用了 new。
那么如何来解决这样一个矛盾问题呢?
先来看一副类图
上面这副类图反映的就是一个简单工厂模式了,确切的说,简单工厂不能说是一种设计模式,
其更可以被称作是一种编程习惯,因为我们习惯性的将一些类的实例化放到另外一个类中来完成。
这样在代码的可读性上有很大的帮助,
上面的类图反映的就是将一组相似对象(继承自同一个类)的实例的创建放到另外一个对象中完成,
即通过 ProductFactory 这个类来按需创建对象,何为按需创建对象呢(或者说是动态创建对象)?
这就是要求在客户端传入一个参数给 ProductFactory ,
然后 ProductFactory 根据这个参数来创建指定的对象实例并返回。
下面通过一个 Demo 来将上面的简单工厂进行实现
下面就来看上面的这个水果例子的具体实现了
首先要有一个 Fruit 的抽象类
namespace SimpleFactory
{
public abstract class Fruit
{
/// <summary>
/// 在此仅代表性的定义一个方法
/// </summary>
public abstract void Display();
}
}
然后就是继承自 Fruit 抽象类的四个子类
using System;
namespace SimpleFactory
{
class Apple : Fruit
{
public override void Display()
{
Console.WriteLine("我的名字是:苹果 ,主要特性为养颜护肤");
}
}
}
using System;
namespace SimpleFactory
{
class Orange:Fruit
{
public override void Display()
{
Console.WriteLine("我的名字是:橘子 ,主要特性为滋肝润胃");
}
}
}
using System;
namespace SimpleFactory
{
class Pear:Fruit
{
public override void Display()
{
Console.WriteLine("我的名字是:梨子 ,主要特性为滋肝润胃");
}
}
}
using System;
namespace SimpleFactory
{
class Banana:Fruit
{
public override void Display()
{
Console.WriteLine("我的名字是:香蕉 ,主要特性为滋肝润胃");
}
}
}
最后还有一个非常重要的类,那就是工厂类,即 FruitFactory
using System;
namespace SimpleFactory
{
public class FruitFactory
{
public FruitFactory()
{
}
/// <summary>
/// 简单工厂中必须要有一个方法来根据指定的逻辑创建实例
/// </summary>
/// <param name="fruitType"></param>
/// <returns></returns>
public static Fruit CreateFruit(string fruitType)
{
if (!String.IsNullOrEmpty(fruitType))
{
switch (fruitType)
{
case "Apple":
return new Apple();
case "Orange":
return new Orange();
case "Pear":
return new Pear();
case "Banana":
return new Banana();
}
}
return null;
}
}
}
最后就是来看客户端代码和运行结果了
using System;
using SimpleFactory;
namespace SimpleFactoryTest
{
class Program
{
static void Main(string[] args)
{
//创建一个苹果实例
//并且调用这个实例的 Display 方法
FruitFactory.CreateFruit("Apple").Display();
//创建一个橘子实例
//并且调用这个实例的 Display 方法
FruitFactory.CreateFruit("Orange").Display();
//创建一个梨子实例
//并且调用这个实例的 Display 方法
FruitFactory.CreateFruit("Pear").Display();
Console.ReadLine();
}
}
}
上面写的这个 Demo 呢,是典型的简单工厂了,
通过简单工厂呢,我们实现了对实例化这个活动的封装,
因为把所有的实例化全部放入了另外一个对象(也就是工厂)中,
同时实现了针对接口编程,而非实现,在上面的 Demo 中,这里体现在了工厂 FruitFactory 中,
我们通过多态来实现了针对接口 Fruit 编程,而非针对具体类,如 Apple 这些来编程。
这样,使用简单工厂便轻松的解决了一开始提出的矛盾问题。
总结一下简单工厂的优点:
简单工厂的工厂类中包含了必要的逻辑判断,这样就可以根据客户端的选择条件来动态的实例化相关的类,
对于客户端来说,其去除了与具体产品(比如 Apple)之间的依赖。
当然简单工厂模式是有缺点的:
最为明显的就是,其违背了开-闭原则,
在上面的 Demo 中,如果我要再增加一种水果---葡萄(Grape),
那么做法如下,首先是定义一个葡萄类 Grape,让其继承自 Fruit ,然后呢,您还必须修改工厂类,
因为您必须在工厂类的 switch 中添加几句代码
case "Grape":
return new Grape();
这样就非常明显了,因为您已经对 FruitFactory 类进行了修改,
且这个修改是必须得,否则您就无法增加葡萄这种水果。
所以,简单工厂是明显的违背了开-闭原则的。
由于简单工厂违背了开-闭原则,所以还必须在其上面改进一下,这样可以得出下面要谈的模式,
也就是工厂方法模式(Factory Method)
先来给出工厂方法的结构图吧
上面的类图稍微简单了点,但是还是可以反映出一些内容的,
和简单工厂相比的话,在工厂方法中,创建对象将不会在集中在一个类中了,
而是通过一个 FactoryMethod 方法在各个子类中实现对象的实例化,
这句话怎么理解呢?
也就是,在上面的类图中,
创建 ConcreteProduct 类的实例这个功能被封装在了 ConcreteProductFactory 中,
而不是在 ProductFactory 中了,
下面再看一副稍微复杂点的类图或许能够帮助更好的理解
这一副类图呢,更好的展现出了工厂方法的基本实现原理,
每一个具体产品类都有与其相对应的具体工厂类,而这个具体工厂类呢,
其唯一职责就是创建与其对应的具体产品类的实例。
比如 ConcreteProductFactoryA 依赖于 ConcreteProductA ,也就是只创建 ConcreteProductA 实例。
在这里还是先给出工厂方法的定义吧
工厂方法模式,定义了一个用于创建对象的接口,让子类来决定要实例化哪一个类,
工厂方法让类把实例化延迟到其子类。
下面呢,在来看一个实际的 Demo ,这个 Demo 还是延续前面介绍简单工厂时的水果的例子
先来看类图吧
从上面的类图中可以看到,拥有一个水果抽象类 Fruit
然后是四个水果具体类,还有一个水果工厂抽象类 FruitFactory ,
然后是四个具体工厂类,分别用来创建四个水果具体类的实例。
下面就来看代码啦
先是 Fruit 抽象类
namespace FactoryMethod
{
public abstract class Fruit
{
public abstract void Display();
}
}
然后是四个具体的水果类
using System;
namespace FactoryMethod
{
class Apple:Fruit
{
public override void Display()
{
Console.WriteLine("我的名字是:苹果 ,主要特性为养颜护肤");
}
}
}
using System;
namespace FactoryMethod
{
class Banana:Fruit
{
public override void Display()
{
Console.WriteLine("我的名字是:香蕉 ,主要特性为滋肝润胃");
}
}
}
using System;
namespace FactoryMethod
{
class Orange : Fruit
{
public override void Display()
{
Console.WriteLine("我的名字是:橘子 ,主要特性为滋肝润胃");
}
}
}
using System;
namespace FactoryMethod
{
class Pear : Fruit
{
public override void Display()
{
Console.WriteLine("我的名字是:梨子 ,主要特性为滋肝润胃");
}
}
}
再来看抽象工厂类 FruitFactory
namespace FactoryMethod
{
public abstract class FruitFactory
{
public abstract Fruit GetFruit();
}
}
然后就是四个具体工厂类
namespace FactoryMethod
{
public class AppleFactory:FruitFactory
{
public override Fruit GetFruit()
{
return new Apple();
}
}
}
namespace FactoryMethod
{
public class BananaFactory : FruitFactory
{
public override Fruit GetFruit()
{
return new Banana();
}
}
}
namespace FactoryMethod
{
public class OrangeFactory : FruitFactory
{
public override Fruit GetFruit()
{
return new Orange();
}
}
}
namespace FactoryMethod
{
public class PearFactory : FruitFactory
{
public override Fruit GetFruit()
{
return new Pear();
}
}
}
所有的类的结构的代码就在上面了,下面将要看的就是客户端代码和效果了
using System;
using FactoryMethod;
namespace FactoryMethodTest
{
class Program
{
static void Main(string[] args)
{
FruitFactory factory;
Fruit fruit;
//首先要实例化一个苹果工厂
factory = new AppleFactory();
//然后通过工厂来获得苹果类对象
fruit = factory.GetFruit();
//访问苹果类对象
fruit.Display();
//首先要实例化一个梨子工厂
factory = new PearFactory();
//然后通过工厂来获得梨子类对象
fruit = factory.GetFruit();
//访问梨子类对象
fruit.Display();
Console.ReadLine();
}
}
}
上面呢,就是关于工厂方法模式的 Demo 的完整演示了,
下面将通过比较简单工厂和工厂方法这两种设计模式来体现出各自的优劣之处:
简单工厂把全部的事情,在一个地方(类)全部处理完,而工厂方法却不同,
其是通过创建一个框架(FruitFactory),
然后让子类(AppleFactory 等)决定要如何实现,简单工厂呢,其将对象的创建封装起来了,
但是其违背开-闭原则,弹性太弱,正如前面介绍简单工厂时,提到的加入一种水果葡萄时会造成修改类,
而工厂方法则恰恰解决了简单工厂的这一毛病,其通过将实例化延迟到子类成功解决了简单工厂的这一问题,
其实呢,可以这样去理解,简单工厂在其主要的工厂方法中,存在了逻辑判断语言,
这是因为,工厂方法必须根据外界给出的条件来创建符合要求的实例,
而工厂方法却不存在这一点,也就是不需要指到外界的情况,
这是为何呢?其实这是因为,工厂方法将这些本来应该存在的判断逻辑移到了客户端代码中,
(通过客户的需求来选择生成那一种水果实例,比如我的演示中生成的就是苹果和梨子这两种实例)
这样的话,可以解决掉简单工厂违背开-闭原则这一毛病,
比如,要在上面的 Demo 中再添加一种水果 Grape 的话,
使用工厂方法的话,您只需要添加一个 Grape 类,让其继承自 Fruit ,
然后再为其添加一个 GrapeFactory 类,让这个类继承自 FruitFactory 类就 OK 了,
其余的代码便是在客户端来完成了。
下面将要介绍的是抽象工厂模式(Abstract Factory)
先来看抽象工厂的大体的结构图
要想明白上面的这幅类图的话,先必须要明确一个概念,
产品族:
在上面的产品列表中呢,有两个产品族,一个是“具体产品A--1”和”具体产品B--1“组成的一个族,
还有一个是“具体产品A--2”和“具体产品B--2”组成的一个族。
产品族就是在不同产品等级结构中,功能相关联的产品组成的家族。
下面就来介绍抽象工厂了(有些内容生涩的话,可以看完 Demo 后回过头来浏览)
下面给出的是抽象工厂的定义:
提供一个创建一系列相关或者是相互依赖对象的接口,而无需指定它们具体的类。
再来看衣服详细的类图
其实从上面这副类图中可以看出,每一个具体的工厂,它都只负责创建一个产品族中的产品的实例,
从抽象工厂中派生出的具体工厂,这些具体工厂产生相同的产品(这些产品都继承自同一父类),
比如,ConcreteFactory1 和 ConcreteFactory2 中的 CreateProductA 这个方法都是产生 AbstractProductA 这种类型的产品,
但是产品的实现却是不同的,比如 ConcreteFactory1 中的 CreateProductA 实现的是产生一个 ConcreteProductA—1 产品,
而 ConcreteFactory2 中的 CreateProductA 实现的是产生一个 ConcreteProductA—2 产品,
总的来说就是不同的具体工厂产生不同的产品族,
而抽象工厂则是定义一个负责创建一组产品(也就是一个产品族)的接口,
比如上面的类图中只存在两个产品族,所以在抽象工厂中便只需要定义两个接口就可以了。
下面来剖析一下抽象工厂的优点和缺点
抽象工厂的最大好处在于交换产品系列非常方便,由于具体工厂类在一个应用中只需要在初始化的时候出现一次,
这样就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置,
比如,我在应用中本来使用的是工厂 ConcreteFactory1 来生成的产品族 1 ,
而现在需求改变了,不再使用产品族 1 ,而必须使用产品族 2 时,
此时,只需要在客户端代码中初始化 ConcreteFactory1 的位置,
把 ConcreteFactory1 改为 ConcreteFactory2 就可以了,这样就成功的将产品族1 切换到了使用产品族2 上面来,
其次,抽象工厂让具体的创建实例过程与客户端分离,客户端是通过他们的抽象接口操纵实例,
产品的具体类名也被具体工厂的实现分类,不会出现在客户端代码中。
这一点上的话,简单工厂和工厂方法也做得很不错,即依赖于抽象。
同时,如果需求需要扩展的话,比如,要重新增加一个产品族,这也很好办,
只需要增加一个具体工厂,然后增加产品族就可以了,
总之是,抽象工厂很好的遵循了开--闭原则和依赖倒置原则。
下面就来看一个 Demo ,从这个 Demo 中看出抽象工厂的优点
先来展现一下具体的类图
上面的类图呢,说明的是有两个具体工厂,一个是 Linux 控件的制造,还有一个是 Windows 控件的制造,
然后,有两个产品族,一个是 WindowsTextBox 和 LinuxTextBox 组成的 TextBox 产品族,
还有一个就是 WindowsButton 和 LinuxButton 组成的 Button 产品族。
下面就来写类了
先来看工厂类吧
namespace AbstractFactory
{
public abstract class AbstractFactory
{
//在抽象工厂中,应该包含所有产品创建的抽象方法
public abstract Button CreateButton();
public abstract TextBox CreateTextBox();
}
}
namespace AbstractFactory
{
public class WindowsFactory:AbstractFactory
{
public override Button CreateButton()
{
return new WindowsButton();
}
public override TextBox CreateTextBox()
{
return new WindowsTextBox();
}
}
}
namespace AbstractFactory
{
public class LinuxFactory:AbstractFactory
{
public override Button CreateButton()
{
return new LinuxButton();
}
public override TextBox CreateTextBox()
{
return new LinuxTextBox();
}
}
}
下面就给出所有的产品类
namespace AbstractFactory
{
public abstract class Button
{
public abstract void DisplayButton();
}
}
using System;
namespace AbstractFactory
{
class LinuxButton:Button
{
public override void DisplayButton()
{
Console.WriteLine("我的类型是:{0}",
this.GetType().ToString());
}
}
}
using System;
namespace AbstractFactory
{
class WindowsButton : Button
{
public override void DisplayButton()
{
Console.WriteLine("我的类型是:{0}",
this.GetType().ToString());
}
}
}
namespace AbstractFactory
{
public abstract class TextBox
{
public abstract void DisplayTextBox();
}
}
using System;
namespace AbstractFactory
{
class LinuxTextBox : TextBox
{
public override void DisplayTextBox()
{
Console.WriteLine("我的类型是:{0}",
this.GetType().ToString());
}
}
}
using System;
namespace AbstractFactory
{
class WindowsTextBox:TextBox
{
public override void DisplayTextBox()
{
Console.WriteLine("我的类型是:{0}",
this.GetType().ToString());
}
}
}
上面就是整个 Demo 的类了,下面就是看一下 Main 函数和效果了
using System;
using AbstractFactory;
namespace AbstractFactoryTest
{
class Program
{
static void Main(string[] args)
{
AbstractFactory.AbstractFactory factory;
Button button;
TextBox textBox;
//Windows 下操作
factory = new WindowsFactory();
button = factory.CreateButton();
textBox = factory.CreateTextBox();
button.DisplayButton();
textBox.DisplayTextBox();
Console.WriteLine();
//Linux 下操作
factory = new LinuxFactory();
button = factory.CreateButton();
textBox = factory.CreateTextBox();
button.DisplayButton();
textBox.DisplayTextBox();
Console.ReadLine();
}
}
}
从上面 Main 函数来看的话,如果你的系统本来是基于 Linux 的话,你只需更改一行代码
factory = new WindowsFactory();
即可实现将系统更改为 Windows ,
抽象工厂在这种情况下是非常有用的,比如,如果要实现后台数据库从 Oracle 转换到 Sql Server,
则采用抽象工厂的思想实现是最好的。
下面总结一下抽象工厂的优缺点
首先,抽象工厂的话,其可以更加方便的实现交换一个产品系列,
就像上面的 Demo 中可以轻易的实现从 Linux 上转换为 Windows,
同时,客户端代码中依赖的是抽象,而非具体的实现,
但是,抽象工厂也是有缺点的,其实这个缺点也很明显,那就是显得过于臃肿,
上面的 Demo 尽管还只有两个产品族,类图就显得有些难看了,
如果产品族一多的话,那么总的类数是成几倍的增加,这样使整个结构变得过于复杂,
类的结构也会变得更为庞大。
尾声
上面呢,接连介绍了简单工厂,工厂方法,抽象工厂,
整个工厂模式的介绍就到此告一段落了。