• Java 设计模式——抽象工厂模式


    1.概念

    (1)Java 设计模式——工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机等。这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。

    (2)本文要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示横轴是产品等级,也就是同一类产品;纵轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。具体来说,抽象工厂模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构

    在这里插入图片描述

    (3)抽象工厂模式是工厂方法模式的升级版本,它们之间有以下几个区别:

    • 粒度不同:抽象工厂模式关注一族相关对象的创建,一个工厂负责创建一族产品对象;而工厂方法模式关注单个对象的创建,每个工厂只负责创建一种产品。
    • 抽象程度不同:抽象工厂模式具有更高的层次和更大的封装性,它通过引入抽象工厂和具体工厂的概念,将一族产品对象的创建交给抽象工厂来完成;而工厂方法模式的抽象程度相对较低,它通过定义一个工厂接口或抽象类来声明创建产品的方法,然后具体工厂类会实现这个接口或抽象类来创建具体产品对象。
    • 关注点不同:抽象工厂模式关注的是一族产品对象的创建,它解决的是多个产品对象之间的组合问题,确保一族产品对象能够相互协作;而工厂方法模式关注的是单个产品对象的创建,它解决的是产品扩展和变化的问题。
    • 扩展性不同:抽象工厂模式的扩展性更强,可以同时添加新的具体工厂和产品类,以及扩展一族产品的组合方式;而工厂方法模式的扩展性相对较低,当需要添加新的产品时,需要新增对应的具体工厂类和具体产品类。

    综上所述,抽象工厂模式和工厂方法模式在粒度、抽象程度、关注点和扩展性等方面存在差异。选择使用哪种模式取决于具体的业务需求和设计要求。如果需要创建一族相关的产品对象,并确保这些产品对象能够相互协作,可以考虑使用抽象工厂模式;如果只需要创建单个产品对象,并且希望能够轻松扩展和添加新的产品类,可以选用工厂方法模式。

    2.结构

    抽象工厂模式的主要角色如下:

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

    3.实现

    现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。其中拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是都属于美式风味)。所以这个案例可以使用抽象工厂模式实现。类图如下:
    在这里插入图片描述
    部分核心代码如下:

    抽象工厂:DessertFactory.java

    //抽象工厂类
    public interface DessertFactory {
        //生产咖啡的功能
        Coffee createCoffee();
        
        //生产甜品的功能
        Dessert createDessert();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    具体工厂:AmericanDessertFactory.java

    package com.itheima.patterns.factory.abstract_factory;
    
    //美式风味的甜品工厂,可以生产美式咖啡和抹茶慕斯
    public class AmericanDessertFactory implements DessertFactory{
        @Override
        public Coffee createCoffee() {
            return new AmericanCoffee();
        }
        
        @Override
        public Dessert createDessert() {
            return new Matchamousse();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    具体工厂:ItalyDessertFactory.java

    //意大利风味甜品工厂,可以生产拿铁咖啡和提拉米苏甜品
    public class ItalyDessertFactory implements DessertFactory{
        @Override
        public Coffee createCoffee() {
            return new LatteCoffee();
        }
        
        @Override
        public Dessert createDessert() {
            return new Trimisu();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Client.java

    public class Client {
        public static void main(String[] args) {
            //创建的是意大利风味甜品工厂对象
            ItalyDessertFactory factory = new ItalyDessertFactory();
            //获取拿铁咖啡和提拉米苏甜品
            Coffee coffee = factory.createCoffee();
            Dessert dessert = factory.createDessert();
        
            System.out.println(coffee.getName());
            dessert.show();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    输出结果如下:

    拿铁咖啡
    提拉米苏
    
    • 1
    • 2

    如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。

    4.优缺点

    (1)优点:

    • 封装性强:抽象工厂模式将一族相关产品的创建逻辑封装在一个工厂中,客户端通过抽象工厂来创建产品对象,无需关心具体的产品类,有效降低了客户端与具体产品类之间的耦合性。
    • 产品族之间的一致性:抽象工厂模式保证创建的产品对象属于同一产品族,它们之间是相互匹配的,因此能够保证创建的产品对象之间能够正常协作。
    • 灵活性高:通过切换具体工厂,可以在运行时创建不同的产品族,使系统具有较高的灵活性和可扩展性。

    (2)缺点:

    • 可扩展性受限:增加新的产品等级结构(新的抽象产品)较为困难,需要修改抽象工厂接口及其所有具体工厂的代码,违背了开闭原则。
    • 复杂性增加:随着产品族和产品等级结构的增多,抽象工厂和具体工厂的数量会增加,导致系统的复杂度增加。
    • 不易于单独新增产品:想要添加单独的产品类需要修改抽象工厂接口及其所有具体工厂的代码。

    5.使用场景

    • 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
    • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
    • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。

    6.模式扩展

    (1)可以通过工厂模式 + 配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全
    类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。

    (2)具体步骤如下:

    • 定义配置文件 bean.properties
    american=com.itheima.patterns.factory.config_factory.AmericanCoffee
    latte=com.itheima.patterns.factory.config_factory.LatteCoffee
    
    • 1
    • 2
    • 改进工厂类
    package com.itheima.patterns.factory.config_factory;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.HashMap;
    import java.util.Properties;
    import java.util.Set;
    
    public class CoffeeFactory {
        
        //加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储
        //1.定义容器对象来存储咖啡对象
        private static HashMap<String,Coffee> map = new HashMap<String, Coffee>();
        
        //2.加载配置文件,只需要加载一次
        static {
            //2.1.创建Properties对象
            Properties properties = new Properties();
            //2.2.调用properties对象中的load方法来加载配置文件
            InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            try {
                properties.load(is);
                //从properties集合中获取全类名并创建对象
                Set<Object> keys = properties.keySet();
                for (Object key : keys) {
                    String className = properties.getProperty((String) key);
                    //通过反射技术创建对象
                    Class clazz = Class.forName(className);
                    Coffee coffee = (Coffee) clazz.newInstance();
                    //将名称和对象存储到容器中
                    map.put((String) key, coffee);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        //根据名称获取对象
        public static Coffee createCoffee(String name) {
            return map.get(name);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 测试
    package com.itheima.patterns.factory.config_factory;
    
    public class Client {
        public static void main(String[] args) {
            Coffee coffee1 = CoffeeFactory.createCoffee("american");
            System.out.println(coffee1.getName());    //美式咖啡
            Coffee coffee2 = CoffeeFactory.createCoffee("latte");
            System.out.println(coffee2.getName());    //拿铁咖啡
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    静态成员变量用来存储创建的对象(键存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只需要执行一次。

    有关 Properties 类的具体知识可以参考 Java 基础——Properties 类这篇文章。

    7.JDK 源码解析——Collection.iterator 方法

    (1)首先来看一下这一段代码:

    public class IteratorDemo {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("Tom");
            list.add("Mike");
            list.add("Marry");
        
            //获取迭代器对象
            Iterator<String> iterator = list.iterator();
            //使用迭代器遍历
            while(iterator.hasNext()){
                String element = iterator.next();
                System.out.println(element);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    大家应该很熟悉上面的代码,它使用迭代器遍历集合,获取集合中的元素。而单列集合获取迭代器的方法就使用到了工厂方法模式。现在通过类图来看其中的结构:
    在这里插入图片描述

    Collection 接口是抽象工厂类,ArrayList 是具体的工厂类;Iterator 接口是抽象商品类,ArrayList 类中的 Iter 内部类是具体的商品类。在具体的工厂类中 iterator() 方法创建具体的商品类的对象。

    • Iterator.java
      在这里插入图片描述
    • Collection.java
      在这里插入图片描述
    • ArrayList.java
      在这里插入图片描述
      在这里插入图片描述

    注:DateForamt 类中的 getInstance() 方法、Calendar 类中的 getInstance() 方法使用的也都是工厂模式。

  • 相关阅读:
    docker安装nacos和sentinel笔记
    Mysql 5.7版本手写sql 实现 Mysql 8.x版本的 dense_rank() over()函数的效果
    Vue--Axios详解
    自动翻译软件-批量批量自动翻译软件推荐
    技术分享 | undo 太大了怎么办
    ipconfig显示的内容分析(一)网卡
    宿主物种丨Jackson告诉你选择二抗的注意事项
    限时开源,来自大佬汇总的Kafka限量笔记,绝对不会后悔!
    JSP详解
    spa之解决ajax跨域问题
  • 原文地址:https://blog.csdn.net/weixin_43004044/article/details/133357006