工厂模式

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 适用场景

  • 客户端不知道它所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
  • 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
相关推荐
Amagi.1 小时前
Spring中Bean的作用域
java·后端·spring
2402_857589361 小时前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
J老熊2 小时前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
Benaso2 小时前
Rust 快速入门(一)
开发语言·后端·rust
sco52822 小时前
SpringBoot 集成 Ehcache 实现本地缓存
java·spring boot·后端
原机小子2 小时前
在线教育的未来:SpringBoot技术实现
java·spring boot·后端
吾日三省吾码2 小时前
详解JVM类加载机制
后端
努力的布布2 小时前
SpringMVC源码-AbstractHandlerMethodMapping处理器映射器将@Controller修饰类方法存储到处理器映射器
java·后端·spring
PacosonSWJTU3 小时前
spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理)
java·后端·springmvc
记得开心一点嘛3 小时前
在Java项目中如何使用Scala实现尾递归优化来解决爆栈问题
开发语言·后端·scala