工厂模式

1. 初衷

写这篇文章的初衷,介绍一下工厂模式,以及具体使用实例。

2. 简单介绍

首先明确一点就是工厂是用来干啥的,工厂是用来创建对象的。

工厂模式的作用

  1. 封装对象的创建过程: 工厂模式将对象的创建过程封装在工厂类中,使客户端代码不需要了解具体的构建过程,只需向工厂请求对象即可。
  2. 提高代码的可维护性: 工厂模式可以集中管理对象的创建,如果需要修改对象的构建方式,只需修改工厂类而不影响客户端代码。
  3. 提高代码的扩展性: 工厂模式允许添加新的产品类型而无需修改现有客户端代码。这样可以方便地扩展应用程序。
  4. 降低耦合度: 使用工厂模式可以降低客户端代码与具体产品类之间的耦合度,因为客户端只与工厂接口交互,不需要直接与产品类交互。
  5. 提高代码的重用性: 工厂模式可以在不同的地方使用相同的工厂来创建对象,提高了代码的重用性。

3. 简单工厂模式

3.1 请求快递实例,无设计模式情况

公共快递下单接口

java 复制代码
public interface IExpressDelivery {

    /**
     * 快递下单
     */
    OrderResponse createOrder(OrderRequest orderRequest);
    
}

京东快递下单

java 复制代码
public class JDExpressDelivery implements IExpressDelivery{
    @Override
    public OrderResponse createOrder(OrderRequest orderRequest) {
        // 省略请求京东的下单api过程
        OrderResponse orderResponse = new OrderResponse();
        orderResponse.setBillNo("京东快递单号");
        return orderResponse;
    }
}

顺丰快递下单

java 复制代码
public class SFExpressDelivery implements IExpressDelivery {
    @Override
    public OrderResponse createOrder(OrderRequest orderRequest) {
        // 省略请求顺丰的下单api过程
        OrderResponse orderResponse = new OrderResponse();
        orderResponse.setBillNo("顺丰快递单号");
        return orderResponse;
    }
}

圆通快递下单

java 复制代码
public class YTExpressDelivery implements IExpressDelivery {
    @Override
    public OrderResponse createOrder(OrderRequest orderRequest) {
        // 省略请求圆通的下单api过程
        OrderResponse orderResponse = new OrderResponse();
        orderResponse.setBillNo("圆通快递单号");
        return orderResponse;
    }
}

客户端调用

java 复制代码
public class Test {

    public static void main(String[] args) {
        String type = "SF";
        OrderResponse orderResponse = createOrderByType(type);
        System.out.println("快递单号为"+orderResponse.getBillNo());
    }

    public static OrderResponse createOrderByType(String type) {
        IExpressDelivery expressDelivery = null;
        if (type.equals("JD")) {
            expressDelivery = new JDExpressDelivery();
        } else if (type.equals("SF")) {
            expressDelivery = new SFExpressDelivery();
        } else {
            expressDelivery = new YTExpressDelivery();
        }
        OrderRequest orderRequest = new OrderRequest();
        return expressDelivery.createOrder(orderRequest);
    }
}

从客户端调用就可以看出客户端是需要依赖实现类的,当前举的例子实例化还是很简单的,如果实例化需要更复杂的代码客户端的代码也会看起来很臃肿。

3.2 请求快递实例,使用简单工厂模式

工厂

java 复制代码
public class ExpressDeliveryFactory {
    public static IExpressDelivery create(String deliveryType) {
        IExpressDelivery expressDelivery = null;
        if (deliveryType.equals("JD")) {
            expressDelivery = new JDExpressDelivery();
        } else if (deliveryType.equals("SF")) {
            expressDelivery = new SFExpressDelivery();
        } else {
            expressDelivery = new YTExpressDelivery();
        }
        return expressDelivery;
    }
}

客户端

java 复制代码
public static void main(String[] args) {
    String type = "SF";
    OrderResponse orderResponse = createOrderByType(type);
    System.out.println("快递单号为"+orderResponse.getBillNo());
}

public static OrderResponse createOrderByType(String type) {
    IExpressDelivery expressDelivery = ExpressDeliveryFactory.create(type);
    OrderRequest orderRequest = new OrderRequest();
    return expressDelivery.createOrder(orderRequest);
}

3.3 简单工厂模式 + 反射优化

快递枚举类

java 复制代码
@Getter
@AllArgsConstructor
public enum ExpressTypeEnum {
    JD( "JD",  "com.example.demo.express.JDExpressDelivery"),
    SF("SF", "com.example.demo.express.SFExpressDelivery"),
    YT("YT", "com.example.demo.express.YTExpressDelivery"),
    ;

    private final String key;
    private final String classPath;

}

优化后的工厂

java 复制代码
public class ExpressDeliveryFactory {

    public static IExpressDelivery create(ExpressTypeEnum expressTypeEnum) {
        try {
            Class<?> clazz = Class.forName(expressTypeEnum.getClassPath());
            return (IExpressDelivery) clazz.getDeclaredConstructor().newInstance();
        } catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException |
                 NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
    
}

客户端

java 复制代码
public class Test {

    public static void main(String[] args) {
        OrderResponse orderResponse = createOrderByType(ExpressTypeEnum.SF);
        System.out.println("快递单号为"+orderResponse.getBillNo());
    }

    public static OrderResponse createOrderByType(ExpressTypeEnum type) {
        IExpressDelivery expressDelivery = ExpressDeliveryFactory.create(type);
        OrderRequest orderRequest = new OrderRequest();
        return expressDelivery.createOrder(orderRequest);
    }
}

3.4 简单工厂模式的主要优点:

  • 将对象的创建和具体产品的实现分离,降低了客户端代码与具体产品类的耦合度。
  • 简化了客户端代码,客户端不需要了解对象的创建细节,只需调用工厂类的方法即可获得所需的对象。
  • 可以轻松切换或替换具体产品类,不影响客户端的代码。

3.5 简单工厂模式一些局限性:

  • 当需要添加新的产品类型时,需要修改工厂类的代码,违反了开闭原则。
  • 工厂类可能会变得相当庞大,难以维护。

4. 工厂方法模式

4.1 请求快递实例

将工厂创建产品的方法提出来创建一个抽象类,把原来的工厂变成抽象工厂

java 复制代码
public interface IExpressFactory {
    IExpressDelivery create();
}

创建多个工厂

京东快递的工厂

java 复制代码
public class JDExpressFactory implements IExpressFactory{
    @Override
    public IExpressDelivery create() {
        return new JDExpressDelivery();
    }
}

顺丰快递的工厂

java 复制代码
public class SFExpressFactory implements IExpressFactory {
    @Override
    public IExpressDelivery create() {
        return new SFExpressDelivery();
    }
}

圆通快递的工厂

java 复制代码
public class YTExpressFactory implements IExpressFactory {
    @Override
    public IExpressDelivery create() {
        return new YTExpressDelivery();
    }
}

工厂类的简单工厂

先建立枚举类

java 复制代码
@Getter
@AllArgsConstructor
public enum ExpressFactoryTypeEnum {
    JD( "JD",  "com.example.demo.express.JDExpressFactory"),
    SF("SF", "com.example.demo.express.SFExpressFactory"),
    YT("YT", "com.example.demo.express.YTExpressFactory"),
    ;

    private final String key;
    private final String classPath;
}

工厂的工厂

java 复制代码
public class ExpressFactoryFactory {
    public static IExpressFactory create(ExpressFactoryTypeEnum expressFactoryTypeEnum) {
        try {
            Class<?> clazz = Class.forName(expressFactoryTypeEnum.getClassPath());
            return (IExpressFactory) clazz.getDeclaredConstructor().newInstance();
        } catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException |
                 NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
}

最后在客户端调用

java 复制代码
public class Test1 {

    public static void main(String[] args) {
        OrderResponse orderResponse = createOrderByType(ExpressFactoryTypeEnum.JD);
        System.out.println("快递单号为"+orderResponse.getBillNo());
    }

    private static OrderResponse createOrderByType(ExpressFactoryTypeEnum typeEnum) {
        IExpressFactory iExpressFactory = ExpressFactoryFactory.create(typeEnum);
        IExpressDelivery expressDelivery = iExpressFactory.create();
        OrderRequest orderRequest = new OrderRequest();
        return expressDelivery.createOrder(orderRequest);
    }
}

4.2 工厂方法模式总结

工厂方法模式是简单工厂模式的延伸,它继承了简单工厂模式的优点,同时还弥补了简单工厂模式的不足。工厂方法模式是使用频率最高的设计模式之一,是很多开源框架和API类库的核心模式。

4.3工厂方法模式的主要优点

  • 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
  • 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,就正是因为所有的具体工厂类都具有同一抽象父类。
  • 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合"开闭原则"。

4.4 工厂方法模式的主要缺点

  • 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
  • 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

4.5 适用场景

  • 客户端不知道它所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
  • 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
相关推荐
Kookoos2 分钟前
ABP VNext + Cosmos DB Change Feed:搭建实时数据变更流服务
数据库·分布式·后端·abp vnext·azure cosmos
tan180°9 小时前
MySQL表的操作(3)
linux·数据库·c++·vscode·后端·mysql
优创学社210 小时前
基于springboot的社区生鲜团购系统
java·spring boot·后端
why技术10 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
幽络源小助理10 小时前
SpringBoot基于Mysql的商业辅助决策系统设计与实现
java·vue.js·spring boot·后端·mysql·spring
ai小鬼头11 小时前
AIStarter如何助力用户与创作者?Stable Diffusion一键管理教程!
后端·架构·github
简佐义的博客11 小时前
破解非模式物种GO/KEGG注释难题
开发语言·数据库·后端·oracle·golang