设计模式(2)—— 简单工厂模式

83 阅读4分钟

1. 引出问题

需求:设计一个程序,可以按照不同的需求以各种图表样式(柱状图、饼状图等)进行数据展示

初始设计如下所示

public class Chart {

    private String type;

    public Chart(Object[][] data, String type){
        
        if(type.equalsIgnoreCase("histogram")){
            //初始化柱状图
        }else if(type.equalsIgnoreCase("pie")){
            //初始化饼状图
        }else if(type.equalsIgnoreCase("line")){
            //初始化折线图
        }
        
    }

    public void display(){
        
        if(type.equalsIgnoreCase("histogram")){
            //显示柱状图
        }else if(type.equalsIgnoreCase("pie")){
            //显示饼状图
        }else if(type.equalsIgnoreCase("line")){
            //显示折线图
        }
        
    }
}

存在的问题:

  1. 大量的条件判断语句,程序冗长,且影响性能
  2. Chart类同时负责图标的初始化和展示,不符合单一职责原则,不利于类的重用和维护
  3. 若需要新增图表类型,需要直接修改源码,不符合开闭原则
  4. 客户端只能通过new来创建Chart对象,导致耦合度较高,对象的创建和使用无法分离

2. 简单工厂模式介绍

定义一个工厂类,它可以根据不同的参数返回不同类的实例,而这些不同的实例都有着共同的父类。

当需要的时候,只需要传入参数,就可以获得实例对象,而无需关注具体的创建细节。

简单工厂模式包含三种角色:

  1. Factory(工厂角色)

简单工厂模式的核心,负责实现所有实例创建的具体逻辑,创建方法可以被外界直接调用,并以父类Product返回

  1. Product(抽象产品角色)

是所有具体产品的父类,定义了所有产品的公共方法,同时通过多态的方式,使Factory类中只需要定义一个创建函数就可以返回各个不同种类的具体实例

  1. ConcreteProduct(具体产品角色)

是工厂的创建目标,继承了抽象产品对象,并且实现了所有抽象方法

3. 以简单工厂模式重构图表案例

抽象产品类:Chart类

public interface Chart {
    
	//展示图表
    public void display();

}

柱状图类

public class HistogramChart implements Chart{

    public HistogramChart() {
        System.out.println("创建柱状图");
    }

    @Override
    public void display() {
        System.out.println("显示柱状图");
    }

}

饼状图类

public class PieChart implements Chart{

    public PieChart() {
        System.out.println("创建饼状图");
    }

    @Override
    public void display() {
        System.out.println("显示饼状图");
    }

}

折线图类

public class LineChart implements Chart{

    public LineChart() {
        System.out.println("创建折线图");
    }

    @Override
    public void display() {
        System.out.println("显示折线图");
    }

}

工厂类

public class ChartFactory {

    public static Chart getChart(String type){

        Chart chart = null;

        if(type.equalsIgnoreCase("histogram")){
            chart = new HistogramChart();
            System.out.println("初始化柱状图");
        }else if(type.equalsIgnoreCase("pie")){
            chart = new PieChart();
            System.out.println("初始化饼状图");
        }else if(type.equalsIgnoreCase("line")){
            chart = new LineChart();
            System.out.println("初始化折线图");
        }

        return chart;

    }

}

编写测试类代码

public class ChartTest {
    public static void main(String[] args) {
        Chart chart = ChartFactory.getChart("histogram");
        chart.display();
    }
}

运行程序,测试结果如下:

4. 方案改进思路

上面的程序中,如果需要改变图表形式,仍然需要修改客户端的源代码,同样违反开闭原则

在实践中,可以通过读取配置文件的方法传入type属性的值,当需要改变图表形式的时候,只需要将配置文件中的type属性值进行修改即可,无需修改代码

5. 创建对象与使用对象

在最初的Chart类中,我们说使用new进行对象创建会导致高耦合,那么具体是为什么呢?

以另一个例子进行说明

public class LoginAction {

    private UserDAO userDAO;

    public LoginAction() {
        this.userDAO = new JDBCUserDAO();  //创建对象
    }

    public void execute(){
        //其他代码
        userDAO.findUserById();  //使用对象
        //其他代码
    }

}

在这个例子中,LoginAction类既负责对象的创建,又负责对象的使用,就会导致问题:

如果需要使用UserDAO的另一个子类HibernateUserDAO替代JDBCUserDAO,那么就必须修改源代码,重新new一个对象出来,这就违反了开闭原则,那么该如何避免呢?

可以使用工厂类,将对象的创建过程从原有的类中抽取出来

public class LoginAction {

    private UserDAO userDAO;

    public LoginAction() {
        this.userDAO = UserDAOFactory.createUserDAO(0)  //创建对象
    }

    public void execute(){
        //其他代码
        userDAO.findUserById();  //使用对象
        //其他代码
    }

}

修改之后:

  1. 如果需要新增或删除UserDAO的子类,只需要修改UserDAOFactory类
  2. 如果UserDAO的子类接口发生变动,只需要修改LoginAction类

这样,引起类变化的原因就只有一个,也就是符合了单一职责原则

6. 简单工厂模式的简化

某些情况下,可以将抽象产品类和工厂类合并,将静态工厂方法移动到抽象产品类中

7. 适用场景

  1. 工厂类负责创建的对象较少,由于对象较少,就不会导致工厂内部逻辑过于复杂
  2. 客户端只知道传入工厂类的参数,不需关心具体的创建实现细节