Java设计模式之模板模式

273 阅读5分钟

这是我参与更文挑战的第16天,活动详情查看:更文挑战

模板模式

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

就像西游记的99八十一难,基本每一关都是:师傅被掳走、打妖怪、妖怪被收走,具体什么妖怪你自己定义,怎么打你想办法,最后收走还是弄死看你本事,我只定义执行顺序和基本策略,具体的每一难由观音来安排。

案例场景模拟

我们模拟爬虫爬取各类商家的商品信息,生成推广海报(海报中含带个人的邀请码)赚取商品返利。声明,这里是模拟爬取,并没有真的爬取。

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

1.因为有些商品只有登录后才可以爬取,并且登录可以看到一些特定的价格这与未登录用户看到的价格不同。

2.不同的电商网站爬取方式不同,解析方式也不同,因此可以作为每一个实现类中的特定实现。

3.生成海报的步骤基本一样,但会有特定的商品来源标识。所以这样三个步骤可以使用模板模式来设定,并有具体的场景做子类实现。

模板模式模型结构

模版模式模型结构

一个定义了抽象方法执行顺序的核心抽象类,以及三个模拟具体的实现(京东、淘宝、当当)的电商服务。

代码实现

定义执行顺序的抽象类

public abstract class NetMall {

    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);
}
  • 这个类是此设计模式的灵魂

  • 定义可被外部访问的方法generateGoodsPoster,用于生成商品推广海报

  • generateGoodsPoster在方法中定义抽象方法的执行顺序1,2,3步

  • 提供三个具体的抽象方法,让外部继承放实现;模拟登录(login)、模拟爬取(reptile)、生成海报(createBase64)

模拟爬虫京东

public class JDNetMall extends NetMall {

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

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

    protected Map<String, String> reptile(String skuUrl) {
        Map<String, String> map = new ConcurrentHashMap<String, String>();
        map.put("name", "JDBook");
        map.put("value", "100");
        map.put("Mall", "JD");
        logger.debug("模拟京东爬取商品信息:{}|{}元", map.get("name"), map.get("value"));
        return map;
    }

    protected String createBase64(Map<String, String> goodsinfo) {
        BASE64Encoder encoder = new BASE64Encoder();
        logger.debug("模拟生成京东商品base64海报");
        return encoder.encode(JSON.toJSONBytes(goodsinfo));
    }
}

模拟爬虫淘宝

public class TaoBaoNetMall extends NetMall {

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

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

    protected Map<String, String> reptile(String skuUrl) {
        Map<String, String> map = new ConcurrentHashMap<String, String>();
        map.put("name", "TaoBaoBook");
        map.put("value", "80");
        map.put("Mall", "TaoBao");
        logger.debug("模拟淘宝爬取商品信息:{}|{}元", map.get("name"), map.get("value"));
        return map;
    }

    protected String createBase64(Map<String, String> goodsinfo) {
        BASE64Encoder encoder = new BASE64Encoder();
        logger.debug("模拟生成淘宝商品base64海报");
        return encoder.encode(JSON.toJSONBytes(goodsinfo));
    }
}

模拟当当爬取

public class DangDangNetMall extends NetMall {

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

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

    protected Map<String, String> reptile(String skuUrl) {
        Map<String, String> map = new ConcurrentHashMap<String, String>();
        map.put("name", "DangDangBook");
        map.put("value", "50");
        map.put("Mall", "DangDang");
        logger.debug("模拟当当爬取商品信息:{}|{}元", map.get("name"), map.get("value"));
        return map;
    }

    protected String createBase64(Map<String, String> goodsinfo) {
        BASE64Encoder encoder = new BASE64Encoder();
        logger.debug("模拟生成当当商品base64海报");
        return encoder.encode(JSON.toJSONBytes(goodsinfo));
    }
}

测试验证

public class NetMallTest {

    @Test
    public void test() {
        JDNetMall netMall = new JDNetMall("root", "123456");
        String base64 = netMall.generateGoodsPoster("https://item.jd.com/100008348542.html");
        System.out.println(base64);
    }
}

总结

  • 通过上面的实现可以看到模板模式在定义统一结构也就是执行标准上非常方便,也就很好的控制了后续的实现者不用关心调用逻辑,按照统一方式执行。那么类的继承者只需要关心具体的业务逻辑实现即可。

  • 另外模板模式也是为了解决子类们的通用方法,放到父类中设计的优化。让每一个子类只做子类需要完成的内容,而不需要关心其他逻辑。这样提取公用代码,行为由父类管理,扩展可变部分,也就非常有利于开发扩展和迭代。

  • 但每一种设计模式都有自己的特定场景,如果超过场景外的建设就需要额外考虑其他模式的运用。而不是非要生搬硬套,否则自己不清楚为什么这么做,也很难让后续者继续维护代码。而想要活学活用就需要多加练习,有实践的经历。

更多

在Spring源码AbstractBeanFactory类中同样用到了模板模式,代码如下:

public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {

    public Object getBean(String name) throws BeansException {
        Object bean = getSingleton(name);
        if (bean != null) {
            return bean;
        }

        BeanDefinition beanDefinition = getBeanDefinition(name);
        return createBean(name, beanDefinition);
    }
    protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeansException;
    protected abstract Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException;
}

对外暴露一个getBean()方法,其中定义了getBeanDefinition和createBean两个方法的执行顺序。