Loading

软件设计模式白话文系列(四)工厂模式

一、简单工厂模式

1、描述

简单工厂模式是属于创建型模式,但不属于23种 GOF 设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。

2、适用性

工厂类负责创建的对象(类型)比较少;客户只知道传入工厂类的参数,对于如何创建对象(逻辑)不关心;由于简单工厂很容易违反高内聚责任分配原则,因此一般只在很简单的情况下应用。

优点:

  • 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类创建对象;

  • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量;

  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。

缺点:

  • 不易拓展,一旦添加新的产品类型,就不得不修改工厂的创建逻辑;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。
  • 产品类型较多时,工厂的创建逻辑可能过于复杂,一旦出错可能造成所有产品的创建失败,不利于系统的维护。

3、实现逻辑

  • 抽象产品类 :简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。定义产品的规范,描述了产品的主要特性和功能。
  • 具体产品类 :实现或者继承抽象产品的子类;是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。
  • 工厂类:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。

4、实战代码

通过从饮品店生成茶和咖啡为例;

/**
 * 抽象产品类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:24:11
 */
public abstract class Drinks {
}

/**
 * 具体产品类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:29:14
 */
public class Tea extends Drinks {

    public Tea() {
        System.out.println("Tea");
    }
}

/**
 * 具体产品类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:26:36
 */
public class Coffee extends Drinks {

    public Coffee() {
        System.out.println("Coffee");
    }
}

/**
 * 工厂类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:19:53
 */
public class SimpleDrinksFactory {

    public Drinks createDrinks(String type) {
        if (type == null || type.length() == 0) {
            throw new RuntimeException("type cannot be empty");
        }
        switch (type) {
            case "Tea":
                return new Tea();
            case "Coffee":
                return new Coffee();
            default:
                throw new RuntimeException("The type was not found");
        }
    }
}

/**
 * 测试类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:41:14
 */
public class Client {

    public static void main(String[] args) {
        SimpleDrinksFactory factory = new SimpleDrinksFactory();
        Drinks tea = factory.createDrinks("Tea");
        Drinks coffee = factory.createDrinks("Coffee");
        Drinks drinks = factory.createDrinks(null);
    }
}

执行结果:

这样我们可以将繁琐创建对象的逻辑根据实际需求封装到工厂中(我们这里直接是通过无参构造 new 出来的,实际生产开发中的对象基本会需要逻辑操作),然后直接传入传入工厂类的参数就可以获取我们所需要的对象。

5、静态工厂

在生产开发中也可以将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是23种设计模式中的。

/**
 * 静态工厂类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:19:53
 */
public class StaticDrinksFactory {

    public static Drinks createDrinks(String type) {
        if (type == null || type.length() == 0) {
            throw new RuntimeException("type cannot be empty");
        }
        switch (type) {
            case "Tea":
                return new Tea();
            case "Coffee":
                return new Coffee();
            default:
                throw new RuntimeException("The type was not found");
        }
    }
}

6、结合配置文件解除耦合

在上面的例子中,如果我们现在需要增加 milk 这个产品,我们需要增加一个 Milk 的具体产品类,同时还需要修改我们的工厂类把 Milk 的生产方式添加进去。这样显然违背了开闭原则。

可以结合配置文件消除工厂类和具体产品的耦合。

// 创建 bean.properties 文件
Coffee=com.eajur.creational.simplefactory.Coffee
Tea=com.eajur.creational.simplefactory.Tea
Milk=com.eajur.creational.simplefactory.Milk
  
/**
 * 具体产品类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:26:36
 */
public class Milk extends Drinks {

    public Milk() {
        System.out.println("Milk");
    }
}

/**
 * 工厂类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-07 21:47:55
 */
public class ConfigDrinksFactory {
    private static Map<String, Drinks> map = new HashMap();

    static {
        Properties p = new Properties();
        InputStream is = ConfigDrinksFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            p.load(is);
            //遍历 Properties 集合对象
            Set<Object> keys = p.keySet();
            for (Object key : keys) {
                String className = p.getProperty((String) key);
                // 通过反射创建对象
                Class clazz = Class.forName(className);
                Drinks obj = (Drinks) clazz.getDeclaredConstructor().newInstance();
                map.put((String) key, obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Drinks createDrinks(String name) {
        return map.get(name);
    }
}

/**
 * 测试类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:41:14
 */
public class ConfigClient {

    public static void main(String[] args) {
        Drinks tea = ConfigDrinksFactory.createDrinks("Tea");
        Drinks coffee = ConfigDrinksFactory.createDrinks("Coffee");
        Drinks Milk = ConfigDrinksFactory.createDrinks("Milk");
    }
}

执行结果:

二、工厂方法模式

1、描述

工厂方法模式是对简单工厂模式的抽象提取。有一个抽象的 Factory 类(可以是抽象类和接口),这个类将不再负责具体的产品生产,而是只制定一些规范,具体的生产工作由其子类去完成。在这个模式中,工厂类和产品类往往可以依次对应。即一个抽象工厂对应一个抽象产品,一个具体工厂对应一个具体产品,这个具体的工厂就负责生产对应的产品。

2、适用性

相对于简单工厂模式,工厂方法模式在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

3、实现逻辑

  • 抽象产品类 :工厂类所创建的所有对象的超类,它负责描述所有实例所共有的公共接口。定义产品的规范,描述了产品的主要特性和功能。
  • 具体产品类 :实现或者继承抽象产品的子类;是工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。在工厂方法模式中某具体产品有专门的具体工厂创建,它们之间往往一一对应。
  • 抽象工厂类:是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。制定具体工厂类的规范。
  • 具体工厂类:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。

4、实战代码

通过从饮品店生成茶和咖啡为例;

/**
 * 抽象产品类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:24:11
 */
public abstract class Drinks {
}

/**
 * 具体产品类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:26:36
 */
public class Coffee extends Drinks {

    public Coffee() {
        System.out.println("Coffee");
    }
}

/**
 * 具体产品类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:29:14
 */
public class Tea extends Drinks {

    public Tea() {
        System.out.println("Tea");
    }
}

/**
 * 抽象工厂类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-06 08:03:13
 */
public interface DrinksFactory {
    Drinks createDrinks();
}

/**
 * 具体工厂类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:19:53
 */
public class CoffeeFactory implements DrinksFactory {

    @Override
    public Coffee createDrinks() {
        return new Coffee();
    }
}

/**
 * 具体工厂类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:19:53
 */
public class TeaFactory implements DrinksFactory {

    @Override
    public Tea createDrinks() {
        return new Tea();
    }
}

/**
 * 测试类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:41:14
 */
public class Client {

    public static void main(String[] args) {
        DrinksFactory drinksFactory = new CoffeeFactory();
        TeaFactory teaFactory = new TeaFactory();
        drinksFactory.createDrinks();
        teaFactory.createDrinks();
    }
}

客户端执行结果:

从以上的编写的代码可以看到,要增加产品类时相应的增加工厂类,不需要修改工厂类的代码了,这样就解决了简单工厂模式新增产品时需要修改工厂类的缺点。

工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。

三、抽象工厂模式

1、描述

抽象工厂模式就是在工厂方法模式的抽象工厂类中规范多个同类产品。通俗一点来讲,抽象工厂模式就是工厂的工厂方法模式。

工厂方法模式是针对一个产品系列的,而抽象工厂模式是针对多个产品系列的,即工厂方法模式是一个产品系列一个工厂,而抽象工厂模式是多个产品系列一个工厂类。

工厂模式中的每一个形态都是针对一定问题的解决方案,工厂方法针对的是多个产品系列结构;而抽象工厂模式针对的是多个产品族结构,一个产品族内有多个产品系列。

2、适用性

抽象工厂模式可以看作工厂方法模式的扩展,在一个系统要由多个产品系列中的一个来配置时,工厂方法模式无法满足,这时就可以使用抽象工厂模式。这样介绍有点晦涩,下面通过实战代码的例子很好理解。

3、实现逻辑

  • 抽象产品类 :工厂类所创建的所有对象的超类,它负责描述所有实例所共有的公共接口。定义产品的规范,描述了产品的主要特性和功能。
  • 具体产品类 :实现或者继承抽象产品的子类;是工厂模式的创建目标,所有创建的对象都是这个具体类的实例。在抽象工厂模式中某具体产品有专门的具体工厂类创建,但不同于工厂方法模式,它们之间是一对多的关系,及一个具体工厂类可以创建多个具体产品类。
  • 抽象工厂类:是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。制定具体工厂类的规范。
  • 具体工厂类:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。可供客户端直接调用某一方法创建出具体产品实例。

4、实战代码

之前的工厂方法模式,我们用的 Tea 和 Coffee 为例。但是在实际生活中,Tea 和 Coffee 都有着许多种类。下面我们通过 BlackTea 和 GreenTea 以及 Nestle 和 Latte 这个几种饮品为具体产品类,DrinksAFactory 和 DrinksBFactory 为分别可以创建 BlackTea 、Nestle 和 GreenTea 、Latte 的工厂类来简单演示抽象工厂模式。

/**
 * 抽象产品类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-06 20:19:25
 */
public abstract class Tea {
}

/**
 * 具体产品类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-06 20:21:19
 */
public class BlackTea extends Tea {
    public BlackTea() {
        System.out.println("BlackTea");
    }
}

/**
 * 具体产品类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-06 20:21:56
 */
public class GreenTea extends Tea{
    public GreenTea() {
        System.out.println("GreenTea");
    }
}

/**
 * 抽象产品类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:26:36
 */
public abstract class Coffee {
}

/**
 * 具体产品类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-06 20:22:26
 */
public class Nestle extends Coffee {
    public Nestle() {
        System.out.println("Nestle");
    }
}

/**
 * 具体产品类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-06 20:23:15
 */
public class Latte extends Coffee {
    public Latte() {
        System.out.println("Latte");
    }
}

/**
 * 抽象工厂类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-06 08:03:13
 */
public interface DrinksFactory {
    Coffee createCoffee();

    Tea createTea();
}

/**
 * 具体工厂类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-06 20:26:53
 */
public class DrinksAFactory implements DrinksFactory {

    @Override
    public Coffee createCoffee() {
        return new Nestle();
    }

    @Override
    public Tea createTea() {
        return new BlackTea();
    }
}

/**
 * 抽象工厂类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-06 20:26:53
 */
public class DrinksBFactory implements DrinksFactory {

    @Override
    public Coffee createCoffee() {
        return new Latte();
    }

    @Override
    public Tea createTea() {
        return new GreenTea();
    }
}

/**
 * 测试类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 18:41:14
 */
public class Client {

    public static void main(String[] args) {
        DrinksFactory drinksAFactory = new DrinksAFactory();
        Coffee coffeeA = drinksAFactory.createCoffee();
        Tea teaA = drinksAFactory.createTea();

        DrinksFactory drinksBFactory = new DrinksBFactory();
        Coffee coffeeB = drinksBFactory.createCoffee();
        Tea teaB = drinksBFactory.createTea();
    }
}

客户端执行结果:

从结果可以看出,A 工厂和 B 工厂根据同样的方法创建不同系列的实例。实际开发中, 系统中有多个产品族,但每次只使用其中的某一族产品。

例如,有的人喜欢 APPLE 品牌,有的人喜欢华为品牌。然后手机、手表、平板都打算使用同一系列,这种情况就非常符合抽象工厂模式。

posted @ 2022-11-06 20:57  Eajur  阅读(425)  评论(0编辑  收藏  举报