MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java模板方法模式的设计与实现

2022-07-304.3k 阅读

模板方法模式概述

在软件开发过程中,我们常常会遇到这样的情况:多个类中存在一些相似的行为或算法,但这些行为或算法的某些步骤在不同的类中可能会有不同的实现。例如,在一个游戏开发项目中,不同类型的角色(如战士、法师、刺客)都有战斗的行为,但战斗的具体方式(如攻击方式、防御方式)可能不同。传统的做法是在每个角色类中重复实现这些相似的行为,这样不仅会导致代码冗余,而且维护起来也很困难。当需要修改这些相似行为的某个共同步骤时,就需要在多个类中进行修改,这无疑增加了出错的风险。

模板方法模式(Template Method Pattern)就是为了解决这类问题而产生的。它定义了一个操作中的算法骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义该算法的某些特定步骤。简单来说,模板方法模式将通用的部分放在父类中,将变化的部分定义为抽象方法,由子类去实现。这样,既保证了代码的复用性,又提高了系统的灵活性。

模板方法模式的结构

模板方法模式包含以下几个角色:

抽象类(Abstract Class)

抽象类定义了模板方法,该方法定义了算法的骨架,由一系列基本方法构成。这些基本方法可以是抽象方法,也可以是具体方法。抽象类的作用是为子类提供一个通用的框架,子类通过继承抽象类并实现其中的抽象方法来定制算法的具体步骤。

具体子类(Concrete Class)

具体子类继承自抽象类,实现抽象类中定义的抽象方法。每个具体子类根据自身的需求,实现与其他子类不同的算法步骤,从而实现了算法的个性化定制。

Java中模板方法模式的实现

下面我们通过一个具体的示例来展示如何在Java中实现模板方法模式。假设我们正在开发一个烹饪系统,不同的菜品有不同的烹饪步骤,但都包含“准备食材”、“烹饪”和“装盘”这几个基本步骤。我们可以使用模板方法模式来设计这个系统。

定义抽象类

首先,我们定义一个抽象类CookingTemplate,它包含模板方法cook以及三个基本方法prepareIngredientscookDishserveDish。其中,prepareIngredientscookDish是抽象方法,需要由具体子类来实现,serveDish是具体方法,提供了一个默认的实现。

public abstract class CookingTemplate {
    // 模板方法,定义烹饪的算法骨架
    public final void cook() {
        prepareIngredients();
        cookDish();
        serveDish();
    }

    // 抽象方法,准备食材
    protected abstract void prepareIngredients();

    // 抽象方法,烹饪菜品
    protected abstract void cookDish();

    // 具体方法,装盘
    protected void serveDish() {
        System.out.println("将做好的菜装盘");
    }
}

定义具体子类

接下来,我们定义两个具体子类PizzaCookingFriedRiceCooking,分别用于烹饪披萨和炒饭。这两个子类继承自CookingTemplate,并实现其中的抽象方法prepareIngredientscookDish

public class PizzaCooking extends CookingTemplate {
    @Override
    protected void prepareIngredients() {
        System.out.println("准备披萨饼皮、番茄酱、芝士、各种蔬菜和肉类");
    }

    @Override
    protected void cookDish() {
        System.out.println("将准备好的食材依次铺在饼皮上,放入烤箱烤制");
    }
}
public class FriedRiceCooking extends CookingTemplate {
    @Override
    protected void prepareIngredients() {
        System.out.println("准备米饭、鸡蛋、蔬菜、肉类等食材");
    }

    @Override
    protected void cookDish() {
        System.out.println("先炒蛋,再加入米饭和其他食材一起翻炒");
    }
}

使用模板方法模式

在客户端代码中,我们可以通过创建具体子类的实例,并调用模板方法cook来执行烹饪过程。

public class Client {
    public static void main(String[] args) {
        CookingTemplate pizzaCooking = new PizzaCooking();
        pizzaCooking.cook();

        CookingTemplate friedRiceCooking = new FriedRiceCooking();
        friedRiceCooking.cook();
    }
}

上述代码的输出结果如下:

准备披萨饼皮、番茄酱、芝士、各种蔬菜和肉类
将准备好的食材依次铺在饼皮上,放入烤箱烤制
将做好的菜装盘
准备米饭、鸡蛋、蔬菜、肉类等食材
先炒蛋,再加入米饭和其他食材一起翻炒
将做好的菜装盘

通过以上示例,我们可以看到模板方法模式的优势。CookingTemplate类定义了烹饪的通用流程,PizzaCookingFriedRiceCooking子类只需要关注自己特有的食材准备和烹饪步骤,而装盘步骤则复用了父类的默认实现。这样,不仅减少了代码冗余,而且当需要添加新的菜品时,只需要创建一个新的具体子类并实现抽象方法即可,不会影响到已有的代码。

模板方法模式的应用场景

多个子类有公有的方法,并且逻辑基本相同时

例如,在一个电商系统中,不同类型的订单(如普通订单、团购订单、秒杀订单)都有下单、支付、发货等基本流程,但在某些步骤上(如支付方式、发货策略)可能会有所不同。此时,可以使用模板方法模式,将通用的下单、支付、发货流程放在抽象类中,将不同的部分(如支付方式、发货策略)定义为抽象方法,由具体的订单子类去实现。

重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能由子类实现

以图像处理软件为例,图像的基本处理流程(如加载图像、处理图像、保存图像)是相对固定的,但具体的图像处理算法(如滤镜效果、缩放算法)可能因需求而异。我们可以将图像的基本处理流程定义为模板方法,将具体的图像处理算法定义为抽象方法,由不同的子类实现不同的图像处理效果。

重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,通过钩子函数约束其行为

在对现有代码进行重构时,如果发现多个类中有相似的代码块,可以考虑使用模板方法模式。将这些相似的代码抽取到父类的模板方法中,将不同的部分抽象出来,由子类实现。同时,可以通过在父类中定义钩子函数(Hook Method)来进一步控制子类的行为。钩子函数是一种特殊的方法,它在父类中提供一个默认的实现,子类可以根据需要选择是否重写该方法。

模板方法模式的优缺点

优点

  1. 提高代码复用性:将通用的部分放在父类中,避免了在子类中重复实现相同的代码,减少了代码冗余,提高了代码的复用性。
  2. 提高可维护性:当需要修改通用部分的代码时,只需要在父类中进行修改,所有子类都会自动继承这些修改,降低了维护成本。
  3. 实现反向控制:模板方法模式通过将算法的骨架定义在父类中,而将具体的实现延迟到子类中,实现了一种反向控制机制。即父类控制算法的整体流程,子类负责实现具体的细节,这种方式使得系统的扩展性和灵活性大大提高。

缺点

  1. 增加系统的抽象性和复杂性:模板方法模式引入了抽象类和抽象方法,使得系统的结构变得更加抽象和复杂。对于初学者来说,理解和使用这种模式可能会有一定的难度。
  2. 子类对父类的依赖较强:子类必须继承自父类,并实现父类中定义的抽象方法。这就导致子类对父类的依赖程度较高,如果父类的结构或接口发生变化,可能会影响到所有的子类。

模板方法模式与其他设计模式的关系

模板方法模式与策略模式

模板方法模式和策略模式都用于处理算法的变化,但它们的侧重点有所不同。模板方法模式通过继承来实现算法的变化,将算法的骨架放在父类中,子类通过重写抽象方法来定制算法的具体步骤。而策略模式则是通过组合的方式,将不同的算法封装成独立的策略类,客户端可以根据需要动态地选择不同的策略。

在实际应用中,如果算法的变化是基于继承关系的,并且算法的骨架相对稳定,适合使用模板方法模式;如果算法的变化是基于不同的策略,并且需要在运行时动态切换策略,适合使用策略模式。

模板方法模式与工厂方法模式

工厂方法模式主要用于创建对象,它将对象的创建过程封装在工厂类中,客户端只需要调用工厂类的方法来获取所需的对象,而不需要关心对象的具体创建细节。模板方法模式则主要用于定义算法的骨架,将算法的部分步骤延迟到子类中实现。

虽然两者的功能不同,但在实际应用中,它们可以结合使用。例如,在一个游戏开发项目中,我们可以使用工厂方法模式来创建不同类型的角色对象,然后使用模板方法模式来定义角色的通用行为(如战斗、移动等)。

总结

模板方法模式是一种非常实用的设计模式,它通过定义算法的骨架,将变化的部分延迟到子类中实现,有效地提高了代码的复用性和可维护性。在软件开发过程中,当遇到多个类中有相似的行为或算法,且这些行为或算法的某些步骤需要根据不同的情况进行定制时,模板方法模式是一个很好的选择。同时,我们也需要注意模板方法模式的缺点,合理地使用它,以避免引入过多的复杂性。在实际应用中,还可以将模板方法模式与其他设计模式结合使用,以发挥更大的作用。希望通过本文的介绍,你对Java中模板方法模式的设计与实现有了更深入的理解,并能在今后的项目中灵活运用这一模式。