模板方法:在一个方法中定义一个算法的骨架,而将一些步骤延伸到子类中。
模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
首先设想以下情景。
我要在有一个特定背景的图上画一个对象,这个对象目前不知道具体是怎样的,它可以因人而异。我们用JAVA如何处理这种情形呢。
首先我们定义一个类把背景画好。
public class Picture {
public static void picture() {
System.out.println("画边框");
System.out.println("画背景");
//画一个未知对象
System.out.println("完成余下的工作");
}
}
这个类只完成背景部分,对象部分未完成。
接下来我们可以在类外面画好特定的对象,然后当参数传进来。这种处理方法太过狭小并不能满足要求,要求是因人而异的对象,你总不能传n个参数进来吧。最好只传一个参数进来就能满足因人而异的要求。
我们可以将这个对象抽象化,我们只负责把画对象的事件定义好,至于怎么完成这件事就可以因人而异。用代码来说就是定义一个接口,接口里定义一个方法来完成这件事,至于怎么完成则由实现这个接口的类完成就可以了,利用多态将接口作为picture()的参数就可以完成因人而异的操作了。
完整代码:
public class Picture {
public static void picture(Object object) {
System.out.println("画边框");
System.out.println("画背景");
//画一个未知对象
object.paint();
System.out.println("完成余下的工作");
}
public static void main(String[] args) {
picture(new man());
picture(new Animal());
}
}
class man implements Object{
public void paint() {
// TODO 自动生成的方法存根
System.out.println("画一个人");
}
}
class Animal implements Object{
public void paint() {
// TODO 自动生成的方法存根
System.out.println("画一个动物");
}
}
public interface Object {
void paint();
}
除了向picture传参,我们还可以将picture中不确定的步骤用一个抽象方法代替。这样每个子类就可以根据不同的情况来实现这个方法。
public abstract class Picture2 {
public void picture() {
System.out.println("画边框");
System.out.println("画背景");
//画一个未知对象
paint();
System.out.println("完成余下的工作");
}
abstract void paint();
}
public class Test {
public static void main(String[] args) {
Picture2 man=new PMan();
Picture2 animal=new PAnimal();
man.picture();
animal.picture();
}
}
class PMan extends Picture2{
void paint() {
// TODO 自动生成的方法存根
System.out.println("画一个人");
}
}
class PAnimal extends Picture2{
void paint() {
// TODO 自动生成的方法存根
System.out.println("画一个动物");
}
}
这个代码跟上面的代码实质是一样,至于用参数还是抽象方法看个人和具体情况吧。
开篇的问题解决了,接下来我们对其进行延伸。首先注意到画一个对象到画里是必须的,所以我们对要用抽象方法来完成它,这样子类必须实现这个方法。
接下来我们对背景进行处理一下,为了使这幅画更加多样化,我们将背景拆分一下,一些景色变成可选的,因为个人审美观不同可以选择是否将这部分背景加上。
void must()方法表示必须的背景,void optional()方法表示可选背景。
我们再加一个方法 boolean choose()来实现可选。
这样子类就可以通过重写choose()方法来控制选与不选了。
注意这里choose()方法不能为抽象方法,如果为抽象方法每个子类就必须实现它,这样会使子类过于累赘。
public abstract class Picture2 {
public void picture() {
System.out.println("画边框");
must();
if (choose()) {
optional();
}
//画一个未知对象
paint();
System.out.println("完成余下的工作");
}
abstract void paint();
private void must() {
System.out.println("画必须背景");
}
private void optional() {
System.out.println("画可选背景");
}
public boolean choose() {
return true;
}
}
public class Test {
public static void main(String[] args) {
Picture2 man=new PMan();
Picture2 animal=new PAnimal();
man.picture();
animal.picture();
}
}
class PMan extends Picture2{
boolean t=true;
void paint() {
// TODO 自动生成的方法存根
System.out.println("画一个人");
}
public boolean choose() {
return t;
}
}
class PAnimal extends Picture2{
boolean t=false;
public boolean choose() {
return t;
}
void paint() {
// TODO 自动生成的方法存根
System.out.println("画一个动物");
}
}
现在我们对上面两种情况做个总结。
明显的是两种情况都有固定的步骤。在固定步骤中间有一些不明确的步骤。而这不明确的步骤ye分两类。
第一类,这部分不明确的步骤是必须的,它不明确在它有多种选择。
第二类,这部分步骤是明确的,它不明确在于要不要实施。
针对这两类不明确情况,我们也有各自的对应方法。
第一类是必须的,所以我们用抽象方法来完成,因为子类必须实现抽象方法。
第二类是可选的,我们用普通方法来做出选择,选择与否通过子类是否重写它。子类重写的方法我们叫它做钩子。所以针对第二种情况我们可以创建钩子方法完成。
当然钩子方法不仅仅可以让子类是否实现步骤中的可选部分。它还可以让子类能够有机会对模板方法(即代码中的picture方法,picture方法作为画风景画这事件的模板)即将发生或已经发生的步骤做出反映。钩子方法也可以有能力为其抽象类作出决定。