搜 索

创建型模式丨工厂模式

  • 220阅读
  • 2021年11月25日
  • 0评论
首页 / 默认分类 / 正文

前言

这种设计模式也是 Java 开发中最常见的⼀种模式,它的主要意图是定义⼀个创建对象的接⼝,让其⼦类⾃⼰决定实例化哪⼀个⼯⼚类,⼯⼚模式使其创建过程延迟到⼦类进⾏。
按实际业务场景划分,工厂模式有 3 种不同的实现方式:

  1. 简单工厂模式
  2. 工厂方法模式
  3. 抽象工厂模式

简单工厂模式

我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”。
举个例子,当我们需要一个苹果手机时,需要知道苹果手机的构造方法,然后 new 一个来使用,当我们需要一个小米手机时,也需要它的构造方法,然后 new 一个来使用......每 new 一个对象,相当于调用者多知道了一个类,增加了类与类之间的联系,不利于程序的松耦合。
更好的实现方式是有一个手机工厂,我们告诉工厂需要什么牌子的手机,工厂将我们需要的手机制造出来给我们就可以了。这样我们就无需知道苹果手机、小米手机是怎么生产出来的,只用和工厂打交道即可。

模式的结构

简单工厂模式的主要角色如下:

  • 简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
  • 抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
  • 具体产品(ConcreteProduct):是简单工厂模式的创建目标。

其结构如下图所示。
简单工厂模式

编码实现

产品1
产品2

public class XiaoMiPhone implements Phone {
    private String name;

    private int memorySize;

    private double price;

    // 省略 setter 与 getter 方法

    public XiaoMiPhone() {

    }

    public XiaoMiPhone(String name, int memorySize, double price) {
        this.name = name;
        this.memorySize = memorySize;
        this.price = price;
    }

    @Override
    public String toString() {
        return "一部【名称:" + name + "|内存:" + memorySize + "G|价格:" + price + "元】的小米手机";
    }

    @Override
    public void showDetails() {
        System.out.println(this);
    }
}

public class ApplePhone implements Phone{

    private String name;

    private int memorySize;

    private double price;

    // 省略 setter 与 getter 方法
    
    public ApplePhone() {

    }
    
    public ApplePhone(String name, int memorySize, double price) {
        this.name = name;
        this.memorySize = memorySize;
        this.price = price;
    }

    @Override
    public String toString() {
        return "一部【名称:"+ name + "|内存:" + memorySize + "G|价格:" + price +"元】的苹果手机";
    }

    @Override
    public void showDetails() {
        System.out.println(this);
    }
}

生产工厂

public class PhoneFactory {
    public Phone create(String type) {
        switch (type) {
            case "苹果":
                return new ApplePhone();
            case "小米":
                return new XiaoMiPhone();
            default:
                throw new IllegalArgumentException("暂时没有这种手机");
        }
    }
}

测试

public class Client {
    public static void main(String[] args) {
        PhoneFactory pf = new PhoneFactory();
        Phone apple = pf.create("苹果");
        Phone xiaomi = pf.create("小米");
        apple.showDetails();
        xiaomi.showDetails();
    }
}

将构建过程封装不仅可以降低耦合,如果某个产品构造方法相当复杂,使用工厂模式可以大大减少代码重复。比如,如果生产一部手机需要名字、内存大小、价格等参数,那么只需将工厂修改如下:

public class PhoneFactory {
    public Phone create(String type) {
        switch (type) {
            case "苹果":
                return new ApplePhone("iPhone13", 512, 8399.0);
            case "小米":
                return new XiaoMiPhone("xiaomi11", 128, 3799.0);
            default:
                throw new IllegalArgumentException("暂时没有这种手机");
        }
    }
}

调用者的代码则完全不需要变化,而且调用者不需要在每次需要手机时,自己去构建名字、内存、价格以获得手机。手机的生产过程再复杂,也只是工厂的事。

优点与缺点

优点

  1. 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
  2. 客户端无需知道所创建具体产品的类名,只需知道参数即可。
  3. 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。

缺点

  1. 如果需要生产的产品过多,此模式会导致工厂类过于庞大,承担过多的职责,变成超级类。一旦增加或修改产品,不得不修改工厂逻辑,这违背了单一职责原则。
  2. 简单工厂模式每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,增加了系统的复杂度,这违背了开闭原则。

工厂方法模式

为了解决简单工厂模式的这两个弊端,工厂方法模式应运而生,它规定每个产品都有一个专属工厂:苹果手机有专属的苹果工厂,小米手机有专属的小米工厂。

模式的结构

工厂方法模式的主要角色如下。

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

其结构如下图所示。
工厂方法模式

编码实现

为了方便,具体手机类就不展示了,与上面的基本一致。手机产品工厂都实现一个公共的手机工厂接口。

产品工厂1
产品工厂2

public class OnePlusFactory implements PhoneFactory{

    @Override
    public MobilePhone newPhone() {
        return new OnePlusPhone();
    }
}

public class SamSungFactory implements PhoneFactory{

    @Override
    public MobilePhone newPhone() {
        return new SamSungPhone();
    }
}


测试

public class Client {
    public static void main(String[] args) {
        OnePlusFactory opf = new OnePlusFactory();
        SamSungFactory ssf = new SamSungFactory();
        MobilePhone op = opf.newPhone();
        MobilePhone ss = ssf.newPhone();
        op.showDetails();
        ss.showDetails();
    }
}

优点与缺点

优点

  1. 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品以及它的具体创建过程;很明显耦合度降低了。
  2. 灵活性增强,对于新产品的创建,无需更改既有的工厂,只需多写一个相应的工厂类。保持了面向对象的可扩展性,符合开闭原则。

缺点

  1. 类的个数容易过多,增加复杂度。
  2. 抽象产品只能生产一种产品,有几种产品就需要知道几个工厂类。

抽象工厂模式

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品(如:畜牧场只养殖动物、汽车厂只生产汽车、外国语学院主要培养西方语言学专业的学生),而抽象工厂模式可生产多个等级的产品(如:农场经营各种农产品和畜牧产品、五菱不只生产汽车还造口罩、大学不止一个院系)。

模式的结构

抽象工厂模式同工厂方法模式一样,也是由抽象工厂、具体工厂、抽象产品和具体产品等 4 个要素构成,但抽象工厂中方法个数不同,抽象产品的个数也不同。
抽象工厂模式的主要角色如下。

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
  • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。

抽象工厂模式的结构如下图所示。
抽象工厂模式

编码实现

动物产品接口中定义一个显示动物体重的方法 showWeight(),植物产品接口中定义一个显示植物颜色的方法 showColor(),农场接口中定义一个生成动物的方法 newAnimal() 和一个培养植物的方法 newPlant()。为了偷懒,具体产品类就不写了。
本例用抽象工厂模式来设计两个农场,一个农场 A 用于养奶牛和种苹果,一个农场 B 用于养绵羊和种香蕉。

农场一
农场二

public class FarmA implements Farm{
    @Override
    public Animal newAnimal() {
        return new Cow(1100);
    }

    @Override
    public Plant newPlant() {
        return new Apple("红");
    }
}

public class FarmB implements Farm{
    @Override
    public Animal newAnimal() {
        return new Sheep(110);
    }

    @Override
    public Plant newPlant() {
        return new Banana("黄");
    }
}

测试

public class Client {
    public static void main(String[] args) {
        Farm a = new FarmA();
        Farm b = new FarmB();
        Animal animalA = a.newAnimal();
        Plant plantB = b.newPlant();
        animalA.showWeight();
        plantB.showColor();
    }
}

可以看到,在创建时指定了具体的工厂类后,在使用时就无需再关心是哪个工厂类,只需要将此工厂当作抽象的 Farm 接口使用即可。由于客户端只和 Farm 打交道了,调用的是接口中的方法,使用时根本不需要知道是在哪个具体工厂中实现的这些方法,这就使得替换工厂变得非常容易。
Farm 接口中的抽象方法不多时,还看不出抽象工厂模式的威力。例如将程序中的 MySQL 数据库整个替换为 MongoDb 数据库,使用抽象方法模式的话,只需在接口中定义好增删改查四个方法,让 MySQLFactory 和 MongoDbFactory 实现此接口,调用时直接使用接口中的抽象方法即可,调用者无需知道使用的什么数据库,我们就可以非常方便的整个替换程序的数据库,并且让客户端毫不知情。

优点与缺点

优点

  1. 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
  2. 当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
  3. 抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则、依赖倒置原则。

缺点

  1. 当产品族中需要增加一个新的产品时,所有的具体工厂类都需要进行修改。

所以抽象工厂模式适用于增加同类工厂这样的横向扩展需求,不适合新增功能这样的纵向扩展

参考

LeetBook - 深入浅出设计模式
C语言中文网 - 设计模式
深入设计模式

创建型模式提供创建对象的机制, 增加已有代码的灵活性和可复用性。

打 赏
  • 支付宝
  • 微信
Alipay
WeChatPay
评论区
暂无评论
avatar