设计模式(2)——工厂模式详解

一、前言

  今天这篇博客来介绍一下设计模式中非常常用的一种——工厂模式。事实上,工厂模式这个名称只是一个统称,它又分为简单工厂、工厂方法以及抽象工厂。接下来我就来分别介绍一下它们。


二、正文

 2.1 针对接口编程

  针对接口编程是面向对象程序设计语言中的一个设计原则,这指示我们在编程的过程中,应该尽量的使用接口去引用我们的具体对象,而不是使用具体的类,这么做的目的是让使用这个对象的代码不会过度依赖于具体的类型。如果我们使用的是接口,而不是具体的类型,那我们就为这里的代码保留了一些弹性,因为我们可以很简单地更换另一个子类,而不会影响使用它的代码。

注意:这里所说的接口并没有规定就是interface,它表示的是所有的父类型,可以是一个interface,可以是一个抽象类,甚至可以是一个普通类,它泛指某种类型的上层类型;

  而工厂方法的实现就是遵循了这个原则,从而实现了解耦。


 2.2 简单工厂

  简单工厂和它的名字一样,实现起来非常简单,它的定义如下:

在创建对象时不向用户暴露创建对象的细节,仅仅提供一个通用的接口,通过这个接口来创建对象;

  简单工厂将创建具有公共父类对象的代码单独拿出来,放入到了一个类中,并提供一个接口,可以通过调用这个接口来创建对象,但是具体创建的是哪一个子类对象由简单工厂决定。我们直接通过以下代码来理解。假设我们需要创建A类产品,而A类产品有四种具体的产品,如下所示:

// A类产品
abstract class A {
}

// 以下均为具体产品
class A1 extends A {
}

class A2 extends A{
}

class A3 extends A{
}

  假设上面三个具体产品的编号分别为1、2、3,我们在代码中需要根据编号来选择需要创建的产品,于是我们可以这样写:

public static void main(String[] args) throws IOException {
    int type = System.in.read();
    A a = null;
    if (type == 1) {
        a = new A1();
    } else if (type == 2) {
        a = new A2();
    } else if (type == 3) {
        a = new A3();
    } else {
        System.out.println("no product");
    }
}

  很明显,上面的代码是非常不好的,它会导致创建对象的代码与具体的对象类型有很大的耦合性。从代码复用的角度来说,假设我们在其他地方也需要创建A产品,则每一个需要使用的地方都要写上上面这段代码;再从扩展性的角度来说,假设我们需要新加入一个产品A4,或者修改产品的编号,那对于每一个使用了上面这段代码的地方都需要进行修改,大大增加了程序维护的难度,这就是高耦合的危害。所以我们需要对上面的代码进行解耦,让具体逻辑和创建对象的代码分离。根据我们编程的经验,可以这要进行:

class AFactory {
    public static A createA(int type) {
        if (type == 1) {
            return new A1();
        } else if (type == 2) {
            return new A2();
        } else if (type == 3) {
            return new A3();
        } else {
            System.out.println("no product");
        }
        return null;
    }
}

  以上就是简单工厂的实现。我们将创建对象的过程提取到了一个类的公共方法中,方法的返回值就是这些产品的公共父类,这样我们在需要创建对象的地方直接调用上面的工厂即可。而且方法隐藏了对象的具体类型,降低了对具体类型的耦合。而且在需要修改时,只需要修改方法即可,对调用它的代码是隐蔽的。

  实际上,简单工厂并不是一个设计模式,它是我们编码时的一个良好习惯——抽取重复使用的代码,便于复用。


 2.3 工厂方法模式

  我们先来看看工厂方法的定义:

工厂方法模式定义了一个创建对象的接口,但是这个接口由子类进行实现,子类决定具体需要创建的对象。工厂方法将创建对象的工作延迟到了子类;

  工厂方法首先定义了一个顶层的工厂类,它提供了一个创建对象的方法,但是这个方法一般没有具有实现,因为它也不确定需要创建哪一个对象。而具体创建对象的工作是交由子类工厂进行实现的。程序员根据子类工厂的职责,来实现父工厂创建对象的接口方法,由子类决定自己需要创建的具体类型。而选择不同的子类工厂,自然也会创建出不同的对象。下面我们通过代码来看一看。

  假设我们现在需要创建Product类型的产品,而Product类型有两种具体的产品,而且Product类型的产品都有一个show操作,代码如下:

// 抽象的产品
abstract class Product {
    // 定义产品的公共方法
    abstract void show();
}

// 具体产品1
class Product1 extends Product{
    @Override
    void show() {
        System.out.println("Product1");
    }
}

// 具体产品2
class Product2 extends Product{
    @Override
    void show() {
        System.out.println("Product2");
    }
}

  我们需要工厂Factory来创建这个产品,但是我们希望有多个工厂,每个工厂负责创建不同类型的产品。但是我们希望这些工厂在使用时没有任何区别,不想知道具体使用的是哪个工厂,只是希望在使用它时,能够给我创建需要的产品。于是我们可以创建一个公共的工厂类,但是它只是一个公共的标准,并不会工作,而真正工作的是基于这个标准建造的工厂,它们分别负责创建Product1Product2

// 抽象的工厂类提供了一个创建对象的接口
// 它是具体工厂的一个标准
abstract class Factory {
    /**
     * 创建对象的接口,但是没有实现,由子类来决定创建什么对象
     * 不需要在意产品的具体类型,因为它们都有公共的父类
     */
    abstract public Product createProduct();
}

// 子类实现抽象的工厂类,实现具体的创建对象的接口
class Factory1 extends Factory{
    @Override
    public Product createProduct() {
        // Factory1创建Product1
        return new Product1();
    }
}

class Factory2 extends Factory{
    @Override
    public Product createProduct() {
        // Factory2创建Product2
        return new Product2();
    }
}

  于是现在我们可以创建具体的对象了。我们选择不同的工厂,从而创建不同的产品:

public static void main(String[] args) {
    // 根据选择的工厂不同,创建的产品也不同
    // 但是不需要在意产品的具体类型,因为产品有公共的分类Product

    // 选择工厂1
    Factory f1 = new Factory1();
    Product p1 = f1.createProduct();
    p1.show();  // 执行Product1的show方法

    // 选择工厂2
    Factory f2 = new Factory2();
    Product p2 = f2.createProduct();
    p2.show();  // 执行Product2的show方法
}

// 执行结果
Product1
Product2

  可以看到,根据我们创建的工厂不同,最后生产出的产品也不同,但是不论选用的是哪一个工厂,都可以使用Factory类型引用,而不管创建是是哪一个产品,都可以用Product接收,我们并不关心它们指向的是哪个工厂或者哪个产品,我们真正在意的只是它们能够做出正确的动作,仅此而已,这就是面向接口编程的好处。


 2.4 抽象工厂模式

  抽象共厂其实是基于工厂方法的,它的定义如下:

抽象工厂模式提供了一个接口,用来创建一组相关的对象,但是不需要明确指定这些对象的具体类型;

  上面的定义说明,抽象工厂模式是用来生产多个相互联系的一组对象,但是却不需要关心这些对象的具体类型,这样就可以让这些对象与使用它们的代码解耦。而创建这一系列对象中的每一个对象的方法,使用的就是上面所述的工厂方法模式。也就是说,抽象工厂中包含了多个工厂方法。我们还是通过一个案例来看看吧。

  假设我们要生产一辆车Car,为了简单,假设这个车由窗户和轮子两个零件组成,而不同类型的轮子和窗户可以组成不同的车。我们需要一些不同类型的轮子和窗户,下面定义了一个抽象类型Wheel表示轮子,而又定义了两个不同类型的轮子,一个是橡胶轮子,用来生产小汽车,而一个是木头轮子,用来生产木头车;

// 轮子抽象类
abstract class Wheel {
}

// 轮子的第一个产品:橡胶轮子
class RubberWheel extends Wheel {
}

// 轮子的第二个产品:木头轮子
class WoodenWheel extends Wheel {
}

  然后我们还需要窗户,于是定义了一个抽象类Window表示窗户,但是有玻璃窗户和木头窗户两种子类:

// 窗户抽象类
abstract class Window {
}

// 玻璃窗户
class GlassWindow extends Window {
}

// 木头窗户
class WoodenWindow extends Window {
}

  然后我们需要有一个Car类表示车,这个Car由轮子和窗户组成:

// 车
class Car {
    // 车轮
    Wheel wheel;
    // 车窗
    Window window;

    public Car(Wheel wheel, Window window) {
        this.wheel = wheel;
        this.window = window;
    }
}

  接下来我们就要考虑如何生产不同类型的车了。我们需要一个工厂,它可以生产轮子,也可以生产窗户,然后还能够将轮子和窗户组合起来,拼成一辆车。于是我们定义了一个车工厂,它有两个抽象方法,用来生产轮子和窗户,同时还有一个方法用来组装车。车的组装过程是固定的,就是将轮子和窗户组合起来,但是根据需要生产的车的不同,可能需要的零件不同,所以下面这个类只是定义了一种生产车的标准,但是具体如何生成车,需要用到哪些零件,这需要子类根据自己的功能去决定:

// 造车工厂:需要生产一系列车需要的零件,组合成车
abstract class CarFactory {
    // 造轮子
    abstract Wheel createWheel();

    // 造窗户
    abstract Window createWindow();

    // 将零件组合成车,这个过程是固定的,所以不需要在子类中实现
     Car createCar() {
        Wheel wheel = createWheel();
        Window window = createWindow();
        return new Car(wheel, window);
    }
}

  我们希望我们有两个工厂,一个生成小汽车,使用玻璃窗户和橡胶轮子;一个生成木头车,使用木头轮子和木头窗户。于是我们需要继承上面定义的生产车的标准CarFactory,然后选择创建自己需要的零件,如下:

// 制造小汽车
class CompactCarFactory extends CarFactory{
    @Override
    Wheel createWheel() {
        // 小汽车需要橡胶轮子
        return new RubberWheel();
    }

    @Override
    Window createWindow() {
        // 徐爱汽车需要玻璃窗户
        return new GlassWindow();
    }
}


// 制造木头车
class WoodenCarFactory extends CarFactory{
    @Override
    Wheel createWheel() {
        // 木头车需要木头轮子
        return new WoodenWheel();
    }

    @Override
    Window createWindow() {
        // 木头车需要木头窗户
        return new WoodenWindow();
    }
}

  于是现在,根据我们选择的工厂的不同,就能够生产不同类型的车子了,使用抽象工厂的代码如下。我们如果选择的是小汽车工厂,那调用生产车的接口,实际生产的就是玻璃窗户和橡胶轮子,组合出来的就是小汽车;如果我们选择的是木头车工厂,使用的就是木头车需要的零件,生产出来的是木头车。

public static void main(String[] args) {
    // 使用小汽车工厂创建小汽车
    CarFactory f1 = new CompactCarFactory();
    Car compactCar = f1.createCar();
    System.out.println(compactCar.wheel.getClass());
    System.out.println(compactCar.window.getClass());

    // 使用木头车工厂创建木头车
    CarFactory f2 = new WoodenCarFactory();
    Car woodenCar = f2.createCar();
    System.out.println(woodenCar.wheel.getClass());
    System.out.println(woodenCar.window.getClass());
}

  不难看出,在抽象工厂中,生产每一个零件的方法,使用的其实就是工厂方法模式,而将这些工厂方法放到一起,生产一系列相关的对象,就成了抽象工厂。抽象工厂让我们在创建多个相关联对象的过程中与使用它们的代码解耦,我们不需要在意具体工厂的类型,因为它们都是它们公共父类的类型,我们只需要调用父类提供的接口,多态机制就会进行正确的子类操作。我们也不需要在意我们生产的是哪些对象,因为我们使用的是它们的父类型去引用,但是并不会影响实际的功能。如果我们需要修改某个工厂生产的对象,只需要修改某个工厂方法即可,而对于调用它的代码来说,不会存在任何的影响,因为接口和返回结果看起来都没有改变。


三、总结

  工厂模式的主要目的就是将创建对象的逻辑与使用对象的代码解耦合,它遵循了针对接口编程的原则,实际上还遵循了依赖倒置原则。其实不仅仅是工厂模式,几乎所有的设计模式,其作用都是为了降低代码的耦合性,提高代码的复用性。


四、参考

  • 《Head First设计模式》
posted @ 2020-03-29 21:43  特务依昂  阅读(305)  评论(1编辑  收藏  举报