构建工厂方法模式






4.95/5 (12投票s)
GoF 工厂方法模式的基本实现
引言
我希望介绍一个非常基础的 GoF 设计模式实现。这并非旨在成为最简单的实现(一个更简单的实现当然可以从维基百科上关于这个主题的条目 http://en.wikipedia.org/wiki/Factory_method_pattern#Java 中推导出来)。目的是尝试演示工厂方法模式的作用,并给出一个简单的实现。本文将相对简短,并希望对于那些努力理解这个经常被误解的模式的人来说是清晰的。
请注意,这是一篇简短的新手文章。新手指的是刚接触这个设计模式的读者,要求读者理解基本的继承(尤其是接口)和可见性修饰符(主要是 internal
和 public
)。
背景 - 工厂方法模式是干什么用的?
我见过它的意图被定义为“给定一个创建对象的接口,但让子类决定实例化哪个类,然后子类使用工厂方法。工厂方法模式允许一个类将其实例化推迟到子类。”。在我看来,这更多地是在陈述它做什么,而不是清晰地解释它为什么这样做。让我们从基本原理开始。在 C 语言系列(或至少在我写作时我所知的语言)中,构造函数:
- 不能在接口中定义
- 必须使其容器类型被调用方已知 - 类型和构造函数都必须对调用代码可见,这样
MyType foo = new
才能工作MyType
()
ILogger
接口。在我们的框架中,我们将提供两个 ILogger
实现:FileLogger
和 SqlAndSql
,以表示两个不同的日志目标。现在,假设出于某种原因,我们不想让“客户端”代码访问具体的类。在这种情况下,接口中不能定义构造函数,因此使用 ILogger
将不起作用,而且我们不能直接对这些类调用 new
,因为我们故意使它们不可用。那么我们该怎么办?一种方法是使用工厂方法模式。
创建模式
日志类 - 制造要克服的问题。
如上所述,我们希望公开一些日志功能,第一步是创建 ILogger
接口。为简单起见,我将定义我们的接口如下:
namespace LoggerClassLibrary
{
public interface ILogger
{
string Log();
}
}
那里没什么复杂的。现在我们要创建前面提到的接口的两个实现:SqlLogger
和 DiskLogger
。在这篇文章中,我将提供一个假的、存根实现,以保持代码的简单性,从而避免分散文章本身的注意力。这些类将是:
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
。这导致任何“客户端”代码都无法访问具体实现,这些实现对日志框架程序集是不可见的(至少无法在不进行一些耍花招的情况下访问 " /> )。由于
ILogger
接口是 public
而不是 internal
,因此它可以从 DLL 外部(因此对客户端)访问,所以我已经为我们的问题设置了条件:无法访问的具体类,以及由此产生的无法访问的构造函数。我们如何让日志框架的用户实际进行日志记录?
重构以实现工厂方法模式
迈向一个简洁解决方案的第一步是将具体日志类(logger classes)的创建抽象到客户端可以访问的类型中。这是一个非常简单的步骤,我们在框架程序集中添加以下内容:
public class SqlLoggerCreator
{
public ILogger CreateLogger()
{
return new SqlLogger();
}
}
对于磁盘日志记录器,有一个几乎相同的类:
public class DiskLoggerCreator
{
public ILogger CreateLogger()
{
return new DiskLogger();
}
}
到目前为止,我们可以休息一下,因为我们已经解决了问题,客户端可以调用这些代码。但我们比这更聪明 " />。日志框架的用户不会想写这样的代码:
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());
}
这不仅是重复工作(不好),而且如果添加了额外的具体日志记录器类型,我们的用户将需要实现上面这样的新方法。由于被调用的方法是相同的(我承认 - 这是故意的 " />),我们可以进行进一步的抽象:我们可以让创建者类实现一个共同的接口,这样它们就成为我们第二个迭代中的样子:
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());
}
这现在实际上是工厂方法模式的一种实现(尽管我们还没有详细介绍,但它实现了引言中描述的意图),而且我们也实现了我们的目标!
如果您查看示例代码,主程序会依次调用每个日志记录器,但它本身不访问具体类,因此我们成功地隔离了它们。在此过程中,我们还必然通过在基本接口中定义的创建者类来创建具体类实例。在某种意义上,我们创建了一个“虚拟构造函数”——这是该模式的另一个名称。
从我们的代码理解工厂方法模式
首先,我们应该回顾一下我们所做的:
- 要生成的具体类(
SqlLogger
和DiskLogger
)有一个基本接口ILogger
。 - 由于具体类不可访问,我们最终定义了一个创建者接口,该接口定义了一个返回
ILogger
的方法。 - 对于每种具体类型,我们定义一个具体的创建者类型(
SqlLoggerCreator
和DiskLoggerCreator
)。它们实现了一个共同的接口 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;
}
}
}
其中斜体是使用的接口/类型(在我们的例子中是 ILogger
、DiskLogger
和 SqlLogger
),其中鉴别器可以是用于定义发出哪种类型的任何类型。虽然它通常被称为工厂方法(可能比本文中的更合理!),但它不是工厂方法模式的实现。
当你在现实世界中使用工厂方法模式时,它随着时间的推移往往会变得不充分。通常会应用一个不同但密切相关的模式——抽象工厂模式。
历史
2013/05/07 - 初稿
2013/05/08 - 整理和澄清(希望如此)