模拟场景: 开发某一项业务需要对接支付,当前只需要对接微信,支付宝,银联,但是后续还有可能会使用苹果支付,小米钱包或者银行卡等等渠道.
非工厂模式:
单纯的为了满足需求而进行编写我们可能会在一个类甚至一个方法内用大量的if-else实现
例如:
package top.lqh.design_pattern.factory;
/**
* 支付
*
* @author lqh
* @version 1.0
* @date: 2021/7/19 21:19
*/
public class Pay {
/**
* 支付<br>
* 此处单纯的演示,所以没有业务逻辑,单纯的根据不同的支付方式标志返回支付方式字符串,
* 平时开发肯定会有各种各样的实现逻辑
*
* @param payType 支付方式标志:0-微信 | 1-支付宝 | 2-银联
* @return String 支付方式名称
* @date 21:21 2021/7/19
* @author lqh
*/
public String pay(Integer payType) {
if (payType.equals(0)) { return "使用微信支付"; }
if (payType.equals(1)) { return "使用支付宝支付"; }
if (payType.equals(2)) { return "使用银联支付"; }
throw new RuntimeException("没有此支付方式!");
}
}
测试:
package top.lqh.design_pattern.factory;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* 支付测试
* @author lqh
* @version 1.0
* @date: 2021/7/19 21:28
*/
class PayTest {
@Test
void pay() {
Pay pay = new Pay();
for (int i = 0; i < 4; i++) {
String payStr = pay.pay(i);
System.out.println("payStr = " + payStr);
}
}
}
结果:
payStr = 使用微信支付
payStr = 使用支付宝支付
payStr = 使用银联支付
java.lang.RuntimeException: 没有此支付方式!
思考:
单纯的这么看来好像还很简洁,但是因为实际业务需要肯定不是单纯的返回一个字符串就可以了,有可能一个每种支付方式都会验证支付金额,支付订单好,支付人等等支付信息,然后生成支付订单过程中还有很多的逻辑,支付订单生成结束又有不同的逻辑,如果只是单纯的固定死的一两个支付方式这么写没啥问题,如果后续可能会增加未知数量的支付方式我们不能再继续在这个类里面继续不停的加if-else判断.
工厂模式
使用工厂模式或称为简单工厂模式可以很好的解决这个窘境
例如:
上面的多个if判断中其实都是实现了支付功能,我们可以提取成接口,然后不同的支付方式实现这个接口后编写属于自己的业务逻辑,
接口:
package top.lqh.design_pattern.factory;
/**
* 支付接口
* @author lqh
* @version 1.0
* @date: 2021/7/19 21:40
*/
public interface Pay {
/**
* 支付
*
* @date 21:41 2021/7/19
* @return java.lang.String 支付方式字符串
* @author lqh
*/
String pay();
}
微信支付实现类
package top.lqh.design_pattern.factory.impl;
import top.lqh.design_pattern.factory.Pay;
/**
* 微信支付
* @author lqh
* @version 1.0
* @date: 2021/7/19 21:51
*/
public class WeChatPay implements Pay {
/**
* 支付
*
* @return java.lang.String 支付方式字符串
* @date 21:41 2021/7/19
* @author lqh
*/
@Override
public String pay() {
return "微信支付";
}
}
支付宝实现
package top.lqh.design_pattern.factory.impl;
import top.lqh.design_pattern.factory.Pay;
/**
* 支付宝
* @author lqh
* @version 1.0
* @date: 2021/7/19 21:52
*/
public class AliPay implements Pay {
/**
* 支付
*
* @return java.lang.String 支付方式字符串
* @date 21:41 2021/7/19
* @author lqh
*/
@Override
public String pay() {
return "支付宝";
}
}
银联支付
package top.lqh.design_pattern.factory.impl;
import top.lqh.design_pattern.factory.Pay;
/**
* 银联支付
* @author lqh
* @version 1.0
* @date: 2021/7/19 21:53
*/
public class UnionPay implements Pay {
/**
* 支付
*
* @return java.lang.String 支付方式字符串
* @date 21:41 2021/7/19
* @author lqh
*/
@Override
public String pay() {
return "银联支付";
}
}
支付工厂
工厂模式可以按需创建对象,不会对外暴露创建细节,并且可以通过一个统一的接口创建所有的对象.
package top.lqh.design_pattern.factory;
import top.lqh.design_pattern.factory.impl.AliPay;
import top.lqh.design_pattern.factory.impl.UnionPay;
import top.lqh.design_pattern.factory.impl.WeChatPay;
import java.util.Objects;
/**
* 支付工厂
* @author lqh
* @version 1.0
* @date: 2021/7/19 21:54
*/
public class PayStore {
/**
* 获取支付服务类
*
* @date 21:55 2021/7/19
* @param payType 支付方式标志
* @return top.lqh.design_pattern.factory.Pay
* @author lqh
*/
public Pay getPayService(Integer payType){
if (Objects.isNull(payType)) { throw new NullPointerException("支付标志为空!"); }
if (payType.equals(0)){ return new WeChatPay(); }
if (payType.equals(1)){ return new AliPay(); }
if (payType.equals(2)){ return new UnionPay(); }
throw new RuntimeException("不存在的支付方式!");
}
}
测试:
package top.lqh.design_pattern.factory;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author lqh
* @version 1.0
* @date: 2021/7/19 22:01
*/
class PayTest {
@Test
void pay() {
for (int i = 0; i < 4; i++) {
Pay pay = new PayStore().getPayService(i);
System.out.println("pay.pay() = " + pay.pay());
}
}
}
结果:
pay.pay() = 微信支付
pay.pay() = 支付宝
pay.pay() = 银联支付
java.lang.RuntimeException: 不存在的支付方式!
思考:
回头一看会发现其实我们只是单纯的将业务逻辑实现的代码摘了出去并用接口的形式实现,本质还是在工厂类中使用if-else判断,所以应该可以有另一种不需要多个判断的方式实现
使用Map改进
实现
在工厂类中使用键值对保存所有的类,直接使用Map的get方法获取到不同的实现类并返回
package top.lqh.design_pattern.factory;
import top.lqh.design_pattern.factory.impl.AliPay;
import top.lqh.design_pattern.factory.impl.UnionPay;
import top.lqh.design_pattern.factory.impl.WeChatPay;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 支付工厂
*
* @author lqh
* @version 1.0
* @date: 2021/7/19 21:54
*/
public class PayStore {
/**
* 支付标志为空的错误提示
*/
private static final String NIL_PAY_TYPE_ERROR = "支付标志为空!";
/**
* 不正确的支付方式异常提示
*/
private static final String INCORRECT_PAY_TYPE_ERROR = "不存在的支付方式!";
/**
* 服务类未实现Pay接口异常提示
*/
private static final String INCORRECT_PAY_INSTANCE_ERROR = "错误的支付服务对象!";
/**
* 存放不同服务类的键值集合
*/
private static final Map<Integer, Class> map = new HashMap<>();
static {
map.put(0, WeChatPay.class);
map.put(1, AliPay.class);
map.put(2, UnionPay.class);
}
/**
* 获取支付服务类
*
* @param payType 支付标志
* @return top.lqh.design_pattern.factory.Pay
* @throws InstantiationException 实例化服务类异常
* @throws IllegalAccessException 实例化服务类异常
* @date 22:13 2021/7/19
* @author lqh
*/
public Pay getPayService(Integer payType) throws InstantiationException, IllegalAccessException {
//空入参校验
if (Objects.isNull(payType)) {
throw new NullPointerException(NIL_PAY_TYPE_ERROR);
}
//获取支付服务类
Class clazz = map.get(payType);
//判空
if (Objects.isNull(clazz)) {
throw new RuntimeException(INCORRECT_PAY_TYPE_ERROR);
}
//实例化
Object instance = clazz.newInstance();
//验证获取的服务类是否正确
if (!(instance instanceof Pay)){
throw new RuntimeException(INCORRECT_PAY_INSTANCE_ERROR);
}
return (Pay) instance;
}
}
思考
测试之后结果和之前相同,这里可以不用有新的支付方式需要对接时不停的添加大量的判断语句了,只需要实现Pay接口后再添加一个键值对映射即可.但是既然用到了反射,也许可以直接在不同的支付实现类里面内置属于自己的支付标志,然后通过反射获取到这个标志后进行判断并实例化
使用反射改进
实现
首先写一个抽象的支付类实现支付接口
package top.lqh.design_pattern.factory.impl;
import top.lqh.design_pattern.factory.Pay;
/**
* 抽象支付实现类
* @author lqh
* @version 1.0
* @date: 2021/7/19 22:38
*/
public abstract class AbstractPayImpl implements Pay {
/**
* 获取支付类型标志
*
* @date 22:40 2021/7/19
* @return java.lang.Integer
* @author lqh
*/
public abstract Integer getType();
}
然后具体实现的支付服务类集成这个抽象类并重写获取支付标志方法
支付宝
package top.lqh.design_pattern.factory.impl;
/**
* 支付宝
* @author lqh
* @version 1.0
* @date: 2021/7/19 21:52
*/
public class AliPay extends AbstractPayImpl{
/**
* 支付
*
* @return java.lang.String 支付方式字符串
* @date 21:41 2021/7/19
* @author lqh
*/
@Override
public String pay() {
return "支付宝";
}
/**
* 获取支付类型标志
*
* @return java.lang.Integer
* @date 22:40 2021/7/19
* @author lqh
*/
@Override
public Integer getType() {
return 1;
}
}
银联
package top.lqh.design_pattern.factory.impl;
import top.lqh.design_pattern.factory.Pay;
/**
* 银联支付
* @author lqh
* @version 1.0
* @date: 2021/7/19 21:53
*/
public class UnionPay extends AbstractPayImpl{
/**
* 支付
*
* @return java.lang.String 支付方式字符串
* @date 21:41 2021/7/19
* @author lqh
*/
@Override
public String pay() {
return "银联支付";
}
/**
* 获取支付类型标志
*
* @return java.lang.Integer
* @date 22:40 2021/7/19
* @author lqh
*/
@Override
public Integer getType() {
return 2;
}
}
微信
package top.lqh.design_pattern.factory.impl;
import top.lqh.design_pattern.factory.Pay;
/**
* 微信支付
* @author lqh
* @version 1.0
* @date: 2021/7/19 21:51
*/
public class WeChatPay extends AbstractPayImpl{
/**
* 支付
*
* @return java.lang.String 支付方式字符串
* @date 21:41 2021/7/19
* @author lqh
*/
@Override
public String pay() {
return "微信支付";
}
/**
* 获取支付类型标志
*
* @return java.lang.Integer
* @date 22:40 2021/7/19
* @author lqh
*/
@Override
public Integer getType() {
return 0;
}
}
支付工厂
package top.lqh.design_pattern.factory;
import org.reflections.Reflections;
import top.lqh.design_pattern.factory.impl.AbstractPayImpl;
import java.util.Objects;
import java.util.Set;
/**
* 支付工厂
*
* @author lqh
* @version 1.0
* @date: 2021/7/19 21:54
*/
public class PayStore {
/**
* 支付标志为空的错误提示
*/
private static final String NIL_PAY_TYPE_ERROR = "支付标志为空!";
/**
* 不正确的支付方式异常提示
*/
private static final String INCORRECT_PAY_TYPE_ERROR = "不存在的支付方式!";
/**
* 服务类未实现Pay接口异常提示
*/
private static final String INCORRECT_PAY_INSTANCE_ERROR = "错误的支付服务对象!";
/**
* 获取支付服务类
*
* @param payType 支付标志
* @return top.lqh.design_pattern.factory.Pay
* @throws InstantiationException 实例化服务类异常
* @throws IllegalAccessException 实例化服务类异常
* @date 22:13 2021/7/19
* @author lqh
*/
public Pay getPayService(Integer payType) throws InstantiationException, IllegalAccessException {
//入参校验
if (Objects.isNull(payType)){ throw new NullPointerException(NIL_PAY_TYPE_ERROR); }
/*
使用反射工具包reflections
https://mvnrepository.com/artifact/org.reflections/reflections/0.9.12
*/
//获取指定包下所有的类
Reflections reflections = new Reflections("top.lqh.design_pattern.factory.impl");
//获取继承了抽象支付服务类的支付类
Set<Class<? extends AbstractPayImpl>> payServices = reflections.getSubTypesOf(AbstractPayImpl.class);
//遍历所有支付类
for (Class<? extends AbstractPayImpl> pay : payServices) {
//实例化
AbstractPayImpl abstractPay = pay.newInstance();
//获取类的内置支付标志,如果和入参相同就返回这个服务类
if (abstractPay.getType().equals(payType)){
return (Pay) abstractPay;
}
}
//不存在对应的服务类就抛出异常
throw new RuntimeException(INCORRECT_PAY_TYPE_ERROR);
}
}
思考
这里用到了一个反射工具包,获取到所有的支付服务实现类后遍历并实例化然后判断他们的支付标志是否使我们所需要的,是就返回这个实现类.
这样子工厂类只需要第一次写完就可以不用再修改,后续在有新的支付方式只需要在指定的包下重写继承实现抽象类和支付接口内的方法就可以了,
但是因为用到了反射,且需要遍历对应的类并实例化,所以对于性能影响会比较大,不过也可以将注入微信支付宝这几个经常用的直接拿出来先做判断,
几个常用的支付渠道都不匹配后再进入循环.
枚举类实现
除了使用Map外也可以单独的建一个枚举类,每个支付方式就是一个枚举,然后重写此枚举类的获取服务方法
实现
微信支付宝几个支付服务类就以工厂模式为准不需要修改
新增PayEnum枚举类
package top.lqh.design_pattern.factory.impl;
import top.lqh.design_pattern.factory.Pay;
/**
* 支付服务枚举类
* todo 这个写法不知道怎么学会的了,也不知怎么解释单纯的会使用,等待学习
*
* @author lqh
* @version 1.0
* @date: 2021/7/21 21:02
*/
public enum PayEnum {
/*每个枚举项重写获取实例化服务方法,返回对应的支付服务实例*/
/**
* 微信支付
*/
WECHATPAY(0, WeChatPay.class){
@Override
public Pay getService() throws InstantiationException, IllegalAccessException {
return service.newInstance();
}
},
/**
* 支付宝支付
*/
ALIPAY(1, AliPay.class){
@Override
public Pay getService() throws InstantiationException, IllegalAccessException {
return service.newInstance();
}
},
/**
* 银联支付
*/
UNIONPAY(2, UnionPay.class){
@Override
public Pay getService() throws InstantiationException, IllegalAccessException {
return service.newInstance();
}
},
;
/**
* 支付类型标志
*/
private Integer flag;
/**
* 支付服务:
* todo 目前不知道为什么private会导致无法生成get方法(初步猜想是因为没有实例化)
*/
protected Class<? extends Pay> service;
/**
* 构造方法
*
* @date 21:06 2021/7/21
* @param flag 支付类型标志
* @param service 服务
* @return
* @author lqh
*/
PayEnum(Integer flag, Class<? extends Pay> service) {
this.flag = flag;
this.service = service;
}
/**
* 获取服务类
*
* @date 21:14 2021/7/21
* @return top.lqh.design_pattern.factory.Pay
* @author lqh
* @throws InstantiationException,IllegalAccessException 实例化异常
*/
public Pay getService() throws InstantiationException, IllegalAccessException { return service.newInstance(); }
/**
* 获取支付类枚举
*
* @date 21:10 2021/7/21
* @param flag 支付类型
* @return top.lqh.design_pattern.factory.impl.PayEnum
* @author lqh
* @throws RuntimeException 未找到支付方式时抛出异常
*/
public static PayEnum getEnum(Integer flag){
/*获取所有的枚举项*/
PayEnum[] enums = PayEnum.values();
/*遍历查询对应的支付枚举项并返回*/
for (PayEnum payEnum : enums) {
if (payEnum.flag.equals(flag)) {
return payEnum;
}
}
/*没有找到就抛出异常*/
throw new RuntimeException("不存在的支付方式!");
}
/**
* 获取标志
*
* @date 21:21 2021/7/21
* @return java.lang.Integer
* @author lqh
*/
public Integer getFlag() {
return flag;
}
/**
* 设置标志
*
* @date 21:21 2021/7/21
* @param flag 标志
* @return void
* @author lqh
*/
public void setFlag(Integer flag) {
this.flag = flag;
}
/**
* 设置服务
*
* @date 21:21 2021/7/21
* @param service 支付服务
* @return void
* @author lqh
*/
public void setService(Class<? extends Pay> service) {
this.service = service;
}
/**
* 打印
*
* @date 21:21 2021/7/21
* @return java.lang.String
* @author lqh
*/
@Override
public String toString() {
return "PayEnum{" +
"flag=" + flag +
", service=" + service +
'}';
}
}
获取支付服务工厂类
package top.lqh.design_pattern.factory;
import top.lqh.design_pattern.factory.impl.PayEnum;
/**
* 支付工厂
*
* @author lqh
* @version 1.0
* @date: 2021/7/19 21:54
*/
public class PayStore {
/**
* 获取支付服务类
*
* @param payType 支付标志
* @return top.lqh.design_pattern.factory.Pay
* @date 22:13 2021/7/19
* @author lqh
*/
public Pay getPayService(Integer payType) throws InstantiationException, IllegalAccessException {
return PayEnum.getEnum(payType).getService();
}
}
经过测试和之前结果相同
思考
这个用枚举的方式忘记在哪里学到的了,也不知道该如何解释,只是单纯的会使用这种方式,而且不知道为什么Class无法生成Getter方法