设计模式之工厂方法模式

原创
2019/08/05 23:59
阅读数 21

简介

工厂方法模式(Factory Method Pattern) 隶属于设计模式中的创建型模式,前面的简单工厂模式是工厂方法模式的简化版,因此两者在很多方面都是相似的。

定义

工厂方法模式:定义一个用于创建对象的接口,由子类决定如何实例化和实例化哪个类,工厂方法模式使得类的实例化延迟到其子类。

角色

由上面的UML图可知工厂方法模式存在四种角色,分别是工厂接口、工厂实现、抽象产品接口、具体产品实现。

对于产品这个概念我们上一篇文章中解释过

这里的产品也是很抽象的一个概念。按照平时的实践,抽象产品角色可以是策略模式,具体产品角色就是某个策略;抽象产品可以是命令模式,具体产品角色就是某个命令;抽象产品角色也可以是创建型模式中的抽象工厂,具体产品角色就是某个具体的工厂类。只要涉及到继承关系,简单工厂模式就有可能的用武之地,而设计模式是继承使用的重场景,所以简单工厂模式是可以和设计模式的其他模式很好的结合起来的。

不过在工厂方法模式中,产品会更重一些,因为工厂方法一个工厂类对应一个产品类,如果产品是很简单的使用 new 关键字就可以实例化出来的话,其实是没有必要使用工厂方法模式的。

模式结构

工厂方法模式在子类中实例化具体产品类,很多网上示例都是一个简单的 new 就可以实例化出来的对象,完全没有必要使用工厂方法模式这么重的创建型模式,简单工厂方法更合适一点,我们在这举造车的例子。

车包含多个部件,有引擎,轮子,底盘,外壳、车内装饰等等,使用方对这些部件其实是没有必要了解的,我们就可以使用工厂方法模式来封装内部实现,只给用户暴露车这个接口。

// 产品接口
public interface Engine {
    public void start();
    public void stop();
}

public interface Wheel {
    public void move();
    public void turn();
}
public interface Shell {
    public void move();
    public void turn();
    
    public void turnLightOn();
    public void turnLightOff();
}
public interface Light() {
    public void on();
    public void off();
}
public interface Furniture {
    public void furnish();
}
// 车的接口
public interface Car {
    public void start();
    public void stop();
    public void move();
    public void turnAround();
}

//产品实现
public class DefaultEngine implements Engine{
    public void start() {
        System.out.println("engine start");
    }
    public void stop() {
        System.out.println("engine stop")
    }
}

public class DefaultWheel {
    public void move() {
        System.out.println("wheel move");
    }
    public void turn() {
        System.out.println("turn around");
    }
}

public class DefaultShell() {
    private List<Wheel> wheels = new LinkedList();
    private List<Light> lights = new LinkedList();
    
    public void addLight(Light light) {
        lights.add(light);
    }
    public void addWheel(Wheel wheel) {
        wheels.add(wheel);
    }
    
    public void move() {
        for (Wheel wheel : wheels) {
            wheel.move();
        }
    }
    public void turn() {
        for (Wheel wheel : wheels) {
            wheel.turn();
        }
    }
    // 亮灯,关灯等操作不添加了
}

public class DefaultLight {
    public void on() {
        System.out.println("light is on");
    }
    public void off() {
        System.out.println("light is off");
    }
}
public class DefaultFurniture {
    public void furnish() {
        System.out.println("furniture furnish");
    }
}

public class DefaultCar {
    private Engine engine;
    private Shell shell;
    private Furniture furniture;
    
    public void start() {
        engine.start();
    }
    public void stop() {
        engine.stop();
    }
    public void move() {
        shell.move();
    }
    public void turnAround() {
        shell.turn();
    }
    // setter method
    public void setEngine(Engine engine) {
        this.engine = engine;
    }
    public void setShell(Shell shell) {
        this.shell = shell;
    }
    public void setFurniture(Furniture furniture) {
        this.furniture = furniture;
    }
}

 

如果让用户来自行组装车辆,那么用户就需要了解引擎构造,外壳上可以安装什么东西等等知识,根据迪米特法则,用户不应该了解自己不需要的知识,因此直接让用户构造车的实例是违反最小知识原则的。

 

因此我们提供一个工厂方法来给用户使用

// 工厂接口
public interface CarFactory {
    public Car getCar();
}
// 工厂实现
public interface DefaultCarFactory {
    public Car getCar() {
        Wheel frontLeft = new DefaultWheel();
        // ... 其他轮子实例化
        Light light = new DefaultLight();
        //... 其他灯实例化
        
        Shell shell = new DefaultShell(); 
        shell.addWheel(frontLeft);
        //.... 添加其余三个轮子
        shell.addLight(light);
        //.... 添加其他灯
        
        Engine engine = new DefaultEngine();
        Furniture furniture = new DefaultFurniture();
        
        Car car = new DefaultCar();
        car.setEngine(engine);
        car.setShell(shell);
        car.setFurniture(furniture);
        
        return car;
    }
}

有了工厂类,用户就可以不用操心如何创建车这个实例了,直接调用工厂方法的 getCar 就可以了

public class Main {
    public void main(String[] args) {
        CarFactory factory = new DefaultCarFactory();
        Car car = factory.getCar();
        
        car.start();
        car.move();
        car.stop();
    }
}

使用场景

1. 生成复杂对象。工厂方法由于由子类来决定如何实例化产品类,因此适合需要生成复杂对象的地方,像上面模式结构中举的车例子,对于简单对象,简单工厂模式可能更合适一点。

2. 需要屏蔽构造细节。工厂方法通过暴露工厂类给用户,相当于把产品的具体细节隐藏了起来,使得用户不需关心如何构造对象,却可以使用对象功能。

3. 获得更多的可扩展性。工厂模式依赖于抽象,不同的产品可以有不同的工厂实例来构造,扩展性相对较好

4. 框架。框架需要为用户提供 SPI 接口,因此工厂类是很好的扩展点,Spring 提供了 FactoryBean 的工厂接口,用户可以实现这个接口来提供 Bean。

 

优缺点

1. 类增长,同时关注点分离。每一个产品需要对应一个工厂类,增加了一倍的类数量,但同时将创建类和使用类两个职责分离开,对用户友好。

2. 调用链条变长导致可理解性变差,同时屏蔽了细节,封装了变化。由于用户在使用工厂类生成产品类,最终使用的是产品类提供的功能,因此用户不知产品的具体细节,使用方便,理解困难。

3. 一个具体工厂对应一个具体产品的构建,如果产品的构造方式发生了变化,那么需要修改的就不仅仅是产品类,对应的工厂类也需要修改,但同时如果直接由用户修改的话,修改的就不仅仅是一个地方了。

4. 依赖倒置。原本由用户来实例化产品类,现在实例化控制权到了工厂类手中,即创建方手中,实现用户和框架的解耦。

 

最佳实践

1. 如果不需要过多的子类来实例化产品类,只有一个子类来负责产品的实例化,此时可以去掉抽象工厂接口,模式退化为简单工厂模式,所以这两个模式的职责类似,区别只是使用场景用哪个更合适。

2. 如果产品分为很多类,则模式可以膨胀为抽象工厂模式

3. 工厂方法模式在框架中使用是最多的,可以学习 Spring, Dubbo 等框架中对模式的应用

 

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部