设计模式的征途—2.简单工厂(Simple Factory)模式
工厂模式是最常用的一种创建型模式,通常所说的工厂模式一般是指工厂方法模式。本篇是是工厂方法模式的“小弟”,我们可以将其理解为工厂方法模式的预备知识,它不属于GoF 23种设计模式,但在软件开发中却也应用地比较频繁。此外,工厂方法模式还有一位“大哥”—抽象工厂模式,会在后面进行介绍。
简单工厂模式(Simple Factory) | 学习难度:★★☆☆☆ | 使用频率:★★★☆☆ |
一、从一个图表库谈起
M公司想要基于C#语言开发一套图表库,该图表库可以为应用系统提供各种不同外观的图标,例如柱状图、饼状图或折线图等。M公司图表库设计开发人员希望为应用系统开发人员提供一套灵活易用的图表库,而且可以较为方便地对图表库进行扩展,以便于在将来增加一些新类型的图表。
M公司的程序员提出了一个初始设计方案,将所有图表的实现代码封装在一个Chart类中,其框架代码如下所示:
public class Chart
{
private string type; // 图表类型 public Chart(object[][] data, string type)
{
this.type = type; if (this.type.Equals("histogram", StringComparison.OrdinalIgnoreCase))
{
// 初始化柱状图
}
else if (this.type.Equals("pie", StringComparison.OrdinalIgnoreCase))
{
// 初始化饼状图
}
else if (this.type.Equals("line", StringComparison.OrdinalIgnoreCase))
{
// 初始化折线图
}
} public void Display()
{
if (this.type.Equals("histogram", StringComparison.OrdinalIgnoreCase))
{
// 显示柱状图
}
else if (this.type.Equals("pie", StringComparison.OrdinalIgnoreCase))
{
// 显示饼状图
}
else if (this.type.Equals("line", StringComparison.OrdinalIgnoreCase))
{
// 显示折线图
}
}
}
客户端代码通过调用Chart类的构造函数来创建图表对象,根据参数type的不同可以得到不同类型的图标,然后再调用Display()方法来显示相应的图表。
但是,不难看出,Chart类是一个巨大的类,存在很多问题:
- 在Chart类中包含很多if-else代码块,相当冗长,可读性很差;
- Chart类的职责过重,负责初始化和显示各种图表对象,违反了单一职责原则;
- 当需要增加新的图表类型时,必须修改Chart类的源代码,违反了开闭原则;
- 客户端只能通过new关键字来直接创建Chart对象,Chart类与客户端类耦合度较高,对象的创建和使用无法分离;
- 客户端在创建Chart对象之前可能还需要进行大量初始化设置,例如设置柱状图的颜色和高度等,如果在Chart类的构造函数中没有提供一个默认设置,那就只能由客户端来完成初始设置,这些代码在每次创建Chart对象时都会出现,导致代码的重复;
二、简单工厂模式概述
2.1 要点
简单工厂模式并不属于GoF 23种经典设计模式,但通常将它作为学习其他工厂模式的基础。
简单工厂(Simple Factory)模式:定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法模式,它属于创建型模式。
简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需的对象,而无须知道其创建细节。
2.2 结构图
简单工厂模式包含3个角色:
- Factory - 工厂角色:该模式的核心,负责实现创建所有产品实例的内部逻辑,提供一个静态的工厂方法GetProduct(),返回抽象产品类型Product的实例。
- Product - 抽象产品角色:所有产品类的父类,封装了各种产品对象的共有方法,它的引入将提高系统的灵活性,使得在工厂类中只需要定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。
- ConcreteProduct - 具体产品角色:简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。
在简单工厂模式中,客户端通过工厂类来创建一个产品类的实例,而无须直接使用new关键字来创建对象。(可以看出,它是工厂模式家族中最简单的一员)
三、重构图表库的实现
3.1 新的结构图
为了将Chart类的职责分离,同时将Chart对象的创建和使用分离,M公司开发人员决定使用简单工厂模式对图表库进行重构,重构后的结构图如下所示:
3.2 新的代码实现
(1)抽象产品角色:IChartable接口
public interface IChartable
{
void Display();
}
(2)具体产品角色:各种图表类型
public class HistogramChart : IChartable
{
public HistogramChart()
{
Console.WriteLine("创建柱状图...");
} public void Display()
{
Console.WriteLine("显示柱状图...");
}
} public class LineChart : IChartable
{
public LineChart()
{
Console.WriteLine("创建折线图...");
} public void Display()
{
Console.WriteLine("显示折线图...");
}
} public class PieChart : IChartable
{
public PieChart()
{
Console.WriteLine("创建饼状图...");
} public void Display()
{
Console.WriteLine("显示饼状图...");
}
}
(3)工厂角色:ChartFactory
public class ChartFactory
{
public static IChartable GetChart(string type)
{
IChartable chart = null; if (type.Equals("histogram", StringComparison.OrdinalIgnoreCase))
{
chart = new HistogramChart();
Console.WriteLine("初始化设置柱状图...");
}
else if (type.Equals("pie", StringComparison.OrdinalIgnoreCase))
{
chart = new PieChart();
Console.WriteLine("初始化设置饼状图...");
}
else if (type.Equals("line", StringComparison.OrdinalIgnoreCase))
{
chart = new PieChart();
Console.WriteLine("初始化设置折线图...");
} return chart;
}
}
(4)客户端调用:
public static void Main()
{
IChartable chart = ChartFactory.GetChart("histogram");
if (chart != null)
{
chart.Display();
} chart = ChartFactory.GetChart("pie");
if (chart != null)
{
chart.Display();
}
}
运行结果如下:
在客户端代码中,使用工厂类的静态方法来创建具体产品对象,如果需要更换产品,只需要修改静态工厂方法中的参数即可。例如:将柱状图改为饼状图,只需要将代码:
IChartable chart = ChartFactory.GetChart("histogram");
改为:
IChartable chart = ChartFactory.GetChart("pie");
3.3 改进的方案
M公司开发人员发现在创建具体Chart对象时,每次更换一个Chart对象都需要修改客户端中静态工厂方法的参数,客户端代码需要重新编译,这对于客户端而言,是违反了开闭原则的。于是,开发人员希望有一种方法能够在不修改客户端代码地前提下更换具体产品对象。
因此,他们考虑使用配置文件(XML)来实现:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="charttype" value="histogram"/>
</appSettings>
</configuration>
客户端因此改为:
public static void Main()
{
string type = AppConfigHelper.GetChartType(); // 读取配置文件中的charttype
if (string.IsNullOrEmpty(type))
{
return;
} IChartable chart = ChartFactory.GetChart(type);
if (chart != null)
{
chart.Display();
}
}
运行结果如下:
四、简单工厂模式总结
4.1 主要优点
- 实现了对象创建和使用的分离:客户端可以免除直接创建产品对象的职责,而仅仅“消费”产品。
- 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的的参数即可。
- 通过引入配置文件,可以在不修改任何客户端代码地情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
4.2 主要缺点
- 由于工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受影响。
- 使用简单工厂模式势必会增加系统中类的个数(引入新的工厂类),增加了系统的复杂度和理解难度。
- 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能会造成工厂逻辑过于复杂,不利于系统的扩展和维护。
- 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。
4.3 适用场景
- 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
- 客户端只需要知道传入工厂类的参数,对于如何创建对象并不关心。
参考资料
刘伟,《设计模式的艺术—软件开发人员内功修炼之道》
设计模式的征途—2.简单工厂(Simple Factory)模式的更多相关文章
- 设计模式:简单工厂(Simple Factory)
定义:根据提供的数据或参数返回几种可能类中的一种. 示例:实现计算器功能,要求输入两个数和运算符号,得到结果. 结构图: HTML: <html xmlns="http://www.w ...
- 简单工厂(Simple Factory),最合适的设计模式首秀.
简单工厂又称为静态工厂方法(static factory method)模式,简单工厂是由一个工厂来决定创建出哪一种个体的实现,在很多的讨论中,简单工厂做为工厂方法模式(Factory Method) ...
- 使用C# (.NET Core) 实现简单工厂(Simple Factory) 和工厂方法设计模式 (Factory Method Pattern)
本文源自深入浅出设计模式. 只不过我是使用C#/.NET Core实现的例子. 前言 当你看见new这个关键字的时候, 就应该想到它是具体的实现. 这就是一个具体的类, 为了更灵活, 我们应该使用的是 ...
- 设计模式学习(四): 1.简单工厂 (附C#实现)
New 这是一个典型的情况, 我们需要在运行时来实例化一些具体的类. 在需要修改或者扩展的时候我们就需要改这段代码. 一个程序中可能会多次出现类似的代码, 这使得维护和更新非常困难而且容易出错. 通过 ...
- Unity C# 设计模式(二)简单工厂模式
定义: 简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一. 简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例 ...
- Java设计模式(一) 简单工厂模式不简单
摘要:本文介绍了简单工厂模式的概念,优缺点,实现方式,以及结合Annotation和反射的改良方案(让简单工厂模式不简单).同时介绍了简单工厂模式(未)遵循的OOP原则.最后给出了简单工厂模式在JDB ...
- PHP设计模式(一)简单工厂模式 (Simple Factory For PHP)
最近天气变化无常,身为程序猿的寡人!~终究难耐天气的挑战,病倒了,果然,程序猿还需多保养自己的身体,有句话这么说:一生只有两件事能报复你:不够努力的辜负和过度消耗身体的后患.话不多说,开始吧. 一.什 ...
- 设计模式(二)简单工厂模式(Simple Factory Pattern)
一.引言 这个系列也是自己对设计模式的一些学习笔记,希望对一些初学设计模式的人有所帮助的,在上一个专题中介绍了单例模式,在这个专题中继续为大家介绍一个比较容易理解的模式——简单工厂模式. 二.简单工厂 ...
- Headfirst设计模式的C++实现——简单工厂模式(Simple Factory)之二
为了引出后续的工厂方法,把在简单工厂模式的基础上增加了新功能——加盟店 简而言之就是把原来的单一简单工厂(能生产cheese和greek两种pizza)细分成了纽约地区的和芝加哥地区的(每种地区都能生 ...
随机推荐
- hibernate系列笔记(2)---Hibernate的核心API
Hibernate的核心API 一般我们通过hibernate进行操作的时候,都会遵循下面的流程,那么接下来我对每一个步骤进行讲解: 1 public void testInsert() { 2 // ...
- 【转】Spring注解
原文链接:http://bbs.51cto.com/thread-1136892-1.html 使用Spring2.5的新特性--Autowired可以实现快速的自动注入,而无需在xml文档里面添加b ...
- jquery 日期获取
来自:http://blog.csdn.NET/liujun198773/article/details/7554628 感谢 $(function(){ var mydate = new Date ...
- Android--带你一点点封装项目 MVP+BaseActivity+Retrofit+Dagger+RxJava(三)
1,这一篇博客是和大家一起来封装我们最后的Dagger2,其实之前也写过关于简单的Dagger2,这里是地址,完全没了解的同学可以先去看一下这篇,感谢很多小伙伴一直在耐心的等待这一篇 2,Dagger ...
- ThreadLocal笔记
1.ThreadLocal的作用是什么? ThreadLocal是一个泛型类,将保存在其中的值与当前的线程关联起来,这样每个线程看到的值对于其他线程来说都是不可见的,这个技术被称为线程封 ...
- Tp框架 之对控制器的一些操作等
在浏览器中输入tp框架入口文件的地址,如图 要注意,localhost/后面跟的是www的下一级,tp文件的上一级,因为我直接把tp文件做成了www目录的下一级,所以我写的地址localhost后面跟 ...
- ACM 序号互换
序号互换 时间限制:1000 ms | 内存限制:65535 KB 难度:2 描述 Dr.Kong设计了一个聪明的机器人卡多,卡多会对电子表格中的单元格坐标快速计算出来.单元格的行坐标是由数字 ...
- 4063: [Cerc2012]Darts
4063: [Cerc2012]Darts Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 85 Solved: 53[Submit][Status] ...
- 基于JAVA语言的selenium测试基础总结
目录一.基本语句1.循环控制(break,continue)3.字符的替换(replace,repalceFirst,replaceAll,regex)4.字符串的连接("+",a ...
- 手把手教你做个AR涂涂乐
前段时间公司有一个AR涂涂乐的项目,虽然之前接触过AR也写过小Demo,但是没有完整开发过AR项目.不过经过1个多星期的学习,现在已经把项目相关的技术都学会了,在此向互联网上那些乐于分享的程序员前辈们 ...