工厂设计模式,这几个问题你知道吗?

1,060 阅读10分钟

你知道吗?

  • 关于工厂的设计模式有几种?
  • 哪一种工厂模式不属于 23 种设计模式
  • 三种工厂模式都解决了什么问题,在框架中有什么运用

如果你能流利的答出以上所有问题,那么🎉恭喜你,可以直接点赞这篇文章,然后退出了。如果你不能流利的答出,那么好好看一看这篇文章,希望你有所得

目录如下

  • 1-简单工厂
    • 定义
    • 场景举例
    • 瞎写代码
    • 优雅实现
    • 框架运用
  • 2-工厂方法
    • 同上
  • 3-抽象工厂
    • 同上
  • 4-小结

测试代码地址:工厂模式

1-简单工厂

定义

🏭 简单工厂属于创建型的设计模式

虽然不属于 GOF 23 种设计模式中的一种,但是它是一个不简单的工厂,我们后面所接触到的工厂方法和抽象工厂都是由此演变出来,学习好简单工厂,是我们学习好工厂方法和抽象工厂的重要地基

简单工厂优点也很明显,只需传入正确的参数,就可以获取到你想要的对象,缺点就是职责过重,一旦增加新的产品,就需要修改这个工厂的逻辑

简单工厂:我很好,但是我不配!

场景举例

简单工厂在实际生产中运用较少,一般适用的场景有,负责创建的对象比较少、应用层只关心其需要的对象不关心细节

举个例子🌰:我们去小卖部买辣条的时候,一般有一个聚合支付的二维码,我们可以使用支付宝也可以使用微信支付,那么这种场景我们如何拿到支付宝支付对象,或者微信支付对象呢?

🦐 瞎写代码

我们写一个抽象的支付类

public abstract class Pay {
    /**
     * 支付抽象接口
     */
    public abstract void pay();
}

然后再写一个支付宝的支付类和微信的支付类 去继承抽象支付类

public class AiPay extends Pay{
   
    @Override
    public void pay() {
        System.out.println("阿里支付");
    }
}
public class WeChatPay extends Pay{

    @Override
    public void pay() {
        System.out.println("我是微信支付");
    }
}

至此为止,我们已经可以拿到支付宝支付、微信支付的对象了,写个测试类证明一下我们的厉害

public class Test {
    public static void main(String[] args) {
        // 父类引用指向子类对象
        Pay pay = new AiPay();
        // 调用子类方法
        pay.pay();
    }
}

阿里支付
Process finished with exit code 0

这样就能实现支付宝支付了,但是问题在于,我们的这个测试类即我们的应用层和我们的具体的实现类(阿里支付类、微信支付类)有着强耦合,我们需要一个变化,这就是简单工厂的由来

优雅实现

让我们动手写一个简单工厂和测试类来体验一下,简单工厂的妙处

public class PayFactory {

    public Pay pay(String payType) {
        if (Objects.equals("1", payType)) {
            return new AiPay();
        }
        if (Objects.equals("2",payType)) {
            return new WeChatPay();
        }
        return null;
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        PayFactory payFactory = new PayFactory();
        Pay pay = payFactory.pay("1");
        pay.pay();
    }
}

输出

阿里支付
Process finished with exit code 0

就是这么简单的一个工厂,实现了我们的应用层和实现类的解耦

当然我们还可以使用反射来弥补简单工厂的拓展性,上代码

public class PayFactory {
    public Pay pay(Class c) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Pay pay = null;
        pay = (Pay) Class.forName(c.getName())
            .newInstance();
        return pay;
    }
}

测试类

public class Test {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        PayFactory payFactory = new PayFactory();
        Pay pay = payFactory.pay(AiPay.class);
        pay.pay();
    }
}

这样做的好处是,一旦我们新增了一种支付方式,就不需要传入特定类型了,只需要传入相应的支付类即可,也就是说 我们工厂不需要变化

框架运用

我们可以看一个 JDK 中的一个简单运用 Calendar.java

    public static Calendar getInstance()
    {
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
    }

点击进去,观察 1672 行

        Calendar cal = null;

        if (aLocale.hasExtensions()) {
            String caltype = aLocale.getUnicodeLocaleType("ca");
            if (caltype != null) {
                switch (caltype) {
                case "buddhist":
                cal = new BuddhistCalendar(zone, aLocale);
                    break;
                case "japanese":
                    cal = new JapaneseImperialCalendar(zone, aLocale);
                    break;
                case "gregory":
                    cal = new GregorianCalendar(zone, aLocale);
                    break;
                }
            }
        }

是不是有点眼熟了?可以和我们的实现进行类比一下。

工厂方法

定义

🏭 工厂方法 是我们比较常见的一种设计模式,通常用来消除很多的 ifelse,只需要定义一个创建对象的接口,但让实现这个接口的类决定实例化哪个类,让类的实例化推迟到子类中实现

用户只需要关心产品对应的工厂,也无须关心创建细节,并且加入新的产品符合开闭原则,提高了我们的系统扩展性。缺点就在于类的个数容易过多,增加了复杂度和系统理解能力

场景举例

工厂方法适用创建对象需要大量重复的代码,并且应用层不关心产品被创建和实现的细节

同样利用我们上面去小卖部买辣条的场景举例

瞎写代码

在上面的场景中,我们可以看到

public class PayFactory {

    public Pay pay(String payType) {
        if (Objects.equals("1", payType)) {
            return new AiPay();
        }
        if (Objects.equals("2",payType)) {
            return new WeChatPay();
        }
        return null;
    }
}

无论是阿里支付类,还是微信支付类,都是由我们超类代码来负责生产出来的,而工厂方法希望把实现推迟到子类中,我们对他进行改造,使其进行解耦

优雅实现

我们先写一个支付工厂接口,让这个接口去定义一个规范,规范我们去获得什么类型的子类。当然这里你使用抽象类也是可以的,前提是你知道有一些默认实现,我们这边就直接使用接口

public interface PayFactory {
    Pay getPayType();
}

我们希望生产一个阿里支付对象,我们写一下

public abstract class Pay {
    public abstract void pay();
}
public class AiPay extends Pay {
    @Override
    public void pay() {
        System.out.println("阿里支付");
    }
}

该对象交由子类去生产,我们需要写一个阿里支付的子工厂类,去继承统一的支付工厂类,该类仅负责生产相对应的支付对象

public class AiPayFactory implements PayFactory {

    @Override
    public Pay getPayType() {
        return new AiPay();
    }
}

至此,我们的一个改造就完毕了,微信支付工厂同理可得。 此时我们,就已经把具体实现下放到子类了

public class Test {
    public static void main(String[] args)  {
        PayFactory payFactory = new WeChatPayFactory();
        Pay pay = payFactory.getPayType();
        pay.pay();
    }
}

框架运用

让我们在 Idea 里搜一下 Collection.java

关注第 189 行

Iterator<E> iterator();

搜索 ArrayList 点击进去, 可以清楚了看到 iterator 返回了一个内部类

我们可以类比一下我们之前写的小demo,Collection.java 其实就相当于我们的 PayFactory,都是 将具体的类的实例化推迟到子类中实现

Collection.java retuen 回去的对象,相当于我们的 PayFactory 回去的具体支付类对象

这么一讲是否豁然开朗?如果没有开朗就在看一遍

抽象工厂

定义

抽象工厂 虽然名字起的抽象,但是理解起来并不抽象,它是提供了一个创建一系列相关或相互依赖对象的接口

一系列的理解就是同一个产品族(产品族定义具体会放到4-小结 中讲解,大家先留个印象,可以简单理解为都是一个牌子下的东西,联想旗下的主机、显示屏、平板 都属于同一个产品族)

抽象工厂的优点和其他工厂一样,将具体的产品在应用层代码隔离,无须关心创建细节,缺点也显而易见,当想要创建新的产品的时候,需要修改抽象工厂的接口

场景举例

抽象工厂同样强调应用层不关心产品实现细节,强调的生产的是同一个产品族的产品

我们举一个新的业务场景,联想旗下的台式电脑需要主机、显示屏才是一个完整的台式电脑,我们有一个电脑工厂,专门来生产联想的台式电脑,或者生产戴尔的台式电脑 等等等

优雅实现

既然需要生产显示屏和主机,我们就需要两个抽象类或者接口规范生产显示屏和主机

public abstract class Computer {
  
    public abstract void produce();
}

public abstract class DisplayScreen {

    public abstract void produce();
}

基础打好了,要生产各式各样的台式电脑,我们就需要一个大的电脑工厂,来规定产主机和显示屏

public interface DesktopComputerFactory {

    Computer getComputer();
    
    DisplayScreen getDisplayScreen();
}

ok,接下来来生产联想牌的主机和显示屏

public class LenovoComputer extends Computer {

    @Override
    public void produce() {
        System.out.println("生产联想主机");
    }
}
public class LenovoDisplayScreen extends DisplayScreen{
    @Override
    public void produce() {
        System.out.println("生产联想显示屏");
    }
}

联想的主机和显示屏已经到位,我们接下来,只需要一个联想的台式电脑工厂去继承我们的统一的电脑工厂,这样就可以生产联想牌子的台式电脑了,奥利给,

public class LenovoDesktopComputerFactory implements DesktopComputerFactory {

    @Override
    public Computer getComputer() {
        return new LenovoComputer();
    }

    @Override
    public DisplayScreen getDisplayScreen() {
        return new LenovoDisplayScreen();
    }
}

我们可以看到联想的电脑工厂是生产同一产品族下的东西,生产了联想的显示器、生产了联想的主机

接下来厂子动工,看下是否可以正确的生产

public class Test {
    public static void main(String[] args) {
        DesktopComputerFactory lenovoDesktopComputerFactory = new LenovoDesktopComputerFactory();
        Computer computer = lenovoDesktopComputerFactory.getComputer();
        DisplayScreen displayScreen = lenovoDesktopComputerFactory.getDisplayScreen();
        computer.produce();
        displayScreen.produce();
    }
}


生产联想电脑
生产联想电脑显示屏

Process finished with exit code 0

已经可以完美的生产联想牌的台式电脑的,假设想进一步的发家致富,按照步骤继续搞戴尔、华硕等等牌子就可以了,而且还符合我们的开闭原则

框架运用

我们打开 java.sql.Connection,这个负责 Java 中数据库连接的接口

我们可以关注到 104 行和 138 行

Statement createStatement() throws SQLException;
 
PreparedStatement prepareStatement(String sql) throws SQLException;

在这个 connection 中返回的都是同一产品族的东西,mysql 进来 就是 mysql 的 Statement,PreparedStatement,Oracle 进来就是 Oracle 的 Statement,PreparedStatement

让我们进一步点进去观察 Statement 这个类,其实这个类也是一个抽象工厂,其返回的也都是同一个产品族的对象,这里不展开说明了。

小结

小结部分,我们先来说下,产品等级和产品族 是怎么理解的

如图,产品族就是指同一个牌子旗下的所有产品,比如华为的手机、华为的平板属于同一产品族,而华为的手机和苹果的手机,又是同一产品等级,他们都属于手机

对于简单工厂来说,主要关注的是同一产品等级的任意产品。(对于增加新的产品,主要是新增产品,就要修改工厂类。符合单一职责原则。不符合开放-封闭原则)

对于工厂方法来说,主要关注的是同一产品等级的固定产品。(支持增加任意产品,新增产品时不需要更改已有的工厂,需要增加该产品对应的工厂。符合单一职责原则、符合开放-封闭原则。但是引入了复杂性)

对于抽象工厂来说,用来生产不同产品族的全部产品。(增加新产品时,需要修改工厂,增加产品族时,需要增加工厂。符合单一职责原则,部分符合开放-封闭原则,降低了复杂性)

三种设计各有优劣,只有结合实际业务场景才能选出最适合的方案


参考:

Holis 《你以为工厂模式很简单,可能是因为你懂的只是冰山的一角》

Geely 《设计模式精讲》

谨思慎言,我是 Skow

点个赞再走吧,我们下期再见

公众号