我什么时候应该使用抽象工厂模式

时间:2014-10-10 02:47:58

标签: c# oop design-patterns

我一直在阅读抽象工厂模式,但即使我理解它,我也不知道何时使用它。模式的定义说它用于抽象对象实例化,但我不清楚为什么我需要这样做,我为什么要关心对象实例化以及为什么(或何时)对抽象很重要它

4 个答案:

答案 0 :(得分:2)

该模式的主要好处是它通过将对象创建与对象使用分离来实现干净和可重用的代码。

不幸的是,我没有方便的C#示例,但本周我使用的示例用例是设计一个需要打开套接字的对象。这是在Android上,某些版本错误地实现了SSL / TLS。

要解决此错误,在这些Android版本上,我们必须对SSL环境进行一些大量自定义才能成功与后端通信。使用抽象工厂允许我们编写套接字客户端,这样它就不需要知道任何有关凌乱细节的信息 - 它只是有一个工厂来获取套接字。

一个例子:

// this is pretty gross, but what can you do
public class SocketFactorySupplier implements Supplier<SSLSocketFactory> {
  @Override public SSLSocketFactory get() {
    if (androidVersion >= 2.1 && androidVersion <= 2.6) {
      return createShiftyWorkaround();
    } else {
      return getDefaultSocketFactory();
    }
  }

  // here are ~500 lines of SSL code
}

...

public class NetworkClient {
  private final Supplier<SSLSocketFactory> supplier;
  private Socket socket;

  public NetworkClient(Supplier<SSLSocketFactory> supplier) {
    this.supplier = supplier;
  }

  public void connect() {
    socket = supplier.get().createSocket();
    socket.connect();

    // code that doesn't care about SSL at all and is simpler for it
  }
}

这显然不是真正的代码,但它展示了抽象工厂模式的主要好处:

  • NetworkClient代码更清晰,因为它不关心如何构建套接字
  • 通过提供模拟套接字,将其与网络隔离,可以轻松测试客户端
  • SSL逻辑可以在需要套接字的其他类中重用

答案 1 :(得分:1)

让我们假装你正在编写一个需要与数据库通信的应用程序。你可以有一个Database类,它像工厂一样用Database.CreateCommand方法创建命令。如果您需要使用不同的数据库引擎,则每个引擎都需要Database的不同实现。

您可能在运行时不知道您将需要哪个命令工厂,因此您创建一个DatabaseManager类,其具有DatabaseManager.GetDatabase(databaseType)函数,该类返回特定类型的DataBase。 DatabaseType可能来自配置文件,因此可以轻松更改。

在此示例中,每个Database都是常规工厂,而DatabaseManager则是抽象工厂。这是一家创建其他工厂的工厂。

所以你可以在本质上做这样的事情:

Dim sqlCommand as ICommand = DatabaseManager.GetDatabase("MsSQL").CreateCommand

Dim oracleCommand as ICommand = DatabaseManager.GetDatabase("Oracle").CreateCommand

答案 2 :(得分:1)

抽象工厂模式是一个对学术范例没有多大意义的模式。我更愿意考虑设计模式不是基于它们的实现方式,而是基于它们要解决的问题。在这种情况下,抽象工厂模式适用于您的工厂需要根据编译时无法获得的信息进行更改的情况。要以不同的方式说明,当您希望工厂根据仅在您运行时可用的信息创建不同类型的产品时,它会很有用。功能上的差异包含在工厂产品本身中,而不是消耗它们的代码中。

我见过的最好的学术实例是单元测试。当您对工厂产品的消费者进行测试时,您通常不需要产品的完整重量级功能,而是需要简单的填充,以便您可以隔离消费者的逻辑。使用工厂模式,这不起作用。使用抽象的工厂模式,它是微不足道的。您没有一个总是使用的工厂,而是拥有2个工厂 - 一个生产虚拟对象,另一个生产真实产品。单元测试运行时使用虚拟工厂,正常运行时使用实际工厂。

另一个很好的例子是跨平台支持。您可能希望您的工厂生产不同的产品,具体取决于您是否在OSX,Windows,Linux,IOS,Android等上运行。抽象工厂允许您为每个操作系统实施工厂,并选择适当的工厂在运行时。

这是一个非常简单的用例。额外的抽象层允许您将各种OO设计策略应用于工厂层次结构,最明显的是继承。我发现抽象工厂在与dependency injection结合使用时特别强大,无论您是在Unity等托管容器中还是手工处理(这仍然是一个很好的设计实践) )。

答案 3 :(得分:0)

Factories的主要优点是它们允许调用对象保持简单,而不必在将新功能添加到应用程序时进行更改。

假设您有一个简单的购物车用户界面,并且您的公司有一个产品。这个非常具体的代码最初会正常工作。

public class MyShoppingCartUI()
{
    private List<IProduct> _productsInCart = new List<IProduct>();

    public void ClickAddProductButton()
    {
        IProduct product = new ProductOne();
        _productsInCart.Add(product);
    }
}

但是当您的公司添加新产品时,您必须更改您的UI代码以直接引用新产品。因此,您必须将UI代码更改为:

public class MyShoppingCartUI()
{
    private List<IProduct> _productsInCart = new List<IProduct>();

    public void ClickAddProductOneButton()
    {
        IProduct product = new ProductOne();
        _productsInCart.Add(product);
    }

    public void ClickAddProductTwoButton()
    {
        IProduct product = new ProductTwo();
        _productsInCart.Add(product);
    }
}

正如您所看到的,每次有新产品时,您都必须添加更多代码,并且您的UI类越做越大。为什么您的购物车界面取决于可用的产品?

这可以通过工厂解决。这是一个解耦的代码示例:

public class ProductFactory()
{
    public IProduct Create(string productName)
    {
        if (productName == "Product1")
            return new ProductOne();
        else if (productName == "Product2")
            return new ProductTwo();            
    }
}

public class MyShoppingCartUI()
{
    private ProductFactory _factory = new ProductFactory();
    private List<IProduct> _productsInCart = new List<IProduct>();

    public void AddItem(string productName)
    {
        IProduct product = _factory.Create(productName);
        _productsInCart.Add(product);
    }
}

这样,无论您添加多少产品,您都不必更改UI代码,因为它不关心您制作的产品。您可以使用产品名称或ID的列表绑定所有控件,Factory将为您提供这些对象。

另一个优点是应用程序的其余部分也可以使用Factory来获取它的Product对象,因此您不必单独维护创建Product Objects的代码。无论您的应用程序有多大,工厂类中只有1-2个新行将永远覆盖您。