理解和实现 C# 中的工厂模式





5.00/5 (25投票s)
在本文中,我们将尝试理解什么是工厂模式,该模式的优点以及如何在 C# 中实现该模式。
引言
在本文中,我们将尝试理解什么是工厂模式,该模式的优点以及如何在 C# 中实现该模式。
背景
应用程序只包含一个类几乎是不可能的。通常,一个应用程序将涉及许多类,每个类都有专门的职责,以实现所需的功能。这意味着类之间不可避免地需要进行通信。如果我们让类实例化它需要的类,然后调用这些类上的方法,就可以很容易地实现这一点。
因此,如果我们有一个类 A
想要调用类 B
的方法,我们可以在 A
中简单地拥有一个 B
的对象,并在需要时调用它的方法。代码将如下所示:
public class B
{
public void DoTaskOne()
{
Console.WriteLine("B.DoSomething");
}
}
public class A
{
private B b;
public A()
{
b = new B();
}
public void GetOneDone()
{
b.DoTaskOne();
}
}
这种将类实例包含在其他类中的方法是可行的,但它有一些缺点。第一个问题是每个类都需要知道它想要使用的所有其他类。这将使此应用程序成为维护的噩梦。此外,上述方法将增加类之间的耦合。
从最佳实践的角度来看,无论何时设计类,在类之间的依赖关系方面,我们都应该牢记依赖倒置原则。依赖倒置原则指出,高级模块应始终依赖于抽象,而不是直接依赖于低级模块。因此,我们应该始终以这样的方式设计类,使它们始终依赖于接口或 abstract
类,而不是其他具体类。
因此,我们在上面的示例中看到的类将会改变。我们首先需要一个接口,A
可以使用它来调用 DoTaskOne
。类 B
应该实现此接口。新类将如下所示:
interface IDoable
{
void DoTaskOne();
}
public class B : IDoable
{
public void DoTaskOne()
{
Console.WriteLine("B.DoSomething");
}
}
public class A
{
private IDoable doable;
public A()
{
// How to create the doable object here???
// doable = new B();
// This seems wrong
}
public void GetOneDone()
{
doable.DoTaskOne();
}
}
上面的代码显示了完美设计的类,其中高级模块依赖于抽象,低级模块实现这些抽象。但是等等……我们如何创建 B
的对象呢?我们是否仍然像以前的代码中那样做,即在 A
的构造函数中对 B
进行 new 操作?但这难道不会违背松耦合的整个目的吗?
这正是工厂模式有用的地方。工厂完全隐藏了创建对象的过程。工厂模式完全将创建类的职责从客户端类中抽象出来。这样做主要的优点是我们的客户端代码完全不了解依赖类的创建过程。因此,要在我们上面的代码中实例化实际的,我们必须在构造函数中执行以下操作:
public class DoableFactory
{
public B GetConcreteDoable()
{
return new B();
}
}
// Constructor of A
public A()
{
DoableFactory factory = new DoableFactory();
doable = factory.GetConcreteDoable();
}
这种松散耦合从可扩展性的角度来看也是很好的。有了工厂模式,客户端代码还可以使用多个依赖类,只要这些依赖类遵守契约,即实现接口。因此,在上面的示例中,客户端代码将不仅仅是调用工厂方法,还将提供一些信息,这些信息可用于识别需要创建的具体对象。
Using the Code
我们上面看到的例子有点牵强。为了理解工厂模式,让我们尝试实现一个示例应用程序。假设我们有一个电子商务应用程序,并且我们集成了两个支付网关。我们称这些支付网关为 BankOne
和 BankTwo
。如果订单金额低于 50 美元,BankOne
会对信用卡收取 2% 的费用;如果订单金额超过 50 美元,则收取 1% 的费用。另一方面,BankTwo
对所有金额收取固定的 1.5% 费用。
因此,我们的支付模块向用户提供三个选项:
银行一
银行二
- 最适合我
让我们首先看看我们的 Product
模型。这个产品模型将代表用户正在尝试购买的产品。
class Product
{
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
}
现在让我们首先看看 IPaymentGateway
接口。这个接口将定义所有支付网关,即银行类的类应该遵循的契约。
interface IPaymentGateway
{
void MakePayment(Product product);
}
注意:在实际世界中,MakePayment
也会接受用户信息来识别用户。我们这里不展示它,以使示例免于离题。
现在让我们创建包含通过调用银行特定 API 进行支付的实际代码的类。
public class BankOne : IPaymentGateway
{
public void MakePayment(Product product)
{
// The bank specific API call to make the payment
Console.WriteLine("Using bank1 to pay for {0}, amount {1}", product.Name, product.Price);
}
}
public class BankTwo : IPaymentGateway
{
public void MakePayment(Product product)
{
// The bank specific API call to make the payment
Console.WriteLine("Using bank2 to pay for {0}, amount {1}", product.Name, product.Price);
}
}
现在是时候创建我们的工厂类来处理创建这些对象的所有繁琐细节了。为了能够识别用户选择了哪种支付机制,让我们定义一个简单的 Enum
PaymentMethod
。
enum PaymentMethod
{
BANK_ONE,
BANK_TWO,
BEST_FOR_ME
}
工厂类将使用此 enum
来确定应该创建哪个支付网关具体类。现在让我们看看我们的工厂类实现。
public class PaymentGatewayFactory
{
public virtual IPaymentGateway CreatePaymentGateway(PaymentMethod method, Product product)
{
IPaymentGateway gateway = null;
switch(method)
{
case PaymentMethod.BANK_ONE:
gateway = new BankOne();
break;
case PaymentMethod.BANK_TWO:
gateway = new BankTwo();
break;
case PaymentMethod.BEST_FOR_ME:
if(product.Price < 50)
{
gateway = new BankTwo();
}
else
{
gateway = new BankOne();
}
break;
}
return gateway;
}
}
我们的工厂类正在做的是接受用户选择的支付网关,然后根据选择创建具体的支付网关类。正如我们所看到的,它有大量的逻辑来决定选择哪个支付网关,我们已经有效地将所有这些繁琐的细节从客户端代码中抽象出来。否则,每个想要使用支付网关的类都必须编写所有这些逻辑。现在让我们看看 client
类如何使用此工厂方法进行支付。
public class PaymentProcessor
{
IPaymentGateway gateway = null;
public void MakePayment(PaymentMethod method, Product product)
{
PaymentGatewayFactory factory = new PaymentGatewayFactory();
this.gateway = factory.CreatePaymentGateway(method, product);
this.gateway.MakePayment(product);
}
}
现在我们的客户端类不依赖于具体的支付网关类。它也不必担心具体支付网关类的创建逻辑。所有这些都很好地抽象在工厂类本身中。
工厂模式在使我们的客户端代码与依赖类解耦方面是一个非常有用的模式。它使应用程序更容易维护。它还使其非常容易扩展,因为可以添加新的具体类,而不会影响现有的具体类和客户端代码。
查看 GoF 工厂方法
GoF 将工厂方法定义为“定义一个用于创建对象的接口,但让子类决定实例化哪个类。工厂方法让一个类将其实例化延迟到子类”。如果我们查看工厂方法的类图(参考:GoF 设计模式)
让我们看看这些类中的每一个都代表什么
Product
:定义工厂方法创建的对象的接口(IPaymentGateway
)ConcreteProduct
:实现Product
接口(BankOne
,BankTwo
)Creator
:声明工厂方法,该方法返回一个Product
类型的对象(PaymentGatewayFactory
)ConcreteCreator
:重写工厂方法以返回ConcreteProduct
的实例
现在,如果我们将当前的实现与 GoF 工厂方法进行比较,我们有接口 IPaymentGateway
,它是工厂方法创建的对象的接口。我们有 BankOne
和 BankTwo
类,它们是 ConcreteProducts
。至于工厂类,我们使用一个单一的工厂类 PaymentGatewayFactory
,而不是拥有一个层次结构。但仔细观察,我们会发现我们的工厂类实际上是 GoF 模式的 Creator
类。唯一的区别是,我们的类带有一些抽象行为,而不是一个纯粹的 abstract
类。
那么,我们如何将 ConcreteCreator
插入并用于我们的设计呢?假设我们想创建更多具体的支付网关类,供应用程序的其他部分使用。为此,我们首先必须为新的具体类提供新的 enum
值,如下所示:
enum PaymentMethod
{
BANK_ONE,
BANK_TWO,
BEST_FOR_ME,
PAYPAL,
BILL_DESK
}
现在我们可以有一个派生自我们的 PaymentGatewayFactory
类的工厂类,它将包含这些新支付网关的逻辑。
public class PaymentGatewayFactory2 : PaymentGatewayFactory
{
public virtual IPaymentGateway CreatePaymentGateway
(PaymentMethod method, Product product)
{
IPaymentGateway gateway = null;
switch (method)
{
case PaymentMethod.PAYPAL:
// gateway = new PayPal();
break;
case PaymentMethod.BILL_DESK:
// gateway = new BillDesk();
break;
default:
base.CreatePaymentGateway(method, product);
break;
}
return gateway;
}
}
现在,无论我们想使用新添加的支付机制,我们只需创建 PaymentGatewayFactory2
而不是 PaymentGatewayFactory
,所有 5 个支付网关具体类都将可供客户端代码使用。
public class PaymentProcessor2
{
IPaymentGateway gateway = null;
public void MakePayment(PaymentMethod method, Product product)
{
PaymentGatewayFactory2 factory = new PaymentGatewayFactory2();
this.gateway = factory.CreatePaymentGateway(method, product);
this.gateway.MakePayment(product);
}
}
现在我们的 Creator 不是一个纯粹的 abstract
类,但它带有一些默认功能,可以被派生自它的具体工厂覆盖。
结论
在本文中,我们探讨了简单工厂模式以及工厂模式可能有用处的场景。我们使用 C# 实现了一个类工厂模式。本文是从初学者的角度编写的。希望本文能提供一些信息。
历史
- 2015 年 2 月 9 日:第一版