设计模式-工厂模式

94 阅读5分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

工厂模式(Factory Pattern) 是 Java 中最常用的设计模式之一,Spring中的依赖注入即是通过工厂模式实现的。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

工厂模式主要有三种:简单工厂、工厂方法、抽象工厂。

工厂模式

简单工厂模式

当我们创建一个对象的时候,一般采用new的方式创建一个新对象。

A a = new A();

这种方法在一般情况下没有问题。但是当该对象经常被创建,并且创建对象初始化过程太复杂的时候,代码会显得太过冗余,并且可维护性也很差。

这时候我们可以采用一个工厂类将对象的创建过程封装,可以避免这个问题。

public class Factory {
    public static Car create(String brand){
        if (brand.equals("特斯拉")){
            Tesla tesla = new Tesla();
            // 初始化代码...
            return tesla;
        }else if (brand.equals("宝马")){
            BMW bmw = new BMW();
            // 初始化代码...
            return bmw;
        }else if (brand.equals("奔驰")){
            Mercedes mercedes = new Mercedes();
            // 初始化代码...
            return mercedes;
        }
        return null;
    }
}

如以上代码所示,当我们需要创建某个方法,直接调用Factory中的create(String brand)方法即可获得相应的实例化对象(参数brand采用enum形式实现会更合适)。类图如下图所示。

image-20210824150717377

工厂方法模式

简单工厂模式的代码实现中,使用了大量的if...else语句,这会带来一个问题:当我们对产品种类进行添加或者减少的时候,我们需要对if...else进行添加或者删除,这违反了开闭原则。

开闭原则:软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的。

要想解决if...else语句问题,我们可以为每个产品创建一个工厂子类,每个工厂子类实现抽象工厂接口。这样一来,我们可以直接调用工厂子类来获取产品对象,这就是工厂方法模式。

工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

public interface Factory {
     Car create();
}
​
public class TeslaFactory implements Factory{
    @Override
    public Car create() {
        Tesla tesla = new Tesla();
        // 初始化...
        return tesla;
    }
}
// ...

类图如下所示:

image-20210824153539071

抽象工厂模式

工厂方法模式也不是十全十美的,当产品数量过多的时候,会导致产生大量的工厂子类,太过繁琐。对此,我们可以不将每个产品对应一个工厂子类,而是将产品进行分组,每组产品对应一个工厂子类,即可解决这个问题,这就是抽象工厂模式。

抽象工厂模式:抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体的类。

对于汽车工厂的示例来说,我们按品牌分可以分为宝马、奔驰等品牌,品牌内按能源可以分为电动汽车和燃油汽车 ,类图如下:

image-20210824162923492

代码示例如下:

public interface Factory {
    ElectricCar createElectricCar();
    FuelCar createFuelCar();
}
​
public class MercedesFactory implements Factory{
    @Override
    public ElectricCar createElectricCar() {
        ElectricMercedes electricMercedes = new ElectricMercedes();
        return electricMercedes;
    }
​
    @Override
    public FuelCar createFuelCar() {
        FuelMercedes fuelMercedes = new FuelMercedes();
        return fuelMercedes;
    }
}
​
public class BMWFactory implements Factory{
    @Override
    public ElectricCar createElectricCar() {
        ElectricBMW electricBMW = new ElectricBMW();
        return electricBMW;
    }
​
    @Override
    public FuelCar createFuelCar() {
        FuelBMW fuelBMW = new FuelBMW();
        return fuelBMW;
    }
}

在该场景下,相比工厂方法模式的4个工厂子类,抽象工厂模式只需要2个工厂模式即可达成目的,弥补了工厂方法模式的缺点。但是,当需要扩展工厂子类内的产品组需要扩展的时候,只能对接口进行修改,扩展性较差。

总结

工厂模式主要有三种:简单工厂模式,工厂方法模式和抽象工厂模式。

  • 简单工厂模式:由一个工厂对象决定创建出哪一种产品类的实例。
  • 工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
  • 抽象工厂模式:抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体的类。

使用工厂模式的目的:

  • 解耦:把对象的创建和使用的过程分开。就是当Class A 想调用 Class B 的时候,只是调用B的方法,不需要负责对B进行实例化,B的实例化交给工厂类负责。
  • 降低代码重复:如果创建对象B的过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。我们可以这些创建对象B的代码放到工厂里统一管理。既减少了重复代码,也方便以后对B的创建过程的修改维护。(当然,也可以把这些创建过程的代码放到类的构造函数里,同样可以降低重复率,而且构造函数本身的作用也是初始化对象。不过,这样也会导致构造函数过于复杂,代码可读性比较差)。
  • 降低维护成本:由于创建过程都由工厂统一管理,所以发生业务逻辑变化,不需要找到所有需要创建B的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。同理,想把所有调用B的地方改成B的子类B1,只需要在对应生产B的工厂中或者工厂的方法中修改其生产的对象为B1即可,而不需要找到所有的new B()改为new B1()。
  • 降低错误率:因为工厂管理了对象的创建逻辑,使用者并不需要知道具体的创建过程,只管使用即可,减少了使用者因为创建逻辑导致的错误。