Design pattern study notes (2) Mixed use of factory pattern, template pattern and strategy pattern

1. Factory mode

The factory pattern, also known as the factory method pattern, is a creational design pattern that generally provides a method for creating objects in the parent class, allowing subclasses to determine the type of instantiated object.

1.1 Introduction to factory mode

The factory pattern is a relatively common design pattern in Java. The implementation method is to define a uniform interface for creating objects, and let its subclasses decide to instantiate the factory class to solve the problem of creating different instances under different conditions. The factory method pattern will be combined with other design patterns when it is actually used, rather than used alone. For example, the distribution of prizes in the Lottery project is the factory + template + strategy mode.


More ways to obtain learning materials: Leave a message in the comment area or get it by private message

1.2 Implementation of factory pattern

For example, if we want to realize the distribution of different prizes, such as coupons, physical goods and membership electronic cards, then we can define the interfaces of these three types of prizes:

serial number type interface describe
1 coupon CouponResult sendCoupon(String uId, String couponNumber, String uuid) Return coupon information (object type)
2 physical goods Boolean deliverGoods(DeliverReq req) Returns whether to send physical goods (Boolean type)
3 iQIYI membership e-card void grantToken(String bindMobileNumber, String cardId) Execute issuing membership card (empty type)

As can be seen from the above table, different prizes have different return type requirements, so how should we process these data and return them accordingly? The general idea can be thought of through unified entry AwardReqand exit AwardRes, plus one PrizeControllerto specifically realize the data processing tasks of these prizes:

AwardReq
AwardRes
PrizeController

But this will inevitably lead to PrizeControllertoo many logical judgments in this category, and it will be very difficult and troublesome to continue to expand the types of prizes later. For example, you can look at PrizeControllerthe code in:

public class PrizeController {

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

    public AwardRes AwardToUser(AwardReq awardReq) {
        String reqJson = JSON.toJSONString(awardReq);
        AwardRes awardRes = null;

        try {
            logger.info("奖品发放开始{}。 awardReq:{}", awardReq.getuId(), reqJson);
            if (awardReq.getAwardType() == 1) {
                CouponService couponService = new CouponService();
                CouponResult couponResult = couponService.sendCoupon(awardReq.getuId(), awardReq.getAwardNumber(), awardReq.getBizId());
                if ("0000".equals(couponResult.getCode())) {
                    awardRes = new AwardRes(0000, "发放成功");
                } else {
                    awardRes = new AwardRes(0001, "发送失败");
                }
            } else if (awardReq.getAwardType() == 2) {
                GoodsService goodsService = new GoodsService();
                DeliverReq deliverReq = new DeliverReq();
                deliverReq.setUserName(queryUserName(awardReq.getuId()));
                deliverReq.setUserPhone(queryUserPhoneNumber(awardReq.getuId()));
                deliverReq.setSku(awardReq.getAwardNumber());
                deliverReq.setOrderId(awardReq.getBizId());
                deliverReq.setConsigneeUserName(awardReq.getExtMap().get("consigneeUserName"));
                deliverReq.setConsigneeUserPhone(awardReq.getExtMap().get("consigneeUserPhone"));
                deliverReq.setConsigneeUserAddress(awardReq.getExtMap().get("consigneeUserAddress"));
                Boolean isSuccess = goodsService.deliverGoods(deliverReq);
                if (isSuccess) {
                    awardRes = new AwardRes(0000, "发放成功");
                } else {
                    awardRes = new AwardRes(0001, "发送失败");
                }
            } else {
                IQiYiCardService iQiYiCardService = new IQiYiCardService();
                iQiYiCardService.grantToken(queryUserPhoneNumber(awardReq.getuId()), awardReq.getAwardNumber());
                awardRes = new AwardRes(0000, "发送成功");
            }
            logger.info("奖品发放完成{}。", awardReq.getuId());
        } catch (Exception e) {
            logger.error("奖品发放失败{}。req:{}", awardReq.getuId(), reqJson, e);
            awardRes = new AwardRes(0001, e.getMessage());
        }
        return awardRes;

    }

In PrizeControllerthe class of , we found that many simple if-elsejudgments are used. Moreover, the entire code looks very long, which will cause a lot of trouble for subsequent iterations and expansions. Therefore, after considering the single responsibility principle of the design pattern, we can use the factory pattern to extract the return phase of the prize processing, so that each business logic in Complete in your own class.

First of all, we found from the business logic that no matter what kind of prize it is, it needs to be sent, so we can extract a unified entry interface and sending method: , ICommodity entry sendCommodity(String uId, String awardId, String bizId, Map<String, String> extMap)content includes 用户Id, 奖品Id, yewuId, 扩展字段to realize the unification of business logic, as shown in the UML diagram

image.png

Then, we can implement the corresponding logic inside the specific prize.

Finally, create a prize factory StoreFactory, which can realize different prize services by judging the prize type, as follows:

public class StoreFactory {

    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("不存在的商品服务类型");
    }
}

2. Template pattern

The core of the template pattern is: through a publicly defined method template in an abstract class, let subclasses inheriting the abstract class override the method to implement the template. It is a behavioral pattern .

2.1 Introduction to template mode

Define the general framework of an operation, and then implement the specific details in subclasses. That is, by defining the template method in the abstract class, let the subclass inherit the details of the template method. Let's take a look at the UML diagram of the template pattern:

image.png

  • AbstractClass: Abstract class. A series of basic operations are defined in the abstract class. These operations can be concrete or abstract. Each basic operation corresponds to a step of the algorithm. These steps can be redefined or implemented in its subclasses . At the same time, a template method is implemented in the abstract class TemplateMethod(), which is used to define the framework of an algorithm.
  • ConcreteClass: Concrete subclass that implements the abstract methods declared in the abstract class and completes the steps of the subclass-specific algorithm
  • Client: client, using the template method pattern

2.2 Template mode implementation

For example, when crawling different web resources and generating corresponding promotion posters, we will have fixed steps, such as: simulate login, crawl information, and generate posters. At this time, the process template can be extracted, and the corresponding subclass can implement specific steps. For example, crawling the webpage service information of WeChat official account, Taobao, JD.com, and Dangdang.com.

First, define an abstract class NetMall, and then define the corresponding abstract methods for simulating login login, crawling information reptile, and generating posters in this class createBasefor subclasses to inherit. The specific code is as follows:

public abstract class NetMall {

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

    public NetMall(String uId, String uPwd) {
        this.uId = uId;
        this.uPwd = uPwd;
    }
    // 1.模拟登录
    protected abstract Boolean login(String uId, String uPwd);

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

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

    /**
     * 生成商品推广海报
     *
     * @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\. 组装海报
    }

}

Next, take the crawling of Jingdong webpage information as an example to realize the specific steps:

public class JDNetMall extends NetMall {

    public JDNetMall(String uId, String uPwd) {
        super(uId, uPwd);
    }
    //1.模拟登录
    public Boolean login(String uId, String uPwd) {
        return true;
    }
    //2.网页爬取
    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");
        return map;
    }
    //3.生成海报
    public String createBase64(Map<String, String> goodsInfo) {
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(JSON.toJSONString(goodsInfo).getBytes());
    }

}

Finally run the test:

@Test
public void test_NetMall() {
    NetMall netMall = new JDNetMall("ethan", "******");
    String base64 = netMall.generateGoodsPoster("https://item.jd.com/100008348542.html");
}

The template mode is mainly to extract the core public code in the subclasses, and let each subclass correspond to the required content.

3. Strategy Pattern

The strategy pattern is a behavior type pattern. If there are many classes in a system, and the only thing that distinguishes them is their behavior, the strategy pattern can be used to switch at this time.

3.1 Introduction to Strategy Pattern

In the side rate pattern, we create objects representing various strategies and a context object whose behavior changes as the side rate object changes.

For example, Zhuge Liang's tricks, each trick is a strategy. In business logic, we generally use the same kind of alternative behavioral logic algorithm scenarios, such as different types of transaction methods (credit card, Alipay, WeChat), strategies for generating unique IDs (UUID, Snowflake algorithm, Leaf algorithm), etc. We can all use the strategy pattern to wrap its behavior first, and then provide it to the outside world for calling.

Note that if there are more than four strategies in a system, you need to consider using a mixed mode to solve the problem of strategy class expansion. Let's take a look at the corresponding UML structure diagram:

image.png

  • Stategy: Abstract strategy structure, which defines various algorithm implementation interfaces, and the context Contextcalls different algorithms through this interface
  • ConcreteStrategy1、ConcreteStrategy2: Implement the interface defined by the abstract policy and provide specific algorithm implementation
  • Context: The context class, also called the environment class, holds a reference to the strategy class and is the interface for calling the strategy from the outside world

3.2 Strategy Pattern Implementation

Take the unique ID generation business as an example. For example, before the Snowflake algorithm was proposed, we generally used UUID to confirm the unique ID. However, if you need to generate IDs in an orderly manner, you should consider other generation methods at this time, such as algorithms such as Snowflake and Leaf.

Maybe at the beginning we wrote a class directly, and called the UUID algorithm in the class to generate it, but when we need to call other methods, we have to use logic judgment in this class, and then convert it into if-elseanother algorithm. This approach, like the factory pattern mentioned earlier, will increase the coupling between classes. Therefore, we can use the strategy pattern to extract these strategies and implement them separately to prevent confusion caused by later expansion.

First, define an interface for ID generationIIdGenerator

public interface IIdGenerator {
    /**
     * 获取ID, 目前有三种实现方式
     * 1.雪花算法,主要用于生成单号
     * 2.日期算法,用于生成活动标号类,特性是生成数字串较短,但是指定时间内不能生成太多
     * 3.随机算法,用于生成策略ID
     * @return ID 返回ID
     */
    long nextId();
}

Let different generation ID strategies implement this interface:

image.png

The following is the specific implementation of the snowflake algorithm:

public class SnowFlake implements IIdGenerator {

    private Snowflake snowflake;

    @PostConstruct
    public void init() {
        //总共有5位,部署0~32台机器
        long workerId;
        try {
            workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
        } catch (Exception e) {
            workerId = NetUtil.getLocalhostStr().hashCode();
        }

        workerId = workerId >> 16 & 31;

        long dataCenterId = 1L;
        snowflake = IdUtil.createSnowflake(workerId, dataCenterId);
    }

    @Override
    public long nextId() {
        return snowflake.nextId();
    }
}

Next, define an ID policy control class IdContext , and use a unified method to perform ID policy calculation through different external policies, as shown below:

@Configuration
public class IdContext {

    @Bean
    public Map<Constants.Ids, IIdGenerator> idGenerator(SnowFlake snowFlake, ShortCode shortCode, RandomNumeric randomNumeric) {
        Map<Constants.Ids, IIdGenerator> idGeneratorMap = new HashMap<>(8);
        idGeneratorMap.put(Constants.Ids.SnowFlake, snowFlake);
        idGeneratorMap.put(Constants.Ids.ShortCode, shortCode);
        idGeneratorMap.put(Constants.Ids.RandomNumeric, randomNumeric);
        return idGeneratorMap;
    }
}

Therefore, in the final test, idGeneratorMapthe invocation of different strategy services can be realized by direct invocation:

 @Test
 public void init() {
     logger.info("雪花算法策略,生成ID: {}", idGeneratorMap.get(Constants.Ids.SnowFlake).nextId());
     logger.info("日期算法策略,生成ID: {}", idGeneratorMap.get(Constants.Ids.ShortCode).nextId());
     logger.info("随机算法策略,生成ID: {}", idGeneratorMap.get(Constants.Ids.RandomNumeric).nextId());
 }

Fourth, the mixed use of the three modes

In actual business development, generally a variety of design patterns are mixed together. The factory mode and the strategy mode are used together to eliminate if-elsethe nesting. The following is a combination of the cases in the factory mode to introduce:

4.1 Strategy mode + factory mode

In the factory mode in the first section, we use the factory to implement different types of prize distribution, but StoreFactorythere are still if-elsenesting problems in:

public class StoreFactory {

    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("不存在的商品

At this time, you can use the strategy mode to eliminate if-elsethe statement:

public class StoreFactory {
    /**设置策略Map**/
    private static Map<Integer, ICommodity> strategyMap = Maps.newHashMap();

    public static ICommodity getCommodityService(Integer commodityType) {
        return strategyMap.get(commodityType);
    }
    /**提前将策略注入 strategyMap **/
    public static void register(Integer commodityType, ICommodity iCommodity) {
        if (0 == commodityType || null == iCommodity) {
            return;
        }
        strategyMap.put(commodityType, iCommodity);
    }
}

Inherited in the prize interface InitializingBean, easy to inject strategiesstrategyMap

public interface ICommodity extends InitializingBean {

    void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap);
}

Then inject the corresponding strategy into the implementation of the specific strategy:

@Component
public class GoodsCommodityService implements ICommodity {

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

    private GoodsService goodsService = new GoodsService();

    @Override
    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) {
        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);
        if (!isSuccess) {
            throw new RuntimeException("实物商品发送失败");
        }
    }

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

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

    @Override
    public void afterPropertiesSet() throws Exception {
        StoreFactory.register(2, this);
    }
}

Finally run the test:

@SpringBootTest
public class ApiTest {

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

    @Test
    public void commodity_test() {
        //1.优惠券
        ICommodity commodityService = StoreFactory.getCommodityService(1);
        commodityService.sendCommodity("10001", "sdfsfdsdfsdfs", "1212121212", null);

        //2.实物商品
        ICommodity commodityService1 = StoreFactory.getCommodityService(2);
        Map<String, String> extMap = new HashMap<String, String>();
        extMap.put("consigneeUserName", "ethan");
        extMap.put("consigneeUserPhone", "12312341234");
        extMap.put("consigneeUserAddress", "北京市 海淀区 xxx");
        commodityService1.sendCommodity("10001", "sdfsfdsdfsdfs", "1212121212", extMap);

        //3.第三方兑换卡
        ICommodity commodityService2 = StoreFactory.getCommodityService(3);
        commodityService2.sendCommodity("10001", "SSDIIUIUHJHJ","12312312312",null);

    }
}

image.png

4.2 Strategy mode + factory mode + template mode

Still using the previous example, above we have implemented the business with the strategy + factory pattern, how to apply the template pattern to it? Let's look at the core ICommodityinterface first:

public interface ICommodity extends InitializingBean {

    void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap);
}

In this interface, there is only one sendCommoditymethod, so if different implementation methods are required in the class that implements the strategy, at this time we can use the idea of ​​template mode to replace the interface with an abstract class:

public abstract class AbstractCommodity implements InitializingBean {

    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) {
        //不支持操作异常,继承的子类可以任意选择方法进行实现
        throw new UnsupportedOperationException();
    }

    public String templateTest(String str) {
        throw new UnsupportedOperationException();
    }
}

As above, the inherited subclass methods can implement specific strategies arbitrarily, taking coupons as an example:

@Component
public class CouponCommodityService extends AbstractCommodity {

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

    private CouponService couponService = new CouponService();

    @Override
    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) {

        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 (couponResult.getCode() != 0000) {
            throw new RuntimeException(couponResult.getInfo());
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        StoreFactory.register(1, this);
    }
}

The advantage of this is that subclasses can choose to inherit some methods in the abstract class according to their needs, so as to realize the functions corresponding to their needs.

To sum up, the use of design patterns in daily business logic does not have to have design patterns in the code, and simple logic can be used if-else. If there is complex business logic and it conforms to the corresponding design pattern, then the usage pattern can really improve the logic and scalability of the code.

More ways to obtain learning materials: Leave a message in the comment area or get it by private message

Guess you like

Origin blog.csdn.net/weixin_45536242/article/details/125760509