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

构建工厂方法模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (12投票s)

2013 年 5 月 7 日

CPOL

7分钟阅读

viewsIcon

30906

downloadIcon

189

GoF 工厂方法模式的基本实现

引言

我希望介绍一个非常基础的 GoF 设计模式实现。这并非旨在成为最简单的实现(一个更简单的实现当然可以从维基百科上关于这个主题的条目 http://en.wikipedia.org/wiki/Factory_method_pattern#Java 中推导出来)。目的是尝试演示工厂方法模式的作用,并给出一个简单的实现。本文将相对简短,并希望对于那些努力理解这个经常被误解的模式的人来说是清晰的。

请注意,这是一篇简短的新手文章。新手指的是刚接触这个设计模式的读者,要求读者理解基本的继承(尤其是接口)和可见性修饰符(主要是 internalpublic)。

背景 - 工厂方法模式是干什么用的?

我见过它的意图被定义为“给定一个创建对象的接口,但让子类决定实例化哪个类,然后子类使用工厂方法。工厂方法模式允许一个类将其实例化推迟到子类。”。在我看来,这更多地是在陈述它做什么,而不是清晰地解释它为什么这样做。让我们从基本原理开始。在 C 语言系列(或至少在我写作时我所知的语言)中,构造函数: 

  • 不能在接口中定义  
  • 必须使其容器类型被调用方已知 - 类型和构造函数都必须对调用代码可见,这样 MyType foo = new MyType() 才能工作  
假设我们要编写一个日志框架,并且需要创建一个假设的 ILogger 接口。在我们的框架中,我们将提供两个 ILogger 实现:FileLoggerSqlAndSql,以表示两个不同的日志目标。现在,假设出于某种原因,我们不想让“客户端”代码访问具体的类。在这种情况下,接口中不能定义构造函数,因此使用 ILogger 将不起作用,而且我们不能直接对这些类调用 new,因为我们故意使它们不可用。那么我们该怎么办?一种方法是使用工厂方法模式。

创建模式  

日志类 - 制造要克服的问题。  

如上所述,我们希望公开一些日志功能,第一步是创建 ILogger 接口。为简单起见,我将定义我们的接口如下:   

namespace LoggerClassLibrary
{
    public interface ILogger
    {
        string Log();
    }
}    

那里没什么复杂的。现在我们要创建前面提到的接口的两个实现:SqlLoggerDiskLogger。在这篇文章中,我将提供一个假的、存根实现,以保持代码的简单性,从而避免分散文章本身的注意力。这些类将是: 

namespace LoggerClassLibrary.ConcreteLoggers
{
    internal class SqlLogger : ILogger
    {
        public string Log()
        {
            return "Logging to SQL";
        }
    }
} 

 和 

namespace LoggerClassLibrary.ConcreteLoggers
{
    internal class DiskLogger : ILogger
    {
        public string Log()
        {
            return "Logging to Disk";
        }
    }
}

请注意,以上所有内容都保存在同一个程序集中,并且我已经将具体类声明为 internal。这导致任何“客户端”代码都无法访问具体实现,这些实现对日志框架程序集是不可见的(至少无法在不进行一些耍花招的情况下访问 微笑 | <img src= " /> )。由于 ILogger 接口是 public 而不是 internal,因此它可以从 DLL 外部(因此对客户端)访问,所以我已经为我们的问题设置了条件:无法访问的具体类,以及由此产生的无法访问的构造函数。我们如何让日志框架的用户实际进行日志记录?

重构以实现工厂方法模式    

迈向一个简洁解决方案的第一步是将具体日志类(logger classes)的创建抽象到客户端可以访问的类型中。这是一个非常简单的步骤,我们在框架程序集中添加以下内容:  

public class SqlLoggerCreator
{
    public ILogger CreateLogger()
    {
        return new SqlLogger();
    }
}  

对于磁盘日志记录器,有一个几乎相同的类: 

public class DiskLoggerCreator
{
    public ILogger CreateLogger()
    {
        return new DiskLogger();
    }
}  

到目前为止,我们可以休息一下,因为我们已经解决了问题,客户端可以调用这些代码。但我们比这更聪明 眨眼 | <img src= " />。日志框架的用户不会想写这样的代码:    

public void Log(DiskLoggerCreator  creator)
{
    ILogger logger = creator.CreateLogger();
    Console.WriteLine(logger.Log());
}

public void Log(SqlLoggerCreator  creator)
{
    ILogger logger = creator.CreateLogger();
    Console.WriteLine(logger.Log());
}       

这不仅是重复工作(不好),而且如果添加了额外的具体日志记录器类型,我们的用户将需要实现上面这样的新方法。由于被调用的方法是相同的(我承认 - 这是故意的 微笑 | <img src= " />),我们可以进行进一步的抽象:我们可以让创建者类实现一个共同的接口,这样它们就成为我们第二个迭代中的样子:  

public interface ILoggerCreator
{
    ILogger CreateLogger();
}

public class DiskLoggerCreator : ILoggerCreator
{
    public ILogger CreateLogger()
    {
        return new DiskLogger();
    }
}

public class SqlLoggerCreator : ILoggerCreator
{
    public ILogger CreateLogger()
    {
        return new SqlLogger();
    }
}   

现在,从我们用户的角度来看,客户端代码变得更易于管理(以及我稍后将列出的其他好处):  

public void Log(ILoggerCreator creator)
{
    ILogger logger = creator.CreateLogger();
    Console.WriteLine(logger.Log());
}    

 

这现在实际上是工厂方法模式的一种实现(尽管我们还没有详细介绍,但它实现了引言中描述的意图),而且我们也实现了我们的目标! 

如果您查看示例代码,主程序会依次调用每个日志记录器,但它本身不访问具体类,因此我们成功地隔离了它们。在此过程中,我们还必然通过在基本接口中定义的创建者类来创建具体类实例。在某种意义上,我们创建了一个“虚拟构造函数”——这是该模式的另一个名称。

从我们的代码理解工厂方法模式  

 首先,我们应该回顾一下我们所做的:  

  1. 要生成的具体类(SqlLogger DiskLogger)有一个基本接口 ILogger 。
  2. 由于具体类不可访问,我们最终定义了一个创建者接口,该接口定义了一个返回 ILogger 的方法。    
  3. 对于每种具体类型,我们定义一个具体的创建者类型(SqlLoggerCreatorDiskLoggerCreator)。它们实现了一个共同的接口 ILoggerCreator,该接口定义了执行返回 ILogger 的工作的方法 

用 UML 表示(忽略具体类侧的可见性——现在没那么重要):  

 

我们可以去除实现特定的内容,以产生该模式的一个实现  

  

可以将其进一步简化,以产生维基百科页面上的图表 (http://en.wikipedia.org/wiki/Factory_method_pattern)。最大的区别在于将创建者与其创建的类链接的“依赖”箭头。此外,维基百科条目中的 UML 图只处理一种“产品”类型,因此只有一个创建者类。 

请注意,我们可以用基类或(更好的选择)抽象类替换图中的所有接口,并且它仍然会起作用。我选择了接口,因为它们提供了最低的耦合度和复杂性,它们也更适合单元测试,因为我可以模拟我的实现,所以我认为接口是这三种继承选项中最好的。  

其他好处    

现在我们已经构建了模式,它还给我们带来了什么?   

  • 我们可以在创建方法中放置复杂的设置逻辑。这尤其有用,因为设置逻辑自然不属于被构造的类或使用它的类。  
  • 设置中常用的代码也可以放在创建方法中——这消除了代码重复。     
  • 我们现在可以给构造类的这个方法一个更具描述性的名称——直接调用构造函数时,我们受限于类型名称。   
  • 遵循模式的设计——被创建接口的用户可以使用他们完全不知道的类型——他们只需要访问这些类型的创建者即可。 
  • 我们在创建方法中不限于创建一个新实例——例如,我们可以返回一个单例实例。    

值得关注的点  

如引言所述,这不是最简单的实现,这也表明它不是该模式的唯一实现,尽管它是一种常见的实现。  

关于这个模式似乎有很多困惑。一种常见的(形式上不正确的)实现方法是做类似下面的事情:  

public class Factory
{
     public IWhatever Create(int discriminator)
    {
        switch (discriminator)
        {
            case 1:
                return new Whatever1(); 
            break; 
            case 2: 
                return new Whatever2(); 
            break;    
        }  
    }  
}     

其中斜体是使用的接口/类型(在我们的例子中是 ILoggerDiskLoggerSqlLogger),其中鉴别器可以是用于定义发出哪种类型的任何类型。虽然它通常被称为工厂方法(可能比本文中的更合理!),但它不是工厂方法模式的实现。 

当你在现实世界中使用工厂方法模式时,它随着时间的推移往往会变得不充分。通常会应用一个不同但密切相关的模式——抽象工厂模式。  

历史   

2013/05/07 - 初稿

2013/05/08 - 整理和澄清(希望如此) 

© . All rights reserved.