文章

重学设计模式

重学设计模式
  • content

工厂模式

模式介绍:工厂模式又称工厂方法模式,是一种创建型设计模式,其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。它的主要意图是定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

简单说就是为了提供代码结构的扩展性,屏蔽每一个功能类中的具体实现逻辑。让外部可以更加简单的只是知道调用即可,当然这可能也有一些缺点,比如需要实现的类非常多,如何去维护,怎样减低开发成本。但这些问题都可以在后续的设计模式结合使用中,逐步降低。

模拟发奖多种商品场景

在这里我们模拟积分兑换中的发放多种类型商品,假如现在我们有如下三种类型的商品接口:

1:优惠券接口为:CouponResult sendCoupon(String uId, String couponNumber, String uuid)

2:实物商品接口为:Boolean deliverGoods(DeliverReq req)

3:爱奇艺兑换卡接口:void grantToken(String bindMobileNumber, String cardId)

三个接口返回类型不同,有对象类型、布尔类型、还有一个空类型。

入参不同,发放优惠券需要仿重、兑换卡需要卡ID、实物商品需要发货位置(对象中含有)。

另外可能会随着后续的业务的发展,会新增其他种商品类型。因为你所有的开发需求都是随着业务对市场的拓展而带来的。

工程结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
itstack-demo-design-1-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── store    
    │           │   ├── impl
    │           │   │   ├── CardCommodityService.java (爱奇艺兑换卡发奖接口实现)
    │           │   │   ├── CouponCommodityService.java (优惠券发奖接口实现)
    │           │   │   └── GoodsCommodityService.java  (实物商品发奖接口实现)
    │           │   └── ICommodity.java (底层发奖总接口)
    │           └── StoreFactory.java (接口实现工厂)
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

接口

底层发奖总接口

1
2
3
4
5
6
public interface ICommodity {
        //所有的奖品无论是实物、虚拟还是第三方,都需要通过我们的程序实现此接口进行处理,以保证最终入参出参的统一性
    //接口的入参包括;用户ID、奖品ID、业务ID以及扩展字段用于处理发放实物商品时的收获地址
    void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;

}

接口实现

优惠券发奖接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CouponCommodityService implements ICommodity {

    private Logger logger = LoggerFactory.getLogger(CouponCommodityService.class);

    private CouponService couponService = new CouponService();

    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
        CouponResult couponResult = couponService.sendCoupon(uId, commodityId, bizId);
        logger.info("请求参数[优惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
        logger.info("测试结果[优惠券]:{}", JSON.toJSON(couponResult));
        if (!"0000".equals(couponResult.getCode())) throw new RuntimeException(couponResult.getInfo());
    }

}

实物商品发奖接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class GoodsCommodityService implements ICommodity {

    private Logger logger = LoggerFactory.getLogger(GoodsCommodityService.class);

    private GoodsService goodsService = new GoodsService();

    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
        DeliverReq deliverReq = new DeliverReq();
        deliverReq.setUserName(queryUserName(uId));
        deliverReq.setUserPhone(queryUserPhoneNumber(uId));
        deliverReq.setSku(commodityId);
        deliverReq.setOrderId(bizId);
        deliverReq.setConsigneeUserName(extMap.get("consigneeUserName"));
        deliverReq.setConsigneeUserPhone(extMap.get("consigneeUserPhone"));
        deliverReq.setConsigneeUserAddress(extMap.get("consigneeUserAddress"));

        Boolean isSuccess = goodsService.deliverGoods(deliverReq);

        logger.info("请求参数[优惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
        logger.info("测试结果[优惠券]:{}", isSuccess);

        if (!isSuccess) throw new RuntimeException("实物商品发放失败");
    }

    private String queryUserName(String uId) {
        return "花花";
    }

    private String queryUserPhoneNumber(String uId) {
        return "15200101232";
    }

}

爱奇艺兑换卡发奖接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CardCommodityService implements ICommodity {

    private Logger logger = LoggerFactory.getLogger(CardCommodityService.class);

    // 模拟注入
    private IQiYiCardService iQiYiCardService = new IQiYiCardService();

    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
        String mobile = queryUserMobile(uId);
        iQiYiCardService.grantToken(mobile, bizId);
        logger.info("请求参数[爱奇艺兑换卡] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
        logger.info("测试结果[爱奇艺兑换卡]:success");
    }

    private String queryUserMobile(String uId) {
        return "15200101232";
    }

}

工厂

接口实现工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
public class StoreFactory {

    //后续新增的商品在这里扩展即可。如果你不喜欢if判断,也可以使用switch或者map配置结构,会让代码更加干净。
    //如果使用spring可以依赖注入一个map,key为bean名称,value为对应的bean
    public ICommodity getCommodityService(Integer commodityType) {
        if (null == commodityType) return null;
        if (1 == commodityType) return new CouponCommodityService();
        if (2 == commodityType) return new GoodsCommodityService();
        if (3 == commodityType) return new CardCommodityService();
        throw new RuntimeException("不存在的商品服务类型");
    }

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void test_commodity() throws Exception {
    StoreFactory storeFactory = new StoreFactory();
    // 1. 优惠券
    ICommodity commodityService_1 = storeFactory.getCommodityService(1);
    commodityService_1.sendCommodity("10001", "EGM1023938910232121323432", "791098764902132", null);
    // 2. 实物商品
    ICommodity commodityService_2 = storeFactory.getCommodityService(2);

    Map<String,String> extMap = new HashMap<String,String>();
    extMap.put("consigneeUserName", "谢飞机");
    extMap.put("consigneeUserPhone", "15200292123");
    extMap.put("consigneeUserAddress", "吉林省.长春市.双阳区.XX街道.檀溪苑小区.#18-2109");

    commodityService_2.sendCommodity("10001","9820198721311","1023000020112221113", extMap);
    // 3. 第三方兑换卡(爱奇艺)
    ICommodity commodityService_3 = storeFactory.getCommodityService(3);
    commodityService_3.sendCommodity("10001","AQY1xjkUodl8LO975GdfrYUio",null,null);
}

总结

避免创建者与具体的产品逻辑耦合,满足单一原则,每一个业务逻辑实现都在所属自己的类中完成,满足开闭原则,无需更改使用调用方就可以在程序中引入新的产品类型。

类型增多后,实现的子类会急速扩张,因此需要搭配其他设计模式进行优化

抽象工厂模式

模式介绍:抽象工厂模式,所要解决的问题就是在一个产品族,存在多个不同类型的产品(Redis集群、操作系统)情况下,接口选择的问题。

抽象工厂是一个中心工厂,创建其他工厂的模式

Redis双集群升级场景

预见问题:

1:很多服务用到了redis需要一起升级到集群。

2:需要兼容集群A和集群B,便于后续的容灾

3:两套集群提供的接口和方法各有差异,需要做适配。

4:不能影响目前运行的系统。

工程结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
itstack-demo-design-2-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── factory    
    │           │   ├── impl
    │           │   │   ├── EGMCacheAdapter.java 
    │           │   │   └── IIRCacheAdapter.java
    │           │   ├── ICacheAdapter.java
    │           │   ├── JDKInvocationHandler.java
    │           │   └── JDKProxy.java
    │           ├── impl
    │           │   └── CacheServiceImpl.java    
    │           └── CacheService.java 
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

这里的抽象工厂的创建和获取方式,会采用代理类的方式进行实现。所被代理的类就是目前的Redis操作方法类,让这个类在不需要任何修改下,就可以实现调用集群A和集群B的数据服务。

由于集群A和集群B在部分方法提供上是不同的,因此需要做一个接口适配,而这个适配类就相当于工厂中的工厂,用于创建把不同的服务抽象为统一的接口做相同的业务。

抽象工厂模型结构

ICacheAdapter:定义了适配接口,分别包装两个集群中差异化的接口名称:EGMCacheAdapter,IIRCacheAdapter

JDKProxy,JDKInvocationHandler:是代理类的定义和实现,这部分也就是抽象工厂的另外一种实现方式。通过这样的方式可以很好的把原有操作Redis的方法进行代理操作,通过控制不同的入参对象,控制缓存的使用。

适配接口

定义适配接口

1
2
3
4
5
6
7
8
9
10
11
12
public interface ICacheAdapter {

    //获取
    String get(String key);

    void set(String key, String value);

    void set(String key, String value, long timeout, TimeUnit timeUnit);

    void del(String key);

}

这个接口是最底层接口,redis最根本的方法

集群使用服务

各个集群实现适配接口,然后实现自己的业务调用逻辑

EGMCacheAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class EGMCacheAdapter implements ICacheAdapter {

    private EGM egm = new EGM();

    public String get(String key) {
        return egm.gain(key);
    }

    public void set(String key, String value) {
        egm.set(key, value);
    }

    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
        egm.setEx(key, value, timeout, timeUnit);
    }

    public void del(String key) {
        egm.delete(key);
    }
}

IIRCacheAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class IIRCacheAdapter implements ICacheAdapter {

    private IIR iir = new IIR();

    public String get(String key) {
        return iir.get(key);
    }

    public void set(String key, String value) {
        iir.set(key, value);
    }

    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
        iir.setExpire(key, value, timeout, timeUnit);
    }

    public void del(String key) {
        iir.del(key);
    }

}

代理类和实现

获取集群服务的入口,判断应该调用哪一个集群

JDKProxy

1
2
3
4
5
6
public static <T> T getProxy(Class<T> interfaceClass, ICacheAdapter cacheAdapter) throws Exception {
    InvocationHandler handler = new JDKInvocationHandler(cacheAdapter);
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    Class<?>[] classes = interfaceClass.getInterfaces();
    return (T) Proxy.newProxyInstance(classLoader, new Class[]{classes[0]}, handler);
}

JDKInvocationHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class JDKInvocationHandler implements InvocationHandler {

    //适配器
    private ICacheAdapter cacheAdapter;

    public JDKInvocationHandler(ICacheAdapter cacheAdapter) {
        this.cacheAdapter = cacheAdapter;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
    }

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test_CacheService() throws Exception {
    CacheService proxy_EGM = JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
    proxy_EGM.set("user_name_01","小傅哥");
    String val01 = proxy_EGM.get("user_name_01");
    System.out.println(val01);

    CacheService proxy_IIR = JDKProxy.getProxy(CacheServiceImpl.class, new IIRCacheAdapter());
    proxy_IIR.set("user_name_01","小傅哥");
    String val02 = proxy_IIR.get("user_name_01");
    System.out.println(val02);
}

总结

如果后续有扩展的需求,也可以按照这样的类型方式进行补充,同时对于改造上来说并没有改动原来的方法,降低了修改成本。

那么这个设计模式满足了;单一职责、开闭原则、解耦等优点,但如果说随着业务的不断拓展,可能会造成类实现上的复杂度。但也可以说算不上缺点,因为可以随着其他设计方式的引入和代理类以及自动生成加载的方式降低此项缺点。

建造者模式

建造者模式所完成的内容就是通过将多个简单对象通过一步步的组装构建出一个复杂对象的过程。

房子装修物料选择场景

我们就模拟装修公司想推出一些套餐装修服务,按照不同的价格设定品牌选择组合,以达到使用建造者模式的过程。

工程目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
itstack-demo-design-3-00
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── ceilling
                │   ├── LevelOneCeiling.java (一级吊顶)
                │   └── LevelTwoCeiling.java(二级吊顶)
                ├── coat
                │   ├── DuluxCoat.java(多乐士涂料)
                │   └── LiBangCoat.java(立邦涂料)
                ├── floor
                │   ├── DerFloor.java(德尔地板)
                │   └── ShengXiangFloor.java(圣象地板)
                ├── tile
                │   ├── DongPengTile.java(东鹏瓷砖)
                │   └── MarcoPoloTile.java(马可波罗瓷砖)
                └── Matter.java (物料接口)


itstack-demo-design-3-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── Builder.java    
    │           ├── DecorationPackageMenu.java
    │           └── IMenu.java 
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

物料接口

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Matter {

    String scene();      // 场景;地板、地砖、涂料、吊顶

    String brand();      // 品牌

    String model();      // 型号

    BigDecimal price();  // 价格

    String desc();       // 描述

}

装修包接口

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IMenu {

    IMenu appendCeiling(Matter matter); // 吊顶

    IMenu appendCoat(Matter matter);    // 涂料

    IMenu appendFloor(Matter matter);   // 地板

    IMenu appendTile(Matter matter);    // 地砖

    String getDetail();                 // 明细 

}

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class DecorationPackageMenu implements IMenu {

    private List<Matter> list = new ArrayList<Matter>();  // 装修清单
    private BigDecimal price = BigDecimal.ZERO;      // 装修价格

    private BigDecimal area;  // 面积
    private String grade;     // 装修等级;豪华欧式、轻奢田园、现代简约

    private DecorationPackageMenu() {
    }

    public DecorationPackageMenu(Double area, String grade) {
        this.area = new BigDecimal(area);
        this.grade = grade;
    }

    public IMenu appendCeiling(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(new BigDecimal("0.2")).multiply(matter.price()));
        return this;
    }

    public IMenu appendCoat(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(new BigDecimal("1.4")).multiply(matter.price()));
        return this;
    }

    public IMenu appendFloor(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(matter.price()));
        return this;
    }

    public IMenu appendTile(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(matter.price()));
        return this;
    }

    public String getDetail() {

        StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
                "装修清单" + "\r\n" +
                "套餐等级:" + grade + "\r\n" +
                "套餐价格:" + price.setScale(2, BigDecimal.ROUND_HALF_UP) + " 元\r\n" +
                "房屋面积:" + area.doubleValue() + " 平米\r\n" +
                "材料清单:\r\n");

        for (Matter matter: list) {
            detail.append(matter.scene()).append(":").append(matter.brand()).append("、").append(matter.model()).append("、平米价格:").append(matter.price()).append(" 元。\n");
        }

        return detail.toString();
    }

}

建造者方法

通过不同的物料填充出不同的装修风格,如果将来业务扩展也可以将这部分内容配置到数据库自动生成。但整体的思想还可以使用创建者模式进行搭建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Builder {

    public IMenu levelOne(Double area) {
        return new DecorationPackageMenu(area, "豪华欧式")
                .appendCeiling(new LevelTwoCeiling())    // 吊顶,二级顶
                .appendCoat(new DuluxCoat())             // 涂料,多乐士
                .appendFloor(new ShengXiangFloor());     // 地板,圣象
    }

    public IMenu levelTwo(Double area){
        return new DecorationPackageMenu(area, "轻奢田园")
                .appendCeiling(new LevelTwoCeiling())   // 吊顶,二级顶
                .appendCoat(new LiBangCoat())           // 涂料,立邦
                .appendTile(new MarcoPoloTile());       // 地砖,马可波罗
    }

    public IMenu levelThree(Double area){
        return new DecorationPackageMenu(area, "现代简约")
                .appendCeiling(new LevelOneCeiling())   // 吊顶,二级顶
                .appendCoat(new LiBangCoat())           // 涂料,立邦
                .appendTile(new DongPengTile());        // 地砖,东鹏
    }

}

测试

1
2
3
4
5
6
7
8
9
10
@Test
public void test_Builder(){
    Builder builder = new Builder();
    // 豪华欧式
    System.out.println(builder.levelOne(132.52D).getDetail());
    // 轻奢田园
    System.out.println(builder.levelTwo(98.25D).getDetail());
    // 现代简约
    System.out.println(builder.levelThree(85.43D).getDetail());
}

总结

当一些基本物料不会变,而其组合经常发生变化的时候,就可以选择这样的设计模式来构建代码

此设计模式满足了单一职责原则以及可复用的技术、建造者独立、易扩展、便于控制细节风险。但同时当出现特别多的物料以及很多的组合后,类的不断扩展也会造成难以维护的问题。但这种设计结构模型可以把重复的内容抽象到数据库中,按照需要配置。这样就可以减少代码中大量的重复。

原型模式

原型模式主要解决的问题就是创建重复对象,这部分对象本身比较复杂,生成过程可能从库或者RPC接口中获取数据的耗时较长,因此采用克隆的方式节省时间。

试卷题目答案乱序场景

在保证大家的公平性一样的题目下,开始出现试题混排更有做的好的答案选项也混排。

工程目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
itstack-demo-design-4-00
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── AnswerQuestion.java(问答题)
                └── ChoiceQuestion.java(选择题)


itstack-demo-design-4-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── util
    │           │   ├── Topic.java
    │           │   └── TopicRandomUtil.java
    │           ├── QuestionBank.java
    │           └── QuestionBankController.java 
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

试题类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class ChoiceQuestion {

    private String name;                 // 题目
    private Map<String, String> option;  // 选项;A、B、C、D
    private String key;                  // 答案;B

    public ChoiceQuestion() {
    }

    public ChoiceQuestion(String name, Map<String, String> option, String key) {
        this.name = name;
        this.option = option;
        this.key = key;
    }

    // ...get/set
}


public class AnswerQuestion {

    private String name;  // 问题
    private String key;   // 答案

    public AnswerQuestion() {
    }

    public AnswerQuestion(String name, String key) {
        this.name = name;
        this.key = key;
    }

    // ...get/set
}

选项乱序工具包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * 乱序Map元素,记录对应答案key
 * @param option 题目
 * @param key    答案
 * @return Topic 乱序后 {A=c., B=d., C=a., D=b.}
 */
static public Topic random(Map<String, String> option, String key) {
    Set<String> keySet = option.keySet();
    ArrayList<String> keyList = new ArrayList<String>(keySet);
    Collections.shuffle(keyList);
    HashMap<String, String> optionNew = new HashMap<String, String>();
    int idx = 0;
    String keyNew = "";
    for (String next : keySet) {
        String randomKey = keyList.get(idx++);
        if (key.equals(next)) {
            keyNew = randomKey;
        }
        optionNew.put(randomKey, option.get(next));
    }
    return new Topic(optionNew, keyNew);
}

克隆对象处理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class QuestionBank implements Cloneable {

    private String candidate; // 考生
    private String number;    // 考号

    private ArrayList<ChoiceQuestion> choiceQuestionList = new ArrayList<ChoiceQuestion>();
    private ArrayList<AnswerQuestion> answerQuestionList = new ArrayList<AnswerQuestion>();

    public QuestionBank append(ChoiceQuestion choiceQuestion) {
        choiceQuestionList.add(choiceQuestion);
        return this;
    }

    public QuestionBank append(AnswerQuestion answerQuestion) {
        answerQuestionList.add(answerQuestion);
        return this;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        QuestionBank questionBank = (QuestionBank) super.clone();
        questionBank.choiceQuestionList = (ArrayList<ChoiceQuestion>) choiceQuestionList.clone();
        questionBank.answerQuestionList = (ArrayList<AnswerQuestion>) answerQuestionList.clone();

        // 题目乱序
        Collections.shuffle(questionBank.choiceQuestionList);
        Collections.shuffle(questionBank.answerQuestionList);
        // 答案乱序
        ArrayList<ChoiceQuestion> choiceQuestionList = questionBank.choiceQuestionList;
        for (ChoiceQuestion question : choiceQuestionList) {
            Topic random = TopicRandomUtil.random(question.getOption(), question.getKey());
            question.setOption(random.getOption());
            question.setKey(random.getKey());
        }
        return questionBank;
    }

    public void setCandidate(String candidate) {
        this.candidate = candidate;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    @Override
    public String toString() {

        StringBuilder detail = new StringBuilder("考生:" + candidate + "\r\n" +
                "考号:" + number + "\r\n" +
                "--------------------------------------------\r\n" +
                "一、选择题" + "\r\n\n");

        for (int idx = 0; idx < choiceQuestionList.size(); idx++) {
            detail.append("第").append(idx + 1).append("题:").append(choiceQuestionList.get(idx).getName()).append("\r\n");
            Map<String, String> option = choiceQuestionList.get(idx).getOption();
            for (String key : option.keySet()) {
                detail.append(key).append(":").append(option.get(key)).append("\r\n");;
            }
            detail.append("答案:").append(choiceQuestionList.get(idx).getKey()).append("\r\n\n");
        }

        detail.append("二、问答题" + "\r\n\n");

        for (int idx = 0; idx < answerQuestionList.size(); idx++) {
            detail.append("第").append(idx + 1).append("题:").append(answerQuestionList.get(idx).getName()).append("\r\n");
            detail.append("答案:").append(answerQuestionList.get(idx).getKey()).append("\r\n\n");
        }

        return detail.toString();
    }

}

初始化题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class QuestionBankController {

    private QuestionBank questionBank = new QuestionBank();

    public QuestionBankController() {

        Map<String, String> map01 = new HashMap<String, String>();
        map01.put("A", "JAVA2 EE");
        map01.put("B", "JAVA2 Card");
        map01.put("C", "JAVA2 ME");
        map01.put("D", "JAVA2 HE");
        map01.put("E", "JAVA2 SE");

        Map<String, String> map02 = new HashMap<String, String>();
        map02.put("A", "JAVA程序的main方法必须写在类里面");
        map02.put("B", "JAVA程序中可以有多个main方法");
        map02.put("C", "JAVA程序中类名必须与文件名一样");
        map02.put("D", "JAVA程序的main方法中如果只有一条语句,可以不用{}(大括号)括起来");

        questionBank.append(new ChoiceQuestion("JAVA所定义的版本中不包括", map01, "D"))
                .append(new ChoiceQuestion("下列说法正确的是", map02, "A"))
                .append(new AnswerQuestion("小红马和小黑马生的小马几条腿", "4条腿"))
                .append(new AnswerQuestion("铁棒打头疼还是木棒打头疼", "头最疼"))
                .append(new AnswerQuestion("什么床不能睡觉", "牙床"))
                .append(new AnswerQuestion("为什么好马不吃回头草", "后面的草没了"));
    }

    public String createPaper(String candidate, String number) throws CloneNotSupportedException {
        QuestionBank questionBankClone = (QuestionBank) questionBank.clone();
        questionBankClone.setCandidate(candidate);
        questionBankClone.setNumber(number);
        return questionBankClone.toString();
    }

}

测试

1
2
3
4
5
6
7
@Test
public void test_QuestionBank() throws CloneNotSupportedException {
    QuestionBankController questionBankController = new QuestionBankController();
    System.out.println(questionBankController.createPaper("花花", "1000001921032"));
    System.out.println(questionBankController.createPaper("豆豆", "1000001921051"));
    System.out.println(questionBankController.createPaper("大宝", "1000001921987"));
}

总结

另外原型设计模式的优点包括;便于通过克隆方式创建复杂对象、也可以避免重复做初始化操作、不需要与类中所属的其他类耦合等。但也有一些缺点如果对象中包括了循环引用的克隆,以及类中深度使用对象的克隆,都会使此模式变得异常麻烦

单例模式

单例模式主要解决的是,一个全局使用的类频繁的创建和消费,从而提升提升整体的代码的性能

懒汉模式

单例模式有一个特点就是不允许外部直接创建,也就是new Singleton_01(),因此这里在默认的构造函数上添加了私有属性 private

不安全的实现:瞬间进来三个访问,可能会创建出三个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton_01 {

    private static Singleton_01 instance;

    private Singleton_01() {
    }

    public static Singleton_01 getInstance(){
        if (null != instance) return instance;
        instance = new Singleton_01();
        return instance;
    }

}

线程安全的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton_02 {

    private static Singleton_02 instance;

    private Singleton_02() {
    }

    //加锁,保证线程安全
    public static synchronized Singleton_02 getInstance(){
        if (null != instance) return instance;
        instance = new Singleton_02();
        return instance;
    }

}

饿汉模式

线程安全

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton_03 {

    private static Singleton_03 instance = new Singleton_03();

    private Singleton_03() {
    }

    public static Singleton_03 getInstance() {
        return instance;
    }

}

这种方式并不是懒加载,无论程序中是否用到,都会在程序启动时进行创建。

静态内部类

线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton_04 {

    private static class SingletonHolder {
        private static Singleton_04 instance = new Singleton_04();
    }

    private Singleton_04() {
    }

    public static Singleton_04 getInstance() {
        return SingletonHolder.instance;
    }

}

既保证了线程安全,又保证了懒汉模式,同时不会因为加锁而降低性能。

一个类的构造方法在多线程环境下可以被正确的加载

推荐使用

双重锁校验

线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton_05 {

    private static volatile Singleton_05 instance;

    private Singleton_05() {
    }

    public static Singleton_05 getInstance(){
       if(null != instance) return instance;
       synchronized (Singleton_05.class){
           if (null == instance){
               instance = new Singleton_05();
           }
       }
       return instance;
    }

}

方法级锁的优化,减少了获取实例的耗时,同时也满足了懒汉模式

CAS

线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Singleton_06 {

    private static final AtomicReference<Singleton_06> INSTANCE = new AtomicReference<Singleton_06>();

    private static Singleton_06 instance;

    private Singleton_06() {
    }

    public static final Singleton_06 getInstance() {
        for (; ; ) {
            Singleton_06 instance = INSTANCE.get();
            if (null != instance) return instance;
            INSTANCE.compareAndSet(null, new Singleton_06());
            return INSTANCE.get();
        }
    }

    public static void main(String[] args) {
        System.out.println(Singleton_06.getInstance()); // org.itstack.demo.design.Singleton_06@2b193f2d
        System.out.println(Singleton_06.getInstance()); // org.itstack.demo.design.Singleton_06@2b193f2d
    }

}

使用CAS的好处就是不需要使用传统的加锁方式,而是依赖CAS的自旋算法,底层硬件的实现保证线程安全

没有线程的切换和阻塞,可以支持较大的并发,但是缺点是忙等,若一直没获取到,会死循环

枚举单例

线程安全

1
2
3
4
5
6
7
8
public enum Singleton_07 {

    INSTANCE;
    public void test(){
        System.out.println("hi~");
    }

}

解决了最主要的线程安全,自由串行化和单一实例问题。

调用方式

1
2
3
4
@Test
public void test() {
    Singleton_07.INSTANCE.test();
}

在存在继承的场景下,此方式是不可用的

适配器模式

适配器模式的主要作用是把原本不兼容的接口通过适配修改做到统一,方便调用方使用

MQ消息体兼容场景

营销活动架构

场景模拟;接收多类型MQ消息

工程目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
itstack-demo-design-6-00
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── mq
                │   ├── create_account.java
                │   ├── OrderMq.java
                │   └── POPOrderDelivered.java
                └── service
                    ├── OrderServicejava
                    └── POPOrderService.java
                ├── impl
                │   ├── InsideOrderService.java
                │   └── POPOrderAdapterServiceImpl.java
                ├── MQAdapter,java
                ├── OrderAdapterService,java
                └── RebateInfo,java

注册开户MQ消息体

1
2
3
4
5
6
7
8
9
public class create_account {

    private String number;      // 开户编号
    private String address;     // 开户地
    private Date accountDate;   // 开户时间
    private String desc;        // 开户描述

    // ... get/set     
}

内部订单MQ消息体

1
2
3
4
5
6
7
8
9
public class OrderMq {

    private String uid;           // 用户ID
    private String sku;           // 商品
    private String orderId;       // 订单ID
    private Date createOrderTime; // 下单时间     

    // ... get/set      
}

第三方订单MQ消息体

1
2
3
4
5
6
7
8
9
10
11
public class POPOrderDelivered {

    private String uId;     // 用户ID
    private String orderId; // 订单号
    private Date orderTime; // 下单时间
    private Date sku;       // 商品
    private Date skuName;   // 商品名称
    private BigDecimal decimal; // 金额

    // ... get/set      
}

查询用户内部下单数量接口

1
2
3
4
5
6
7
8
9
10
public class OrderService {

    private Logger logger = LoggerFactory.getLogger(POPOrderService.class);

    public long queryUserOrderCount(String userId){
        logger.info("自营商家,查询用户的订单是否为首单:{}", userId);
        return 10L;
    }

}

查询用户第三方下单首单接口

1
2
3
4
5
6
7
8
9
10
public class POPOrderService {

    private Logger logger = LoggerFactory.getLogger(POPOrderService.class);

    public boolean isFirstOrder(String uId) {
        logger.info("POP商家,查询用户的订单是否为首单:{}", uId);
        return true;
    }

}

适配器关系

适配器模型结构

MQ适配,接收各种mq消息

MQ适配

必要属性类

1
2
3
4
5
6
7
8
9
public class RebateInfo {

    private String userId;  // 用户ID
    private String bizId;   // 业务ID
    private Date bizTime;   // 业务时间
    private String desc;    // 业务描述

    // ... get/set
}

MQ消息统一适配类,主要用于把不同类型的MQ中的各种属性映射成需要的属性并返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MQAdapter {

    public static RebateInfo filter(String strJson, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        return filter(JSON.parseObject(strJson, Map.class), link);
    }

    public static RebateInfo filter(Map obj, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        RebateInfo rebateInfo = new RebateInfo();
        for (String key : link.keySet()) {
            Object val = obj.get(link.get(key));
            RebateInfo.class.getMethod("set" + key.substring(0, 1).toUpperCase() + key.substring(1), String.class).invoke(rebateInfo, val.toString());
        }
        return rebateInfo;
    }

}

验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Test
public void test_MQAdapter() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    create_account create_account = new create_account();
    create_account.setNumber("100001");
    create_account.setAddress("河北省.廊坊市.广阳区.大学里职业技术学院");
    create_account.setAccountDate(new Date());
    create_account.setDesc("在校开户");          

    HashMap<String, String> link01 = new HashMap<String, String>();
    link01.put("userId", "number");
    link01.put("bizId", "number");
    link01.put("bizTime", "accountDate");
    link01.put("desc", "desc");
    RebateInfo rebateInfo01 = MQAdapter.filter(create_account.toString(), link01);
    System.out.println("mq.create_account(适配前)" + create_account.toString());
    System.out.println("mq.create_account(适配后)" + JSON.toJSONString(rebateInfo01));

    System.out.println("");

    OrderMq orderMq = new OrderMq();
    orderMq.setUid("100001");
    orderMq.setSku("10928092093111123");
    orderMq.setOrderId("100000890193847111");
    orderMq.setCreateOrderTime(new Date()); 

    HashMap<String, String> link02 = new HashMap<String, String>();
    link02.put("userId", "uid");
    link02.put("bizId", "orderId");
    link02.put("bizTime", "createOrderTime");
    RebateInfo rebateInfo02 = MQAdapter.filter(orderMq.toString(), link02);

    System.out.println("mq.orderMq(适配前)" + orderMq.toString());
    System.out.println("mq.orderMq(适配后)" + JSON.toJSONString(rebateInfo02));
}

接口适配

业务发展,只有首单用户才发放奖励,一个接口是查询用户的订单数量判断首单,一个接口直接查询是否首单,要做统一

定义统一适配接口

1
2
3
public interface OrderAdapterService {
    boolean isFirst(String uId);
}

内部商品接口实现

1
2
3
4
5
6
7
8
public class InsideOrderService implements OrderAdapterService {

    private OrderService orderService = new OrderService();

    public boolean isFirst(String uId) {
        return orderService.queryUserOrderCount(uId) <= 1;
    }
}

第三方商品接口实现

1
2
3
4
5
6
7
8
public class POPOrderAdapterServiceImpl implements OrderAdapterService {

    private POPOrderService popOrderService = new POPOrderService();

    public boolean isFirst(String uId) {
        return popOrderService.isFirstOrder(uId);
    }
}

验证

1
2
3
4
5
6
7
8
@Test
public void test_itfAdapter() {
    OrderAdapterService popOrderAdapterService = new POPOrderAdapterServiceImpl();
    System.out.println("判断首单,接口适配(POP):" + popOrderAdapterService.isFirst("100001"));   

    OrderAdapterService insideOrderService = new InsideOrderService();
    System.out.println("判断首单,接口适配(自营):" + insideOrderService.isFirst("100001"));
}

桥接模式

桥接模式的主要作用是通过将抽象部分与实现部分分离,将多种可匹配的使用进行组合

核心实现是在A类中含有B类接口,通过构造函数传递B类的实现,这个B类就是设计的桥,类似于蓝牙,可以连接多种智能家居

典型的应用场景是jdbc多种驱动程序的实现

多支付渠道场景

整合支付方式

场景模拟;多种支付和模式

工程结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
itstack-demo-design-7-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design.pay
    │           ├── channel
    │           │   ├── Pay.java
    │           │   ├── WxPay.java
    │           │   └── ZfbPay.java
    │           └── mode
    │               ├── IPayMode.java
    │               ├── PayCypher.java
    │               ├── PayFaceMode.java
    │               └── PayFingerprintMode.java
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

模型结构

image-20211125224626322

支付类型

抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class Pay {

    protected Logger logger = LoggerFactory.getLogger(Pay.class);

    protected IPayMode payMode;

    public Pay(IPayMode payMode) {
        this.payMode = payMode;
    }

    public abstract String transfer(String uId, String tradeId, BigDecimal amount);

}

微信支付

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class WxPay extends Pay {

    public WxPay(IPayMode payMode) {
        super(payMode);
    }

    public String transfer(String uId, String tradeId, BigDecimal amount) {
        logger.info("模拟微信渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        boolean security = payMode.security(uId);
        logger.info("模拟微信渠道支付风控校验。uId:{} tradeId:{} security:{}", uId, tradeId, security);
        if (!security) {
            logger.info("模拟微信渠道支付划账拦截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
            return "0001";
        }
        logger.info("模拟微信渠道支付划账成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        return "0000";
    }

}

支付宝支付

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ZfbPay extends Pay {

    public ZfbPay(IPayMode payMode) {
        super(payMode);
    }

    public String transfer(String uId, String tradeId, BigDecimal amount) {
        logger.info("模拟支付宝渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        boolean security = payMode.security(uId);
        logger.info("模拟支付宝渠道支付风控校验。uId:{} tradeId:{} security:{}", uId, tradeId, security);
        if (!security) {
            logger.info("模拟支付宝渠道支付划账拦截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
            return "0001";
        }
        logger.info("模拟支付宝渠道支付划账成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        return "0000";
    }

}

支付模式

风控接口

1
2
3
4
5
public interface IPayMode {

    boolean security(String uId);

}

刷脸

1
2
3
4
5
6
7
8
9
10
public class PayFaceMode implements IPayMode{

    protected Logger logger = LoggerFactory.getLogger(PayCypher.class);

    public boolean security(String uId) {
        logger.info("人脸支付,风控校验脸部识别");
        return true;
    }

}

指纹

1
2
3
4
5
6
7
8
9
10
public class PayFingerprintMode implements IPayMode{

    protected Logger logger = LoggerFactory.getLogger(PayCypher.class);

    public boolean security(String uId) {
        logger.info("指纹支付,风控校验指纹信息");
        return true;
    }

}

密码

1
2
3
4
5
6
7
8
9
10
public class PayCypher implements IPayMode{

    protected Logger logger = LoggerFactory.getLogger(PayCypher.class);

    public boolean security(String uId) {
        logger.info("密码支付,风控校验环境安全");
        return true;
    }

}

测试

1
2
3
4
5
6
7
8
9
10
@Test
public void test_pay() {
    System.out.println("\r\n模拟测试场景;微信支付、人脸方式。");
    Pay wxPay = new WxPay(new PayFaceMode());
    wxPay.transfer("weixin_1092033111", "100000109893", new BigDecimal(100));

    System.out.println("\r\n模拟测试场景;支付宝支付、指纹方式。");
    Pay zfbPay = new ZfbPay(new PayFingerprintMode());
    zfbPay.transfer("jlu19dlxo111","100000109894",new BigDecimal(100));
}

组合模式

组合模式类似于乐高玩具,通过一系列的连接组织出一颗结构树,这种通过把相似对象或方法组合成一组可被调用的结构树对象的设计思路,就是组合模式

营销决策树场景

根据性别和年龄的不同组合,发放不同类型优惠券

场景模式;营销决策树

为了让我们可以把不同类型的决策节点和最终的果实组装成一棵可被运行的决策树,需要做适配设计和工厂方法调用,具体会体现在定义接口以及抽象类和初始化配置决策节点(性别、年龄)上。

工程结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
itstack-demo-design-8-02
└── src
    ├── main
    │   └── java
    │      └── org.itstack.demo.design.domain
    │          ├── model
    │          │   ├── aggregates
    │          │   │   └── TreeRich.java
    │          │   └── vo
    │          │       ├── EngineResult.java
    │          │       ├── TreeNode.java
    │          │       ├── TreeNodeLink.java    
    │          │       └── TreeRoot.java    
    │          └── service
    │              ├── engine
    │              │   ├── impl    
    │              │   │   └── TreeEngineHandle.java       
    │              │   ├── EngineBase.java 
    │              │   ├── EngineConfig.java       
    │              │   └── IEngine.java    
    │              └── logic
    │                  ├── impl    
    │                  │   ├── LogicFilter.java     
    │                  │   └── LogicFilter.java        
    │                  └── LogicFilter.java    
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

结构模型

image-20211125230946014

基础对象

包路径介绍
model.aggregatesTreeRich聚合对象,包含组织树信息
model.voEngineResult决策返回对象信息
model.voTreeNode树节点;子叶节点、果实节点
model.voTreeNodeLink树节点链接链路
model.voTreeRoot树根信息

树节点逻辑过滤器接口

接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface LogicFilter {

    /**
     * 逻辑决策器
     *
     * @param matterValue          决策值
     * @param treeNodeLineInfoList 决策节点
     * @return 下一个节点Id
     */
    Long filter(String matterValue, List<TreeNodeLink> treeNodeLineInfoList);

    /**
     * 获取决策值
     *
     * @param decisionMatter 决策物料
     * @return 决策值
     */
    String matterValue(Long treeId, String userId, Map<String, String> decisionMatter);

}

决策抽象类

提供基础服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public abstract class BaseLogic implements LogicFilter {

    @Override
    public Long filter(String matterValue, List<TreeNodeLink> treeNodeLinkList) {
        for (TreeNodeLink nodeLine : treeNodeLinkList) {
            if (decisionLogic(matterValue, nodeLine)) return nodeLine.getNodeIdTo();
        }
        return 0L;
    }

    @Override
    public abstract String matterValue(Long treeId, String userId, Map<String, String> decisionMatter);

    private boolean decisionLogic(String matterValue, TreeNodeLink nodeLink) {
        switch (nodeLink.getRuleLimitType()) {
            case 1:
                return matterValue.equals(nodeLink.getRuleLimitValue());
            case 2:
                return Double.parseDouble(matterValue) > Double.parseDouble(nodeLink.getRuleLimitValue());
            case 3:
                return Double.parseDouble(matterValue) < Double.parseDouble(nodeLink.getRuleLimitValue());
            case 4:
                return Double.parseDouble(matterValue) <= Double.parseDouble(nodeLink.getRuleLimitValue());
            case 5:
                return Double.parseDouble(matterValue) >= Double.parseDouble(nodeLink.getRuleLimitValue());
            default:
                return false;
        }
    }

}

树节点逻辑实现

年龄节点

1
2
3
4
5
6
7
8
public class UserAgeFilter extends BaseLogic {

    @Override
    public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) {
        return decisionMatter.get("age");
    }

}

性别节点

1
2
3
4
5
6
7
8
public class UserGenderFilter extends BaseLogic {

    @Override
    public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) {
        return decisionMatter.get("gender");
    }

}

决策引擎接口

定义统一接口

1
2
3
4
5
public interface IEngine {

    EngineResult process(final Long treeId, final String userId, TreeRich treeRich, final Map<String, String> decisionMatter);

}

决策节点配置

可以配置到map中,可以抽取到数据库中,需要变更时,不用改代码,便于管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class EngineConfig {

    static Map<String, LogicFilter> logicFilterMap;

    static {
        logicFilterMap = new ConcurrentHashMap<>();
        logicFilterMap.put("userAge", new UserAgeFilter());
        logicFilterMap.put("userGender", new UserGenderFilter());
    }

    public Map<String, LogicFilter> getLogicFilterMap() {
        return logicFilterMap;
    }

    public void setLogicFilterMap(Map<String, LogicFilter> logicFilterMap) {
        this.logicFilterMap = logicFilterMap;
    }

}

基础决策引擎

主要提供决策树流程的处理过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public abstract class EngineBase extends EngineConfig implements IEngine {

    private Logger logger = LoggerFactory.getLogger(EngineBase.class);

    @Override
    public abstract EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter);

    protected TreeNode engineDecisionMaker(TreeRich treeRich, Long treeId, String userId, Map<String, String> decisionMatter) {
        TreeRoot treeRoot = treeRich.getTreeRoot();
        Map<Long, TreeNode> treeNodeMap = treeRich.getTreeNodeMap();
        // 规则树根ID
        Long rootNodeId = treeRoot.getTreeRootNodeId();
        TreeNode treeNodeInfo = treeNodeMap.get(rootNodeId);
        //节点类型[NodeType];1子叶、2果实
        while (treeNodeInfo.getNodeType().equals(1)) {
            String ruleKey = treeNodeInfo.getRuleKey();
            LogicFilter logicFilter = logicFilterMap.get(ruleKey);
            String matterValue = logicFilter.matterValue(treeId, userId, decisionMatter);
            Long nextNode = logicFilter.filter(matterValue, treeNodeInfo.getTreeNodeLinkList());
            treeNodeInfo = treeNodeMap.get(nextNode);
            logger.info("决策树引擎=>{} userId:{} treeId:{} treeNode:{} ruleKey:{} matterValue:{}", treeRoot.getTreeName(), userId, treeId, treeNodeInfo.getTreeNodeId(), ruleKey, matterValue);
        }
        return treeNodeInfo;
    }

}

决策引擎实现

做具体决策

1
2
3
4
5
6
7
8
9
10
11
public class TreeEngineHandle extends EngineBase {

    @Override
    public EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter) {
        // 决策流程
        TreeNode treeNode = engineDecisionMaker(treeRich, treeId, userId, decisionMatter);
        // 决策结果
        return new EngineResult(userId, treeId, treeNode.getTreeNodeId(), treeNode.getNodeValue());
    }

}

初始化决策树

动态化配置,可以提取到数据库中,也可以扩展到图形界面配置操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
@Before
public void init() {
    // 节点:1
    TreeNode treeNode_01 = new TreeNode();
    treeNode_01.setTreeId(10001L);
    treeNode_01.setTreeNodeId(1L);
    treeNode_01.setNodeType(1);
    treeNode_01.setNodeValue(null);
    treeNode_01.setRuleKey("userGender");
    treeNode_01.setRuleDesc("用户性别[男/女]");
    // 链接:1->11
    TreeNodeLink treeNodeLink_11 = new TreeNodeLink();
    treeNodeLink_11.setNodeIdFrom(1L);
    treeNodeLink_11.setNodeIdTo(11L);
    treeNodeLink_11.setRuleLimitType(1);
    treeNodeLink_11.setRuleLimitValue("man");
    // 链接:1->12
    TreeNodeLink treeNodeLink_12 = new TreeNodeLink();
    treeNodeLink_12.setNodeIdTo(1L);
    treeNodeLink_12.setNodeIdTo(12L);
    treeNodeLink_12.setRuleLimitType(1);
    treeNodeLink_12.setRuleLimitValue("woman");
    List<TreeNodeLink> treeNodeLinkList_1 = new ArrayList<>();
    treeNodeLinkList_1.add(treeNodeLink_11);
    treeNodeLinkList_1.add(treeNodeLink_12);
    treeNode_01.setTreeNodeLinkList(treeNodeLinkList_1);
    // 节点:11
    TreeNode treeNode_11 = new TreeNode();
    treeNode_11.setTreeId(10001L);
    treeNode_11.setTreeNodeId(11L);
    treeNode_11.setNodeType(1);
    treeNode_11.setNodeValue(null);
    treeNode_11.setRuleKey("userAge");
    treeNode_11.setRuleDesc("用户年龄");
    // 链接:11->111
    TreeNodeLink treeNodeLink_111 = new TreeNodeLink();
    treeNodeLink_111.setNodeIdFrom(11L);
    treeNodeLink_111.setNodeIdTo(111L);
    treeNodeLink_111.setRuleLimitType(3);
    treeNodeLink_111.setRuleLimitValue("25");
    // 链接:11->112
    TreeNodeLink treeNodeLink_112 = new TreeNodeLink();
    treeNodeLink_112.setNodeIdFrom(11L);
    treeNodeLink_112.setNodeIdTo(112L);
    treeNodeLink_112.setRuleLimitType(5);
    treeNodeLink_112.setRuleLimitValue("25");
    List<TreeNodeLink> treeNodeLinkList_11 = new ArrayList<>();
    treeNodeLinkList_11.add(treeNodeLink_111);
    treeNodeLinkList_11.add(treeNodeLink_112);
    treeNode_11.setTreeNodeLinkList(treeNodeLinkList_11);
    // 节点:12
    TreeNode treeNode_12 = new TreeNode();
    treeNode_12.setTreeId(10001L);
    treeNode_12.setTreeNodeId(12L);
    treeNode_12.setNodeType(1);
    treeNode_12.setNodeValue(null);
    treeNode_12.setRuleKey("userAge");
    treeNode_12.setRuleDesc("用户年龄");
    // 链接:12->121
    TreeNodeLink treeNodeLink_121 = new TreeNodeLink();
    treeNodeLink_121.setNodeIdFrom(12L);
    treeNodeLink_121.setNodeIdTo(121L);
    treeNodeLink_121.setRuleLimitType(3);
    treeNodeLink_121.setRuleLimitValue("25");
    // 链接:12->122
    TreeNodeLink treeNodeLink_122 = new TreeNodeLink();
    treeNodeLink_122.setNodeIdFrom(12L);
    treeNodeLink_122.setNodeIdTo(122L);
    treeNodeLink_122.setRuleLimitType(5);
    treeNodeLink_122.setRuleLimitValue("25");
    List<TreeNodeLink> treeNodeLinkList_12 = new ArrayList<>();
    treeNodeLinkList_12.add(treeNodeLink_121);
    treeNodeLinkList_12.add(treeNodeLink_122);
    treeNode_12.setTreeNodeLinkList(treeNodeLinkList_12);
    // 节点:111
    TreeNode treeNode_111 = new TreeNode();
    treeNode_111.setTreeId(10001L);
    treeNode_111.setTreeNodeId(111L);
    treeNode_111.setNodeType(2);
    treeNode_111.setNodeValue("果实A");
    // 节点:112
    TreeNode treeNode_112 = new TreeNode();
    treeNode_112.setTreeId(10001L);
    treeNode_112.setTreeNodeId(112L);
    treeNode_112.setNodeType(2);
    treeNode_112.setNodeValue("果实B");
    // 节点:121
    TreeNode treeNode_121 = new TreeNode();
    treeNode_121.setTreeId(10001L);
    treeNode_121.setTreeNodeId(121L);
    treeNode_121.setNodeType(2);
    treeNode_121.setNodeValue("果实C");
    // 节点:122
    TreeNode treeNode_122 = new TreeNode();
    treeNode_122.setTreeId(10001L);
    treeNode_122.setTreeNodeId(122L);
    treeNode_122.setNodeType(2);
    treeNode_122.setNodeValue("果实D");
    // 树根
    TreeRoot treeRoot = new TreeRoot();
    treeRoot.setTreeId(10001L);
    treeRoot.setTreeRootNodeId(1L);
    treeRoot.setTreeName("规则决策树");
    Map<Long, TreeNode> treeNodeMap = new HashMap<>();
    treeNodeMap.put(1L, treeNode_01);
    treeNodeMap.put(11L, treeNode_11);
    treeNodeMap.put(12L, treeNode_12);
    treeNodeMap.put(111L, treeNode_111);
    treeNodeMap.put(112L, treeNode_112);
    treeNodeMap.put(121L, treeNode_121);
    treeNodeMap.put(122L, treeNode_122);
    treeRich = new TreeRich(treeRoot, treeNodeMap);
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test_tree() {
    logger.info("决策树组合结构信息:\r\n" + JSON.toJSONString(treeRich));

    IEngine treeEngineHandle = new TreeEngineHandle();
    Map<String, String> decisionMatter = new HashMap<>();
    decisionMatter.put("gender", "man");
    decisionMatter.put("age", "29");

    EngineResult result = treeEngineHandle.process(10001L, "Oli09pLkdjh", treeRich, decisionMatter);

    logger.info("测试结果:{}", JSON.toJSONString(result));
}

装饰器模式

装饰器的核心就是在不改变原有类的基础上给类新增功能。可以避免继承导致的子类过多,也可以避免AOP带来的复杂性。

SSO单点登录场景

场景模拟

场景模拟;单点登录功能扩展

工程结构

1
2
3
4
5
6
7
8
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── HandlerInterceptor.java
                └── SsoInterceptor.java
                ├── LoginSsoDecorator.java
                └── SsoDecorator.java

这里模拟的是spring中的类:HandlerInterceptor,实现接口功能SsoInterceptor模拟的单点登录拦截服务

实际的单点登录开发会基于;org.springframework.web.servlet.HandlerInterceptor 实现。

模拟

1
2
3
4
5
public interface HandlerInterceptor {

    boolean preHandle(String request, String response, Object handler);

}

这里的模拟实现非常简单只是截取字符串,实际使用需要从HttpServletRequest request对象中获取cookie信息,解析ticket值做校验,这里返回success标识成功

1
2
3
4
5
6
7
8
9
public class SsoInterceptor implements HandlerInterceptor{

    public boolean preHandle(String request, String response, Object handler) {
        // 模拟获取cookie
        String ticket = request.substring(1, 8);
        // 模拟校验
        return ticket.equals("success");
    }
}

在装饰器模式中有四个抽象点

1:抽象构建角色(component)定义抽象接口

2:具体构建角色(ConcreteComponent)实现抽象接口,可以是一组

3:装饰角色(Decorator)定义抽象类并继承接口中的方法,保证唯一性

4:集体装饰角色(ConcreteDecorator)扩展装饰具体的实现逻辑

模型结构

装饰器模式模型结构

抽象类装饰角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class SsoDecorator implements HandlerInterceptor {

    private HandlerInterceptor handlerInterceptor;

    private SsoDecorator(){}

    public SsoDecorator(HandlerInterceptor handlerInterceptor) {
        this.handlerInterceptor = handlerInterceptor;
    }

    public boolean preHandle(String request, String response, Object handler) {
        return handlerInterceptor.preHandle(request, response, handler);
    }

}

装饰角色逻辑实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class LoginSsoDecorator extends SsoDecorator {

    private Logger logger = LoggerFactory.getLogger(LoginSsoDecorator.class);

    private static Map<String, String> authMap = new ConcurrentHashMap<String, String>();

    static {
        authMap.put("huahua", "queryUserInfo");
        authMap.put("doudou", "queryUserInfo");
    }

    public LoginSsoDecorator(HandlerInterceptor handlerInterceptor) {
        super(handlerInterceptor);
    }

    @Override
    public boolean preHandle(String request, String response, Object handler) {
        boolean success = super.preHandle(request, response, handler);
        if (!success) return false;
        String userId = request.substring(8);
        String method = authMap.get(userId);
        logger.info("模拟单点登录方法访问拦截校验:{} {}", userId, method);
        // 模拟方法校验
        return "queryUserInfo".equals(method);
    }
}

测试

这里测试了对装饰器模式的使用,通过透传原有单点登录类new SsoInterceptor(),传递给装饰器,让装饰器可以执行扩充的功能

同时对于传递者和装饰器都可以是多组的,在一些实际的业务开发中,往往也是由于太多类型的子类实现而导致不易于维护,从而使用装饰器模式替代

1
2
3
4
5
6
7
@Test
public void test_LoginSsoDecorator() {
    LoginSsoDecorator ssoDecorator = new LoginSsoDecorator(new SsoInterceptor());
    String request = "1successhuahua";
    boolean success = ssoDecorator.preHandle(request, "ewcdqwt40liuiu", "t");
    System.out.println("登录校验:" + request + (success ? " 放行" : " 拦截"));
}

外观模式

主要解决的是降低调用方的使用接口的复杂逻辑组合。这样调用方与实际的接口提供方提供了一个中间层,用于包装逻辑提供API接口。

统一控制接口白名单场景

在项目不断壮大发展的路上,每一次发版上线都需要进行测试,而这部分测试验证一般会进行白名单开量或者切量的方式进行验证。那么如果在每一个接口中都添加这样的逻辑,就会非常麻烦且不易维护。另外这是一类具备通用逻辑的共性需求,非常适合开发成组件,以此来治理服务,让研发人员更多的关心业务功能开发。

一般情况下对于外观模式的使用通常是用在复杂或多个接口进行包装统一对外提供服务上,此种使用方式也相对简单在我们平常的业务开发中也是最常用的。你可能经常听到把这两个接口包装一下,但在本例子中我们把这种设计思路放到中间件层,让服务变得可以统一控制。

场景模拟;所有服务添加白名单校验

工程目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
itstack-demo-design-10-00
└── src
    ├── main
    │   ├── java
    │   │   └── org.itstack.demo.design
    │   │       ├── domain
    │   │       │    └── UserInfo.java
    │   │       ├── web    
    │   │       │    └── HelloWorldController.java
    │   │       └── HelloWorldApplication.java
            └── org.itstack.demo.design.door
    │   │       ├── annotation
    │   │       │    └── DoDoor.java    
    │   │       ├── config
    │   │       │    ├── StarterAutoConfigure.java
    │   │       │    ├── StarterService.java
    │   │       │    └── StarterServiceProperties.java
    │   │       └── DoJoinPoint.java
    │   └── resources    
            └── META_INF
               └── spring.factories
    │       └── application.yml    
    └── test
        └── java
            └── org.itstack.demo.test
                └── ApiTest.java

基础查询接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class HelloWorldController {

    @Value("${server.port}")
    private int port;

    /**
     * key:需要从入参取值的属性字段,如果是对象则从对象中取值,如果是单个值则直接使用
     * returnJson:预设拦截时返回值,是返回对象的Json
     *
     * http://localhost:8080/api/queryUserInfo?userId=1001
     * http://localhost:8080/api/queryUserInfo?userId=小团团
     */
    @RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET)
    public UserInfo queryUserInfo(@RequestParam String userId) {
        return new UserInfo("虫虫:" + userId, 19, "天津市南开区旮旯胡同100号");
    }

}

启动类

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@Configuration
public class HelloWorldApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloWorldApplication.class, args);
    }

}

结构模型

门面模式模型结构

配置服务类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class StarterService {

    private String userStr;

    public StarterService(String userStr) {
        this.userStr = userStr;
    }

    public String[] split(String separatorChar) {
        return StringUtils.split(this.userStr, separatorChar);
    }

}

配置类注解定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ConfigurationProperties("itstack.door")
public class StarterServiceProperties {

    private String userStr;

    public String getUserStr() {
        return userStr;
    }

    public void setUserStr(String userStr) {
        this.userStr = userStr;
    }

}

配置类信息读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
@ConditionalOnClass(StarterService.class)
@EnableConfigurationProperties(StarterServiceProperties.class)
public class StarterAutoConfigure {

    @Autowired
    private StarterServiceProperties properties;

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "itstack.door", value = "enabled", havingValue = "true")
    StarterService starterService() {
        return new StarterService(properties.getUserStr());
    }

}

切面注解定义

1
2
3
4
5
6
7
8
9
10
11
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoDoor {

    //获取某个字段例如用户ID
    String key() default "";

    //确定白名单拦截后返回的具体内容
    String returnJson() default "";

}

切面逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@Aspect
@Component
public class DoJoinPoint {

    private Logger logger = LoggerFactory.getLogger(DoJoinPoint.class);

    @Autowired
    private StarterService starterService;

    @Pointcut("@annotation(org.itstack.demo.design.door.annotation.DoDoor)")
    public void aopPoint() {
    }

    @Around("aopPoint()")
    public Object doRouter(ProceedingJoinPoint jp) throws Throwable {
        //获取内容
        Method method = getMethod(jp);
        DoDoor door = method.getAnnotation(DoDoor.class);
        //获取字段值
        String keyValue = getFiledValue(door.key(), jp.getArgs());
        logger.info("itstack door handler method:{} value:{}", method.getName(), keyValue);
        if (null == keyValue || "".equals(keyValue)) return jp.proceed();
        //配置内容
        String[] split = starterService.split(",");
        //白名单过滤
        for (String str : split) {
            if (keyValue.equals(str)) {
                return jp.proceed();
            }
        }
        //拦截
        return returnObject(door, method);
    }

    private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        return getClass(jp).getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
    }

    private Class<? extends Object> getClass(JoinPoint jp) throws NoSuchMethodException {
        return jp.getTarget().getClass();
    }

    //返回对象
    private Object returnObject(DoDoor doGate, Method method) throws IllegalAccessException, InstantiationException {
        Class<?> returnType = method.getReturnType();
        String returnJson = doGate.returnJson();
        if ("".equals(returnJson)) {
            return returnType.newInstance();
        }
        return JSON.parseObject(returnJson, returnType);
    }

    //获取属性值
    private String getFiledValue(String filed, Object[] args) {
        String filedValue = null;
        for (Object arg : args) {
            try {
                if (null == filedValue || "".equals(filedValue)) {
                    filedValue = BeanUtils.getProperty(arg, filed);
                } else {
                    break;
                }
            } catch (Exception e) {
                if (args.length == 1) {
                    return args[0].toString();
                }
            }
        }
        return filedValue;
    }

}

测试

yml

1
2
3
4
itstack: # 自定义中间件配置
  door:
    enabled: true
    userStr: 1001,aaaa,ccc #白名单用户ID,多个逗号隔开

入口

1
2
3
4
5
6
7
8
 //http://localhost:8080/api/queryUserInfo?userId=1001
 //http://localhost:8080/api/queryUserInfo?userId=小团团

@DoDoor(key = "userId", returnJson = "{\"code\":\"1111\",\"info\":\"非白名单可访问用户拦截!\"}")
@RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET)
public UserInfo queryUserInfo(@RequestParam String userId) {
    return new UserInfo("虫虫:" + userId, 19, "天津市南开区旮旯胡同100号");
}

享元模式

享元模式,主要在于共享通用对象,减少内存的使用,提升系统的访问效率。而这部分共享对象通常比较耗费内存或者需要查询大量接口或者使用数据库资源,因此统一抽离作为共享对象使用。

这个设计的模式思想是减少内存的使用提升效率,与我们之前使用的原型模式通过克隆对象的方式生成复杂对象,减少rpc的调用,都是此类思想。

Redis商品秒杀场景

模拟使用享元模式工厂结构,提供活动商品的查询。活动商品相当于不变的信息,而库存部分属于变化的信息。

工程目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
itstack-demo-design-11-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── util
    │           │    └── RedisUtils.java    
    │           ├── Activity.java
    │           ├── ActivityController.java
    │           ├── ActivityFactory.java
    │           └── Stock.java
    └── test
        └── java
            └── org.itstack.demo.test
                └── ApiTest.java

模型结构

享元模式模型结构

信息类

活动信息

1
2
3
4
5
6
7
8
9
10
11
public class Activity {

    private Long id;        // 活动ID
    private String name;    // 活动名称
    private String desc;    // 活动描述
    private Date startTime; // 开始时间
    private Date stopTime;  // 结束时间
    private Stock stock;    // 活动库存

    // ...get/set
}

库存信息

1
2
3
4
5
6
7
public class Stock {

    private int total; // 库存总量
    private int used;  // 库存已用

    // ...get/set
}

享元工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ActivityFactory {

    static Map<Long, Activity> activityMap = new HashMap<Long, Activity>();

    public static Activity getActivity(Long id) {
        Activity activity = activityMap.get(id);
        if (null == activity) {
            // 模拟从实际业务应用从接口中获取活动信息
            activity = new Activity();
            activity.setId(10001L);
            activity.setName("图书嗨乐");
            activity.setDesc("图书优惠券分享激励分享活动第二期");
            activity.setStartTime(new Date());
            activity.setStopTime(new Date());
            activityMap.put(id, activity);
        }
        return activity;
    }

}

模拟库存消耗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class RedisUtils {

    private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

    private AtomicInteger stock = new AtomicInteger(0);

    public RedisUtils() {
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            // 模拟库存消耗
            stock.addAndGet(1);
        }, 0, 100000, TimeUnit.MICROSECONDS);

    }

    public int getStockUsed() {
        return stock.get();
    }

}

活动控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ActivityController {

    private RedisUtils redisUtils = new RedisUtils();

    public Activity queryActivityInfo(Long id) {
        Activity activity = ActivityFactory.getActivity(id);
        // 模拟从Redis中获取库存变化信息
        Stock stock = new Stock(1000, redisUtils.getStockUsed());
        activity.setStock(stock);
        return activity;
    }

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    private ActivityController activityController = new ActivityController();

    @Test
    public void test_queryActivityInfo() throws InterruptedException {
        for (int idx = 0; idx < 10; idx++) {
            Long req = 10001L;
            Activity activity = activityController.queryActivityInfo(req);
            logger.info("测试结果:{} {}", req, JSON.toJSONString(activity));
            Thread.sleep(1200);
        }
    }

}

代理模式

主要解决的是为某些资源的访问、对象的类的易用操作上提供方便使用的代理服务。

mybatis接口代理场景

对于Mybatis的使用中只需要定义接口不需要写实现类就可以完成增删改查操作

  1. BeanDefinitionRegistryPostProcessor,spring的接口类用于处理对bean的定义注册。
  2. GenericBeanDefinition,定义bean的信息,在mybatis-spring中使用到的是;ScannedGenericBeanDefinition 略有不同。
  3. FactoryBean,用于处理bean工厂的类,这个类非常见。

工程目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
itstack-demo-design-12-00
└── src
    ├── main
    │   ├── java
    │   │   └── org.itstack.demo.design
    │   │       ├── agent
    │   │       │    ├── MapperFactoryBean.java
    │   │       │    ├── RegisterBeanFactory.java
    │   │       │    └── Select.java
    │   │       └── IUserDao.java
    │   └── resources    
    │       └── spring-config.xml
    └── test
        └── java
            └── org.itstack.demo.test
                └── ApiTest.java

模型结构

代理模式中间件模型结构

自定义注解

1
2
3
4
5
6
7
8
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Select {

    String value() default "";  // sql语句

}

接口层

1
2
3
4
5
6
public interface IUserDao {

    @Select("select userName from user where id = #{uId}")
    String queryUserInfo(String uId);

}

代理类定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MapperFactoryBean<T> implements FactoryBean<T> {

    private Logger logger = LoggerFactory.getLogger(MapperFactoryBean.class);

    private Class<T> mapperInterface;

    public MapperFactoryBean(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @Override
    public T getObject() throws Exception {
        InvocationHandler handler = (proxy, method, args) -> {
            Select select = method.getAnnotation(Select.class);
            logger.info("SQL:{}", select.value().replace("#{uId}", args[0].toString()));
            return args[0] + ",小傅哥,bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!";
        };
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperInterface}, handler);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

}
  • 如果你有阅读过mybatis源码,是可以看到这样的一个类;MapperFactoryBean,这里我们也模拟一个这样的类,在里面实现我们对代理类的定义。
  • 通过继承FactoryBean,提供bean对象,也就是方法;T getObject()
  • 在方法getObject()中提供类的代理以及模拟对sql语句的处理,这里包含了用户调用dao层方法时候的处理逻辑。
  • 还有最上面我们提供构造函数来透传需要被代理类,Class<T> mapperInterface,在mybatis中也是使用这样的方式进行透传。
  • 另外getObjectType()提供对象类型反馈,以及isSingleton()返回类是单例的。

注册到容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(MapperFactoryBean.class);
        beanDefinition.setScope("singleton");
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(IUserDao.class);

        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDao");
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        // left intentionally blank
    }

}
  • 这里我们将代理的bean交给spring容器管理,也就可以非常方便让我们可以获取到代理的bean。这部分是spring中关于一个bean注册过程的源码。
  • GenericBeanDefinition,用于定义一个bean的基本信息setBeanClass(MapperFactoryBean.class);,也包括可以透传给构造函数信息addGenericArgumentValue(IUserDao.class);
  • 最后使用 BeanDefinitionReaderUtils.registerBeanDefinition,进行bean的注册,也就是注册到DefaultListableBeanFactory中。

测试

配置文件

1
<bean id="userDao" class="org.itstack.demo.design.agent.RegisterBeanFactory"/>

在mybatis的使用中一般会配置扫描的dao层包,这样就可以减少这部分的配置

测试类

1
2
3
4
5
6
7
@Test
public void test_IUserDao() {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
    IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);
    String res = userDao.queryUserInfo("100001");
    logger.info("测试结果:{}", res);
}

责任链模式

责任链模式的核心是解决一组服务中的先后执行处理关系,你可以理解想象成当你要跳槽的时候被安排的明明白白的被各个领导签字放行。

上线流程多级审批场景

流程图

image-20211202221621794

模拟审核业务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AuthService {

    private static Map<String, Date> authMap = new ConcurrentHashMap<String, Date>();

    //查询审核结果
    public static Date queryAuthInfo(String uId, String orderId) {
        return authMap.get(uId.concat(orderId));
    }

    //处理审核
    public static void auth(String uId, String orderId) {
        authMap.put(uId.concat(orderId), new Date());
    }

}

责任链模式可以让各个服务模块更加清晰,而每一个模块间可以通过next的方式进行获取。而每一个next是由继承的统一抽象类实现的。最终所有类的职责可以动态的进行编排使用,编排的过程可以做成可配置化。

模型结构

责任链模式模型结构

责任链结果对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AuthInfo {

    private String code;
    private String info = "";

    public AuthInfo(String code, String ...infos) {
        this.code = code;
        for (String str:infos){
            this.info = this.info.concat(str);
        }
    }

    // ...get/set
}

链路抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public abstract class AuthLink {

    protected Logger logger = LoggerFactory.getLogger(AuthLink.class);

    protected SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 时间格式化
    protected String levelUserId;                           // 级别人员ID
    protected String levelUserName;                         // 级别人员姓名
    private AuthLink next;                                  // 责任链

    public AuthLink(String levelUserId, String levelUserName) {
        this.levelUserId = levelUserId;
        this.levelUserName = levelUserName;
    }

    public AuthLink next() {
        return next;
    }

    public AuthLink appendNext(AuthLink next) {
        this.next = next;
        return this;
    }

    public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);

}

审核实现类

一级审核

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Level1AuthLink extends AuthLink {

    public Level1AuthLink(String levelUserId, String levelUserName) {
        super(levelUserId, levelUserName);
    }

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "单号:", orderId, " 状态:待一级审批负责人 ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:一级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }

}

二级审核

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Level2AuthLink extends AuthLink {

    private Date beginDate = f.parse("2020-06-11 00:00:00");
    private Date endDate = f.parse("2020-06-20 23:59:59");

    public Level2AuthLink(String levelUserId, String levelUserName) throws ParseException {
        super(levelUserId, levelUserName);
    }

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "单号:", orderId, " 状态:待二级审批负责人 ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        if (authDate.before(beginDate) || authDate.after(endDate)) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }

}

三级审核

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Level3AuthLink extends AuthLink {

    private Date beginDate = f.parse("2020-06-01 00:00:00");
    private Date endDate = f.parse("2020-06-25 23:59:59");

    public Level3AuthLink(String levelUserId, String levelUserName) throws ParseException {
        super(levelUserId, levelUserName);
    }

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "单号:", orderId, " 状态:待三级审批负责人 ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批负责人完成", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        if (authDate.before(beginDate) || authDate.after(endDate)) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批负责人完成", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void test_AuthLink() throws ParseException {
    AuthLink authLink = new Level3AuthLink("1000013", "王工")
            .appendNext(new Level2AuthLink("1000012", "张经理")
                    .appendNext(new Level1AuthLink("1000011", "段总")));

    logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));

    // 模拟三级负责人审批
    AuthService.auth("1000013", "1000998004813441");
    logger.info("测试结果:{}", "模拟三级负责人审批,王工");
    logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));

    // 模拟二级负责人审批
    AuthService.auth("1000012", "1000998004813441");
    logger.info("测试结果:{}", "模拟二级负责人审批,张经理");
    logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));

    // 模拟一级负责人审批
    AuthService.auth("1000011", "1000998004813441");
    logger.info("测试结果:{}", "模拟一级负责人审批,段总");
    logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));
}

命令模式

命令模式是行为模式中的一种,以数据驱动的方式将命令对象,可以使用构造函数的方式传递给调用者。调用者再提供相应的实现为命令执行提供操作方法。

在这个设计模式的实现过程中有如下几个比较重要的点;

  1. 抽象命令类;声明执行命令的接口和方法
  2. 具体的命令实现类;接口类的具体实现,可以是一组相似的行为逻辑
  3. 实现者;也就是为命令做实现的具体实现类
  4. 调用者;处理命令、实现的具体操作者,负责对外提供命令服务

点餐场景

命令场景的核心的逻辑是调用方与不需要去关心具体的逻辑实现,在这个场景中也就是点餐人员只需要把需要点的各种菜系交个小二就可以,小二再把各项菜品交给各个厨师进行烹饪。也就是点餐人员不需要跟各个厨师交流,只需要在统一的环境里下达命令就可以。

命令模式可以将上述的模式拆解三层大块,命令、命令实现者、命令的调用者,当有新的菜品或者厨师扩充时候就可以在指定的类结构下进行实现添加即可,外部的调用也会非常的容易扩展。

工程目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
itstack-demo-design-14-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── cook
    │           │    ├── impl
    │           │    │   ├── GuangDongCook.java
    │           │    │   ├── JiangSuCook.java
    │           │    │   ├── ShanDongCook.java
    │           │    │   └── SiChuanCook.java
    │           │    └── ICook.java
    │           ├── cuisine
    │           │    ├── impl
    │           │    │   ├── GuangDoneCuisine.java
    │           │    │   ├── JiangSuCuisine.java
    │           │    │   ├── ShanDongCuisine.java
    │           │    │   └── SiChuanCuisine.java
    │           │    └── ICuisine.java
    │           └── XiaoEr.java
    └── test
        └── java
            └── org.itstack.demo.test
                └── ApiTest.java

模型结构

命令模式模型结构

抽象命令定义

菜系

1
2
3
4
5
public interface ICuisine {

    void cook(); // 烹调、制作

}

命令实现

广东粤菜

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class GuangDoneCuisine implements ICuisine {

    //
    private ICook cook;

    public GuangDoneCuisine(ICook cook) {
        this.cook = cook;
    }

    public void cook() {
        cook.doCooking();
    }

}

其他菜系类似

抽象实现者定义

1
2
3
4
5
6
//厨师
public interface ICook {

    void doCooking();

}

实现者实现

1
2
3
4
5
6
7
8
9
public class GuangDongCook implements ICook {

    private Logger logger = LoggerFactory.getLogger(ICook.class);

    public void doCooking() {
        logger.info("广东厨师,烹饪鲁菜,宫廷最大菜系,以孔府风味为龙头");
    }

}

调用者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//小二
public class XiaoEr {

    private Logger logger = LoggerFactory.getLogger(XiaoEr.class);

    private List<ICuisine> cuisineList = new ArrayList<ICuisine>();

    public void order(ICuisine cuisine) {
        cuisineList.add(cuisine);
    }

    public synchronized void placeOrder() {
        for (ICuisine cuisine : cuisineList) {
            cuisine.cook();
        }
        cuisineList.clear();
    }

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void test(){

    // 菜系 + 厨师;广东(粤菜)、江苏(苏菜)、山东(鲁菜)、四川(川菜)
    ICuisine guangDoneCuisine = new GuangDoneCuisine(new GuangDongCook());
    JiangSuCuisine jiangSuCuisine = new JiangSuCuisine(new JiangSuCook());
    ShanDongCuisine shanDongCuisine = new ShanDongCuisine(new ShanDongCook());
    SiChuanCuisine siChuanCuisine = new SiChuanCuisine(new SiChuanCook());

    // 点单
    XiaoEr xiaoEr = new XiaoEr();
    xiaoEr.order(guangDoneCuisine);
    xiaoEr.order(jiangSuCuisine);
    xiaoEr.order(shanDongCuisine);
    xiaoEr.order(siChuanCuisine);

    // 下单
    xiaoEr.placeOrder();
}

迭代器模式

迭代器模式的特点是实现Iterable接口,通过next的方式获取集合元素,同时具备对元素的删除等操作。而增强的for循环是不可以的。

这种设计模式的优点是可以让我们以相同的方式,遍历不同的数据结构元素,这些数据结构包括;数组链表等,而用户在使用遍历的时候并不需要去关心每一种数据结构的遍历处理逻辑,从让使用变得统一易用。

迭代遍历公司组织架构树场景

场景图

场景模拟;公司树形组织架构

先阅读下javalist方法关于iterator的实现部分,几乎所有的迭代器开发都会按照这个模式来实现,这个模式主要分为以下几块;

  1. Collection,集合方法部分用于对自定义的数据结构添加通用方法;addremoveiterator等核心方法。
  2. Iterable,提供获取迭代器,这个接口类会被Collection继承。
  3. Iterator,提供了两个方法的定义;hasNextnext,会在具体的数据结构中写实现方式

模型结构

迭代器模式模型结构

实体类

1
2
3
4
5
6
7
8
9
//员工
public class Employee {

    private String uId;   // ID
    private String name;  // 姓名
    private String desc;  // 备注

    // ...get/set
}

树节点链路

1
2
3
4
5
6
7
public class Link {

    private String fromId; // 雇员ID
    private String toId;   // 雇员ID    

    // ...get/set
}

迭代器定义

1
2
3
4
5
6
7
8
9
public interface Iterator<E> {

    //是否有下一个元素
    boolean hasNext();

    //指向下一个元素
    E next();

}

可迭代接口

1
2
3
4
5
public interface Iterable<E> {

    Iterator<E> iterator();

}

集合功能接口

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Collection<E, L> extends Iterable<E> {

    boolean add(E e);

    boolean remove(E e);

    boolean addLink(String key, L l);

    boolean removeLink(String key);

    Iterator<E> iterator();

}

迭代器功能实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
public class GroupStructure implements Collection<Employee, Link> {

    // 组织ID,也是一个组织链的头部ID
    private String groupId;
    // 组织名称
    private String groupName;
    // 雇员列表
    private Map<String, Employee> employeeMap = new ConcurrentHashMap<String, Employee>();
    // 组织架构关系;id->list
    private Map<String, List<Link>> linkMap = new ConcurrentHashMap<String, List<Link>>();
    // 反向关系链
    private Map<String, String> invertedMap = new ConcurrentHashMap<String, String>();       

    public GroupStructure(String groupId, String groupName) {
        this.groupId = groupId;
        this.groupName = groupName;
    }

    public boolean add(Employee employee) {
        return null != employeeMap.put(employee.getuId(), employee);
    }

    public boolean remove(Employee o) {
        return null != employeeMap.remove(o.getuId());
    }

    public boolean addLink(String key, Link link) {
        invertedMap.put(link.getToId(), link.getFromId());
        if (linkMap.containsKey(key)) {
            return linkMap.get(key).add(link);
        } else {
            List<Link> links = new LinkedList<Link>();
            links.add(link);
            linkMap.put(key, links);
            return true;
        }
    }

    public boolean removeLink(String key) {
        return null != linkMap.remove(key);
    }

    public Iterator<Employee> iterator() {

        return new Iterator<Employee>() {

            HashMap<String, Integer> keyMap = new HashMap<String, Integer>();

            int totalIdx = 0;
            private String fromId = groupId;  // 雇员ID,From
            private String toId = groupId;   // 雇员ID,To

            public boolean hasNext() {
                return totalIdx < employeeMap.size();
            }

            public Employee next() {
                List<Link> links = linkMap.get(toId);
                int cursorIdx = getCursorIdx(toId);

                // 同级节点扫描
                if (null == links) {
                    cursorIdx = getCursorIdx(fromId);
                    links = linkMap.get(fromId);
                }

                // 上级节点扫描
                while (cursorIdx > links.size() - 1) {
                    fromId = invertedMap.get(fromId);
                    cursorIdx = getCursorIdx(fromId);
                    links = linkMap.get(fromId);
                }

                // 获取节点
                Link link = links.get(cursorIdx);
                toId = link.getToId();
                fromId = link.getFromId();
                totalIdx++;

                // 返回结果
                return employeeMap.get(link.getToId());
            }

            // 给每个层级定义宽度遍历进度
            public int getCursorIdx(String key) {
                int idx = 0;
                if (keyMap.containsKey(key)) {
                    idx = keyMap.get(key);
                    keyMap.put(key, ++idx);
                } else {
                    keyMap.put(key, idx);
                }
                return idx;
            }
        };
    }

}

迭代器实现思路

  1. 这里的树形结构我们需要做的是深度遍历,也就是左侧的一直遍历到最深节点。
  2. 当遍历到最深节点后,开始遍历最深节点的横向节点。
  3. 当横向节点遍历完成后则向上寻找横向节点,直至树结构全部遍历完成。

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Test
public void test_iterator() { 
    // 数据填充
    GroupStructure groupStructure = new GroupStructure("1", "小傅哥");  

    // 雇员信息
    groupStructure.add(new Employee("2", "花花", "二级部门"));
    groupStructure.add(new Employee("3", "豆包", "二级部门"));
    groupStructure.add(new Employee("4", "蹦蹦", "三级部门"));
    groupStructure.add(new Employee("5", "大烧", "三级部门"));
    groupStructure.add(new Employee("6", "虎哥", "四级部门"));
    groupStructure.add(new Employee("7", "玲姐", "四级部门"));
    groupStructure.add(new Employee("8", "秋雅", "四级部门"));   

    // 节点关系 1->(1,2) 2->(4,5)
    groupStructure.addLink("1", new Link("1", "2"));
    groupStructure.addLink("1", new Link("1", "3"));
    groupStructure.addLink("2", new Link("2", "4"));
    groupStructure.addLink("2", new Link("2", "5"));
    groupStructure.addLink("5", new Link("5", "6"));
    groupStructure.addLink("5", new Link("5", "7"));
    groupStructure.addLink("5", new Link("5", "8"));       

    Iterator<Employee> iterator = groupStructure.iterator();
    while (iterator.hasNext()) {
        Employee employee = iterator.next();
        logger.info("{},雇员 Id:{} Name:{}", employee.getDesc(), employee.getuId(), employee.getName());
    }
}

中介者模式

中介者模式要解决的就是复杂功能应用之间的重复调用,在这中间添加一层中介者包装服务,对外提供简单、通用、易扩展的服务能力。

仿mybatis开发ORM框架场景

场景图

场景模拟;模仿Mybatis手写ORM框架

传统模式

用jdbc的模式连接数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class JDBCUtil {

    private static Logger logger = LoggerFactory.getLogger(JDBCUtil.class);

    public static final String URL = "jdbc:mysql://127.0.0.1:3306/itstack-demo-design";
    public static final String USER = "root";
    public static final String PASSWORD = "123456";

    public static void main(String[] args) throws Exception {
        //1. 加载驱动程序
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获得数据库连接
        Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
        //3. 操作数据库
        Statement stmt = conn.createStatement();
        ResultSet resultSet = stmt.executeQuery("SELECT id, name, age, createTime, updateTime FROM user");
        //4. 如果有数据 resultSet.next() 返回true
        while (resultSet.next()) {
            logger.info("测试结果 姓名:{} 年龄:{}", resultSet.getString("name"),resultSet.getInt("age"));
        }
    }

}

模型结构

中介者模式模型结构

ORM框架实现的核心类,包括了;加载配置文件、对xml解析、获取数据库session、操作数据库以及结果返回。

左上是对数据库的定义和处理,基本包括我们常用的方法;<T> T selectOne<T> List<T> selectList

右侧蓝色部分是对数据库配置的开启session的工厂处理类,这里的工厂会操作DefaultSqlSession

红色地方的SqlSessionFactoryBuilder,这个类是对数据库操作的核心类;处理工厂、解析文件、拿到session等

sqlSession接口

对数据库的查询接口

1
2
3
4
5
6
7
8
9
10
11
12
public interface SqlSession {

    <T> T selectOne(String statement);

    <T> T selectOne(String statement, Object parameter);

    <T> List<T> selectList(String statement);

    <T> List<T> selectList(String statement, Object parameter);

    void close();
}

sqlSession实现

包装jdbc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class DefaultSqlSession implements SqlSession {

    private Connection connection;
    private Map<String, XNode> mapperElement;

    public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {
        this.connection = connection;
        this.mapperElement = mapperElement;
    }

    @Override
    public <T> T selectOne(String statement) {
        try {
            XNode xNode = mapperElement.get(statement);
            PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
            ResultSet resultSet = preparedStatement.executeQuery();
            List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
            return objects.get(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> List<T> selectList(String statement) {
        XNode xNode = mapperElement.get(statement);
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
            ResultSet resultSet = preparedStatement.executeQuery();
            return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // ...

    private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {
        List<T> list = new ArrayList<>();
        try {
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            // 每次遍历行值
            while (resultSet.next()) {
                T obj = (T) clazz.newInstance();
                for (int i = 1; i <= columnCount; i++) {
                    Object value = resultSet.getObject(i);
                    String columnName = metaData.getColumnName(i);
                    String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
                    Method method;
                    if (value instanceof Timestamp) {
                        method = clazz.getMethod(setMethod, Date.class);
                    } else {
                        method = clazz.getMethod(setMethod, value.getClass());
                    }
                    method.invoke(obj, value);
                }
                list.add(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    @Override
    public void close() {
        if (null == connection) return;
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

SqlSessionFactory接口

封装sqlsession

1
2
3
4
5
public interface SqlSessionFactory {

    SqlSession openSession();

}

SqlSessionFactory实现

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration.connection, configuration.mapperElement);
    }

}

SqlSessionFactoryBuilder实现

构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
public class SqlSessionFactoryBuilder {

    //构建实例化元素
    //这个类主要用于创建解析xml文件的类,以及初始化SqlSession工厂类DefaultSqlSessionFactory
    public DefaultSqlSessionFactory build(Reader reader) {
        SAXReader saxReader = new SAXReader();
        try {
            //是为了保证在不联网的时候一样可以解析xml,否则会需要从互联网获取dtd文件
            saxReader.setEntityResolver(new XMLMapperEntityResolver());
            Document document = saxReader.read(new InputSource(reader));
            Configuration configuration = parseConfiguration(document.getRootElement());
            return new DefaultSqlSessionFactory(configuration);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return null;
    }

    //解析配置
    //是对xml中的元素进行获取,这里主要获取了;dataSource、mappers,而这两个配置一个是我们数据库的链接信息,另外一个是对数据库操作语句的解析
    private Configuration parseConfiguration(Element root) {
        Configuration configuration = new Configuration();
        configuration.setDataSource(dataSource(root.selectNodes("//dataSource")));
        configuration.setConnection(connection(configuration.dataSource));
        configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));
        return configuration;
    }

    // 获取数据源配置信息
    private Map<String, String> dataSource(List<Element> list) {
        Map<String, String> dataSource = new HashMap<>(4);
        Element element = list.get(0);
        List content = element.content();
        for (Object o : content) {
            Element e = (Element) o;
            String name = e.attributeValue("name");
            String value = e.attributeValue("value");
            dataSource.put(name, value);
        }
        return dataSource;
    }

    //连接数据库
    private Connection connection(Map<String, String> dataSource) {
        try {
            Class.forName(dataSource.get("driver"));
            return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 获取SQL语句信息
    private Map<String, XNode> mapperElement(List<Element> list) {
        Map<String, XNode> map = new HashMap<>();

        Element element = list.get(0);
        List content = element.content();
        for (Object o : content) {
            Element e = (Element) o;
            String resource = e.attributeValue("resource");

            try {
                Reader reader = Resources.getResourceAsReader(resource);
                SAXReader saxReader = new SAXReader();
                Document document = saxReader.read(new InputSource(reader));
                Element root = document.getRootElement();
                //命名空间
                String namespace = root.attributeValue("namespace");

                // SELECT
                List<Element> selectNodes = root.selectNodes("select");
                for (Element node : selectNodes) {
                    String id = node.attributeValue("id");
                    String parameterType = node.attributeValue("parameterType");
                    String resultType = node.attributeValue("resultType");
                    String sql = node.getText();

                    // ? 匹配
                    Map<Integer, String> parameter = new HashMap<>();
                    Pattern pattern = Pattern.compile("(#\\{(.*?)})");
                    Matcher matcher = pattern.matcher(sql);
                    for (int i = 1; matcher.find(); i++) {
                        String g1 = matcher.group(1);
                        String g2 = matcher.group(2);
                        parameter.put(i, g2);
                        sql = sql.replace(g1, "?");
                    }

                    XNode xNode = new XNode();
                    xNode.setNamespace(namespace);
                    xNode.setId(id);
                    xNode.setParameterType(parameterType);
                    xNode.setResultType(resultType);
                    xNode.setSql(sql);
                    xNode.setParameter(parameter);

                    map.put(namespace + "." + id, xNode);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }

        }
        return map;
    }

}

测试验证

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class User {

    private Long id;
    private String name;
    private Integer age;
    private Date createTime;
    private Date updateTime;

    // ... get/set
}


public class School {

    private Long id;
    private String name;
    private String address;
    private Date createTime;
    private Date updateTime;  

    // ... get/set
}

dao

1
2
3
4
5
6
7
8
9
10
11
12
public interface IUserDao {

     User queryUserInfoById(Long id);

}


public interface ISchoolDao {

    School querySchoolInfoById(Long treeId);

}

ORM配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/itstack_demo_design?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/User_Mapper.xml"/>
        <mapper resource="mapper/School_Mapper.xml"/>
    </mappers>

</configuration>

mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<mapper namespace="org.itstack.demo.design.dao.IUserDao">

    <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="org.itstack.demo.design.po.User">
        SELECT id, name, age, createTime, updateTime
        FROM user
        where id = #{id}
    </select>

    <select id="queryUserList" parameterType="org.itstack.demo.design.po.User" resultType="org.itstack.demo.design.po.User">
        SELECT id, name, age, createTime, updateTime
        FROM user
        where age = #{age}
    </select>

</mapper>


<mapper namespace="org.itstack.demo.design.dao.ISchoolDao">

    <select id="querySchoolInfoById" resultType="org.itstack.demo.design.po.School">
        SELECT id, name, address, createTime, updateTime
        FROM school
        where id = #{id}
    </select>

</mapper>

单结果查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void test_queryUserInfoById() {
    String resource = "mybatis-config-datasource.xml";
    Reader reader;
    try {
        reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
        SqlSession session = sqlMapper.openSession();
        try {
            User user = session.selectOne("org.itstack.demo.design.dao.IUserDao.queryUserInfoById", 1L);
            logger.info("测试结果:{}", JSON.toJSONString(user));
        } finally {
            session.close();
            reader.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

集合查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test_queryUserList() {
    String resource = "mybatis-config-datasource.xml";
    Reader reader;
    try {
        reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
        SqlSession session = sqlMapper.openSession();
        try {
            User req = new User();
            req.setAge(18);
            List<User> userList = session.selectList("org.itstack.demo.design.dao.IUserDao.queryUserList", req);
            logger.info("测试结果:{}", JSON.toJSONString(userList));
        } finally {
            session.close();
            reader.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

备忘录模式

备忘录模式是以可以恢复或者说回滚,配置、版本、悔棋为核心功能的设计模式,而这种设计模式属于行为模式。在功能实现上是以不破坏原对象为基础增加备忘录操作类,记录原对象的行为从而实现备忘录模式。

上线配置文件回滚场景

场景图

场景模拟;系统发布上线配置回滚

备忘录的设计模式实现方式,重点在于不更改原有类的基础上,增加备忘录类存放记录。

模型结构

备忘录模式模型结构

配置信息类

配置信息

1
2
3
4
5
6
7
8
9
public class ConfigFile {

    private String versionNo; // 版本号
    private String content;   // 内容
    private Date dateTime;    // 时间
    private String operator;  // 操作人

    // ...get/set
}

备忘录类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ConfigMemento {

    private ConfigFile configFile;

    public ConfigMemento(ConfigFile configFile) {
        this.configFile = configFile;
    }

    public ConfigFile getConfigFile() {
        return configFile;
    }

    public void setConfigFile(ConfigFile configFile) {
        this.configFile = configFile;
    }

}

记录者类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ConfigOriginator {

    private ConfigFile configFile;

    public ConfigFile getConfigFile() {
        return configFile;
    }

    public void setConfigFile(ConfigFile configFile) {
        this.configFile = configFile;
    }

    //保存备忘录的时候会创建一个备忘录信息,并返回回去,交给管理者处理
    public ConfigMemento saveMemento(){
        return new ConfigMemento(configFile);
    }

    //获取的之后并不是直接返回,而是把备忘录的信息交给现在的配置文件this.configFile,这部分需要注意
    public void getMemento(ConfigMemento memento){
        this.configFile = memento.getConfigFile();
    }

}

管理员类

记录配置文件信息,也就是备忘录的效果,之后提供可以回滚和获取的方法,拿到备忘录的具体内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Admin {

    private int cursorIdx = 0;
    private List<ConfigMemento> mementoList = new ArrayList<ConfigMemento>();
    private Map<String, ConfigMemento> mementoMap = new ConcurrentHashMap<String, ConfigMemento>();

    public void append(ConfigMemento memento) {
        mementoList.add(memento);
        mementoMap.put(memento.getConfigFile().getVersionNo(), memento);
        cursorIdx++;
    }

    public ConfigMemento undo() {
        if (--cursorIdx <= 0) return mementoList.get(0);
        return mementoList.get(cursorIdx);
    }

    public ConfigMemento redo() {
        if (++cursorIdx > mementoList.size()) return mementoList.get(mementoList.size() - 1);
        return mementoList.get(cursorIdx);
    }

    public ConfigMemento get(String versionNo){
        return mementoMap.get(versionNo);
    }

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Test
public void test() {
    Admin admin = new Admin();
    ConfigOriginator configOriginator = new ConfigOriginator();
    configOriginator.setConfigFile(new ConfigFile("1000001", "配置内容A=哈哈", new Date(), "小傅哥"));
    admin.append(configOriginator.saveMemento()); // 保存配置
    configOriginator.setConfigFile(new ConfigFile("1000002", "配置内容A=嘻嘻", new Date(), "小傅哥"));
    admin.append(configOriginator.saveMemento()); // 保存配置
    configOriginator.setConfigFile(new ConfigFile("1000003", "配置内容A=么么", new Date(), "小傅哥"));
    admin.append(configOriginator.saveMemento()); // 保存配置
    configOriginator.setConfigFile(new ConfigFile("1000004", "配置内容A=嘿嘿", new Date(), "小傅哥"));
    admin.append(configOriginator.saveMemento()); // 保存配置  

    // 历史配置(回滚)
    configOriginator.getMemento(admin.undo());
    logger.info("历史配置(回滚)undo:{}", JSON.toJSONString(configOriginator.getConfigFile()));  

    // 历史配置(回滚)
    configOriginator.getMemento(admin.undo());
    logger.info("历史配置(回滚)undo:{}", JSON.toJSONString(configOriginator.getConfigFile()));  

    // 历史配置(前进)
    configOriginator.getMemento(admin.redo());
    logger.info("历史配置(前进)redo:{}", JSON.toJSONString(configOriginator.getConfigFile()));   

    // 历史配置(获取)
    configOriginator.getMemento(admin.get("1000002"));
    logger.info("历史配置(获取)get:{}", JSON.toJSONString(configOriginator.getConfigFile()));
}

观察者模式

观察者模式就是当一个行为发生时传递信息给另外一个用户接收做出相应的处理,两者之间没有直接的耦合关联。

例如我们经常使用的MQ服务,虽然MQ服务是有一个通知中心并不是每一个类服务进行通知,但整体上也可以算作是观察者模式的思路设计。再比如可能有做过的一些类似事件监听总线,让主线服务与其他辅线业务服务分离,为了使系统降低耦合和增强扩展性,也会使用观察者模式进行处理。

汽车摇号场景

模型结构

观察者模式模型结构

事件监听接口

1
2
3
4
5
public interface EventListener {

    void doEvent(LotteryResult result);

}

短消息事件

1
2
3
4
5
6
7
8
9
10
public class MessageEventListener implements EventListener {

    private Logger logger = LoggerFactory.getLogger(MessageEventListener.class);

    @Override
    public void doEvent(LotteryResult result) {
        logger.info("给用户 {} 发送短信通知(短信):{}", result.getuId(), result.getMsg());
    }

}

MQ发送事件

1
2
3
4
5
6
7
8
9
10
public class MQEventListener implements EventListener {

    private Logger logger = LoggerFactory.getLogger(MQEventListener.class);

    @Override
    public void doEvent(LotteryResult result) {
        logger.info("记录用户 {} 摇号结果(MQ):{}", result.getuId(), result.getMsg());
    }

}

事件处理

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class EventManager {

    Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();

    public EventManager(Enum<EventType>... operations) {
        for (Enum<EventType> operation : operations) {
            this.listeners.put(operation, new ArrayList<>());
        }
    }

    public enum EventType {
        MQ, Message
    }

    /**
     * 订阅
     * @param eventType 事件类型
     * @param listener  监听
     */
    public void subscribe(Enum<EventType> eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.add(listener);
    }

    /**
     * 取消订阅
     * @param eventType 事件类型
     * @param listener  监听
     */
    public void unsubscribe(Enum<EventType> eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.remove(listener);
    }

    /**
     * 通知
     * @param eventType 事件类型
     * @param result    结果
     */
    public void notify(Enum<EventType> eventType, LotteryResult result) {
        List<EventListener> users = listeners.get(eventType);
        for (EventListener listener : users) {
            listener.doEvent(result);
        }
    }

}

业务抽象类接口

接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class LotteryService {

    private EventManager eventManager;

    public LotteryService() {
        eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);
        eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener());
        eventManager.subscribe(EventManager.EventType.Message, new MessageEventListener());
    }

    public LotteryResult draw(String uId) {
        LotteryResult lotteryResult = doDraw(uId);
        // 需要什么通知就给调用什么方法
        eventManager.notify(EventManager.EventType.MQ, lotteryResult);
        eventManager.notify(EventManager.EventType.Message, lotteryResult);
        return lotteryResult;
    }

    protected abstract LotteryResult doDraw(String uId);

}

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LotteryServiceImpl extends LotteryService {

    private MinibusTargetService minibusTargetService = new MinibusTargetService();

    @Override
    protected LotteryResult doDraw(String uId) {
        // 摇号
        String lottery = minibusTargetService.lottery(uId);
        // 结果
        return new LotteryResult(uId, lottery, new Date());
    }

}

测试

1
2
3
4
5
6
@Test
public void test() {
    LotteryService lotteryService = new LotteryServiceImpl();
    LotteryResult result = lotteryService.draw("2765789109876");
    logger.info("测试结果:{}", JSON.toJSONString(result));
}

状态模式

状态模式描述的是一个行为下的多种状态变更,比如我们最常见的一个网站的页面,在你登录与不登录下展示的内容是略有差异的

营销活动状态流程场景

模型结构

状态模式模型结构

状态抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public abstract class State {

    /**
     * 活动提审
     *
     * @param activityId    活动ID
     * @param currentStatus 当前状态
     * @return 执行结果
     */
    public abstract Result arraignment(String activityId, Enum<Status> currentStatus);

    /**
     * 审核通过
     *
     * @param activityId    活动ID
     * @param currentStatus 当前状态
     * @return 执行结果
     */
    public abstract Result checkPass(String activityId, Enum<Status> currentStatus);

    /**
     * 审核拒绝
     *
     * @param activityId    活动ID
     * @param currentStatus 当前状态
     * @return 执行结果
     */
    public abstract Result checkRefuse(String activityId, Enum<Status> currentStatus);

    /**
     * 撤审撤销
     *
     * @param activityId    活动ID
     * @param currentStatus 当前状态
     * @return 执行结果
     */
    public abstract Result checkRevoke(String activityId, Enum<Status> currentStatus);

    /**
     * 活动关闭
     *
     * @param activityId    活动ID
     * @param currentStatus 当前状态
     * @return 执行结果
     */
    public abstract Result close(String activityId, Enum<Status> currentStatus);

    /**
     * 活动开启
     *
     * @param activityId    活动ID
     * @param currentStatus 当前状态
     * @return 执行结果
     */
    public abstract Result open(String activityId, Enum<Status> currentStatus);

    /**
     * 活动执行
     *
     * @param activityId    活动ID
     * @param currentStatus 当前状态
     * @return 执行结果
     */
    public abstract Result doing(String activityId, Enum<Status> currentStatus);

}

状态流转实现

编辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class EditingState extends State {

    public Result arraignment(String activityId, Enum<Status> currentStatus) {
        ActivityService.execStatus(activityId, currentStatus, Status.Check);
        return new Result("0000", "活动提审成功");
    }

    public Result checkPass(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "编辑中不可审核通过");
    }

    public Result checkRefuse(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "编辑中不可审核拒绝");
    }

    @Override
    public Result checkRevoke(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "编辑中不可撤销审核");
    }

    public Result close(String activityId, Enum<Status> currentStatus) {
        ActivityService.execStatus(activityId, currentStatus, Status.Close);
        return new Result("0000", "活动关闭成功");
    }

    public Result open(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "非关闭活动不可开启");
    }

    public Result doing(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "编辑中活动不可执行活动中变更");
    }

}

提审

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class CheckState extends State {

    public Result arraignment(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "待审核状态不可重复提审");
    }

    public Result checkPass(String activityId, Enum<Status> currentStatus) {
        ActivityService.execStatus(activityId, currentStatus, Status.Pass);
        return new Result("0000", "活动审核通过完成");
    }

    public Result checkRefuse(String activityId, Enum<Status> currentStatus) {
        ActivityService.execStatus(activityId, currentStatus, Status.Refuse);
        return new Result("0000", "活动审核拒绝完成");
    }

    @Override
    public Result checkRevoke(String activityId, Enum<Status> currentStatus) {
        ActivityService.execStatus(activityId, currentStatus, Status.Editing);
        return new Result("0000", "活动审核撤销回到编辑中");
    }

    public Result close(String activityId, Enum<Status> currentStatus) {
        ActivityService.execStatus(activityId, currentStatus, Status.Close);
        return new Result("0000", "活动审核关闭完成");
    }

    public Result open(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "非关闭活动不可开启");
    }

    public Result doing(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "待审核活动不可执行活动中变更");
    }

}

状态处理服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class StateHandler {

    private Map<Enum<Status>, State> stateMap = new ConcurrentHashMap<Enum<Status>, State>();

    public StateHandler() {
        stateMap.put(Status.Check, new CheckState());     // 待审核
        stateMap.put(Status.Close, new CloseState());     // 已关闭
        stateMap.put(Status.Doing, new DoingState());     // 活动中
        stateMap.put(Status.Editing, new EditingState()); // 编辑中
        stateMap.put(Status.Open, new OpenState());       // 已开启
        stateMap.put(Status.Pass, new PassState());       // 审核通过
        stateMap.put(Status.Refuse, new RefuseState());   // 审核拒绝
    }

    public Result arraignment(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).arraignment(activityId, currentStatus);
    }

    public Result checkPass(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).checkPass(activityId, currentStatus);
    }

    public Result checkRefuse(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).checkRefuse(activityId, currentStatus);
    }

    public Result checkRevoke(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).checkRevoke(activityId, currentStatus);
    }

    public Result close(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).close(activityId, currentStatus);
    }

    public Result open(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).open(activityId, currentStatus);
    }

    public Result doing(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).doing(activityId, currentStatus);
    }

}

测试

测试编辑中To提审活动,的状态流转

1
2
3
4
5
6
7
8
9
@Test
public void test_Editing2Arraignment() {
    String activityId = "100001";
    ActivityService.init(activityId, Status.Editing);
    StateHandler stateHandler = new StateHandler();
    Result result = stateHandler.arraignment(activityId, Status.Editing);
    logger.info("测试结果(编辑中To提审活动):{}", JSON.toJSONString(result));
    logger.info("活动信息:{} 状态:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityInfo(activityId).getStatus()));
}

测试编辑中To开启活动,的状态流转

1
2
3
4
5
6
7
8
9
@Test
public void test_Editing2Open() {
    String activityId = "100001";
    ActivityService.init(activityId, Status.Editing);
    StateHandler stateHandler = new StateHandler();
    Result result = stateHandler.open(activityId, Status.Editing);
    logger.info("测试结果(编辑中To开启活动):{}", JSON.toJSONString(result));
    logger.info("活动信息:{} 状态:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityInfo(activityId).getStatus()));
}

测试拒绝To活动中,的状态流转

1
2
3
4
5
6
7
8
9
@Test
public void test_Refuse2Doing() {
    String activityId = "100001";
    ActivityService.init(activityId, Status.Refuse);
    StateHandler stateHandler = new StateHandler();
    Result result = stateHandler.doing(activityId, Status.Refuse);
    logger.info("测试结果(拒绝To活动中):{}", JSON.toJSONString(result));
    logger.info("活动信息:{} 状态:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityInfo(activityId).getStatus()));
}

测试测试结果(拒绝To撤审),的状态流转

1
2
3
4
5
6
7
8
9
@Test
public void test_Refuse2Revoke() {
    String activityId = "100001";
    ActivityService.init(activityId, Status.Refuse);
    StateHandler stateHandler = new StateHandler();
    Result result = stateHandler.checkRevoke(activityId, Status.Refuse);
    logger.info("测试结果(拒绝To撤审):{}", JSON.toJSONString(result));
    logger.info("活动信息:{} 状态:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityInfo(activityId).getStatus()));
}

策略模式

策略模式是一种行为模式,也是替代大量ifelse的利器。它所能帮你解决的是场景,一般是具有同类可替代的行为逻辑算法场景。比如;不同类型的交易方式(信用卡、支付宝、微信)、生成唯一ID策略(UUID、DB自增、DB+Redis、雪花算法、Leaf算法)等,都可以使用策略模式进行行为包装,供给外部使用。

优惠券折扣金额计算场景

栗子

场景模拟;商品支付使用营销优惠券

模型结构

策略模式模型结构

优惠券接口

1
2
3
4
5
6
7
8
9
10
11
public interface ICouponDiscount<T> {

    /**
     * 优惠券金额计算
     * @param couponInfo 券折扣信息;直减、满减、折扣、N元购
     * @param skuPrice   sku金额
     * @return           优惠后金额
     */
    BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice);

}

满减

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MJCouponDiscount implements ICouponDiscount<Map<String,String>>  {

    /**
     * 满减计算
     * 1. 判断满足x元后-n元,否则不减
     * 2. 最低支付金额1元
     */
    public BigDecimal discountAmount(Map<String,String> couponInfo, BigDecimal skuPrice) {
        String x = couponInfo.get("x");
        String o = couponInfo.get("n");

        // 小于商品金额条件的,直接返回商品原价
        if (skuPrice.compareTo(new BigDecimal(x)) < 0) return skuPrice;
        // 减去优惠金额判断
        BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(o));
        if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;

        return discountAmount;
    }
}

直减

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ZJCouponDiscount implements ICouponDiscount<Double>  {

    /**
     * 直减计算
     * 1. 使用商品价格减去优惠价格
     * 2. 最低支付金额1元
     */
    public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
        BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(couponInfo));
        if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;
        return discountAmount;
    }

}

折扣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ZKCouponDiscount implements ICouponDiscount<Double> {


    /**
     * 折扣计算
     * 1. 使用商品价格乘以折扣比例,为最后支付金额
     * 2. 保留两位小数
     * 3. 最低支付金额1元
     */
    public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
        BigDecimal discountAmount = skuPrice.multiply(new BigDecimal(couponInfo)).setScale(2, BigDecimal.ROUND_HALF_UP);
        if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;
        return discountAmount;
    }

}

n元购

1
2
3
4
5
6
7
8
9
10
11
public class NYGCouponDiscount implements ICouponDiscount<Double> {

    /**
     * n元购购买
     * 1. 无论原价多少钱都固定金额购买
     */
    public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
        return new BigDecimal(couponInfo);
    }

}

策略控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Context<T> {

    //这里也可以包装成map结构,让外部只需要对应的泛型类型即可使用相应的服务
    private ICouponDiscount<T> couponDiscount;

    public Context(ICouponDiscount<T> couponDiscount) {
        this.couponDiscount = couponDiscount;
    }

    public BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice) {
        return couponDiscount.discountAmount(couponInfo, skuPrice);
    }

}

测试

直减

1
2
3
4
5
6
7
@Test
public void test_zj() {
    // 直减;100-10,商品100元
    Context<Double> context = new Context<Double>(new ZJCouponDiscount());
    BigDecimal discountAmount = context.discountAmount(10D, new BigDecimal(100));
    logger.info("测试结果:直减优惠后金额 {}", discountAmount);
}

满减

1
2
3
4
5
6
7
8
9
10
@Test
public void test_mj() {
    // 满100减10,商品100元
    Context<Map<String,String>> context = new Context<Map<String,String>>(new MJCouponDiscount());
    Map<String,String> mapReq = new HashMap<String, String>();
    mapReq.put("x","100");
    mapReq.put("n","10");
    BigDecimal discountAmount = context.discountAmount(mapReq, new BigDecimal(100));
    logger.info("测试结果:满减优惠后金额 {}", discountAmount);
}

折扣

1
2
3
4
5
6
7
@Test
public void test_zk() {
    // 折扣9折,商品100元
    Context<Double> context = new Context<Double>(new ZKCouponDiscount());
    BigDecimal discountAmount = context.discountAmount(0.9D, new BigDecimal(100));
    logger.info("测试结果:折扣9折后金额 {}", discountAmount);
}

n元购

1
2
3
4
5
6
@Test
public void test_nyg() {
    // n元购;100-10,商品100元
    Context<Double> context = new Context<Double>(new NYGCouponDiscount());
    BigDecimal discountAmount = context.discountAmount(90D, new BigDecimal(100));
    logger.info("测试结果:n元购优惠后金额 {}", discountAmount);

对接不同AI大模型对话

策略接口中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import cn.hutool.core.lang.Assert;
import com.org.bebas.exception.BusinessException;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * 策略接口中心
 * <p>* 用于处理基于策略模式的服务查找与选择。其核心功能是管理和根据给定的键(key)来获取相应的策略服务实例。</p>
 * <p>* 仅适用Spring管理的策略接口</p>
 *
 */
public abstract class StrategyServiceContext<Service> {

    /**
     * 存储了一系列实现了某个特定业务逻辑的 Service 类型对象,这些对象构成了不同策略的集合。
     */
    protected List<Service> serviceList;

    public StrategyServiceContext(List<Service> serviceList) {
        this.serviceList = serviceList;
    }

    /**
     * 需要在子类中实现。为每个具体的 Service 对象提供一个唯一的键值(字符串类型),作为区分不同策略服务的关键依据。
     * @return {@link Function}<{@link Service}, {@link String}>
     */
    protected abstract Function<Service, String> serviceKey();

    /**
     * 根据key获取对应策略器
     *
     * @param key               指定唯一标识
     * @param exceptionSupplier 自定义异常信息
     * @return
     */
    public Service getService(String key, Supplier<BusinessException> exceptionSupplier) {
        for (Service service : serviceList) {
            String serviceTypeKey = this.serviceKey().apply(service);
            Assert.notEmpty(serviceTypeKey, exceptionSupplier);
            if (serviceTypeKey.toUpperCase(Locale.ROOT).equals(key.toUpperCase(Locale.ROOT))) {
                return service;
            }
        }
        if (Objects.nonNull(exceptionSupplier)){
            throw exceptionSupplier.get();
        }
        return null;
    }

}

聊天业务服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import com.hugai.chatsdk.common.service.ChatBusinessService;
import com.hugai.common.support.strategy.StrategyServiceContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.function.Function;

/**
 * 通过策略模式管理ChatBusinessService的实例,并根据服务类型获取相应的实例
 */
@Slf4j
@Component
public class ChatBusinessServiceContext extends StrategyServiceContext<ChatBusinessService> {

    /**
     * 注入chatBusinessServices所有实现类的bean
     * @param chatBusinessServices chat服务接口
     */
    @Autowired
    public ChatBusinessServiceContext(List<ChatBusinessService> chatBusinessServices) {
        super(chatBusinessServices);
    }

    @Override
    protected Function<ChatBusinessService, String> serviceKey() {
        return s -> s.type().getKey();
    }
}

聊天业务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import com.hugai.chatsdk.common.entity.ChatSdkStorageResponse;
import com.hugai.chatsdk.common.entity.account.ChatSdkAccount;
import com.hugai.chatsdk.common.entity.session.RecordData;
import com.hugai.common.enums.flow.ChatSdkType;
import java.util.List;
import java.util.function.Consumer;


public interface ChatBusinessService<R> {

    /**
     * chat sdk 类型
     *
     * @return
     */
    ChatSdkType type();

    /**
     * 流式对话请求
     *
     * @param recordList
     * @param chatSdkAccount
     * @return
     */
    ChatSdkStorageResponse chatCompletionStream(List<RecordData> recordList, ChatSdkAccount chatSdkAccount);

    /**
     * 非流式对话
     *
     * @param recordList
     * @param chatSdkAccount
     * @return
     */
    ChatSdkStorageResponse chatCompletion(List<RecordData> recordList, ChatSdkAccount chatSdkAccount);

    /**
     * 转换为api请求参数
     *
     * @param recordList
     * @param account
     * @param requestConsumer
     * @return
     */
    R convertRequest(List<RecordData> recordList, ChatSdkAccount account, Consumer<R> requestConsumer);

}

接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
@Slf4j
@Service
public class OpenaiChatBusinessServiceImpl implements ChatBusinessService<ChatCompletionRequest> {

    @Resource
    private ChatResponseSyncDataService chatResponseSyncDataService;

    @Override
    public ChatSdkType type() {
        return ChatSdkType.openai;
    }

    @Override
    public ChatSdkStorageResponse chatCompletionStream(List<RecordData> recordList, ChatSdkAccount chatSdkAccount) {

        String connectId = chatSdkAccount.getConnectId();

        log.info("[平台-{}]  流式请求参数:{}", chatSdkAccount.getSdkUniqueKey(), JSON.toJSONString(recordList));

        MessageSendHandler messageSendHandler = new MessageSendHandler(connectId);

        ChatSdkStorageResponse response = new ChatSdkStorageResponse();
        response.setAccount(chatSdkAccount);

        try {
            OpenAiService service = OpenaiClientFactory.getService(chatSdkAccount);
            ChatCompletionRequest chatCompletionRequest = this.convertRequest(recordList, chatSdkAccount, e -> e.setStream(true));

            RecordData recordData = response.pushData(new RecordData());
            try {
                service.streamChatCompletion(chatCompletionRequest)
                        .blockingForEach(chunk -> {
                            List<ChatCompletionChoice> choices = chunk.getChoices();

                            Optional.ofNullable(choices).orElseGet(ArrayList::new).forEach(res -> {
                                ChatMessage message = Optional.ofNullable(res.getMessage()).orElseGet(ChatMessage::new);
                                OR.run(message.getRole(), StrUtil::isNotEmpty, recordData::setRole);
                                String resContent = Optional.ofNullable(message.getContent()).orElse("");
                                recordData.getContentSB().append(resContent);
                                // 发送消息
                                messageSendHandler.queueAdd(resContent);
                            });
                        });
            } catch (BusinessException e) {
                if (e.getCode() == HttpStatus.HTTP_OK) {
                    return response;
                }
                throw e;
            } finally {
                recordData.setContent(recordData.getContentSB().toString());
                service.shutdownExecutor();
            }
        } catch (Exception throwable) {
            throwable.printStackTrace();
            if (throwable.getCause() instanceof ConnectException) {
                throw new BusinessException("服务器节点异常,请稍后重试或联系管理员");
            }
            if (throwable instanceof OpenAiHttpException e) {
                int statusCode = e.statusCode;
                String code = e.code;
                List<Integer> accountErrorCode = CollUtil.newArrayList(HttpStatus.HTTP_UNAUTHORIZED, 429);
                if (accountErrorCode.contains(statusCode) || "insufficient_quota".equals(code)) {
                    response.setAccountError(true);
                }
                return response;
            }
            throw new BusinessException(throwable.getMessage());
        } finally {
            messageSendHandler.close();
        }

        chatResponseSyncDataService.syncChatResponse(chatSdkAccount, response, recordList);

        return response;
    }

    @Override
    public ChatSdkStorageResponse chatCompletion(List<RecordData> recordList, ChatSdkAccount chatSdkAccount) {
        String connectId = chatSdkAccount.getConnectId();

        log.info("[平台-{}]  非流式请求参数:{}", chatSdkAccount.getSdkUniqueKey(), JSON.toJSONString(recordList));

        MessageSendHandler messageSendHandler = new MessageSendHandler(connectId);

        ChatSdkStorageResponse response = new ChatSdkStorageResponse();

        try {
            OpenAiService service = OpenaiClientFactory.getService(chatSdkAccount);
            ChatCompletionRequest chatCompletionRequest = this.convertRequest(recordList, chatSdkAccount, e -> e.setStream(true));

            RecordData recordData = response.pushData(new RecordData());
            try {
                chatCompletionRequest.setStream(false);
                ChatCompletionResult chatCompletion = service.createChatCompletion(chatCompletionRequest);

                Optional.ofNullable(chatCompletion.getChoices()).orElseGet(ArrayList::new).forEach(res -> {
                    ChatMessage message = Optional.ofNullable(res.getMessage()).orElseGet(ChatMessage::new);
                    OR.run(message.getRole(), StrUtil::isNotEmpty, recordData::setRole);
                    String resContent = Optional.ofNullable(message.getContent()).orElse("");
                    recordData.getContentSB().append(resContent);
                    // 发送消息
                    messageSendHandler.queueAdd(resContent);
                });
            } catch (BusinessException e) {
                if (e.getCode() == HttpStatus.HTTP_OK) {
                    return response;
                }
                throw e;
            } finally {
                recordData.setContent(recordData.getContentSB().toString());
                service.shutdownExecutor();
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (e.getCause() instanceof ConnectException) {
                throw new BusinessException("服务器节点异常,请稍后重试或联系管理员");
            }
            throw new BusinessException(e.getMessage());
        } finally {
            messageSendHandler.close();
        }
        chatResponseSyncDataService.syncChatResponse(chatSdkAccount, response, recordList);

        return response;
    }

    /**
     * 转换为api请求参数
     *
     * @param recordList
     * @param account
     * @param requestConsumer
     * @return
     */
    @Override
    public ChatCompletionRequest convertRequest(List<RecordData> recordList, ChatSdkAccount account, Consumer<ChatCompletionRequest> requestConsumer) {
        ChatCompletionRequest request = new ChatCompletionRequest();
        request.setModel(account.getModelValue());
        request.setN(1);
        request.setMaxTokens(account.getOnceToken());
        request.setLogitBias(new HashMap<>());

        List<ChatMessage> chatMessageList = recordList.stream().map(item -> {
            ChatMessage chatMessage = new ChatMessage();
            chatMessage.setRole(item.getRole());
            chatMessage.setContent(item.getContent());
            return chatMessage;
        }).toList();
        request.setMessages(chatMessageList);

        OR.run(account.getExtendParam(), CollUtil::isNotEmpty, extendParam -> {
            OR.run(extendParam.get("temperature"), Objects::nonNull, e -> {
                request.setTemperature(Double.valueOf(e.toString()));
            });
            OR.run(extendParam.get("topP"), Objects::nonNull, e -> {
                request.setTopP(Double.valueOf(e.toString()));
            });
            OR.run(extendParam.get("presencePenalty"), Objects::nonNull, e -> {
                request.setPresencePenalty(Double.valueOf(e.toString()));
            });
            OR.run(extendParam.get("frequencyPenalty"), Objects::nonNull, e -> {
                request.setFrequencyPenalty(Double.valueOf(e.toString()));
            });
            OR.run(extendParam.get("user"), Objects::nonNull, e -> {
                request.setUser(e.toString());
            });
        });
        requestConsumer.accept(request);
        return request;
    }

}

使用

1
2
3
ChatBusinessService chatBusinessService = chatBusinessServiceContext.getService(chatSdkAccount.getSdkUniqueKey(), () -> new BusinessException("系统未找到对应对话模型"));

                    ChatSdkStorageResponse response = chatBusinessService.chatCompletion(CollUtil.newArrayList(RecordData.builder().role(ChatRole.user.name()).content(drawPromptOptimizeContent + prompt.get()).build()), chatSdkAccount);

构建规则引擎

在复杂业务系统中,规则频繁变动且涉及多条件判断是常见问题。

策略模式 + 注册表模式。将这两种模式结合使用,你可以动态地插入规则,并清晰流畅地执行它们

如下将是我们将要构建的相关类:

  1. Rule接口,规则定义必须实现该接口

  2. RuleA,RuleB等,不同的规则策略

  3. RuleRegistry,将规则映射到具体的实现

  4. RuleEngine,根据条件评估执行具体的规则

定义Rule接口

1
2
3
4
5
6
7
public interface Rule<T extends Context, R> {

  /**规则名称*/
  String getName() ;
  /**评估规则*/
  R evaluate(T context) ;
}

定义上下文对象

上下文(Context) 是规则引擎的核心数据载体,用于封装规则执行所需的业务数据和环境信息。它为规则提供统一的访问入口,隔离规则逻辑与具体数据来源,实现数据与规则解耦。通过上下文,规则可灵活适配不同业务场景,同时保持逻辑的纯净性和可复用性。该上下文对象,没有定义任何的方法,具体的系统都是有子类实现。

1
2
3
public interface Context {

}

定义具体业务上下文对象

1
2
3
4
5
6
7
8
9
public class UserContext implements Context {
  private final User user ;
  public UserContext(User user) {
    this.user = user ;
  }
  public User getUser() {
    return user;
  }
}

定义规则实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//用户年龄规则
@Component
public class UserAgeRule implements Rule<UserContext, Boolean> {
  @Override
  public String getName() {
    return "user_age_rule";
  }
  @Override
  public Boolean evaluate(UserContext context) {
    if (context == null) {
      throw new IllegalArgumentException("Context must not be null");
    }
    return context.getAge() > 18 ;
  }
}

规则2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//用户等级规则
@Component
public class UserLevelRule implements Rule<UserContext, Boolean> {
  @Override
  public String getName() {
    return "user_level_rule";
  }
  @Override
  public Boolean evaluate(UserContext context) {
    if (context == null) {
      throw new IllegalArgumentException("Context must not be null");
    }
    return context.getUser().getLevel() >= 3 ;
  }
}

定义注册表Registry

RuleRegistry 是规则注册中心,通过集中存储和管理所有规则实例,提供按名称快速检索规则的能力,实现规则的统一调度与访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class RuleRegistry {
  private final Map<String, Rule<?, ?>> ruleStore = new HashMap<>();
  public RuleRegistry(List<Rule<?, ?>> rules) {
    rules.forEach(rule -> ruleStore.put(rule.getName(), rule));
  }
  public <T extends Context, R> R execute(String ruleName, T context) {
    Rule<T, R> rule = getRuleInternal(ruleName);
    return rule.evaluate(context);
  }
  @SuppressWarnings("unchecked")
  private <T extends Context, R> Rule<T, R> getRuleInternal(String ruleName) {
    Rule<?, ?> rule = ruleStore.get(ruleName);
    if (rule == null) {
      throw new IllegalArgumentException("Rule not found: " + ruleName);
    }
    return (Rule<T, R>) rule;
  }
}

规则调度引擎

RuleEngine 是规则执行的核心调度器,负责根据规则名称和上下文,协调调用注册的规则进行业务逻辑评估与决策。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class RuleEngine {
  private final RuleRegistry ruleRegistry;
  public RuleEngine(RuleRegistry ruleRegistry) {
    this.ruleRegistry = ruleRegistry;
  }
  public <T extends Context, R> R execute(String ruleName, T context) {
    try {
      return ruleRegistry.execute(ruleName, context) ;
    } catch (IllegalArgumentException e) {
      throw new RuntimeException("Rule '" + ruleName + "' not found or invalid context", e);
    }
  }
}

接口测试

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping("/rules")
public class RuleController {
  private final RuleEngine ruleEngine;
  public RuleController(RuleEngine ruleEngine) {
    this.ruleEngine = ruleEngine;
  }
  @PostMapping("/evaluate")
  public ResponseEntity<Boolean> evaluate(@RequestParam String rule, @RequestBody User user) {
    UserContext context = new UserContext(user) ;
    return ResponseEntity.ok(ruleEngine.execute(rule, context));
  }
}

模板模式

模板模式的核心设计思路是通过在,抽象类中定义抽象方法的执行顺序,并将抽象方法设定为只有子类实现,但不设计独立访问的方法。简单说也就是把你安排的明明白白的。

推广海报场景

整个的爬取过程分为;模拟登录、爬取信息、生成海报,这三个步骤,另外;

  1. 因为有些商品只有登录后才可以爬取,并且登录可以看到一些特定的价格这与未登录用户看到的价格不同。
  2. 不同的电商网站爬取方式不同,解析方式也不同,因此可以作为每一个实现类中的特定实现。
  3. 生成海报的步骤基本一样,但会有特定的商品来源标识。所以这样三个步骤可以使用模版模式来设定,并有具体的场景做子类实现

模型结构

模版模式模型结构

执行顺序抽象类

灵魂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public abstract class NetMall {
/**
 * 基础电商推广服务
 * 1. 生成最优价商品海报
 * 2. 海报含带推广邀请码
 */
    protected Logger logger = LoggerFactory.getLogger(NetMall.class);

    String uId;   // 用户ID
    String uPwd;  // 用户密码

    public NetMall(String uId, String uPwd) {
        this.uId = uId;
        this.uPwd = uPwd;
    }

    /**
     * 生成商品推广海报
     *
     * @param skuUrl 商品地址(京东、淘宝、当当)
     * @return 海报图片base64位信息
     */
    public String generateGoodsPoster(String skuUrl) {
        if (!login(uId, uPwd)) return null;             // 1. 验证登录
        Map<String, String> reptile = reptile(skuUrl);  // 2. 爬虫商品
        return createBase64(reptile);                   // 3. 组装海报
    }

    // 模拟登录
    protected abstract Boolean login(String uId, String uPwd);

    // 爬虫提取商品信息(登录后的优惠价格)
    protected abstract Map<String, String> reptile(String skuUrl);

    // 生成商品海报信息
    protected abstract String createBase64(Map<String, String> goodsInfo);

}

模拟爬虫

抓取JD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class JDNetMall extends NetMall {

    public JDNetMall(String uId, String uPwd) {
        super(uId, uPwd);
    }

    public Boolean login(String uId, String uPwd) {
        logger.info("模拟京东用户登录 uId:{} uPwd:{}", uId, uPwd);
        return true;
    }

    public Map<String, String> reptile(String skuUrl) {
        String str = HttpClient.doGet(skuUrl);
        Pattern p9 = Pattern.compile("(?<=title\\>).*(?=</title)");
        Matcher m9 = p9.matcher(str);
        Map<String, String> map = new ConcurrentHashMap<String, String>();
        if (m9.find()) {
            map.put("name", m9.group());
        }
        map.put("price", "5999.00");
        logger.info("模拟京东商品爬虫解析:{} | {} 元 {}", map.get("name"), map.get("price"), skuUrl);
        return map;
    }

    public String createBase64(Map<String, String> goodsInfo) {
        BASE64Encoder encoder = new BASE64Encoder();
        logger.info("模拟生成京东商品base64海报");
        return encoder.encode(JSON.toJSONString(goodsInfo).getBytes());
    }

}

抓取TB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class TaoBaoNetMall extends NetMall {

    public TaoBaoNetMall(String uId, String uPwd) {
        super(uId, uPwd);
    }

    @Override
    public Boolean login(String uId, String uPwd) {
        logger.info("模拟淘宝用户登录 uId:{} uPwd:{}", uId, uPwd);
        return true;
    }

    @Override
    public Map<String, String> reptile(String skuUrl) {
        String str = HttpClient.doGet(skuUrl);
        Pattern p9 = Pattern.compile("(?<=title\\>).*(?=</title)");
        Matcher m9 = p9.matcher(str);
        Map<String, String> map = new ConcurrentHashMap<String, String>();
        if (m9.find()) {
            map.put("name", m9.group());
        }
        map.put("price", "4799.00");
        logger.info("模拟淘宝商品爬虫解析:{} | {} 元 {}", map.get("name"), map.get("price"), skuUrl);
        return map;
    }

    @Override
    public String createBase64(Map<String, String> goodsInfo) {
        BASE64Encoder encoder = new BASE64Encoder();
        logger.info("模拟生成淘宝商品base64海报");
        return encoder.encode(JSON.toJSONString(goodsInfo).getBytes());
    }

}

抓取当当

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class DangDangNetMall extends NetMall {

    public DangDangNetMall(String uId, String uPwd) {
        super(uId, uPwd);
    }

    @Override
    public Boolean login(String uId, String uPwd) {
        logger.info("模拟当当用户登录 uId:{} uPwd:{}", uId, uPwd);
        return true;
    }

    @Override
    public Map<String, String> reptile(String skuUrl) {
        String str = HttpClient.doGet(skuUrl);
        Pattern p9 = Pattern.compile("(?<=title\\>).*(?=</title)");
        Matcher m9 = p9.matcher(str);
        Map<String, String> map = new ConcurrentHashMap<String, String>();
        if (m9.find()) {
            map.put("name", m9.group());
        }
        map.put("price", "4548.00");
        logger.info("模拟当当商品爬虫解析:{} | {} 元 {}", map.get("name"), map.get("price"), skuUrl);
        return map;
    }

    @Override
    public String createBase64(Map<String, String> goodsInfo) {
        BASE64Encoder encoder = new BASE64Encoder();
        logger.info("模拟生成当当商品base64海报");
        return encoder.encode(JSON.toJSONString(goodsInfo).getBytes());
    }

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test_NetMall() {
  /**
 * 测试链接
 * 京东;https://item.jd.com/100008348542.html
 * 淘宝;https://detail.tmall.com/item.htm
 * 当当;http://product.dangdang.com/1509704171.html
 */
    NetMall netMall = new JDNetMall("1000001","*******");
    String base64 = netMall.generateGoodsPoster("https://item.jd.com/100008348542.html");
    logger.info("测试结果:{}", base64);
}

访问者模式

访问者要解决的核心事项是,在一个稳定的数据结构下,例如用户信息、雇员信息等,增加易变的业务访问逻辑。为了增强扩展性,将这两部分的业务解耦的一种设计模式。

说白了访问者模式的核心在于同一个事物不同视角下的访问信息不同,比如一个美女手里拿个冰激凌。小朋友会注意冰激凌,大朋友会找自己喜欢的地方观测敌情

学校不同视角场景

模型结构

访问者模式模型结构

用户抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 基础用户信息
public abstract class User {

    public String name;      // 姓名
    public String identity;  // 身份;重点班、普通班 | 特级教师、普通教师、实习教师
    public String clazz;     // 班级

    public User(String name, String identity, String clazz) {
        this.name = name;
        this.identity = identity;
        this.clazz = clazz;
    }

    // 核心访问方法
    public abstract void accept(Visitor visitor);

}

用户信息

老师

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Teacher extends User {

    public Teacher(String name, String identity, String clazz) {
        super(name, identity, clazz);
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    // 升本率
    public double entranceRatio() {
        return BigDecimal.valueOf(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

}

学生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Student extends User {

    public Student(String name, String identity, String clazz) {
        super(name, identity, clazz);
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public int ranking() {
        return (int) (Math.random() * 100);
    }

}

访问数据接口

1
2
3
4
5
6
7
8
9
public interface Visitor {

    // 访问学生信息
    void visit(Student student);

    // 访问老师信息
    void visit(Teacher teacher);

}

校长

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Principal implements Visitor {

    private Logger logger = LoggerFactory.getLogger(Principal.class);

    public void visit(Student student) {
        logger.info("学生信息 姓名:{} 班级:{}", student.name, student.clazz);
    }

    public void visit(Teacher teacher) {
        logger.info("学生信息 姓名:{} 班级:{} 升学率:{}", teacher.name, teacher.clazz, teacher.entranceRatio());
    }

}

家长

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Parent implements Visitor {

    private Logger logger = LoggerFactory.getLogger(Parent.class);

    public void visit(Student student) {
        logger.info("学生信息 姓名:{} 班级:{} 排名:{}", student.name, student.clazz, student.ranking());
    }

    public void visit(Teacher teacher) {
        logger.info("老师信息 姓名:{} 班级:{} 级别:{}", teacher.name, teacher.clazz, teacher.identity);
    }

}

数据看板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DataView {

    List<User> userList = new ArrayList<User>();

    public DataView() {
        userList.add(new Student("谢飞机", "重点班", "一年一班"));
        userList.add(new Student("windy", "重点班", "一年一班"));
        userList.add(new Student("大毛", "普通班", "二年三班"));
        userList.add(new Student("Shing", "普通班", "三年四班"));
        userList.add(new Teacher("BK", "特级教师", "一年一班"));
        userList.add(new Teacher("娜娜Goddess", "特级教师", "一年一班"));
        userList.add(new Teacher("dangdang", "普通教师", "二年三班"));
        userList.add(new Teacher("泽东", "实习教师", "三年四班"));
    }

    // 展示
    public void show(Visitor visitor) {
        for (User user : userList) {
            user.accept(visitor);
        }
    }

}

测试

1
2
3
4
5
6
7
8
9
10
@Test
public void test(){
    DataView dataView = new DataView();      

    logger.info("\r\n家长视角访问:");
    dataView.show(new Parent());     // 家长

    logger.info("\r\n校长视角访问:");
    dataView.show(new Principal());  // 校长
}

引用

整理自《重学Java设计模式》

https://bugstack.cn/md/develop/design-pattern/2020-07-09-%E9%87%8D%E5%AD%A6%20Java%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%8A%E5%AE%9E%E6%88%98%E8%AE%BF%E9%97%AE%E8%80%85%E6%A8%A1%E5%BC%8F%E3%80%8B.html

本文由作者按照 CC BY 4.0 进行授权