前言
本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址
正文开始...
1. 简介
简单工厂模式不属于GoF23中设计模式之一,但在软件开发中应用也较为频繁,通常做为学习其他工厂模式的入门.
接下来我们从一个虚构的业务场景遇到的问题开始,到如何使用简单工厂模式去解决这个业务场景的问题的角度,来介绍这个模式.
2. 具体业务
有一个图表类,可以在实例化的时候根据传入的参数创建不同的图表类型,比如柱状图、饼状图、折线图等等.
2.1 业务代码
/**
* @author liuboren
* @Title: 图标类
* @Description: 根据不同的参数,创建不同的图表
* @date 2019/7/12 14:31
*/
public class Chart {
private String type;
public Chart(Object[][] obj,String type) {
this.type = type;
if(type.equalsIgnoreCase("histogram")){
//初始化柱状图
}else if(type.equalsIgnoreCase("pie")){
//初始化柱状图
}else if(type.equalsIgnoreCase("line")){
//初始化折线图
}
}
public void display(){
if(this.type.equalsIgnoreCase("histogram")){
// 显示柱状图
}else if(this.type.equalsIgnoreCase("pie")){
//显示饼状图
}else if(this.type.equalsIgnoreCase("Line")){
//显示折线图
}
}
}
客户端代码通过调用Chart类的构造函数来创建图表对象,根据参数type的不同可以得到不同类型的图表,然后再调用display()方法来显示相应的图表.
2.2 问题
上述代码主要有以下五个问题
- 过多的"if...else.."不易维护且影响性能
- 违反了单一职责
- 违反了开闭原则
- 与客户端耦合度高
- 代码重复问题
详细的看看以上的问题
2.2.1 过多的"if...else.."不易维护且影响性能
在Chart类中包含很多"if...else..."代码块,整个类的代码相当冗长,代码越长,阅读难度、维护难度和测试难度也越大;而且大量条件语句的存在还将影响系统的性能,程序在执行过程中需要做大量的判断
2.2.2 违反了单一职责
Chart类的职责过重,它负责初始化和显示所有的图表对象, 各种图表对象的初始化代码和显示代码集中在一个类中实现,违反了"单一职责原则",不利于类的重用和维护;
而且将大量的对象初始化代码都写在构造函数中将导致构造函数非常庞大,对象在创建时需要进行条件判断,降低了对象创建的效率
2.2.3 违反了开闭原则
当需要增加新类型的图表时,必须修改Chart的源代码,违反了"开闭原则".
2.2.4 与客户端耦合度高
客户端只能通过new关键字来直接创建Chart对象,Chart类与客户端类耦合度较高,对象的创建和使用无法分类.
2.2.5 代码重复问题
客户端在创建Chart对象之前可能还需要进行大量初始化设置,例如设置柱状图的颜色、高度等,如果在Chart类的构造函数中没有提供一个默认设置,那就只能由客户端来完成初始设置,这些代码在每次创建Chart对象时都会出现,导致代码的重复.
3. 简单工厂模式
使用简单工厂模式,可以在一定程度上解决.
3.1 简单工厂的基本流程
-
首先将需要创建的各种不同对象(例如各种不同的Chart对象)的相关代码封装到不同的类中,这些类称为具体产品类,而将他们公共的代码进行抽象和提取后封装在一个抽象产品类中,每一个具体产品类都是抽象产品类的子类
-
然后提供一个工厂类用于创建各种产品,在工厂类中提供一个创建产品的工厂方法,该方法可以根据所传入的参数不同创建不同的具体产品对象.
-
客户端只需调用工厂类的工厂方法并传入相应的参数即可得到一个产品对象
3.2 定义
定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有相同的父类.
因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被成为静态工厂方法(Static Factory Method)模式,他属于类创建型模式.
3.3 要点
当你需要什么,只需要掺入一个正确的参数,就可以获取你所需要的对象,而无需知道其创建细节.
简单工厂模式结构比较简单,其核心是工厂类的设计.
3.4 结构图
3.5 角色
工厂模式结构图包含以下几个角色
- Factory(工厂角色)
- Product(抽象产品角色)
- ConcreteProduct(具体产品角色)
3.5.1 Factory(工厂角色)
工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑.
工厂类可以被外界直接调用,创建所需的产品对象.
在工厂类中提供了静态的工厂方法factoryMethod(),它的返回类型为抽象产品类型Product
3.5.2 Product(抽象产品角色)
它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例.
每个具体产品角色都继承了抽象产品角色,需要实现在抽象产品中声明的抽象方法.
3.5.3 ConcreteProduct(具体产品角色)
它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例.
每一个具体角色都集成了抽象产品角色,需要实现在抽象产品中声明的抽象方法.
3.6 类设计
在简单工厂模式中,客户端通过工厂类来创建一个产品类的实例,而无需用new关键字来创建对象,它是工厂模式家族中最简单的一员
3.6.1Product类
将所有产品公共的代码移至抽象产品类,并在抽象长品类中声明一些抽象方法,以供不同的具体产品来实现.
/**
* @author liuboren
* @Title: 产品抽象类
* @Description: 抽离公共方法和抽象业务方法
* @date 2019/7/12 16:38
*/
public abstract class AbstractProduct {
//所有产品类的公共业务方法
public void methodSame(){
// 公共方法的实现
}
//声明抽象业务方法
public abstract void methodDiff();
}
3.6.2 Product实现类
class ConcreteProductA extends AbstractProduct{
@Override
public void methodDiff() {
//业务方法的实现
}
}
class ConcreteProductB extends AbstractProduct{
@Override
public void methodDiff() {
//业务方法的实现
}
}
3.6.3 工厂类
class Factory{
public static AbstractProduct getProduct(String arg){
AbstractProduct product = null;
if(arg.equalsIgnoreCase("A")){
product = new ConcreteProductA();
}else if(arg.equalsIgnoreCase("B")){
product = new ConcreteProductB();
}
return product;
}
}
3.6.4 客户端类
class Client{
public static void main(String[] args) {
AbstractProduct product;
product = Factory.getProduct("A");
product.methodSame();
product.methodDiff();
}
}
4. 使用简单工厂模式解决业务问题
使用简单工厂模式解决上面业务的问题
4.1 类结构图
4.2 代码
Chart类:
/**
* @author liuboren
* @Title: 图形接口
* @Description:
* @date 2019/7/15 9:42
*/
public interface Chart {
public void display();
}
HistogramChart:
/**
* @author liuboren
* @Title: 柱状图
* @Description:
* @date 2019/7/15 9:44
*/
public class HistogramChart implements Chart{
public HistogramChart() {
System.out.println("创建了柱状图");
}
@Override
public void display() {
System.out.println("显示了柱状图");
}
}
PieChart:
/**
* @author liuboren
* @Title: 饼状图
* @Description:
* @date 2019/7/15 9:45
*/
public class PieChart implements Chart{
public PieChart() {
System.out.println("创建了饼状图");
}
@Override
public void display() {
System.out.println("显示了饼状图");
}
}
LineChart:
/**
* @author liuboren
* @Title: 折线图
* @Description:
* @date 2019/7/15 9:47
*/
public class LineChart implements Chart {
public LineChart() {
System.out.println("创建了折线图");
}
@Override
public void display() {
System.out.println("显示了折线图");
}
}
ChartFactory:
/**
* @author liuboren
* @Title: 简单工厂类
* @Description:
* @date 2019/7/15 9:48
*/
public class ChartFactory {
public static Chart getChart(String type) {
Chart chart = null;
if ("histogram".equalsIgnoreCase(type)) {
chart = new HistogramChart();
System.out.println("初始化设置柱状图");
} else if ("pie".equalsIgnoreCase(type)) {
chart = new PieChart();
System.out.println("初始化设置饼状图");
} else if ("line".equalsIgnoreCase(type)) {
chart = new LineChart();
System.out.println("初始化设置折线图");
}
return chart;
}
}
Client:
/**
* @author liuboren
* @Title: 客户端类
* @Description:
* @date 2019/7/15 9:51
*/
public class Client {
public static void main(String[] args) {
Chart chart = ChartFactory.getChart("pie");
chart.display();
}
}
4.3 优化
在两个方面可以进行优化:
- 抽取客户端的参数到配置文件
- 将Chart接口和工厂类合并为一个抽象类
springboot项目可以将参数抽取到yml文件中,使用@value注解注入,不再扩展了.
合并后的的UML类图:
5. 简单工厂模式总结
从优点、缺点及使用场景三个方面进行总结
5.1 优点
- 对象创建和使用的分离
- 减少冗余类名记忆
- 通过抽取参数到配置文件提高灵活性
5.1.1 对象创建和使用的分离
工厂类包含必要的判断逻辑,可以决定什么时候创建哪一个工厂类的实例,客户端可以免除直接创建产品对象的职责,而仅仅"消费"产品,简单工厂模式实现了对象创建和使用的分离
5.1.2 减少冗余类名记忆
客户端无须知道所创建的具体产品类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以再一定程度减少使用者的记忆量.
5.1.3 通过抽取参数到配置文件提高灵活性
通过引入配置文件,可以再不修改任何客户端代码的情况下更换和增加新的具体产品类,自义定程度上提高了系统的灵活性.
5.2 缺点
- 工厂类职责过重
- 增加系统的复杂度和理解难度
- 系统扩展困难
- 静态方法无法继承使用
5.2.1 工厂类职责过重
由于工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响
5.2.2 增加系统的复杂度和理解难度
使用简单工厂模式势必会增加系统中类的个数(引入新的工厂类),增加了系统的复杂度和理解难度.
5.2.3 系统扩展困难
一旦增加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护.
5.2.4 静态方法无法继承使用
简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构.
5.3 使用场景
-
工厂类负责创建的对象比较少,由于创建的对象比较少,不会造成工厂方法中的业务逻辑太过复杂.
-
客户端只知道传入工厂类的参数,对于如何创建对象并不关心.