Java设计模式之模板方法模式和建造者模式

3,461 阅读7分钟

一、前期回顾

上一篇《Java 设计模式之工厂方法模式与抽象工厂模式》介绍了三种工厂模式,分别是工厂方法模式,简单工厂方法模式,抽象工厂模式,文中详细根据实际场景介绍了三种模式的定义,实践,最后总结了三种方式的区别,以及各个模式的适用场景。这一篇博文我们来学习下模板方法模式和建造者模式。

二、模板方法模式和建造者模式的定义与实践

2.1 模板方法模式的定义和实践

定义:Define the skeleton of an algorithm in an operation.deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

翻译:定义一个操作中的算法框架,从而延迟子类中的一些步骤。使得子类可以不改变算法结构的情况下就可以重新定义该算法的某些特定的步骤。

其实根据上面定义,我们很快就可以想到,这个模式的定义不就是继承吗?对,没错,模板方法模式最简单的实现就是继承。我们先用代码来实现一个模板方法模式。相信大家都吃过泡面吧,我们来用模板方法模式来泡个面吧。

public abstract class AbstractTemplate {
    /**
     * 烧开水*/
    public abstract void boilWater();
    /**煮面条*/
    public abstract void cookNoodles();
    /**放调料*/
    public abstract void putCondiment();
    /**定义煮面的模板,先烧水,再放面条,最后放调料*/
    public void finish(){
        boilWater();
        cookNoodles();
        putCondiment();
        System.out.println("煮完啦,开吃咯!");
    }
}
/**煮方便面模板实现类*/
public class InstantNoodlesTemplate extends AbstractTemplate{
    @Override
    public void boilWater() {
        System.out.println("烧开水啦!");
    }

    @Override
    public void cookNoodles() {
        System.out.println("放入方便面啦!");
    }

    @Override
    public void putCondiment() {
        System.out.println("可以放调料啦!");
    }
}
/**客户端场景类*/
public class Client {
    public static void main(String[] args) {
        AbstractTemplate template=new InstantNoodlesTemplate();
        template.finish();
    }
}

上面的的finish()方法就是定义了一个算法框架,定义了煮面条先要烧开水,然后放面条,最后放调料的算法步骤。然后各个子类实现如何烧水,放什么面条,放什么调料这样的具体实现。这就是模板方法模式,仅仅通过继承就实现了。就是这么简单,下面我们来看看和模板方法模式相识的另外一个模式,建造者模式。

2.21 建造者模式的定义和实践

定义:Separate the construction of a complex object from its representation so that the same construction process can create different representations.

翻译:将一个复杂对象的构建与他的表现分离,使得同样的构建过程可以创建不同的表示。

这里化重点,复杂对象的构建和表现分离,这里用在上面煮面的场景中就是说,煮面的算法和定义是分离的,也就是说由各个具体的面条品种决定如何去煮面条,如何去放调料等等这些具体的算法步骤。我们来用代码实现下:

//抽象煮苗条类
public abstract class AbstractNoodlesMode {
    private List<String> stepOrders = new ArrayList<>();

    /**
     * 烧开水
     */
    public abstract void boilWater();

    /**
     * 煮面条
     */
    public abstract void cookNoodles();

    /**
     * 放调料
     */
    public abstract void putCondiment();

    /**
     * 煮面条其他步骤
     */
    public abstract void doOther();
    /**
     * 根据传入的工序进行加工煮面
     */
    final public void finish() {
        for (String stepOrder : this.stepOrders) {
            switch (stepOrder) {
                case "boilWater":
                    boilWater();
                    break;
                case "cookNoodles":
                    cookNoodles();
                    break;
                case "putCondiment":
                    putCondiment();
                    break;
                case "doOther":
                    doOther();
                    break;
                default:
                    System.out.println("无法识别的烹饪指令");
                    break;
            }
        }
    }

    final public void setStepOrders(List<String> stepOrders) {
        this.stepOrders = stepOrders;
    }
}
/**方便面的实现类,由于只需要烧水,煮面,放调料就行了,other方法就为空*/
public class InstantNoodles extends AbstractNoodlesMode {
    @Override
    public void boilWater() {
        System.out.println("煮开水");
    }

    @Override
    public void cookNoodles() {
        System.out.println("放入方便面");
    }

    @Override
    public void putCondiment() {
        System.out.println("放入调料");
    }

    @Override
    public void doOther() {

    }
}
/**意大利面条实现类*/
public class Spaghetti extends AbstractNoodlesMode {
    @Override
    public void boilWater() {
        System.out.println("煮开水");
    }

    @Override
    public void cookNoodles() {
        System.out.println("放入意大利面");
    }

    @Override
    public void putCondiment() {
        System.out.println("放入番茄酱");
    }

    @Override
    public void doOther() {
        System.out.println("放入火腿,早餐肉");
    }
}
/**抽象建造者类*/
public abstract class AbstractBuilder {
    /**定义煮面条工序*/
    public abstract AbstractBuilder cookNoodles();
    /**完成面条*/
    public abstract AbstractNoodlesMode build();
}
/**泡面建造者实现类*/
public class InstantNoodlesBuilder extends AbstractBuilder {
    private AbstractNoodlesMode noodles=new InstantNoodles();
    @Override
    public AbstractBuilder cookNoodles() {
        List<String> steps=new ArrayList<>();
        //烧水
        steps.add("boilWater");
        //放面条
        steps.add("cookNoodles");
        //放调料
        steps.add("putCondiment");
        this.noodles.setStepOrders(steps);
        return this;
    }

    @Override
    public AbstractNoodlesMode build() {
        return this.noodles;
    }

}

/*意大利面条建造者实现类**/
public class SpaghettiBuilder extends AbstractBuilder {
    private AbstractNoodlesMode noodle = new Spaghetti();

    @Override
    public AbstractBuilder cookNoodles() {
        List<String> steps = new ArrayList<>();
        //烧水
        steps.add("boilWater");
        //放面条
        steps.add("cookNoodles");
        //放调料
        steps.add("putCondiment");
        //放火腿,放早餐肉
        steps.add("doOther");
        this.noodle.setStepOrders(steps);
        return this;
    }

    @Override
    public AbstractNoodlesMode build() {
        return this.noodle;
    }
}

/**客户端场景类*/
public class Client {
    public static void main(String[] args) {
        AbstractBuilder instantNoodleBuilder = new InstantNoodlesBuilder();
        AbstractBuilder spaghettiBuilder = new SpaghettiBuilder();
        AbstractNoodlesMode instantNoodle = instantNoodleBuilder.cookNoodles().build();
        instantNoodle.finish();
        System.out.println("--------------------");
        AbstractNoodlesMode spaghe = spaghettiBuilder.cookNoodles().build();
        spaghe.finish();
    }
}

我们运行下结果如下:

上述代码我们整理成一个类图如下:

上面类图我省去了面条实现类,但是这也不影响整个建造者模式的理解。这里有同学肯定会问,这个建造者模式虽然比模板方法模式复杂点,但是感觉他们很相似啊,有点弄混的感觉啊。对,没错,他们确实很相识。但是我们只要记住一点就能很好的区分他们。

  • 模板方法模式已经定义好了建造者的算法,也就是工序,先做什么后做什么。
  • 建造者模式没有定义建造者的工序,而是交给子类去实现建造者的算法,也就是工序。

我们只要记住这一点就能很好的区分模板方法模式和建造者模式,同时也就知道了什么时候采用模板方法模式,什么时候采用建造者模式。

三、模板方法模式和建造者模式的应用场景

3.1 模板方法模式的使用场景

1.多个子类有公有的方法,并且逻辑基本一样时。

2.有重要核心的复杂算法,而且需要很多的复用性时,可以把该算法用模板方法模式实现。

3.代码重构升级时,模板方法模式是很常用的模式,一般为了保证向下兼容,所以采用模板方法模式,在模板方法内会设置钩子函数,根据不同的版本和不同的情况执行不同的算法。

3.2 建造者模式的使用场景

1.相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式

2.多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。

四、模板方法模式和建造者模式的优点与缺点

4.1模板方法模式

优点:

  • 良好的扩展性,子类只要实现指定的算法即可,不用关注后续组合的算法
  • 良好的可维护性。
  • 符合依赖倒置原则

缺点:

  • 个人理解的缺点可能就是封装死了核心组合方法,但是这就是模板方法的特点,姑且算一个缺点吧。

4.2建造者模式

优点:

  • 和模板方法模式一样具有良好的扩展性和可维护性
  • 灵活性,由于连最核心的组合算法都交给子类去实现,所以更灵活。

缺点:

  • 风险性,由于放开了组合算法,降低了约束性。所以可能会导致子类调用定义错了算法,给系统带来潜在的风险。

五、总结

模板方法模式和建造者模式都是属于创造性模式,两个模式代码很相似,很容易弄混,我们只要记住他们的核心区别就可以知道什么时候该用模板方法模式,什么时候该用建造者模式。这里再说一遍他们的主要区别: 模板方法模式定义了核心算法,也就是组装工序,而建造者模式没有定义建造的顺序和构造多少零件,最后的核心工序以及使用零件实现完全由子类去决定。

详细细心的读者肯定以及发现了我的建造模式中的实现出现了instantNoodleBuilder.cookNoodles().build()这样A.B.C的代码,之前在我们的设计模式开篇我们谈到了迪米特法则,迪米特法则有举例说不要出现A.B.C的情况,这里说明下迪米特法则的要求其实是不要出现依赖非朋友类的情况,其实实际上是不要出现A.getB.getC的情况,而这里其实每次返回的都是this,所以都是依赖的自己,并没有出现非朋友类,所以这样的写法没有违法迪米特法则。

六、参考

《设计模式之禅》

七、推荐阅读

Java设计模式之工厂方法模式与抽象工厂模式

Java设计模式之单例模式

JAVA设计模式之开篇

带你走进java集合之ArrayList

带你走进java集合之HashMap

Java锁之ReentrantLock(一)

Java锁之ReentrantLock(二)

Java锁之ReentrantReadWriteLock