前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式之工厂方法模式

设计模式之工厂方法模式

作者头像
Dylan Liu
发布2019-08-07 15:14:55
4120
发布2019-08-07 15:14:55
举报
文章被收录于专栏:dylanliu

简介

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

定义

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

角色

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

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

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

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

模式结构

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

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

代码语言:javascript
复制
// 产品接口
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;
    }
}

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

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

代码语言:javascript
复制
// 工厂接口
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 就可以了

代码语言:javascript
复制
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 等框架中对模式的应用

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 定义
  • 角色
  • 模式结构
  • 使用场景
  • 优缺点
  • 最佳实践
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档