JAVA设计模式之工厂方法模式
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层不用关心底层的调用逻辑,不会做任何调整,支付或者查询金额完全封装到了具体策略中,每个模块负责各自的事,分工明确。
更多推荐


所有评论(0)