设计模式是软件设计中常见问题的解决方案,用于解决代码中反复出现的设计问题。它不是一种算法,更不是一套代码,你可以理解为是一种对特定问题的解决思路,同一种模式在不同的场景下的实现代码可能会不一样。通常软件设计模式被分为三大类:

  1. 创建型模式 – 用于如何创建对象,核心思想是把对象的创建和使用相分离,使两者能够相对独立,增加代码的灵活性和可复用性。
  2. 结构型模式 – 用于如何组合对象,组成一个较大的结构,并保持代码的灵活性和可复用性。
  3. 行为型模式 – 用于如何协同对象,在组合起来的对象之间保持高效沟通和职责分配,就是一组对象如何协同完成一个整体任务。

本文主要涉及的是工厂方法,它属于创建型模式,按照抽象程度可以划分为三种:

  1. 简单工厂模式(Simple Factory)
  2. 工厂方法模式(Factory Method)
  3. 抽象工厂模式(Abstract Factory)

简单工厂模式

简单工厂模式提供统一的创建对象的方法,并决定实例化对象的类型。就是由调用者来决定创建哪种对象,一个很好的比喻就是自助餐厅里的饮料机,一台机器可以产出各种饮料,具体是要可乐还是雪碧由顾客决定。

我们以物流为例,假如我们是一家物流公司,现有两种方式运输:卡车陆运和轮船海运。

// 产品基类:运输方式
class Transport {
    deliver() {};
}

// 卡车
class Truck extends Transport {
    deliver() {
        console.log('Delivered by truck');
    };
}

// 轮船
class Truck extends Transport {
    deliver() {
        console.log('Delivered by ship');
    };
}

// 工厂基类:物流公司
class Logistics {
    // 运输方式由调用方选择
    static createTransport(type) {
        switch (type) {
            case 'truck':
                return new Truck();
            case 'ship':
                return new Ship();
        }
    }
}

// 调用方选择使用卡车陆运
Logistics.createTransport('truck').deliver(); // 输出:Delivered by truck

这时业务扩展,需要新增飞机空运,那么需要怎么做呢?修改工厂基类,这违反了设计模式的开闭原则(Open Closed Principle),该原则是指软件应该对扩展开放,而对修改关闭。如果业务复杂一些就不合适了,而工厂方法模式就是该问题的解决方案。

工厂方法模式

工厂方法模式是由父类提供创建对象的方法,允许子类决定实例化对象的类型。

// 修改工厂基类:物流公司
class Logistics {
    createTransport() {}
}

// 陆运
class RoadLogistics extends Logistics {
    createTransport() {
        return new Truck();
    }
}

// 海运
class SeaLogistics extends Logistics {
    createTransport() {
        return new Ship();
    }
}

// 调用者选择使用卡车陆运
new RoadLogistics().createTransport().deliver(); // 输出:Delivered by truck

这时业务扩展,需要新增飞机空运,只需要新增一种产品的子类和一种工厂的子类即可:

// 飞机
class Airplane extends Transport {
    deliver() {
        console.log('Delivered by airplane');
    };
}

// 空运
class AirLogistics extends Logistics {
    createTransport() {
        return new Airplane();
    }
}

// 调用者选择使用飞机空运
new AirLogistics().createTransport().deliver(); // 输出:Delivered by airplane

又由于业务需要,需要加入不同品牌的卡车和轮船,工厂方法模式已经无法满足需求了,这时抽象工厂模式适时的出现。

抽象工厂模式

抽象工厂模式它能够创建一系列对象,但无需指定其具体类。这个模式比较复杂,在 javascript 中比较难以理解,因为 javascript 没有抽象类的概念。在此我尽量用语言描述,结合下面的代码应该能够较好的解释。

在 java 中(不是 javascript),可以定义一种抽象类,里面定义了一个抽象类必要的接口(interface),子类必须要实现(implements)这些接口,因此可以保证所有的子类都拥有接口里定义的方法。而所谓抽象工厂模式,就是拥有多个抽象产品类(如巨头A公司和巨头B公司的运输工具),并实现多个产品(如巨头A公司的卡车和轮船),同时拥有一个抽象工厂类(如我们的物流公司),并实现多个工厂(如陆运和海运方式),每个工厂将会生产有多种同类的产品(如陆运方式可以派出巨头A公司的卡车和巨头B公司的卡车)。有点晕?没事,程序员的母语是代码,我用代码再解释一遍。

// 第一个抽象产品类:巨头A公司
class MagnateATransport  {
    deliver() {}
}
// 巨头A的卡车
class MATruck extends MagnateATransport {
    deliver() {
        console.log('Delivered by Magnate A truck');
    }
}
// 巨头A的轮船
class MAShip extends MagnateATransport {
    deliver() {
        console.log('Delivered by Magnate A ship');
    }
}

// 第二个抽象产品类:巨头A公司
class MagnateBTransport  {
    deliver() {}
}
// 巨头B的卡车
class MBTruck extends MagnateBTransport {
    deliver() {
        console.log('Delivered by Magnate B truck');
    }
}
// 巨头B的轮船
class MBShip extends MagnateBTransport {
    deliver() {
        console.log('Delivered by Magnate B ship');
    }
}

// 唯一的抽象工厂类:我们的物流公司,当中有两个接口,分别是使用巨头A的运输工具和使用巨头B的运输工具
class Logistics {
    createMATransport() {}
    createMBTransport() {}
}

// 陆运方式:可以派出巨头A卡车和巨头B卡车
class RoadLogistics extends Logistics {
    createMATransport() {
        return new MATruck();
    }

    createMBTransport() {
        return new MBTruck();
    }
}

// 海运方式:可以派出巨头A轮船和巨头B轮船
class SeaLogistics extends Logistics {
    createMATransport() {
        return new MAShip();
    }

    createMBTransport() {
        return new MBShip();
    }
}

// 调用者只需关心抽象工厂类,如选择陆运并巨头B公司的运输工具
new RoadLogistics().createMBTransport().deliver(); // 输出:Delivered by Magnate B truck

最后

平时编写代码的过程中很可能有意无意的使用了某种设计模式,因为设计模式就是这样在实践经验中总结出来的,它不仅仅能够提高你的软件的可维护性可扩展性,还可以作为团队内高效的沟通工具,只需陈述模式名,大家便都理解这背后的想法。

当然,设计模式是为了解决问题而存在的,通常这些问题会在程序结构到达一定规模时出现,在简单的项目里强行使用设计模式也许是一种本末倒置的做法,初学者要注意不要为了设计而设计。