抽象工厂模式

抽象工厂模式(Abstract Factory Pattern)提供了一种创建一组相关或依赖对象的接口,而无需指定它们的具体类。抽象工厂模式允许创建对象家族,而不必指定每个对象的具体类,从而实现了对象的抽象和解耦。

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产同一等级的产品,而抽象工厂模式可以生产多个等级的产品。

结构

以下是抽象工厂模式的主要组成部分:

  1. 抽象工厂(Abstract Factory):定义了一组创建相关对象的抽象接口,通常包括多个工厂方法,每个工厂方法负责创建一个具体对象。抽象工厂通常是一个接口或抽象类。

  2. 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建一组相关的具体对象。每个具体工厂都可以创建一整套产品对象,这些产品对象通常有一定的关联性。

  3. 抽象产品(Abstract Product):定义了一组相关的产品对象的抽象接口。每个抽象产品对应于工厂方法的一种类型。

  4. 具体产品(Concrete Product):实现了抽象产品接口,是由具体工厂创建的实际对象。每个具体产品都属于某个产品家族。

抽象工厂模式的关键点在于,客户端不直接实例化具体产品,而是通过抽象工厂来创建产品。这使得客户端代码更加灵活,因为它不依赖于具体产品的类名,而只依赖于抽象工厂和抽象产品的接口。

使用场景

以下是抽象工厂模式的一些示例应用场景:

  • 多主题界面:假设此时正在开发一个应用程序,需要支持多个不同的用户界面主题(例如,浅色和深色主题)。可以使用抽象工厂来创建每个主题下的按钮、文本框和其他界面元素,而不必在代码中硬编码特定主题的元素。

  • 跨平台应用程序:如果正在构建一个需要同时支持多个操作系统或平台的应用程序,可以使用抽象工厂来创建适用于每个平台的界面元素和操作系统特定的对象。

  • 数据库访问层:在数据库访问层中,抽象工厂可以用来创建数据库连接、命令、事务等对象,以便在不同的数据库系统中切换时能够轻松适应。

实现

仍然以上述手机为例,只不过此时因顾客需求,该工厂不仅要生产手机,还需要生产电脑(包括Windows、Mac两类电脑)。

所以在上面代码的基础上,根据抽象工厂模式的结构,需要定义一个数码抽象工厂

java 复制代码
interface DigitalFactory {

    /**
      * 生产手机的功能
      */
    Phone createPhone();

    /**
      *  生产电脑的功能
      */
    Computer createComputer();
}

然后再定义手机制造工厂和电脑制造工厂(因为这里的手机和电脑不为同一个品牌,所以只能分开定义,实际开发中一般都会为同一个族定义一个工厂)

java 复制代码
class AppleFactory implements DigitalFactory{
    @Override
    public Phone createPhone() {
        return new Apple();
    }

    @Override
    public Computer createComputer() {
        return new Mac();
    }
}

class HarmonyFactory implements DigitalFactory{
    @Override
    public Phone createPhone() {
        return new HUAWEI();
    }

    @Override
    public Computer createComputer() { // 暂时没有,则返回null
        return null;
    }
}

class WindowsFactory implements DigitalFactory{
    @Override
    public Phone createPhone() { // 暂时没有,则返回null
        return null;
    }

    @Override
    public Computer createComputer() {
        return new Windows();
    }
}

最后,定义一个顾客测试一下工厂

java 复制代码
class Customer {
    public static void main(String[] args) {
        // 创建苹果工厂
        AppleFactory factory = new AppleFactory();
        // 获取苹果手机
        Phone apple = factory.createPhone();
        // 获取苹果电脑
        Computer mac = factory.createComputer();
        // 分别展示其信息
        apple.show();
        mac.show();
    }
}

运行结果为:

总之,抽象工厂模式有助于将对象的创建与客户端解耦,提供了一种灵活的方式来创建一组相关对象,同时保持了高层代码对底层实现的独立性。

优点

抽象工厂方法有以下几个优点:

  1. 抽象和实现分离:抽象工厂模式将对象的创建与使用分离开来,客户端代码不需要关心具体对象的创建过程,只需要通过抽象工厂接口来创建对象。这有助于降低系统的耦合度。
  2. 一致性:抽象工厂模式确保创建的对象属于同一产品家族,这可以确保这些对象之间的兼容性和一致性。这在多种不同的对象需要协同工作时特别有用。
  3. 封装变化:抽象工厂允许轻松我们地更改产品家族,而不需要修改客户端代码。这有助于应对变化和扩展需求,同时保持稳定的接口。
  4. 高级别的代码复用:由于抽象工厂模式促进了一组相关对象的创建,它可以提高代码的可重用性。如果需要在系统中的不同地方使用相同的产品组合,只需创建一个新的具体工厂即可。
  5. 可维护性:抽象工厂模式有助于维护代码的结构和清晰度。它提供了一种有组织的方式来组织和创建对象。
缺点
  1. 复杂性增加:引入抽象工厂模式会增加代码的复杂性,因为需要定义多个抽象工厂、具体工厂和产品类。对于简单的系统,可能会感到过度工程。
  2. 不够灵活:抽象工厂模式通常用于创建一组相关的对象,如果需要创建不相关的对象,可能会不太适用。在这种情况下,可能需要使用其他创建型模式,如工厂方法模式。
  3. 扩展困难:在抽象工厂模式中添加新的产品家族可能会比较麻烦,需要修改抽象工厂接口以及所有具体工厂的实现。这可能会导致一些不必要的工作,特别是在已经存在的大规模代码库中。
  4. 性能开销:抽象工厂模式可能会引入一些性能开销,因为它需要额外的对象创建和工厂方法调用,相对于直接创建对象可能会稍慢。

模式扩展

除了上述三种工厂模式的单独使用能降低代码耦合以外,还有一种简单工厂+配置文件的方式也可以降低代码耦合,并且在开发使用场景也很常见,例如熟悉的Spring框架就是基于这种方式实现的。

使用方法是,在工厂类中加载配置文件中的全类名,并通过反射的方式创建对象进行存储,客户端如果需要对象,则直接进行获取即可。

下面,还是使用是上面的手机工厂为例进行演示(这里采用的maven模版创建的工程模块,即包含java和resource包的实际开发所使用的模版)

第一步:定义配置文件

在resource包下创建一个bean.properties的配置文件

java 复制代码
apple=com.onism.pattern.factory.Apple
huawei=com.onism.pattern.factory.HUAWEI

第二步:改进工厂类

java 复制代码
class PhoneFactory {

    /**
     * 定义容器存储手机对象
     */
    private static HashMap<String,Phone> map = new HashMap<>();

    // 加载配置文件,只需要加载一次
    static {
        // 创建properties对象
        Properties properties = new Properties();
        // 调用该对象中的load方法进行配置文件的配置。
        InputStream inputStream = PhoneFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            properties.load(inputStream);
            // 从properties集合中获取全类名并创建对象
            for (Object key : properties.keySet()) {
                String className = properties.getProperty((String) key);
                // 通过反射技术创建对象
                Class<?> clazz = Class.forName(className);
                Phone phone = (Phone) clazz.newInstance();
                // 将名称和对象存储到容器中
                map.put((String) key, phone);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Phone createPhone(String name){
        return map.get(name);
    }
}

下面创建一个顾客类测试一下

java 复制代码
class Customer {
    public static void main(String[] args) {
        Phone phone = PhoneFactory.createPhone("apple");
        phone.show();
    }

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

源码解析

在JDK中,有一些类和接口使用了工厂模式或类似的创建对象的设计模式。例如,最熟悉的Collection。

java 复制代码
public class Demo{
    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        list.add("123");
        list.add("456");
        list.add("789");
        
        // 获取迭代器对象
        Iterator<String> it = list.iterator();
        //使用迭代器遍历
        while(it.hasNext()){
            System.out.println(it.next());
        }
    }
}

上面的代码是很经典的List集合使用方法,其中的获取iterator迭代器方法就使用到了工厂方法模式,通过上面代码结构分析其类图结构

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

此外,除了Collection,还有Calendar类等

java.util.CalendarCalendar类是一个抽象类,用于处理日期和时间。它提供了一种用于创建Calendar实例的工厂方法(工厂方法模式),通常使用getInstance()方法获取一个Calendar对象。具体的Calendar实现类由JVM的Locale和时区信息决定。客户端代码无需关心具体的Calendar实现,只需通过工厂方法获取实例即可。

相关推荐
浮游本尊1 小时前
Java学习第22天 - 云原生与容器化
java
渣哥3 小时前
原来 Java 里线程安全集合有这么多种
java
间彧3 小时前
Spring Boot集成Spring Security完整指南
java
间彧3 小时前
Spring Secutiy基本原理及工作流程
java
数据智能老司机4 小时前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
Java水解4 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
数据智能老司机5 小时前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
洛小豆6 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学7 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole7 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端