65.9K
CodeProject 正在变化。 阅读更多。
Home

工厂模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (23投票s)

2010 年 3 月 27 日

CPOL

7分钟阅读

viewsIcon

87025

downloadIcon

979

一种设计模式,可以使您的应用程序架构更灵活、更脆弱。

factory.png

引言

工厂模式是软件工程领域中使用最广泛的模式。这种模式引入了类之间的松散耦合,这是在设计应用程序架构时应考虑和应用的最重要原则。通过针对抽象实体而非具体实现进行编程,可以在应用程序架构中引入松散耦合。这不仅使我们的架构更灵活,也更不易碎。

使用 NEW 时考虑耦合

当你在 .NET 或 Java 中使用“new”关键字和/或在其他语言中使用newObj时,你不仅是在创建一个对象,而且在底层你还在应用程序中引入了耦合。这并不意味着你不再需要使用它,因为它是实例化对象的唯一方法,但我们应该找到一种更有效的方法来做这件事,因为我们必须考虑到软件工程中(甚至在生活中始终不变的)一个重要因素,那就是“变化”。让我们看一个例子:

VB

Dim myBat As New HardBallBat()

C#

Bat myBat = new HardBallBat();

当有一组相关联的类时,我们通常会扩展这种方法,例如:

VB

If (hardball)
   Dim myBat As Bat = New HardBallBat()
ElseIf (tapeball)
   Dim myBat As Bat = New TapeBallBat()
ElseIf (plasticball)
   Dim myBat As Bat = New PlasticBallBat()

C#

if (hardball)
    Bat myHardBallBat = new HardBallBat();
else if (tapeball)
    Bat myTapeBallBat = new TapeBallBat();
else if (plasticball)
    Bat myPlasticBallBat = new PlasticBallBat();

在这里,我们根据运行时评估的条件实例化几个具体实现。那么,这种方法有什么问题呢?你将来可能会添加几十个类,也可能会删除一些对你无用的类,这表明这段代码不是封闭修改的,你必须重新打开它并进行修改,但一个好的架构应该遵循一个原则,即设计应该对扩展开放,但对修改关闭。而且这种代码也可能分散在你的应用程序的各个地方,这可能是一个维护的噩梦。

分离变化的部分

现在让我们扩展上面提供的例子,假设街边有一家商店销售高质量的球棒,你被指派为该商店开发一个应用程序,你最终可能会在其OrderBat()方法中编写类似这样的代码:

VB

    Public Function OrderBat() As Bat

            Dim myBat As New Bat()

            myBat.clean()
            myBat.applyGrip()
            myBat.applyLogo()
            myBat.applyCover()
            myBat.pack()

            Return myBat

    End Function

C#

public Bat OrderBat()
{
    Bat myBat = new Bat();

    myBat.clean();
    myBat.applyGrip();
    myBat.applyLogo();
    myBat.applyCover();
    myBat.pack();

    return myBat;
}

这个例子的扩展版本可能是在运行时根据客户选择的球棒进行条件评估,并且每个具体实现都扩展了基类“Bat”。因此:

VB

    Private Function OrderBat(ByVal choice As String) As Bat

        Dim myBat As Bat

        Select Case choice

            Case "hardball"
		myBat = New HardBallBat()

            Case "softball"
		myBat = New SoftBallBat()

            Case "plasticball"
		myBat = New PlasticBallBat()

        End Select

        myBat.clean()
        myBat.applyGrip()
        myBat.applyLogo()
        myBat.applyCover()
        myBat.pack()

	Return myBat

    End Function

C#

private Bat OrderBat(string choice)
{
   Bat myBat = default(Bat);

   switch (choice) 
   {
         case "hardball":
            myBat = new HardBallBat();
            break;
        case "softball":
            myBat = new SoftBallBat();
            break;
        case "plasticball":
            myBat = new PlasticBallBat();      
            break;
    }

   myBat.clean();
   myBat.applyGrip();
   myBat.applyLogo();
   myBat.applyCover();
   myBat.pack();

   return myBat;
}

现在你可能已经分析出,这种方法没有对修改关闭,因为如果将来他们决定销售任何新的球棒或想从他们的收藏中删除一些东西,你必须再次重新打开它并进行修改,但你也可能已经分析出,该方法中还有一些其他部分不需要改变,或者至少将来没有改变的可能性,即:

myBat.clean()
myBat.applyGrip()
myBat.applyLogo()
myBat.applyCover()
myBat.pack()

那么为什么不把代码中会变化的部分分离出来,这样我们只需要担心我们代码的那一部分,即:

VB

Select Case choice

    Case "hardball"
	myBat = New HardBallBat()

    Case "softball"
	myBat = New SoftBallBat()

    Case "plasticball"
	myBat = New PlasticBallBat()

End Select

C#

switch (choice) 
{  
   case "hardball": 
      myBat = new HardBallBat(); 
      break; 

   case "softball": 
      myBat = new SoftBallBat(); 
      break; 

   case "plasticball": 
      myBat = new PlasticBallBat(); 
      break; 
}

既然我们已经知道代码中哪些在变,哪些不变,我们现在就可以封装它了。但是等等!我们看到的变化的代码包含了对象创建逻辑,我们想将其与客户端隔离,那么我们应该将其放在哪个方法中呢?没错,工厂方法!因为任何处理对象创建的方法都可以称为工厂方法。

简单的球棒工厂

我们的客户端将使用这个工厂来获取它想要的对象,这种模式的优点是它不让客户端改变它的代码来获取它想要的东西,客户端只需向工厂索取它需要的对象,因为这就是工厂的作用,所以让我们定义一个简单的Bat工厂:

VB

    Public MustInherit Class BatFactory
        MustOverride Function CreateBat(ByVal choice As String) As Bat
    End Class

C#

public abstract class BatFactory
{
    public abstract Bat CreateBat(string choice);
}

这是我们的基础工厂类,所有想要向我们供应球棒的工厂都必须实现它,因为这是我们的客户端识别并只能与之合作的。所以让我们定义它:

VB

    Public Class SimpleBatFactory
        Inherits BatFactory

        Overrides Function CreateBat(ByVal choice As String) As Bat

            Dim myBat As Bat

            Select Case choice

                Case "hardball"
                    myBat = New HardBallBat()

                Case "softball"
                    myBat = New SoftBallBat()

                Case "plasticball"
                    myBat = New PlasticBallBat()

            End Select

            Return myBat

        End Function

    End Class

C#

public class SimpleBatFactory : BatFactory
{  
    public override Bat CreateBat(string choice)
    {
        Bat myBat = default(Bat);
 switch (choice) 
{
            case "hardball":
                myBat = new HardBallBat();
                break;
            case "softball":
                myBat = new SoftBallBat();
                break;
            case "plasticball":
                myBat = new PlasticBallBat();
                break;
        }
        return myBat;
    }
   }

现在,我们的客户端将简单地使用CreateBat方法来获取客户选择的球棒,前提是返回的对象实现了所需的接口。我们现在可以修改客户端代码,这将是我们对客户端类的最后一次修改,即:

VB

 Public Function OrderBat(ByVal choice As String) As Bat

            Dim myBat As Bat = _factory.CreateBat(choice)

            myBat.clean()
            myBat.applyGrip()
            myBat.applyLogo()
            myBat.applyCover()
            myBat.pack()

            Return myBat

        End Function

C#

public Bat OrderBat(string choice)
{
    Bat myBat = _factory.CreateBat(choice);
    myBat.clean();
    myBat.applyGrip();
    myBat.applyLogo();
    myBat.applyCover();
    myBat.pack();
    return myBat;
}

我们的Order Bat方法现在将对象创建的职责委托给了我们的工厂方法。总而言之,客户端类看起来将是这样的:

VB

    Public Class SuperShop

        Dim _factory As BatFactory

        Public Sub New(ByVal factory As BatFactory)

            Me._factory = factory

        End Sub

        Public Function OrderBat(ByVal choice As String) As Bat

            Dim myBat As Bat = _factory.CreateBat(choice)

            myBat.clean()
            myBat.applyGrip()
            myBat.applyLogo()
            myBat.applyCover()
            myBat.pack()

            Return myBat

        End Function

    End Class

C#

public class SuperShop
{
    BatFactory _factory;
    public SuperShop(BatFactory factory)
    {
        this._factory = factory;
    }
    public Bat OrderBat(string choice)
    {
        Bat myBat = _factory.CreateBat(choice);
        myBat.clean();
        myBat.applyGrip();
        myBat.applyLogo();
        myBat.applyCover();
        myBat.pack();
        return myBat;
    }
}

现在实例化SuperShop对象时,我们只需传入我们希望从中获取球棒的工厂引用,例如:

VB

'A factory that will handle the object creation
Dim factory As New SimpleBatFactory()

'Our shop will now use this factory to create the required object
Dim shop As New SuperShop(factory)
shop.OrderBat("hardball")

C#

//A factory that will handle the object creation
SimpleBatFactory factory = new SimpleBatFactory();
   
//Our shop will now use this factory to create the required object
SuperShop shop = new SuperShop(factory);
shop.OrderBat("hardball");

客户端无需担心对象实例化逻辑,因为它现在已由我们的工厂处理。

松散耦合和灵活性

这里要指出的另一件重要事情是,尽管我们已经有效地将代码与客户端类隔离,并且现在我们的维护集中在一个地方,但它也为我们的架构引入了灵活性和松散耦合。例如,如果将来 SuperShop 决定从其他制造商而不是SimpleBatFactory获取球棒,我们只需传递该工厂的实例,简单明了对吗?例如:

VB

 Public Class CABatFactory
        Inherits BatFactory

        Overrides Function CreateBat(ByVal choice As String) As Bat

            Dim myBat As Bat

            Select Case choice

                Case "hardball"
                    myBat = New CAHardBallBat()

                Case "softball"
                    myBat = New CASoftBallBat()

                Case "plasticball"
                    myBat = New CAPlasticBallBat()

            End Select

            Return myBat

        End Function

    End Class

C#

public class CABatFactory : BatFactory
{
    public override Bat CreateBat(string choice)
    {
        Bat myBat = default(Bat);
        switch (choice) {
            case "hardball":
                myBat = new CAHardBallBat();
                break;
            case "softball":
                myBat = new CASoftBallBat();
                break;
            case "plasticball":
                myBat = new CAPlasticBallBat();
                break;
        }
        return myBat;
    }
}

这个工厂现在将为我们提供来自我们首选制造商的球棒,我们的对象实例化现在将使用这个工厂:

VB

Dim factory As New CABatFactory()

Dim shop As New SuperShop(factory)
shop.OrderBat("hardball")

C#

CABatFactory factory = new CABatFactory(); 

SuperShop shop = new SuperShop(factory); 
shop.OrderBat("hardball");

正如你所看到的,我们根本没有改变客户端代码,现在客户端能够处理由我们选择的工厂返回的对象。

更多控制

上面已经描述了一种工厂模式的方法,但如果你想对对象的实例化方式有更多的控制,并且只有你想要的东西才允许第三方修改怎么办?假设 SuperShop 希望在全国各地开设特许经营店,但他们也希望有一些质量控制,以便特许经营商可以从他们首选的供应商那里采购球棒,但其他质量措施不能以任何方式更改!你不觉得他们真的需要一个框架来轻松管理特许经营商的所有要求吗?

球棒工厂的框架

SuperShop 可以向他们的特许经营商提供这个框架,这样他们就可以自己实现对象创建逻辑,但仍然有权管理和强制执行他们的质量措施。让我们看看我们BatFactory类的修改版本。

VB

    Public MustInherit Class BatFactory

        Public Function OrderBat(ByVal choice As String) As Bat

            Dim myBat As Bat = Me.CreateBat(choice)

            myBat.clean()
            myBat.applyGrip()
            myBat.applyLogo()
            myBat.applyCover()
            myBat.pack()

            Return myBat

        End Function

        MustOverride Function CreateBat(ByVal choice As String) As Bat

    End Class

C#

public abstract class BatFactory
{
    public Bat OrderBat(string choice)
    {
        Bat myBat = this.CreateBat(choice);
        myBat.clean();
        myBat.applyGrip();
        myBat.applyLogo();
        myBat.applyCover();
        myBat.pack();

      return myBat;
    }
    public abstract Bat CreateBat(string choice);
}

那么,我们在这里做了什么?我们只是将工厂方法包含在我们的基类(一个抽象)中,它将对象创建的职责委托给其派生类。现在,这个类的实现者不能改变我们的任何质量措施,因为我们已经将它们本地化了,另一方面,他们可以通过覆盖CreateBat()函数自由地提供自己的对象实例化逻辑。OrderBat函数并不真正关心对象是如何创建的,只要返回的类型实现了所需的接口即可。

不止如此

在结束本文之前,为什么不让我们的一个加盟商实现我们的框架,看看他们将如何实际使用它呢?

VB

  Public Class SuperShopFranchisee
        Inherits BatFactory

        Overrides Function CreateBat(ByVal choice As String) As Bat

            Dim myBat As Bat

            Select Case choice

                Case "hardball"
                    myBat = New AddidassHardBallBat()

                Case "softball"
                    myBat = New AddidassSoftBallBat()

                Case "plasticball"
                    myBat = New AddidassPlasticBallBat()

            End Select

            Return myBat

        End Function

    End Class

C#

public class SuperShopFranchisee : BatFactory
{
    public override Bat CreateBat(string choice)
    {
        Bat myBat = default(Bat);
        switch (choice) 
       {
            case "hardball":
                myBat = new AddidassHardBallBat();
                break;
            case "softball":
                myBat = new AddidassSoftBallBat();
                break;
            case "plasticball":
                myBat = new AddidassPlasticBallBat();
                break;
        }
        return myBat;
    }
}

正如你所看到的,我们的加盟商已成功提供了自己的实现,现在他们只需实例化他们商店的对象,就可以开始了:

VB

        
  Dim franchisee As New SuperShopFranchisee
        franchisee.OrderBat("hardball")

C#

SuperShopFranchisee franchisee = new SuperShopFranchisee(); 
franchisee.OrderBat("hardball");

OrderBat 将确认所有加盟商的质量措施都已得到遵守,并将对象创建的责任委托给由我们的加盟商实现的工厂方法来获取对象,因为OrderBat只关心它需要的对象,而不关心创建逻辑,因为这就是工厂方法的目的。

一些理论

现在我想我们有能力理解官方定义究竟是如何描述这种模式的,毕竟,首先从定义中理解模式对我来说是相当困难的,因为设计模式更多的是一种实践性的东西。所以,我们开始吧:

“工厂方法模式定义了一个用于创建对象的接口,但让子类决定实例化哪个类。工厂方法让一个类将实例化推迟到子类。”

现在,这个定义奇迹般地开始对我有了意义!

结论

人们应该记住在自己面临的问题上已经完成的努力。这就像不要重新发明轮子,而是从别人的经验中学习!

参考文献

历史

  • 2010年3月27日:首次发布
© . All rights reserved.