设计模式-工厂

  动机

  工厂设计模式可能是在现代编程语言如:Java 和 C# 中使用最广泛的设计模式。它有几种不同的变化和实现。如果你正在寻找它,你最有可能找到引用自 GoF 模式:工厂方法和抽象工厂。

  在本文中,我们将介绍一个当今流行的常用的工厂设计模式。你也可以比较一下原来的工厂方法模式,它们非常类似。 

 

  目的

  创建对象,而不对客户暴露实例化逻辑。

  通过一个公共接口引用新建对象。

 

  实现

  Factory Implementation - UML Class Diagram

  实现非常简单

  客户需要一个产品,但使用一个新操作来取代直接创建它,它向工厂对象要求一个新产品,提供关于它需要的这个对象的信息。

  工厂实例化一个新的具体产品,然后把这个新建的产品返回给客户(转换成抽象产品类)。

  客户以抽象产品来使用这个产品,而不需要清楚它们具体的实现。

 

  适用范围和示例

  工厂模式可能是最常用的模式之一。

  例如一个处理图形的图像应用,在我们的实现中,绘图的框架是客户,图形是产品。所有的图形都派生自一个抽象图形类(或者接口)。这个图形类定义必须被具体图形实现的绘画和移动方法。我们假设一个命令是从菜单上选择来创建一个圆形。框架以一个字符串参数来接收这个图形类。工厂创建一个新的圆形并且把它转换为一个抽象图形返回给该框架。然后框架以抽象类使用这个对象,而不需要清楚具体对象的类型。

 

  特殊问题和实现

  处理方案  switch/case 新手实例化。

     Factory Noob Implementation - UML Class Diagram

  这也被称为带参数的工厂。产生方法可以写成产生多种产品类的对象,使用一个条件(一个方法参数引入或者读一些全局配置参数 -查看抽象工厂方法)来辨识应该创建对象的类型,如下所示:

public class ProductFactory{
    public Product createProduct(String ProductID){
        if (id==ID1)
            return new OneProduct();
        if (id==ID2) return
            return new AnotherProduct();
        ... // so on for the other Ids
        
        return null; //if the id doesn't have any of the expected values
    }
    ...
}

  这是最简单最直观的实现(我们称它为新手实现)。它的问题是一旦我们增加一个新的具体产品调用,我们就要修改这个工厂类。它不是很灵活,并且违反了开放-封闭原则。当然我们可以继承这个工厂类,但是我们不要忘了工厂类一般用作单例,继承意味着在代码中替换所有引用的工厂类。

 

  注册类  -使用反射

  如果你能使用反射,例如 Java 或 .NET 语言,你可以给这个工厂注册新产品类而不改变工厂本身。为了在不知道对象类型的情况下在工厂中创建对象,我们在产品类型和 productID 之间保持一个映射。在这种情况下当应用增加一个新产品,这个产品必须注册到工厂。      

 

class ProductFactory
{
    private HashMap m_RegisteredProducts = new HashMap();

    public void registerProduct (String productID, Class productClass)
    {
        m_RegisteredProducts.put(productID, productClass);
    }

    public Product createProduct(String productID)
    {
        Class productClass = (Class)m_RegisteredProducts.get(productID);
        Constructor productConstructor = cClass.getDeclaredConstructor(new Class[] { String.class });
        return (Product)productConstructor.newInstance(new Object[] { });
    }
}

  我们可以在我们的代码的任何位置放置注册代码,但是一个方便的位置是放在静态构造器的产品类中,如下例所示:

  1.在产品类外完成注册:

    public static void main(String args[]){
        Factory.instance().registerProduct("ID1", OneProduct.class);
    } 

  2.在产品类中完成注册:

class OneProduct extends Product
{
    static {
        Factory.instance().registerProduct("ID1",OneProduct.class);
    }
    ...
}

  我们必须确保具体产品类在工厂注册之前被加载。为了保证这点,我们准备在 main 类中的静态部分使用 Class.forName 方法。这个部分在 main 类加载后被执行。 Class.forName 返回一个指明类的实例。如果该类还没有被编译器加载,它会在 Class.forName 被调用时加载。因此每个类中的静态区都会在该类加载时被调用:

class Main
{
    static
    {
        try
        {
            Class.forName("OneProduct");
            Class.forName("AnotherProduct");
        }
        catch (ClassNotFoundException any)
        {
            any.printStackTrace();
        }
    }
    public static void main(String args[]) throws PhoneCallNotRegisteredException
    {
        ...
    }
}

该反射实现有它的缺点。主要的一个是性能。在代码中使用该反射比不用反射,性能可能要低 10%。另一个问题在于不是所有的语言都提供反射机制。

 

  注册类  -避免反射

  如我们上段看到的那样,工厂对象在内部使用一个 HashMap 来保持参数(在我们的用例中是 Strings)和具体产品类。注册过程是在工厂外部,因此使用反射,工厂可以创建对象,而不用清楚对象的类型。

  我们不想用反射,同时我们要使工厂和具体产品之间松耦合。既然工厂应该不清楚产品,我们必须把对象的创建过程移到工厂外部的一个清楚具体产品的类的对象中。

  我们在产品抽象类中增加一个抽象方法。每个具体类将实现这个方法来创建一个和其自身类型一样的新对象。我们还必须改变注册方法, 我们将注册具体产品对象,而不是类对象。  

abstract class Product
{
    public abstract Product createProduct();
    ...
}

class OneProduct extends Product
{
    ...
    static
    {
        ProductFactory.instance().registerProduct("ID1", new OneProduct());
    }
    public OneProduct createProduct()
    {
        return new OneProduct();
    }
    ...
}

class ProductFactory
{
    public void registerProduct(String productID, Product p)    {
        m_RegisteredProducts.put(productID, p);
    }

    public Product createProduct(String productID){
        ((Product)m_RegisteredProducts.get(productID)).createProduct();
    }
}

 

  一个更先进的方案  -抽象的工厂设计模式(工厂方法)

Factory Design Pattern With Abstractions - UML Class Diagram

  这个实现展现了除了类注册实现的另一个选择。我们假设需要给应用增加一个新产品。 如果过程式 swith-case 实现 我们需要改变这个工厂类,如果是类注册实现,我们所需要做是把这个类注册给工厂,而不实际修改工厂类。当然这是一个灵活的方案。

  过程化实现是开放-封闭原则的典型糟糕样例。 当然我们知道还有更直观的方案来避免修改工厂类,那就是继承它。

  这是工厂方法模式的典型实现。类注册实现有一些优势和劣势:

  + 当对象被创建时(可能是基于一些参数的初始化),派生的工厂方法可以变为执行一些附加操作。

  -  工厂不能用作单例。

  -  每个工厂在使用前必须先初始化。

  -  实现比较困难。

  -  如果需要增加一个新对象,就必须创建一个新工厂。

  不管怎么说,经典实现还是有好处的,那就是它会帮助我们理解抽象工厂设计模式。

     

  结论

  当你设计一个应用时,请想想你是否真的需要一个工厂来创建对象。可能使用它会带来不必要的复杂度。如果你有许多同一基础类型的对象,你多数会把它们转换成抽象类型来使用,那么你就需要一个工厂。如果你有很多像下面一样的代码,你就应该重新考虑它了:

(if (ConcreteProduct)genericProduct typeof )
    ((ConcreteProduct)genericProduct).doSomeConcreteOperation().

  如果你决定采用工厂,我建议使用一种类注册实现(反射或不反射), 避免工厂方法(抽象的工厂设计模式)。请注意过程式 switch-case(新手) 实现是最简单的,违反了 OCP 原则,仅用来解释原理。只有在临时的模块使用,直到替换成真正的工厂方法才是明智的。

 

posted on 2016-02-02 09:56  Sky.Y.Chen  阅读(108)  评论(0)    收藏  举报