Bootstrap

Java设计模式之——模板方法

模板方法(Template Method作为Java的设计模式之一,一个词概括其优势特点那就是:抽象步骤

接下来以一个问答的形式进行理解记录

base代码:现在你要制作一个蛋糕,你会很多种做法,我们将制作蛋糕具象化成为代码,如下

public class Cake {
 
	public static int METHOD_1 = 1;

    public static int METHOD_2 = 2;

    public void make(int method) {
		System.out.println("make start");
		if (method == METHOD_1) {
			// 用方法1做蛋糕
		} else if (method == METHOD_2) {
			// 用方法2做蛋糕
		}
		System.out.println("make finish");
	}
 
}

提问1:现在你灵光一闪,又想出一种制作方案你该怎么办?

回答1:你会毫不犹豫的说,追加一个method的static字段,以及追加一个if分支条件判断

public class Cake {
 
	......

    public static int METHOD_3 = 3;

    public void make(int method) {
		System.out.println("make start");
		......
		} else if (method == METHOD_3) {
			// 用方法3做蛋糕
		}
        ......
		System.out.println("make finish");
	}
 
}

提问2:这确实是一个解决方案,但是你有没有考虑过如果一个if里面有百余行,而且你又有非常多的制作内容怎么办,怎么解决冗长问题?

也许你会想,就加代码呗,反正我都熟悉。但是如果你不是重头写代码的人,你拿到这个类的时候就有N多种策略超过千行的代码,你是否会吐槽怎么全堆在一起了呢?如果你是一个优秀设计者或leader,review这套代码时能不想去优化吗?

为了更好的可扩展性和可读性我们可以引入模板方法

首先我们应该抽出共通的东西做一个父类(Base类),其次具体的蛋糕制作由子类进一步实现,每一种制作方式我们就拓展一个子类

仔细观察Cake类,你会发现make方法是这个类的核心,make()中便是执行制作的核心代码,为了尽可能将共通的东西都留在父类(base类),我们要进一步将make()中的代码拆分,让其变成众多子步骤方法如下:

public abstract class Cake {

    final void make() {
        makeStart();//log
        makingCakeGerm();//做蛋糕胚
        makingCream();//制作奶油
        wipeCakeGerm();//抹蛋糕胚
        piping();//裱花
        makeFinish();//log
    }

    private void makeStart() {
        System.out.println("make start");
    }

    protected abstract String makingCakeGerm();

    protected abstract String makingCream();

    protected abstract String wipeCakeGerm();

    protected abstract String piping();

    private void makeFinish() {
        System.out.println("make finish");
    }
}

我们可以看到已经把make方法中的步骤进一步拆分细化,有先做蛋糕胚,再做奶油,然后涂抹到蛋糕胚上,最后裱花

我们来注意几个细节:

1、cake类成为了抽象类,没啥好说的,方法都抽象了

2、make方法增加了final修饰,不希望子类覆写这个方法,防止流程被更改

3、步骤抽象化,这是模板方法的核心,步骤的具体实现交由子类自行实现,每一种蛋糕都可以用不同的子类独立实现,这样可读性就大大提高了,而且扩展也非常方便,只需要实现一个新的子类即可

接下来我们实现具体的蛋糕子类:

我们这里可以这样想,蛋糕胚基本上没什么差别,抹奶油也都是常规操作,不一样的是制作什么样的奶油以及裱花的样式

public abstract class Cake {

    final void make() {
        makeStart();//log
        makingCakeGerm();//做蛋糕胚
        makingCream();//制作奶油
        wipeCakeGerm();//抹蛋糕胚
        piping();//裱花
        makeFinish();//log
    }

    private void makeStart() {
        System.out.println("make start");
    }

    private void makingCakeGerm() {
        System.out.println("制作蛋糕胚");
    }

    protected abstract void makingCream();//内容不共通的方法,留在子类实现

    private void wipeCakeGerm() {
        System.out.println("将奶油抹到蛋糕胚上");
    }

    protected abstract void piping();//内容不共通的方法,留在子类实现

    private void makeFinish() {
        System.out.println("make finish");
    }

}

子类实现蛋糕A蛋糕B

public class Cake_A extends Cake {

    @Override
    public void makingCream() {
        System.out.println("植物奶油 抹茶味");
    }

    @Override
    public void piping() {
        System.out.println("裱花八朵");
    }
}

/******************************************/

public class Cake_B extends Cake {

    @Override
    public void makingCream() {
        System.out.println("动物奶油 巧克力味");
    }

    @Override
    public void piping() {
        System.out.println("裱个生日快乐");
    }
}

最后Main类执行:

public class Test{

  public static void main(String[] args){
      Cake_A a = new Cake_A();
      Cake_B b = new Cake_B();
      a.make(); 
      System.out.println("******************");
      b.make(); 
  }   
}

看一下输出结果:

I/System.out: make start
I/System.out: 制作蛋糕胚
I/System.out: 植物奶油 抹茶味
I/System.out: 将奶油抹到蛋糕胚上
I/System.out: 裱花八朵
I/System.out: make finish
I/System.out: ******************
I/System.out: make start
I/System.out: 制作蛋糕胚
I/System.out: 动物奶油 巧克力味
I/System.out: 将奶油抹到蛋糕胚上
I/System.out: 裱个生日快乐
I/System.out: make finish

上述示例就是模板方法的一个实际使用模型。

总结一下优缺点:

优点:

  • 复用性增强,可读性增强将相同部分的代码放在抽象的父类中,部分实现代码在子类中完成
  • 扩展性增强,通过增加子类来设计新的逻辑,一子类对应一套逻辑
  • 遵守了开闭原则,并通过父类调用其子类的扩展方法实现了反向控制

缺点:

       缺点很显然了,你如果拥有海量的蛋糕设计方法,那你就要实现非常多个子类

应用场景:

       在逻辑设计时,将不变(也称共通)的逻辑做在父类中,变化的逻辑以抽象方法的形式在父类中,由子类后续继承实现,且后续的改动和拓展均应由子类完成,维持父类不动

-------------------------------解答时间----------------------------------

问:有人会说Test类应该是父类型引用指向子类对象,然后调用父类型变量中的.make()方法吧?

答:显然没必要,这种设计模式的核心思想不在多态上,向上转型诚然也可以实现,但是模板方法的核心在于抽象步骤,公共复用,所以用不用向上转型影响不大

问:有人学习了策略模式后会感到疑惑,这和模板方法也太像了吧?

答:不要怀疑,确实很像!区别就是策略模式用接口实现逻辑的扩展性,而模板方法用继承的方式完成逻辑的扩展性

---------------------------------------------------------------------------

虽然到这里就是模板方法的大体设计思想了,但是这还不算完,模板方法还有一个punchline

 “钩子方法”

提问3:现在你突然着急吃蛋糕了,那怎么办?砍步骤呗,最后那个裱花不要了,这个时候代码怎么写?

父类增加判断默认执行裱花,子类增加标记位isPipe,并提供设定标记位的函数isPipe(),这个函数就是钩子方法

public abstract class Cake {

    final void make() {
        makeStart();//log
        makingCakeGerm();//做蛋糕胚
        makingCream();//制作奶油
        wipeCakeGerm();//抹蛋糕胚
        if (isPipe()) {
            piping();//裱花
        }
        makeFinish();//log
    }

    ......
    //父类增加一个方法用来判定标记位置,这里默认用true,后续子类拓展
    protected boolean isPipe() {
		return true;
	}
}
public class Cake_C extends Cake {

    private boolean isPipe = true;//是否裱花的标记位

    @Override
    public void makingCream() {
        System.out.println("动物奶油 巧克力味");
    }

    @Override
    public void piping() {
        System.out.println("裱个生日快乐");
    }

    @Override
    public boolean isPipe() {
        return this.isPipe;
    }

    public void setPipe(boolean isPipe) {//设定标记位
		this.isPipe = isPipe;
	}
}

最后我们更改一下Test类的写法:

public class Test{

  public static void main(String[] args){
      Cake_A a = new Cake_A();
      Cake_C c = new Cake_C();
      c.setPipe(false);
      a.make(); 
      System.out.println("******************");
      c.make(); 
  }   
}

我们在Test类中设定蛋糕C不裱花执行,这回蛋糕C没有裱花就做完了。

细想,父类其实并没有变,依然能够完整执行,但是却能靠子类的方法返回值,来改变最终的执行结果,这便是模板方法的点睛之笔了,而这篇日记到此也就完整了。

;