简单工厂

537 阅读4分钟

介绍

在设计原则中有这样一句话“我们应该针对接口编程,而不是针对实现编程”。但是我们还是一直使用new关键字来创建一个对象,这不就是在针对实现编程吗?

在技术上,new没有错,毕竟这是java的基础部分。真正的犯人是我们的老朋友“改变”。如果有一个不像是会改变的类,那么在代码中直接实例化也无大碍。想想看,我们平常还不是在程序中不假思索地实例化字符串对象吗?就没有违反这个原则?当然有!可以这么做吗?可以!为什么?因为字符串不可能改变。

针对接口编程,可以隔离掉以后系统发生的一大堆改变。为什么呢?如果代码是针对接口编写,那么通过多态,它可以与任何新类实现该接口。但是当代码使用大量的具体类时,等于是自找麻烦。

如果我们希望能够调用一个简单的方法,传递一个参数过去,就可以返回一个相应的具体对象。这个时候就可以使用简单工厂模式。

简单工厂其实不是一个设计模式,反而像是一种编程习惯。但是由于经常被使用,有些开发人员的确是把这个编程习惯称为“工厂模式”。

概念

简单工厂模式,可以根据传递的参数不同,返回不同类的实例。简单工厂模式定义了一个类,这个类专门用于创建其它类的实例,这些创建的类都有一个共同的父类。

简单工厂模式结构图:

  • Factory:工厂角色。专门用于创建实例类的工厂,提供一个方法,该方法根据传递参数的不同返回不同类的具体实例。
  • Product:抽象产品角色。是所有具体产品的父类。
  • ConcreateProduct:具体的产品角色。

模式实现

一年一度的春节到了,小马哥打算去附近车厂买一辆车,这个车厂生产三种类型的车,分别是宝马、奔驰和奥迪。下面我们使用简单工厂来模拟实现:

抽象车类(Product):

public abstract class BaseCar {
    public static final String BMW = "Bmw";
    public static final String BENZ = "Benz";
    public static final String AUDI = "Audi";

    /**
     * 启动
     */
    public abstract void start();
}

宝马、奔驰、奥迪(ConcreteProduct)这三辆车都实现了车类:

public class BmwCar extends BaseCar {

    @Override
    public void start() {
        System.out.println("宝马启动了");
    }
}
public class BenzCar extends BaseCar {

    @Override
    public void start() {
        System.out.println("奔驰启动了");
    }
}
public class AudiCar extends BaseCar {

    @Override
    public void start() {
        System.out.println("奥迪启动了");
    }
}

车厂类(Factory),根据传入的参数来生产对应的车:

/**
 * 增加车库类来管理车
 */
public class CarFactory {

    public BaseCar getCar(String carType){
        BaseCar car;
        switch (carType) {
            case BaseCar.BMW:
                car = new BmwCar();
                break;
            case BaseCar.BENZ:
                car = new BenzCar();
                break;
            case AUDI:
                car = new AudiCar();
                break;
            default:
                car = null;
        }
        return car;
    }
}

买一辆奔驰回家:

public class Test {

    public static void main(String[] args) {
        BaseCar car = new CarFactory().getCar(BaseCar.BENZ);
        car.start();
    }
}

继续来看一下UML类图:

优点

  • 简单工厂模式实现了对责任的分割,提供了专门的工厂类用于创建对象。
  • 客户端无需知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可。

缺点

  • 由于工厂类集中了所有产品创建的逻辑,一旦不能正常工作,整个系统都要收到影响。
  • 系统扩展困难,一旦添加新产品不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。

适用场景

  • 工厂类负责创建的对象比较少。
  • 客户端只知道传入工厂类的参数,对于如何创建对象不关心。

总结

简单工厂模式最大的优点在于工厂类中包含了必要的逻辑判断,根据应用层的选择条件动态实例化相关的类,对于应用层来说,去除了具体的产品依赖。

简单工厂模式是用一个工厂来生产不同的产品(工厂方法模式是用不同的工厂来生产不同的产品,一个工厂用来生产一种产品),通过应用层传入的参数来判断应该生产哪一种产品,如果要增加新的产品,就需要修改工厂类的判断逻辑(增加‘Case’的分条件),不符合开闭原则(对修改关闭,对扩展开放)。如果产品类经常更改,可以使用工厂方法模式

源码分析(JDK 13)

Calendar 类的getInstance()方法

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {

    //...(省略无关的代码,下同)
    public static Calendar getInstance() {
        Locale aLocale = Locale.getDefault(Locale.Category.FORMAT);
        //根据语言和国家的不同来返回不同的Calendar对象
        return createCalendar(defaultTimeZone(aLocale), aLocale);
    }
    
    //根据传入的参数返回相应的具体Calendar实例
    private static Calendar createCalendar(TimeZone zone,
                                           Locale aLocale){
        //...
        Calendar cal = null;
        //...
        if (cal == null) {
        
            if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
                cal = new BuddhistCalendar(zone, aLocale);
            } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                       && aLocale.getCountry() == "JP") {
                cal = new JapaneseImperialCalendar(zone, aLocale);
            } else {
                cal = new GregorianCalendar(zone, aLocale);
            }
        }
        return cal;
    }
}

UML类图: