从无工厂到工厂方法






4.88/5 (161投票s)
本文将介绍编程中非常著名的设计模式“工厂模式”。
工厂方法模式——摆脱New关键字的困扰
引言
当我们听到“设计模式”这个词时,我们脑海中首先浮现的问题是:“什么是设计模式?”
设计模式是针对软件开发中反复出现的问题而设计的可重用且经过文档化的解决方案。
四人帮(GOF)将设计模式分为 3 类
- 创建型
- 结构型
- 行为型
当我开始学习设计模式时,我发现工厂方法是一个非常引人入胜的模式。它是最具争议和最令人困惑的创建型模式之一。人们对理解这个模式存在很多困惑。
首先,我想澄清一点,根据 GOF 的设计模式列表,并没有“工厂模式”这种说法,GOF 有一个叫做“工厂方法模式”。(稍后我们会同时遇到这两种术语)
在本文的讨论和解释中,我将向大家介绍两个虚构的人物:亚历山大(一位 .NET 开发者)和托马斯(一家名为 Programmer24X7 的 IT 学院的 CEO)。
故事开始于托马斯联系亚历山大,并请他担任他们内部项目“课程管理系统”的架构师。我们将看到亚历山大如何一天一天地推进他对工厂方法模式的理解,以及他最终如何提出一个绝妙的解决方案。
在您开始阅读本文之前,我给您一个小小的建议:请准备一些零食放在旁边,因为这将是一个漫长的 6 天故事。我 basically 是在考验您的耐心。 别担心,我开玩笑的。这会很有趣,您会喜欢阅读这篇文章,并且在文章的最后,您可以自豪地说:“我知道工厂方法模式了”。
所以,放松下来,让我们开始学习工厂方法模式的旅程吧。
目录
- 第 1 天 - 亚历山大是如何开始的?——方法 1 - 无工厂方法
- 第 2 天 - 亚历山大如何继续?——方法 2 - 简单工厂方法
- 第 3 天 - 方法 3 - 多个简单工厂方法
- 第 4 天 - 讨论多个简单工厂方法
- 第 4 天 - 夜晚 – 对象梦魇
- 第 5 天 - 方法 4 - 工厂方法
- 第 6 天 - 最终演示
- 附加方法和想法
- 源代码下载
第 1 天
亚历山大(我们的 .NET 开发者)从托马斯(我们 Programmer24X7 的 CEO)那里收集需求。他的主要需求之一是构建一个管理 Programmer24X7(技术学院)在线课程的模块。
亚历山大是如何开始的?
I) 亚历山大要开发的系统是课程管理系统,所以第一个逻辑步骤是开始创建课程库。
II) 下一步将是创建课程实例和构建 UI——用户界面。以下是每个程序员都会想到的最显而易见的 P.S. 代码,亚历山大也是如此。
方法 1 – 无工厂方法
AbstractCourse objCourse = null;
switch(StrUserInput)
{
case "N":
objCourse = new NetCourse();
break;
case "J":
objCourse = new JavaCourse();
break;
}
objCourse.CreateCourseMaterial();
objCourse.CreateSchedule();
//Display objCourse.CourseName and objCourse.CourseDuration
最后一步 – 演示
模块准备好后,亚历山大将其演示给了托马斯,托马斯对架构感到非常满意。亚历山大付出的努力得到了高度赞赏,但这并没有结束。然后他将需求提升到下一个逻辑层面,并分享了他关于应用程序的进一步计划。他说:
- 他计划添加新的在线课程,如 C++、VC++ 等,并希望将来停止某些课程。
- 将来,Programmer24X7 将开设线下课程,所以请确保所有内容都可以重用。
托马斯要求亚历山大根据这个高级范围进行更改。这让亚历山大大吃一惊。
这就像亚历山大“逃离了一个火坑,却掉进了一个火盆”。
“添加一门新课程意味着”,需要打开现有的 UI 代码和现有逻辑(添加一些 case 条件,并在将来移除一些现有的 case 条件)。
想象一下这样的情景:在建筑施工结束后,建筑师被要求在第一层和第二层之间增加一层,同时确保建筑不会倒塌。
方法 1 的问题
- 当课程库修改时,**SOLID 原则 OCP**(开放封闭原则)将被违反。(OCP 指出,软件实体(类、模块、函数等)应该对扩展开放,对修改关闭)
- 在另一个 UI 中重用相同的(课程创建)逻辑是不可能的。
亚历山大学到了什么?
亚历山大认识到了软件开发的一个重要方面,并从中吸取了一个重要的教训:“变化是开发不可或缺的一部分,系统应该这样开发,以便能够轻松适应这些变化,而无需修改现有的**经过测试**的部分。”
改变系统却不改变代码,难道不是很神奇吗?嗯,让我们看看如何做到这一点。
第 2 天
亚历山大如何继续?
第二天,亚历山大一早就醒了。咖啡在他的手中,但思绪仍然在思考眼前的问题。他很难在黑暗的天空中找到正确的星星,但最终他找到了月亮,于是就有了方法 2。
|
|
方法 2 – 简单工厂方法
在第一个方法中,UI 负责创建课程对象。
![]() |
现在,如果将对象创建的权力从 UI 中剥离出来,交给其他人,会怎么样呢?这里的其他人就是简单工厂类。 |
什么是简单工厂?
简单工厂是一个类,它有一个公共的静态方法,该方法会根据接收到的输入来执行对象创建任务。
public class CourseFactory
{
public static AbstractCourse CreateCourse(string ScheduleType)
{
AbstractCourse objCourse = null;
switch(ScheduleType)
{
case "N":
objCourse = new NetCourse();
break;
case "J":
objCourse = new JavaCourse();
break;
//Add more case conditions here for VC++ and C++
}
objCourse.CreateCourseMaterial();
objCourse.CreateSchedule();
return objCourse;
}
}
UI
AbstractCourse objCourse = CourseFactory.CreateCourse(StrUserInput); //Display objCourse.CourseName and objCourse.CourseDuration
方法 2 的优点
- 每当引入新课程时,改变的是工厂而不是客户端代码。
- 由于工厂是一个类,任何有权访问的人都可以使用它。简而言之,课程逻辑现在可以被重用了。
最后一步 – 演示
当天晚上,亚历山大来到 Programmer24X7 办公室演示这个修改后的架构。但在亚历山大开始表达他的担忧之前,又出现了一个意想不到的要求。托马斯说:“我认为,为企业培训计划添加另一个 UI 会更好。原因是,如今我的企业培训数量大幅增加,管理起来变得越来越困难。”
I) 现在我们首先应该做的是,更改课程库(因为我们还需要添加企业课程)。
II) 在我们的简单工厂类中添加更多的 case 条件。
public class CourseFactory
{
public static AbstractCourse CreateCourse(string ScheduleType)
{
AbstractCourse objCourse = null;
switch(ScheduleType)
{
case "CN":
objCourse = new CNetCourse();
break;
case "CJ":
objCourse = new CJavaCourse();
break;
case "CB":
objCourse = new CBICourse();
break;
case "OJ":
objCourse = new OJavaCourse();
break;
case "ON":
objCourse = new ONetCourse();
break;
}
objCourse.CreateCourseMaterial();
objCourse.CreateSchedule();
return objCourse;
}
}
方法 2 的问题
新需求很容易通过简单工厂方法得到解决,那么问题出在哪里呢?
- 虽然所有类都派生自抽象课程,但在一个简单工厂中添加五个 case 条件违反了 **SOLID 原则 SRP**——单一职责原则。(SRP 指出,一个类应该只有一个改变的原因。)
在这里,工厂类将发生变化- 每当引入或修改新的企业课程时。
- 每当引入或修改新的在线课程时。
因此,这个简单工厂解决方案在当前问题上下文中行不通。
第 3 天
让我们列出我们所有的需求。
- 我们需要一个用于在线课程的工厂
- 我们需要一个用于企业课程的工厂
- 我们不希望为两个模块创建单个工厂。
- 如果使用多个简单工厂呢?
- OnlineCourseFactory
- CorporateCourseFactory
方法 3 – 多个简单工厂方法
在此方法中,我们将拥有多个简单工厂,每个工厂都有一个静态方法,该方法将根据接收到的输入创建实例。
public class CorporateCourseFactory
{
public static AbstractCourse CreateCourse(string ScheduleType)
{
AbstractCourse objCourse = null;
switch(ScheduleType)
{
case "N":
objCourse = new CNetCourse();
break;
case "J":
objCourse = new CJavaCourse();
break;
case "B":
objCourse = new CBICourse();
break;
}
objCourse.CreateCourseMaterial();
objCourse.CreateSchedule();
return objCourse;
}
}
public class OnlineCourseFactory
{
public static AbstractCourse CreateCourse(string ScheduleType)
{
AbstractCourse objCourse = null;
switch(ScheduleType)
{
case "N":
objCourse = new ONetCourse();
break;
case "J":
objCourse = new OJavaCourse();
break;
}
objCourse.CreateCourseMaterial();
objCourse.CreateSchedule();
return objCourse;
}
}
在线课程 UI
AbstractCourse objCourse = OnlineCourseFactory.CreateCourse(StrUserInput); //Display objCourse.CourseName and objCourse.CourseDuration
企业课程 UI
AbstractCourse objCourse = CorporateCourseFactory.CreateCourse(StrUserInput); //Display objCourse.CourseName and objCourse.CourseDuration
一切都已解决,问题已解决。
第 4 天
第二天,亚历山大来到 Programmer24X7 并向托马斯解释了架构。托马斯的回复是好还是坏?
在看过架构后,我们进行了一次大讨论,以下是其中的一个简短记录。
托马斯: 抱歉,亚历山大,我认为这个解决方案行不通。
亚历山大: 为什么,先生?
托马斯: 您看,为每个课程组拥有一个单独的工厂(OnlineCourseFactory、CoporateCourseFactory)是很棒的。
但是你如何控制每一个工厂呢?我的意思是,你怎么能确保每个工厂都正确地创建对象呢?例如,你怎么能确定每个工厂都遵循公司标准,并在返回对象之前先创建课程材料和计划?
将来,可能会添加另一种类型的工厂,比如 OfflineCourseFactory,用于管理线下课程,而无意中,它就违反了公司标准,并以自己的方式创建课程材料。
亚历山大: 您说得对,先生。让我考虑一个万无一失的最终解决方案,给我一天时间。
托马斯: 没问题,亚历山大。祝您有美好的一天。
方法 3 的问题
- 在这里,每个工厂都是独立的。没有严格的工厂定义规则。在此方法中,每个工厂都可以拥有自己的结构和标准。
第 4 天 - 夜晚
让我们列出我们所有的需求。
- 我们需要一个用于在线课程的工厂
- 我们需要一个用于企业课程的工厂
- 我们不希望为两个模块创建单个工厂。
- 应该有一些规则来定义工厂,每个人都应该遵循。
- 如果使用方法 3 的组合(即需要时创建新工厂)会怎么样?
再加上继承或组合。
- 睡觉时间到了。
第 5 天
亚历山大昨晚在说什么?
- 将所有通用功能放在一个地方(类)并供所有工厂重用。
为了讨论方便,我们称之为“ReusbaleClass”。我们需要可重用性——
我们可以通过 2 种方式实现:- 使用继承 – 让所有工厂继承自 ReusbaleClass -> 称之为解决方案 1
- 使用组合 – 所有工厂都将拥有一个 ReusbaleClass 类型的成员 -> 称之为解决方案 2
- 让工厂重写 ReusbaleClass 中定义的一些功能(每个工厂都不同)。
- 我们需要重写,这意味着解决方案 1 非常适合。
方法 4 – 工厂方法
简而言之,我们在这里需要一个类,它将执行所有通用任务并公开一个虚拟或抽象方法。
所以,让我们创建 AbstractCourseFactory,它将封装通用功能,并附加一个可重写(虚拟或抽象)的方法,然后重新创建我们的 OnlineCourseFactory 和 CorporateCourseFactory。
public abstract class AbstractCourseFactory
{
public AbstractCourse CreateCourse(string ScheduleType)
{
AbstractCourse objCourse = this.GetCourse(ScheduleType);
objCourse.CreateCourseMaterial();
objCourse.CreateSchedule();
return objCourse;
}
public abstract AbstractCourse GetCourse(string ScheduleType);
}
请注意,GetCourse 方法是**抽象**的
现在每个人(每个工厂)都可以轻松地重写这个 GetCourse 方法。
public class CorporateCourseFactory:AbstractCourseFactory
{
public override AbstractCourse GetCourse(string ScheduleType)
{
switch(ScheduleType)
{
case "N":
return new CNetCourse();
case "J":
return new CJavaCourse();
default:
return null;
}
}
}
public class OnlineCourseFactory : AbstractCourseFactory
{
public override AbstractCourse GetCourse(string ScheduleType)
{
switch(ScheduleType)
{
case "N":
return new ONetCourse();
case "J":
return new OJavaCourse();
default:
return null;
}
}
}
Gang of Four 如何定义工厂方法模式
“该模式定义了一个创建对象的接口,但由子类决定实例化哪一个类。工厂方法允许一个类将实例化推迟到子类。”
定义现在是不言而喻的。
第 6 天
是时候向托马斯展示最终演示了。
现在最终的 UI 代码将如下所示:
在线课程 UI
AbstractCourseFactory objFactory = new OnlineCourseFactory();
AbstractCourse objCourse = objFactory.CreateCourse(StrUserInput);
//Display objCourse.CourseName and objCourse.CourseDuration
企业课程 UI
AbstractCourseFactory objFactory = new CorporateCourseFactory();
AbstractCourse objCourse = objFactory.CreateCourse(StrUserInput);
//Display objCourse.CourseName and objCourse.CourseDuration
方法 4 的优点
- 如果将来 Programmer24X7 引入了新的课程组(例如,线下课程),则将从 AbstractCourse 派生一个新的工厂,该工厂将封装与该组相关的所有具体课程(例如,线下 Java 课程、线下 .NET 课程)的创建。就这么简单。
这就是工厂方法模式的全部内容。托马斯似乎终于满意了,因为他所有的要求似乎都得到了满足,亚历山大也很高兴,因为他现在已经掌握了工厂方法模式。
附加方法和想法
- 父工厂必须总是抽象的吗?
这个问题的答案是否定的。如果需要,我们可以使其非抽象,并为 GetCourse 方法添加默认实现,使其成为虚拟方法。 - 工厂中必须使用 switch 语句吗?
不是。在 .NET 中,我们可以用 Dictionary 替换 switch 循环。 - 有可能从二级工厂继承吗?
是的,当然,工厂方法就是这样工作的。最初,我们有 AbstractCourseFactory,它为 GetCourse 方法提供了默认实现,然后 OnlineCourseFactory 将对其进行扩展,为其 GetCourse 方法添加自己的实现,而这反过来又可以是一个虚拟方法。这样,稍后,例如当 Programmer24X7 中引入了新的 OnlineAdvanceCourse 时,现有的 OnlineCourseFactory 可以轻松扩展。 - 在工厂方法模式中使用多级继承有什么优势?
好吧,考虑以下场景。- 我们有 AbstractCourseFactory 和 OnlineCourseFactory。
- OnlineCourseFactory 重写 GetCourse 方法来定义在线课程,如 Java 和 .NET。
- 现在,假设几年后在在线课程中添加了新课程,而我们甚至不愿意打开 OnlineCourseFactory。
- 现在将引入 OnlineCourseFactoryVersion2。
- 现在,如果我们从 AbstractCourseFactory 派生 OnlineAdvanceCourseFactory,我们就必须定义所有以前的课程,如 Java 和 .NET,以及新课程,这会导致代码冗余。
- 除此之外,我们可以借助 Dictionary 来存储所有可用的课程,并在每个级别上添加课程。
让我们看一下相同的代码片段。
public abstract class AbstractCourseFactory
{
protected Dictionary<string, AbstractCourse> ObjCourses;
public AbstractCourseFactory()
{
ObjCourses = new Dictionary<string, AbstractCourse>();
}
}
public class OnlineCourseFactoryVersion2 : OnlineCourseFactory
{
public OnlineCourseFactoryVersion2()
{
ObjCourses.Add("V", new OVCourse());
}
}
注意:因此,在工厂方法模式中使用 Dictionary 始终被认为是一种好方法。
就这样,我关于工厂模式的第一篇文章就完成了……希望您喜欢……请不要忘记在下方留下您的评论……您的积极和消极评论将鼓励我写得更好……谢谢!!!
源代码下载
单击以下链接获取完整的源代码
如需其他设计模式和 .NET 相关的培训,您可以通过 SukeshMarla@Gmail.com 或访问 www.sukesh-marla.com 联系我。
单击此处获取更多关于 .NET 和 C# 学习资料