上一篇的简单工厂模式虽然简单,但是存在一个很严重的问题:当系统中需要引入新产品时,由于静态工厂方法通过所传入参数的不同来创建不同的产品,这必定要修改工厂类的源代码,将违背开闭原则。如何实现新增新产品而不影响已有代码?工厂方法模式为此应运而生。

工厂方法模式(Factory Method) 学习难度:★★☆☆☆ 使用频率:★★★★★

一、简单工厂版的日志记录器

1.1 软件需求说明

Requirement:M公司欲开发一个系统运行日志记录器(Logger),该记录器可以通过多种途径保存系统的运行日志,例如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。在设计各类日志记录器时,M公司的开发人员发现需要对日志记录器进行一些初始化工作,初始化参数的摄制过程比较复杂,而且某些参数的设置有严格的先后次序,否则可能会发生记录失败。如何封装记录器的初始化过程并保证多种记录器切换的灵活性是M公司开发人员面临的一个难题。

  M公司开发人员学习了简单工厂模式对日志记录器进行了设计,初始结构如下图所示。

1.2 基于简单工厂的代码实现

  M公司的程序猿按照结构图,写下了核心代码LoggerFactory的CreateLogger方法:

    // 简单工厂方法
public static ILogger CreateLogger(string args)
{
if (args.Equals("db", StringComparison.OrdinalIgnoreCase))
{
// 连接数据库,代码省略
// 创建数据库日志记录器对象
ILogger logger = new DatabaseLogger();
// 初始化数据库日志记录器,代码省略
return logger;
}
else if(args.Equals("file", StringComparison.OrdinalIgnoreCase))
{
// 创建日志文件,代码省略
// 创建文件日志记录器对象
ILogger logger = new FileLogger();
// 初始化文件日志记录器,代码省略
return logger;
}
else
{
return null;
}
}

  上述代码省略了具体日志记录器类的初始化代码,在LoggerFactory中提供了静态工厂方法CreateLogger(),用于根据所传入的参数创建各种不同类型的日志记录器。通过使用简单工厂模式,将日志记录器对象的创建和使用分离,客户端只需要使用由工厂类创建的日志记录器对象即可,无须关心对象的创建过程。

  But,虽然简单工厂模式实现了对象的创建和使用分离,仍然存在以下两个问题:

  (1)工厂类过于庞大!包含了大量的if-else代码,维护和测试的难度增大不少。

  (2)系统扩展不灵活,如果新增类型的日志记录器,必须修改静态工厂方法的业务逻辑,违反了开闭原则。

  如何解决这两个问题,M公司程序猿苦思冥想,想要改进简单工厂模式,于是开始学习工厂方法模式。

二、工厂方法模式介绍

2.1 工厂方法模式概述

  在简单工厂模式中只提供一个工厂类,该工厂类需要知道每一个产品对象的创建细节,并决定合适实例化哪一个产品类。其最大的缺点就是当有新产品加入时,必须修改工厂类,需要在其中加入必要的业务逻辑,这违背了开闭原则。此外,在简单工厂模式中,所有的产品都由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间的耦合度较高,严重影响了系统的灵活性和扩展性。

  在工厂方法模式中,不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构

工厂方法(Factory Method)模式:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式,也可称为多态工厂模式,它是一种创建型模式。  

2.2 工厂方法模式结构图

  工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法并创建具体的产品对象。

  从图中可以看出,在工厂方法模式结构图中包含以下4个角色:

  (1)Product(抽象产品):定义产品的接口,是工厂方法模式所创建的对象的超类,也就是产品对象的公共父类。

  (2)ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。

  (3)Factory(抽象工厂):抽象工厂类,声明了工厂方法,用于返回一个产品。

  (4)ConcreteFactory(具体工厂):抽象工厂的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。

三、工厂方法版的日志记录器

3.1 解决方案

  M公司的程序猿学习了工厂方法之后,决定使用工厂方法模式来重构设计,其基本结构图如下图所示:

  其中, Logger接口充当抽象产品角色,而FileLogger和DatabaseLogger则充当具体产品角色。LoggerFactory接口充当抽象工厂角色,而FileLoggerFactory和DatabaseLoggerFactory则充当具体工厂角色。

3.2 重构代码

  (1)抽象产品:ILogger接口

    public interface ILogger
{
void WriteLog();
}

  (2)具体产品:FileLogger和DatabaseLogger类

    public class FileLogger : ILogger
{
public void WriteLog()
{
Console.WriteLine("文件日志记录...");
}
} public class DatabaseLogger : ILogger
{
public void WriteLog()
{
Console.WriteLine("数据库日志记录...");
}
}

  (3)抽象工厂:ILoggerFactory接口

    public interface ILoggerFactory
{
ILogger CreateLogger();
}

  (4)具体工厂:FileLoggerFactory和DatabaseLoggerFactory类

    public class FileLoggerFactory : ILoggerFactory
{
public ILogger CreateLogger()
{
// 创建文件日志记录器
ILogger logger = new FileLogger();
// 创建文件,代码省略
return logger;
}
} public class DatabaseLoggerFactory : ILoggerFactory
{
public ILogger CreateLogger()
{
// 连接数据库,代码省略
// 创建数据库日志记录器对象
ILogger logger = new DatabaseLogger();
// 初始化数据库日志记录器,代码省略
return logger;
}
}

  (5)客户端调用

    public static void Main()
{
ILoggerFactory factory = new FileLoggerFactory(); // 可通过引入配置文件实现
if (factory == null)
{
return;
} ILogger logger = factory.CreateLogger();
logger.WriteLog();
}

  运行结果如下图:

  

四、借助反射的重构版本

4.1 逃离修改客户端的折磨

  为了让系统具有更好的灵活性和可扩展性,M公司程序猿决定对日志记录器客户端代码进行重构,使得可以在不修改任何客户端代码的基础之上更换或是增加新的日志记录方式。

  在客户端代码中将不再使用new关键字来创建工厂对象,而是将具体工厂类的类名存在配置文件(例如XML文件)中,通过读取配置文件来获取类名,再借助.NET反射机制来动态地创建对象实例。

4.2 撸起袖子开始重构

  (1)创建配置文件

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="LoggerFactory" value="Manulife.ChengDu.DesignPattern.FactoryMethod.v2.DatabaseLoggerFactory, Manulife.ChengDu.DesignPattern.FactoryMethod" />
</appSettings>
</configuration>

  (2)封装一个简单的AppConfigHelper类

    public class AppConfigHelper
{
public static string GetLoggerFactoryName()
{
string factoryName = null;
try
{
factoryName = System.Configuration.ConfigurationManager.AppSettings["LoggerFactory"];
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return factoryName;
} public static object GetLoggerFactoryInstance()
{
string assemblyName = AppConfigHelper.GetLoggerFactoryName();
Type type = Type.GetType(assemblyName); var instance = Activator.CreateInstance(type);
return instance;
}
}

  (2)重构客户端代码

    public static void Main()
{
ILoggerFactory factory = (ILoggerFactory)AppConfigHelper.GetLoggerFactoryInstance();
if (factory == null)
{
return;
} ILogger logger = factory.CreateLogger();
logger.WriteLog();
}

  运行结果如下图所示:

  

五、工厂方法的隐藏

  有时候,为了进一步简化客户端的使用,还可以对客户端隐藏工厂方法,此时,在工厂类中将直接调用产品类的业务方法,客户端无须调用工厂方法创建产品,直接通过工厂即可使用所创建的对象中的业务方法。

  (1)修改抽象工厂

    public abstract class LoggerFactory
{
// 在工厂类中直接调用日志记录器的业务方法WriteLog()
public void WriteLog()
{
ILogger logger = this.CreateLogger();
logger.WriteLog();
} public abstract ILogger CreateLogger();
}

  (2)修改具体工厂

    public class DatabaseLoggerFactory : LoggerFactory
{
public override ILogger CreateLogger()
{
// 连接数据库,代码省略
// 创建数据库日志记录器对象
ILogger logger = new DatabaseLogger();
// 初始化数据库日志记录器,代码省略
return logger;
}
}

  (3)简化的客户端调用

    public static void Main()
{
LoggerFactory factory = (LoggerFactory)AppConfigHelper.GetLoggerFactoryInstance();
if (factory == null)
{
return;
} factory.WriteLog();
}

六、工厂方法模式总结

5.1 主要优点

  • 工厂方法用于创建客户所需要的产品,还向客户隐藏了哪种具体产品类将被实例化这一细节。因此,用户只需要关心所需产品对应的工厂,无须关心创建细节
  • 在系统中加入新产品时,无需修改抽象工厂和抽象产品提供的接口,也无须修改客户端,还无须修改其他的具体工厂和具体产品,而只要加入一个具体工厂和具体产品就可以了。因此,系统的可扩展性得到了保证,符合开闭原则

5.2 主要缺点

  • 在添加新产品时,需要编写新的具体产品类,还要提供与之对应的具体工厂类,系统中类的个数将成对增加,一定程度上增加了系统的复杂度
  • 由于考虑到系统的可扩展性,需要引入抽象层,且在实现时可能需要用到反射等技术,增加了系统的实现难度。

5.3 适用场景

  • 客户端不知道其所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的的工厂即可,具体的产品对象由具体工厂创建,可将具体工厂的类名存储到配置文件或数据库中。
  • 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏替换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统易于扩展。

参考资料

  刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

设计模式的征途—3.工厂方法(Factory Method)模式的更多相关文章

  1. Headfirst设计模式的C++实现——工厂方法(Factory Method)

    引用原书的一句话:所有的工厂模式都用来封装对象的创建,工厂方法模式通过让子类决定该创建的对象是什么来达到封装的目的. Pizza类及其派生类与上一例相同 PizzaStore.h #ifndef _P ...

  2. 抽象工厂(Abstract Factory),工厂方法(Factory Method),单例模式(Singleton Pattern)

    在谈工厂之前,先阐述一个观点:那就是在实际程序设计中,为了设计灵活的多态代码,代码中尽量不使用new去实例化一个对象,那么不使用new去实例化对象,剩下可用的方法就可以选择使用工厂方法,原型复制等去实 ...

  3. Spring 通过工厂方法(Factory Method)来配置bean

    Spring 通过工厂方法(Factory Method)来配置bean 在Spring的世界中, 我们通常会利用bean config file 或者 annotation注解方式来配置bean. ...

  4. [设计模式-创建型]工厂方法(Factory Method)

    概括 名称 Factory Method 结构 动机 定义一个用于创建对象的接口,让子类决定实例化哪一个类.Factory Method 使一个类的实例化延迟到其子类. 适用性 当一个类不知道它所必 ...

  5. 设计模式——工厂方法(Factory Method)

    定义一个用于创建对象的接口,让子类决定实例化哪一个类.工厂方法使一个类的实例化延迟到其子类. ——DP UML类图 模式说明 抽象业务基类 实际业务类的公共基类,也是工厂要创建的所有对象的父类,这部分 ...

  6. 设计模式二: 工厂方法(Factory Method)

    简介 工厂方法模式是创建型模式的一种, 核心结构有四个角色: 抽象工厂,具体工厂,抽象产品,具体产品; 实现层面上,该模式定义一个创建产品的接口,将实际创建工作推迟到具体工厂类实现, 一个产品对应一个 ...

  7. 工厂方法 Factory Method

    背景:有一个应用框架,它可以向用户显示多个文档.在这个框架中,两个主要的抽象是类Application和Document.这两个类都是抽象的.客户必须通过它们的子类来做与举替应用相关的实现. 分析:因 ...

  8. 设计模式学习心得<工厂方法 Factory Method>

    概述 意图 业务代码中常常有构造对象的过程,它拥有大量的参数.并且有很多地方需要这对象. 简化对象构造过程. 主要解决 一个类在不同场景的频繁地创建,让不同对象的创建更有语义化,提高代码复用性. 何时 ...

  9. 小菜学习设计模式(三)—工厂方法(Factory Method)模式

    前言 设计模式目录: 小菜学习设计模式(一)—模板方法(Template)模式 小菜学习设计模式(二)—单例(Singleton)模式 小菜学习设计模式(三)—工厂方法(Factory Method) ...

随机推荐

  1. 【Java基础】 static

    static static表示"全局"或者"静态"的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量的概念.被 ...

  2. 一个简单的php站点配置

    一个简单的php站点配置   现在我们来看在一个典型的,简单的PHP站点中,nginx怎样为一个请求选择location来处理:   server {     listen      80;     ...

  3. Regular Expression(正则表达式)之邮箱验证

    正则表达式(regular expression, 常常缩写为RegExp) 是一种用特殊符号编写的模式,描述一个或多个文本字符串.使用正则表达式匹配文本的模式,这样脚本就可以轻松的识别和操作文本.其 ...

  4. Laravel的ORM入门

    源码目录在\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Relations下 关系:一对多(One To Many) 场景:每篇 ...

  5. github学习(一)

    初识github篇. 一.什么是github:       GitHub 是一个面向开源及私有软件项目的托管平台,因为只支持 Git 作为唯一的版本库格式进行托管,故名 GitHub.       g ...

  6. 赵本山 教你如何在实战项目中使用WCF

    我们都知道调用WCF直接在Service References中引用可以远程调用的WCF Url就行了. 但是我们想过没,在Development环境中可以这样做,但是QA.UAT.Productio ...

  7. 《Django By Example》第八章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:还有4章!还有4章全书就翻译完成了 ...

  8. 浅谈Java的集合体系

    集合体系框架图 集合接口 Java集合类库将接口(interface)与实现(implementation)分离,如上图,Set是一个集合接口,而HashSet与TreeSet都是实现了Set接口的子 ...

  9. 巧用*_his表记录操作历史

    文章转载自「开发者圆桌」一个关于开发者入门.进阶.踩坑的微信公众号 许多OLTP应用的开发者都知道,一些重要的操作要记录操作历史,把操作前的数据备份到历史表,然后再执行相应的修改操作.这样可以获取某个 ...

  10. python实现视频下载

    最近一两年短视频业务风生水起,各个视频网站都有各自特色的短视频内容.如果有这样一个程序,可以把各大视频网站的热门用户最新发布的视频都下载下来,不仅方便自己观看,还可以将没有版权的视频发布在个人社交网站 ...