1、概念

        工厂方法模式既满足了面向对象的基本原则----开闭原则(对扩展开放,对修改关闭),也体现了JAVA的多态。工厂方法模式用于控制类的实例化,常见的做法是创建一个公共的接口(抽象工厂),使某个类对象的创建(具体产品)延迟到公共接口的具体实现类(具体工厂)当中。

        策略模式和工厂方法模式差不多,但工厂方法模式关注的是类对象的创建,而策略模式更倾向于控制类的行为,这里的行为,简单点来说,就是方法。

2、工厂方法模式具体应用

       工厂方法模式有4个关键角色,抽象工厂、抽象产品、具体工厂、具体产品

初版需求:SpringBoot项目,需要将学生信息通过一个接口返回给前端

抽象产品

/**
 * 抽象产品
 *
 * @param <T>
 */
@Getter
@Setter
public class UserInfo<T> {
    //用户类型
    private String type;
    //数据
    private List<T> data;
}

抽象工厂

/**
 * 抽象工厂角色
 */
public interface InitDataProvider<T> {
    /**
     * 初始化用户信息
     *
     * @return
     */
    UserInfo<T> initData();
}

具体产品(学生信息)

/**
 * 学生信息--具体产品
 */
@Getter
@Setter
public class Student {
    //学生姓名
    private String name;
    //几年级
    private String grade;
    //年龄
    private Integer age;

    public Student(String name, String grade, Integer age) {
        this.name = name;
        this.grade = grade;
        this.age = age;
    }
}

具体工厂(初始化学生信息)

import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;

/**
 * 具体工厂,初始化学生信息
 */
@Component
public class StudentDataProvider implements InitDataProvider {

    @Override
    public UserInfo<Student> initData() {
        UserInfo<Student> userInfo = new UserInfo<Student>();
        userInfo.setType("学生");

        //模拟从数据库获取到的学生信息
        List<Student> stuList = new ArrayList<>();
        Student stu1 = new Student("UMR", "一年级", 8);
        Student stu2 = new Student("Elysia", "五年级", 10);
        stuList.add(stu1);
        stuList.add(stu2);

        userInfo.setData(stuList);
        return userInfo;
    }
}

Controller层暴露一个接口

@RestController
public class DemoController {
    //初始化学生信息的具体工厂
    private StudentDataProvider stuProvider;
    
    //通过构造方法注入数据
    public DemoController(StudentDataProvider stuProvider) {
        this.stuProvider = stuProvider;
    }

    //获取学生信息
    @RequestMapping("/query")
    public UserInfo test01() {
        UserInfo<Student> userInfo = stuProvider.initData();
        return userInfo;
    }
}

需求2.0版本:就在这一个接口的基础上,将教师信息也返回给前端,不能新增其他接口。

具体产品(老师信息)

/**
 * 具体产品--老师信息
 */
@Getter
@Setter
public class Teacher {
    private String name;
    private Integer age;
    private String address;

    public Teacher(String name, Integer age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
}

具体工厂(初始化老师信息)


/**
 * 具体工厂,初始化老师信息
 */
@Component
public class TeacherDataProvider implements InitDataProvider {

    @Override
    public UserInfo<Teacher> initData() {
        UserInfo<Teacher> userInfo = new UserInfo<>();
        userInfo.setType("老师");

        //模拟从数据库获取到的老师信息
        List<Teacher> list = new ArrayList<>();
        Teacher t1 = new Teacher("小埋", 28, "南坪");
        Teacher t2 = new Teacher("爱莉希雅", 25, "沙坪坝");
        list.add(t1);
        list.add(t2);

        userInfo.setData(list);
        return userInfo;
    }
}

做法有两种

①Controller层引入老师的具体工厂,然后获取到老师信息,最后封装到一个List集合当中。

@RestController
public class DemoController {
    //初始化学生信息的具体工厂
    private StudentDataProvider stuProvider;

    private TeacherDataProvider teacherProvider;

    //通过构造方法注入数据
    public DemoController(StudentDataProvider stuProvider,
                          TeacherDataProvider teacherProvider) {
        this.stuProvider = stuProvider;
        this.teacherProvider = teacherProvider;
    }

    //获取学生信息
    @RequestMapping("/query")
    public List<UserInfo> test01() {
        List<UserInfo> result = new ArrayList<>();
        UserInfo<Student> stuInfo = stuProvider.initData();
        UserInfo<Teacher> teacherInfo = teacherProvider.initData();
        result.add(stuInfo);
        result.add(teacherInfo);
        return result;
    }
}

上面这种做法,如果多次来个需求,新的用户类型只能用这一个接口进行返回,那每次都要在Controller层新增一个具体工厂。

②更简单的方式是直接引入一个抽象工厂的集合,由于具体工厂使用了@Component注解修饰,通过构造方法注入的时候,会自动将两个具体工厂的实例引入进来。

@RestController
public class DemoController {
    //初始化学生信息的具体工厂
    private List<InitDataProvider<UserInfo<?>>> providers;

    //通过构造方法注入数据
    public DemoController(List<InitDataProvider<UserInfo<?>>> providers) {
        this.providers = providers;
    }

    //获取学生信息
    @RequestMapping("/query")
    public List<UserInfo> test01() {
        List<UserInfo> result = new ArrayList<>();
        for (InitDataProvider<UserInfo<?>> provider : providers) {
            result.add(provider.initData());
        }
        return result;
    }
}

可以看见,项目启动的时候,成功将具体工厂给引入了进来。

3、工厂方法模式 + 策略模式的具体应用

        需求:接入2个第三方客户端,微信和支付宝,要求提供【支付】和【查询】功能

        通过上面的示例,我们需要明白,为了代码的灵活性和可维护性,最好不要在Service层直接引用第三方客户端,Service层只需要调接口的抽象方法就行,具体该请求哪个第三方,交给它的实现类来决定。(否则,一大堆if...else全部集中在Service,很容易变成s山代码)

3.1  基础配置

模拟的真实开发如何使用

yml文件中的配置信息

alibaba:
  url: http://xxx.alibaba.com/pay
  token: token123456

wechat:
  url: http://xxx.wechat.xxx/pay
  token: token987654

支付宝相关配置类  

/**
 * 通常第三方客户端的配置都是写在配置文件中,
 * 项目运行的时候,会将配置文件的内容读取出来,并创建对象放入容器中
 */
@EnableConfigurationProperties
@Configuration
@Getter
@Setter
@ConfigurationProperties(prefix = "alibaba")
public class AlibabaConfig {
    private String url;
    private String token;
}

微信相关配置类

/**
 * 通常第三方客户端的配置都是写在配置文件中,
 * 项目运行的时候,会将配置文件的内容读取出来,并创建对象放入容器中
 */
@EnableConfigurationProperties
@Configuration
@Getter
@Setter
@ConfigurationProperties(prefix = "wechat")
public class WechatConfig {
    private String url;
    private String token;
}

 请求参数

/**
 * 封装的交易信息
 */
@Getter
@Setter
public class BusinessRequest {
    //支付人
    private String from;
    //收款人
    private String to;
    //交易金额
    private Integer amount;
    //来源:0--微信; 1--支付宝
    private String resource;
}

3.2  核心内容

抽象策略

/**
 * 抽象策略---第三方客户端
 */
public interface ThirdPayClient {
    //支付方法
    void pay(BusinessRequest info);

    //查看账户金额
    void query(String userId);
}

具体策略--支付宝客户端


/**
 * 具体的策略---支付宝客户端
 *
 */
public class AlibabaClient implements ThirdPayClient {
    private String httpURL;

    public AlibabaClient(AlibabaConfig config) {
        this.httpURL = config.getUrl();

    }

    @Override
    public void pay(BusinessRequest info) {
        System.out.println(String.format("调用支付宝的支付接口%s,%s支付%s元",
                httpURL, info.getFrom(), info.getAmount())
        );
    }

    @Override
    public void query(String userId) {
        System.out.println("%s【支付宝】余额为500元");
    }
}

具体策略--微信客户端


/**
 * 具体的策略---微信客户端
 */
public class WechatClient implements ThirdPayClient {

    private String httpURL;
    
    //读取配置文件的信息
    public WechatClient(WechatConfig config) {
        this.httpURL = config.getUrl();
    }

    @Override
    public void pay(BusinessRequest info) {
        System.out.println(String.format("调用微信的支付接口%s,%s支付%s元",
                httpURL, info.getFrom(), info.getAmount())
        );
    }

    @Override
    public void query(String userId) {
        System.out.println("%s【微信】余额为300元");
    }
}

抽象工厂

/**
 * 抽象工厂
 */
public interface BusinessRouterService {
    //支付方法
    void pay(BusinessRequest info);

    //查看账户金额
    void query(String userId, String resource);
}

具体工厂

/**
 * 具体工厂--转发请求到指定客户端
 */
@Component
public class BusinessRouterImpl implements BusinessRouterService {

    private AlibabaClient alibabaClient;

    private WechatClient wechatClient;

    /**
     * 根据引入的配置类,创建客户端对象
     *
     * @param alibabaConfig
     * @param wechatConfig
     */
    public BusinessRouterImpl(AlibabaConfig alibabaConfig,
                              WechatConfig wechatConfig) {
        this.alibabaClient = new AlibabaClient(alibabaConfig);
        this.wechatClient = new WechatClient(wechatConfig);
    }

    @Override
    public void pay(BusinessRequest info) {
        // 0 --说明走微信支付流程
        if ("0".equals(info.getResource())) {
            wechatClient.pay(info);
        } else {
            alibabaClient.pay(info);
        }
    }

    @Override
    public void query(String userId, String resource) {
        if ("0".equals(resource)) {
            wechatClient.query(userId);
        } else {
            alibabaClient.query(resource);
        }
    }

}

        Service业务层,只需要引入BusinessRouterService抽象工厂,具体使用哪个第三方客户端,由它的实现类去控制。

@Service
public class AccountService {
    private BusinessRouterService routerService;

    public AccountService(BusinessRouterService routerService) {
        this.routerService = routerService;
    }


    public void pay() {
        BusinessRequest request = new BusinessRequest();
        request.setFrom("小埋");
        request.setTo("爱莉西雅");
        request.setAmount(520);
        request.setResource("0");
        routerService.pay(request);
    }

    public void query() {
        routerService.query("userId123", "1");
    }

}

        如果后期在接入某个支付平台,我们只需要写一个具体策略(第三方客户端),并在具体工厂中加入它的业务判断就行了。这样的好处是,Service层不用关心底层的调用逻辑,不会做任何调整,支付或者查询金额完全封装到了具体策略中,每个模块负责各自的事,分工明确。

更多推荐