您的位置:首页 > 其它

23种设计模式之模板方法模式

2018-08-23 16:55 274 查看
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_36090463/article/details/81981942

一直以来我总是容易将工厂方法模式与模板方法模式混淆,它们两个实在太像了.对于我来说造成这样的错觉,源于我对模板方法模式的不了解,这里重点学习模板方法模式.

一、什么是模板方法模式

模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤.

模版方法模式的结构

模版方法模式由一个抽象类和一个(或一组)实现类通过继承结构组成,抽象类中的方法分为三种:

  • 抽象方法:父类中只声明但不加以实现,而是定义好规范,然后由它的子类去实现。
  • 模版方法:由抽象类声明并加以实现。一般来说,模版方法调用抽象方法来完成主要的逻辑功能,并且,模版方法大多会定义为final类型,指明主要的逻辑功能在子类中不能被重写。
  • 钩子方法:由抽象类声明并加以实现。但是子类可以去扩展,子类可以通过扩展钩子方法来影响模版方法的逻辑。
  • 抽象类的任务是搭建逻辑的框架,通常由经验丰富的人员编写,因为抽象类的好坏直接决定了程序是否稳定性。

       实现类用来实现细节。抽象类中的模版方法正是通过实现类扩展的方法来完成业务逻辑。只要实现类中的扩展方法通过了单元测试,在模版方法正确的前提下,整体功能一般不会出现大的错误。

注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。

二、如何使用模板方法模式

模板,是指作图或设计方案的固定格式。模板将一个事物的结构规律予以固定化、标准化的成果,它体现的是结构形式的标准化 .模板方法就是提供一个算法框架,框架里面的步骤有些是父类已经定好的,有些需要子类自己实现。比如:现在我们很多家庭都有了豆浆机,豆浆的营养价值不用我多说了。制作豆浆的工序简单点来说就是选材—>添加配料—>浸泡—>放到豆浆机打碎,通过添加不同的配料,可以制作出不同口味的豆浆,但是选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的。

1、创建抽象类

[code]package templatemethod.pattern;

//豆浆类,抽象类
public abstract class SoyaMilk {
//这是模板方法,用final修饰,不允许子类覆盖。模板方法定义了制作豆浆的程序
final void  prepareRecipe(){
selectMaterial();
addCondiments();
soak();
beat();
}

//选材方法,选择黄豆
void selectMaterial(){
System.out.println("第一步、选择好了新鲜黄豆");
}

//可以添加不同的配料,在这里设置为抽象方法,子类必须实现
abstract void addCondiments();

//浸泡
void soak(){
System.out.println("第三步、黄豆和配料开始浸泡,大概需要5个小时");
}

//放到豆浆机打碎
void beat(){
System.out.println("第四步、黄豆的配料放到豆浆机打碎");
}

}

2、创建红枣豆浆

[code]package templatemethod.pattern;

//红枣豆浆
public class ReddatesSoyaMilk extends SoyaMilk{
//实现父类的添加配料方法
@Override
void addCondiments() {
System.out.println("第二步、添加红枣配料");

}

}

3、创建核桃豆浆

[code]package templatemethod.pattern;

//核桃豆浆
public class NutSoyaMilk extends SoyaMilk{
//实现父类的添加配料方法
@Override
void addCondiments() {
System.out.println("第二步、添加核桃配料");
}

}

4、测试制作豆浆

[code]package templatemethod.pattern;

public class SoyaMilkTest {
public static void main(String[] args){
//制作红枣豆浆
System.out.println();
System.out.println("-----制作红枣豆浆步骤-------");
SoyaMilk reddatesSoyaMilk = new ReddatesSoyaMilk();
reddatesSoyaMilk.prepareRecipe();

//制作核桃豆浆
System.out.println();
System.out.println("-----制作核桃豆浆步骤-------");
SoyaMilk nutSoyaMilk = new NutSoyaMilk();
nutSoyaMilk.prepareRecipe();

}
}

三、模板方法模式的优缺点

优点:

容易扩展。一般来说,抽象类中的模版方法是不易反生改变的部分,而抽象方法是容易反生变化的部分,因此通过增加实现类一般可以很容易实现功能的扩展,符合开闭原则。

便于维护。对于模版方法模式来说,正是由于他们的主要逻辑相同,才使用了模版方法,假如不使用模版方法,任由这些相同的代码散乱的分布在不同的类中,维护起来是非常不方便的。

比较灵活。因为有钩子方法,因此,子类的实现也可以影响父类中主逻辑的运行。但是,在灵活的同时,由于子类影响到了父类,违反了里氏替换原则,也会给程序带来风险。这就对抽象类的设计有了更高的要求。

在多个子类拥有相同的方法,并且这些方法逻辑相同时,可以考虑使用模版方法模式。在程序的主框架相同,细节不同的场合下,也比较适合使用这种模式。

缺点:

每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

四、模板方法模式的使用场景

 1、有多个子类共有的方法,且逻辑相同。

2、重要的、复杂的方法,可以考虑作为模板方法。

五、模板方法中的钩子方法

在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。我们还是用上面做豆浆的例子来讲解。

1、创建有钩子方法的父类

[code]package templatemethod.pattern;

//豆浆类,抽象类
public abstract class SoyaMilkWithHook {
//这是模板方法,用final修饰,不允许子类覆盖。模板方法定义了制作豆浆的程序
final void  prepareRecipe(){
selectMaterial();
//判断是否添加配料
if(customerWantsCondiments()){
addCondiments();
}
soak();
beat();
}

//选材方法,选择黄豆
void selectMaterial(){
System.out.println("选择好了新鲜黄豆");
}

//可以添加不同的配料,在这里设置为抽象方法,子类必须实现
abstract void addCondiments();

//浸泡
void soak(){
System.out.println("材料开始浸泡,大概需要5个小时");
}

//放到豆浆机打碎
void beat(){
System.out.println("材料放到豆浆机打碎");
}

//钩子方法,是否添加配料
boolean customerWantsCondiments(){
return true;
}
}

2、创建纯豆浆子类

[code]package templatemethod.pattern;

//制作纯豆浆,不添加任何配料
public class PureSoyaMilk extends SoyaMilkWithHook{

@Override
void addCondiments() {

}

//覆盖钩子方法,不添加配料
@Override
boolean customerWantsCondiments(){
return false;
}
}

3、测试制作纯豆浆

[code]package templatemethod.pattern;

public class PureSoyaMilkTest {
public static void main(String[] args){
//制作纯豆浆
System.out.println();
System.out.println("-----制作纯豆浆步骤-------");
SoyaMilkWithHook pureSoyaMilk = new PureSoyaMilk();
pureSoyaMilk.prepareRecipe();
}
}

钩子方法的作用

  1、让子类实现算法中的可选部分。算法中的某些步骤是可选的,子类可以做出决定是否需要这些步骤。

  2、如果钩子对于子类的实现不重要时,子类可以对钩子置之不理。

  3、钩子可以让子类能够有机会对模板方法中某些即将发生的(或刚刚发生的)步骤作出反应。可以在钩子中实现我们对于某个步骤执行需要作出的动作,模板方法的某个步骤执行时,调用钩子。

参考博客来源:https://www.geek-share.com/detail/2691929251.html

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: