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")){
//显示折线图
}
}
}
存在的问题:
- 大量的条件判断语句,程序冗长,且影响性能
- Chart类同时负责图标的初始化和展示,不符合单一职责原则,不利于类的重用和维护
- 若需要新增图表类型,需要直接修改源码,不符合开闭原则
- 客户端只能通过new来创建Chart对象,导致耦合度较高,对象的创建和使用无法分离
2. 简单工厂模式介绍
定义一个工厂类,它可以根据不同的参数返回不同类的实例,而这些不同的实例都有着共同的父类。
当需要的时候,只需要传入参数,就可以获得实例对象,而无需关注具体的创建细节。
简单工厂模式包含三种角色:
- Factory(工厂角色)
简单工厂模式的核心,负责实现所有实例创建的具体逻辑,创建方法可以被外界直接调用,并以父类Product返回
- Product(抽象产品角色)
是所有具体产品的父类,定义了所有产品的公共方法,同时通过多态的方式,使Factory类中只需要定义一个创建函数就可以返回各个不同种类的具体实例
- 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(); //使用对象
//其他代码
}
}
修改之后:
- 如果需要新增或删除UserDAO的子类,只需要修改UserDAOFactory类
- 如果UserDAO的子类接口发生变动,只需要修改LoginAction类
这样,引起类变化的原因就只有一个,也就是符合了单一职责原则
6. 简单工厂模式的简化
某些情况下,可以将抽象产品类和工厂类合并,将静态工厂方法移动到抽象产品类中
7. 适用场景
- 工厂类负责创建的对象较少,由于对象较少,就不会导致工厂内部逻辑过于复杂
- 客户端只知道传入工厂类的参数,不需关心具体的创建实现细节