关于设计和设计模式

一早就明白一个原则,dry=don't repeat yourself。这个原则引导我去思考怎么在编码过程去掉重复。为了满足这个原则,就想尽办法把重复的代码抽取出来。慢慢明白到,重复代码的背后有其原因,很大的可能是缺乏思考和设计(还有根子里的懒惰)。

当理解到重复的原因,也会理解到设计的目的。按我的理解,设计的目的是用合理的代码结构去表达业务,固定住不可变的部分,限制即保护,预留出要扩展的地方以应对变化。当开始思考设计,就会明白设计模式是怎样的东东,一点不神圣,就只是前辈们留下的经验。

模板模式

其实最先接触的是单例模式(singleton),但是实际工作中至今没有用过,不过是为了糊弄面试。除了它,就是模板模式了。主要是简单。

应用场景

按我的理解解释下,有一些的操作他们的执行流程有以下特点:

  1. 一致的执行顺序(或者可以叫流程骨架)
  2. 某些操作是相同的(记录日志,try-catch)
  3. 某些操作是不同的(具体业务操作)

这个场景就可以用模板模式了。举个例子,一个查询业务可以分为以下几步:

  1. 打印日志
  2. 校验入参,失败抛参数异常
  3. 业务操作:查询相应的数据
  4. try-catch 处理异常情况
  5. 封装返回结果

可以发现 0、1、3、4都是重复的代码,只有2是不同的。DRY!那么我们可以提供一个模板来实现重复的部分。你可以想想你平时的编码中有哪些可以应用模板模式的。

实现方式

用两种方法来实现,稍后我们来比较两种方法的异同。

  • 模板方法,抽象模板父类提供流程骨架,具体实现类提供具体实现
  • 模板模式,模板对象提供流程骨架,回调方法提供具体实现接口定义,具体实现类提供回调方法的实现

好了,直接show code. github代码 :
模板模式
测试用例

模板方法:

组件:抽象模板父类,提供流程接口和扩展点

public abstract class AbstractQuery<F extends Form, R extends Result> {
    private Logger logger = LoggerFactory.getLogger(QueryTemplate.class);

    public R query(F form) {
        logger.info("查询入参:{}", form);

        R r = null;

        try {
            //流程框架,所有流程以此为准,实现类将不能改变流程
            r = initResult();

            form.check();

            r = query(r, form);

            r.setSuccess();

        } catch (IllegalArgumentException e) {
            logger.info("参数错误,{}", e.getMessage());
            r.setFail("INVALID_PARAM", e.getMessage());
        } catch (Exception e) {
            logger.info("内部错误", e);
            r.setFail("INTERNAL_ERROR", "系统内部错误");
        }

        logger.info("查询出参:{},{}", r.getCode(), r.getMessage());
        return r;
    }

    /**
     * 查询方法
     * @param form
     * @return
     */
    protected abstract R query(R r, F form);

    /**
     * 初始化结果
     * @return
     */
    protected abstract R initResult();

}

组件:具体实现类,实现具体的业务逻辑

public class UserQueryService extends AbstractQuery<UserQueryForm, UserQueryResult> {

/**
 * @param r
 * @param form
 * @return
 * @see info.maizz.lifebetterkit.designpattern.template.templatemethod.AbstractQuery#query(info.maizz.lifebetterkit.entities.Result,
 * info.maizz.lifebetterkit.designpattern.template.Form)
 */
@Override
protected UserQueryResult query(UserQueryResult r, UserQueryForm form) {
    String userId = form.getUserId();
    //脑补:查找数据库,这里随便写点数据
    UserInfo userInfo = new UserInfo();
    userInfo.setUserId(userId);
    userInfo.setRegTime(new Date());
    userInfo.setUsername("haha@github.com");
    r.setUserInfo(userInfo);
    return r;
}

/**
 * @return
 * @see info.maizz.lifebetterkit.designpattern.template.templatemethod.AbstractQuery#initResult()
 */
@Override
protected UserQueryResult initResult() {
    return new UserQueryResult();
}

模板模式

组件:template 模板,独立组件,提供流程骨架

public class QueryTemplate {

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

    /**
     * 查询方法,抽取的是所有查询方法共用的部分,扩展的部分由回调方法提供
     * @param form 查询表单
     * @param callBack 查询回调,由实际实现类提供
     * @return
     */
    public <F extends Form, R extends Result> R query(F form, QueryCallBack<F, R> callBack) {
        logger.info("查询入参:{}", form);

        R r = null;

        try {
            //流程框架,所有流程以此为准,实现类将不能改变流程
            r = callBack.initResult();

            form.check();

            r = callBack.query(r, form);

            r.setSuccess();

        } catch (IllegalArgumentException e) {
            logger.info("参数错误,{}", e.getMessage());
            r.setFail("INVALID_PARAM", e.getMessage());
        } catch (Exception e) {
            logger.info("内部错误", e);
            r.setFail("INTERNAL_ERROR", "系统内部错误");
        }

        logger.info("查询出参:{},{}", r.getCode(), r.getMessage());
        return r;
    }
}

组件:回调接口,实现该接口,提供具体的逻辑

public interface QueryCallBack<F extends Form, R extends Result> {

    /**
     * 初始化结果
     * @return
     */
    public R initResult();

    /**
     * 查询方法
     * @param form
     * @return
     */
    public R query(R result, F form);
}

组件:具体实现类,实际业务实现者

public class UserQueryService {

    /**
     * 查询用户
     * @param form
     * @return
     */
    public UserQueryResult queryUser(UserQueryForm form) {
        //具体的服务类只负责实现具体查询逻辑,不需要再处理日志等问题
        return new QueryTemplate().<UserQueryForm, UserQueryResult> query(form,
            new QueryCallBack<UserQueryForm, UserQueryResult>() {

                @Override
                public UserQueryResult initResult() {
                    return new UserQueryResult();
                }

                @Override
                public UserQueryResult query(UserQueryResult r, UserQueryForm form) {
                    String userId = form.getUserId();
                    //脑补:查找数据库,这里随便写点数据
                    UserInfo userInfo = new UserInfo();
                    userInfo.setUserId(userId);
                    userInfo.setRegTime(new Date());
                    userInfo.setUsername("haha@github.com");
                    r.setUserInfo(userInfo);
                    return r;
                }
            });
    }

}

使用方式:
两种方式在使用并无区别。

public void testMethodQuery() {
        UserQueryForm form = new UserQueryForm();
        form.setUserId("A cool guy!");
        //模板方法
        UserQueryResult methodResult = new UserQueryMethodService().query(form);
        print(methodResult);
        //模板模式
        UserQueryResult templateResult = new UserQueryService().queryUser(form);
        print(templateResult);
    }

这两种方式哪种更好呢?
模板方法在父类固定了接口,子类实现只能实现一个方法;而模板方式可以实现多个方法。 模板方式没有占用父类继承,为继承其他类留下了接口

怎么选择就看权衡了。

缺点

  • 模板不适合处理流程

    由于文化不高,以前尝试过用模板方法来解决流程问题,把共同的部分抽在模板方法父类里,然后留下各种扩展点给子类。后来流程变得复杂起来,就搞出来一个继承链,重要的是各种扩展点,那些本来看来是不变的部分也有可能会变了。
    究其根本,模板不是用来处理流程的,在流程处理里他更加适合做的是某一个流程节点的不同实现。而且建议不要将流程骨架设置的过于复杂。

  • 模板会割裂代码,造成理解上的困难

    模板天生会把代码拆成若干个部分,如果用滥了,有比较多的扩展点,要理解业务流程就需要在模板和具体类里面不停跳跃,非常恼火。所以建议将业务的主流程放在某个模板方法里去实现,不要分开到多个方法中,造成阅读上的困难。

综上所述,模板适合的是做好某一个点上的事情,可以通过抽离共同点和不同点,保护不变的部分,又留下变的部分。目前我自己用在了简单的查询方法的封装,基本的facade服务实现上面。这两个场景都可以通过aop来实现,代码也更简洁。

最后附上一段spring(TransactionTemplate)的代码,作为佐证。

package org.springframework.transaction.support;
...

@SuppressWarnings("serial")
public class TransactionTemplate extends DefaultTransactionDefinition
        implements TransactionOperations, InitializingBean {
    ...

    public <T> T execute(TransactionCallback<T> action) throws TransactionException {
        if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
            return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
        }
        else {
            TransactionStatus status = this.transactionManager.getTransaction(this);
            T result;
            try {
                result = action.doInTransaction(status);
            }
            catch (RuntimeException ex) {
                // Transactional code threw application exception -> rollback
                rollbackOnException(status, ex);
                throw ex;
            }
            catch (Error err) {
                // Transactional code threw error -> rollback
                rollbackOnException(status, err);
                throw err;
            }
            catch (Exception ex) {
                // Transactional code threw unexpected exception -> rollback
                rollbackOnException(status, ex);
                throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
            }
            this.transactionManager.commit(status);
            return result;
        }
    }

...

}

用法,是不是很像?

configedNewTransactionTemplate.execute(new TransactionCallback<P>() {
    @Override
    public P doInTransaction(TransactionStatus transactionStatus) {
        try {
            //业务逻辑处理
            P p = processor.process(order, result);
            return p;
        } catch (Exception e) {
            transactionStatus.setRollbackOnly();
            throw e;
        }
    }
});