工厂方法模式

工厂方法模式Factory Method Pattern又称为工厂模式,也叫虚拟构造器Virtual Constructor模式或者多态工厂Polymorphic Factory模式,它属于类创建型模式,在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

描述

简单工厂模式有一个问题,类的创建依赖工厂类,也就是说如果想要拓展程序,必须对工厂类进行修改,而工厂方法模式就是将具体的创建过程交给专门的工厂子类去完成,即将产品类的实例化操作延迟到工厂子类中完成,由此可以不必再去修改工厂类,而只需要定义实现工厂的子类即可。

模式分析

工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。

模式结构

  • Product:抽象产品。
  • ConcreteProduct:具体产品。
  • Factory:抽象工厂。
  • ConcreteFactory:具体工厂。

优点

  • 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
  • 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
  • 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。

缺点

  • 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。

  • 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到反射等技术,增加了系统的实现难度。

实现

工厂方法模式的本意是将实际创建对象的工作推迟到子类中,这样核心类就变成了抽象类,但是在JavaScript中很难像传统面向对象那样去实现创建抽象类,所以在JavaScript中只需要参考其核心思想即可。

class Shape { // 产品基类
    say(){
        console.log(this.name);
    }
}

class Rectangle extends Shape{ // 长方形产品
    constructor(){
        super();
        this.name = "Rectangle";
    }
}

class Square extends Shape{ // 正方形产品
    constructor(){
        super();
        this.name = "Square";
    }
}

class Circle extends Shape{ // 圆形产品
    constructor(){
        super();
        this.name = "Circle";
    }
}

class Factory{ // 工厂基类
    getProduct(){}
}

class RectangleFactory extends Factory{
    constructor(){
        super();
    }

    getProduct(){
        return new Rectangle();
    }
}

class SquareFactory extends Factory{
    constructor(){
        super();
    }

    getProduct(){
        return new Square();
    }
}

class CircleFactory extends Factory{
    constructor(){
        super();
    }

    getProduct(){
        return new Circle();
    }
}


var rectangle = (new RectangleFactory).getProduct();
rectangle.say(); // Rectangle

var square = (new SquareFactory).getProduct();
square.say(); // Square

var circle = (new CircleFactory).getProduct();
circle.say(); // Circle
// 组装图形的例子 // 假如需要组装三个图形
class Shape { // 形状基类
    say(){
        console.log(this.name);
    }
}

class Rectangle extends Shape{ // 长方形
    constructor(){
        super();
        this.name = "Rectangle";
    }
}

class Square extends Shape{ // 正方形
    constructor(){
        super();
        this.name = "Square";
    }
}

class Circle extends Shape{ // 圆形
    constructor(){
        super();
        this.name = "Circle";
    }
}

class Combination{
    constructor(rectangle, square, circle){
        console.log(`Combination: ${rectangle.name} ${square.name} ${circle.name}`);
    }
}

// 直接组装 调用者只需要一个组合完成的图形,而为了组装需要分别实例化三个形状,耦合度太高
(function(){
    var rectangle = new Rectangle();
    var square = new Square();
    var circle = new Circle();
    new Combination(rectangle, square, circle);
})();

// 使用工厂 隐藏具体实现细节 降低耦合度
class CombinationFactory{
    getProduct(){
        var rectangle = new Rectangle();
        var square = new Square();
        var circle = new Circle();
        return new Combination(rectangle, square, circle); 
    }
}

(function(){
    var combination = (new CombinationFactory).getProduct(); 
})();

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://blog.csdn.net/carson_ho/article/details/52343584 https://wiki.jikexueyuan.com/project/java-design-pattern/factory-pattern.html https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/factory_method.html