设计模式之模板方法模式

java程序的设计原则

6大原则:

单一职责:一个类和方法只做一件事。
开闭原则:对修改关闭,对扩展开发。
里氏替换原则:子类可扩展新方法,但不可修改父类已有方法(父类已提供了具体实现的方法)。
依赖倒置:依赖于抽象,而非具体实现,即面向接口编程(如方法参数,类属性使用接口声明,这样可接收任何子类)。
接口隔离:使用多个隔离的接口定义抽象,降低耦合。
最少知道/迪米特原则:降低类之间的依赖,聚合,组合等。

1:模板方法设计模式

模板方法设计模式是行为型设计模式中的一种,用在一个功能的完成需要经过一系列步骤,这些步骤是固定的,但是中间某些步骤具体行为是待定的,在不同的场景中行为不同,此时就可以考虑使用模板方法设计模式来完成,不同的场景对因不同的子类实现。

模板方法包含如下的角色:

抽象类/抽象模板(Abstract Class):负责给出一个算法的轮廓,由一个模板方法和若干个基本方法构成,这些方法定义如下
    1:模板方法(该方法也正是模式的核心),定义算法的框架,按照某种顺序调用其中的基本方法。
    2:基本方法,可以包含如下的几种类型
        抽象方法:由具体的子类实现,作为定制行为,因为是抽象方法所以子类必须实现。
        具体方法:在抽象类中已经提供了具体的实现,一般设置为private。
        钩子方法:在抽象类中已经提供了实现(一般是空实现),类似于抽象方法,但是并非强制子类实现,因为已经提供了默认实现。
具体子类/具体实现(Concrete Class):必须实现抽象模板中的抽象方法,以及选择性的重载钩子方法。

UML图如下:

在这里插入图片描述

接下来通过程序实现UML图。

源码

1.1:抽象模板

// 抽象类
public abstract class AbstractClass {
    // 模板方法,提供公共的调用顺序,不允许子类override,所以设置为final
    public final void templateMethod() {
        specificMethod();
        abstractMethod1();
        abstractMethod2();
        hookMethod();
    }

    // 钩子方法,子类可根据实际情况选择是否要试下该方法
    public void hookMethod() {
    }

    // 具体方法,设置为private,避免子类override,并且不允许外部访问
    private void specificMethod() {
        System.out.println("抽象类中的具体方法被调用...");
    }

    // 抽象方法1
    public abstract void abstractMethod1();

    // 抽象方法2
    public abstract void abstractMethod2();
}

1.2:具体实现

public class ConcreteClass extends AbstractClass {
    public void abstractMethod1() {
        System.out.println("抽象方法1的实现被调用...");
    }

    public void abstractMethod2() {
        System.out.println("抽象方法2的实现被调用...");
    }

    @Override
    public void hookMethod() {
        System.out.println("钩子方法被调用...");
    }
}

1.3:客户端

public class TemplateMethodPattern {
    public static void main(String[] args) {
        AbstractClass tm = new ConcreteClass();
        tm.templateMethod();
    }
}

运行:

抽象类中的具体方法被调用...
抽象方法1的实现被调用...
抽象方法2的实现被调用...
钩子方法被调用...

Process finished with exit code 0

接下来我们再看一个实际生活中的例子。

2:出国留学

源码

分析:出国留学手续一般经过以下流程:索取学校资料,提出入学申请,办理因私出国护照、出境卡和公证,申请签证,体检、订机票、准备行装,抵达目标学校等,其中有些业务对各个学校是一样的,但有些业务因学校不同而不同,所以比较适合用模板方法模式来实现。

在本实例中,我们先定义一个出国留学的抽象类 StudyAbroad,里面包含了一个模板方法 TemplateMethod(),该方法中包含了办理出国留学手续流程中的各个基本方法,其中有些方法的处理由于各国都一样,所以在抽象类中就可以实现,但有些方法的处理各国是不同的,必须在其具体子类(如美国留学类 StudyInAmerica)中实现。如果再增加一个国家,只要增加一个子类就可以了,图 2 所示是其结构图。

在这里插入图片描述

2.1:StudyAbroad->抽象类

// 抽象类: 出国留学
public abstract class StudyAbroad {
    // 模板方法,设置为final不允许子类override
    public final void TemplateMethod() {
        // 索取学校资料
        lookingForSchool();
        // 入学申请
        applyForEnrol();
        // 办理因私出国护照、出境卡和公证
        applyForPassport();
        // 申请签证
        applyForVisa();
        // 体检、订机票、准备行装
        readyGoAbroad();
        // 抵达
        arriving();
    }

    // 具体方法,对外不可见,设置为private
    private void applyForPassport() {
        System.out.println("三.办理因私出国护照、出境卡和公证:");
    }

    // 具体方法,对外不可见,设置为private
    public void applyForVisa() {
        System.out.println("四.申请签证:");
    }

    // 具体方法,对外不可见,设置为private
    public void readyGoAbroad() {
        System.out.println("五.体检、订机票、准备行装:");
    }

    // 索取学校资料
    public abstract void lookingForSchool();

    // 入学申请
    public abstract void applyForEnrol();

    // 抵达
    public abstract void arriving();
}

2.2:StudyInAmerica->美国留学具体实现类

// 具体子类: 美国留学
public class StudyInAmerica extends StudyAbroad {
    @Override
    public void lookingForSchool() {
        System.out.println("一.索取学校以下资料(美国留学):");
    }

    @Override
    public void applyForEnrol() {
        System.out.println("二.入学申请(美国留学):");
    }

    @Override
    public void arriving() {
        System.out.println("六.抵达目标学校(美国留学):");
    }
}

2.3:StudyInVatican->梵蒂冈留学具体实现类

// 具体子类: 梵蒂冈留学
public class StudyInVatican extends StudyAbroad {
    @Override
    public void lookingForSchool() {
        System.out.println("一.索取学校以下资料(梵蒂冈):");
    }

    @Override
    public void applyForEnrol() {
        System.out.println("二.入学申请(梵蒂冈):");
    }

    @Override
    public void arriving() {
        System.out.println("六.抵达目标学校(梵蒂冈):");
    }
}

2.4:客户端

public class StudyAbroadProcess {
    public static void main(String[] args) {
        StudyAbroad tm = new StudyInAmerica();
        tm.TemplateMethod();

        System.out.println();
        System.out.println();

        StudyAbroad tm1 = new StudyInVatican();
        tm1.TemplateMethod();
    }
}

测试:

一.索取学校以下资料(美国留学):
二.入学申请(美国留学):
三.办理因私出国护照、出境卡和公证:
四.申请签证:
五.体检、订机票、准备行装:
六.抵达目标学校(美国留学):


一.索取学校以下资料(梵蒂冈):
二.入学申请(梵蒂冈):
三.办理因私出国护照、出境卡和公证:
四.申请签证:
五.体检、订机票、准备行装:
六.抵达目标学校(梵蒂冈):

Process finished with exit code 0

在这里插入图片描述

上图红色对钩的就是具体子类实现的逻辑。

3:实际应用

3.1:对接大模型sdk时的流式响应接口

公司的人工客服产品,需要对接大模型流式响应,则在调用不同的大模型时,权限校验,发生错误时,执行完毕的动作是一致的,但是不同的大模型获取答案方式不一样。所以,对于权限的校验,ResponseBodyEmitter的errror,complete处理就可以定义在抽象类中作为具体方法,而获取数据就定义为钩子方法,像这样:

public abstract class AbstractChatService implements IChatService {
    public ResponseBodyEmitter completions(ChatProcessAggregate chatProcess) {
		// 1. 校验权限
        if (checkouAuthority()) {
            throw new ChatGPTException(Constants.ResponseCode.TOKEN_ERROR.getCode(), Constants.ResponseCode.TOKEN_ERROR.getInfo());
        }

        // 2. 请求应答
        ResponseBodyEmitter emitter = new ResponseBodyEmitter(3 * 60 * 1000L);
        emitter.onCompletion(() -> {
        	this.onCompleateAction();
        });

        emitter.onError(throwable -> {
       	   this.onErrorAction();
		});

        // 3. 应答处理
        try {
            this.doMessageResponse(chatProcess, emitter);
        } catch (Exception e) {
            throw new ChatGPTException(Constants.ResponseCode.UN_ERROR.getCode(), Constants.ResponseCode.UN_ERROR.getInfo());
        }

        // 4. 返回结果
        return emitter;
     }
    private checkouAuthority() {// 权限校验逻辑}
    private onCompleateAction() {// 数据写完时的逻辑}
    private onErrorAction() {// 写数据时发生错误的逻辑}
    protected abstract void doMessageResponse(ChatProcessAggregate chatProcess, ResponseBodyEmitter responseBodyEmitter) throws JsonProcessingException;
}

子类:
@Service
public class ChatService extends AbstractChatService {

    @Override
    protected void doMessageResponse(ChatProcessAggregate chatProcess, ResponseBodyEmitter emitter) throws JsonProcessingException {
        ...具体获取数据的逻辑
    }
}

所以这里completions就是模板方法,checkouAuthority,onCompleateAction,onErrorAction就是基本方法中的具体方法,doMessageResponse就是基本方法中的钩子方法。
但注意,我们在使用设计模式的时候,做出适当的变化是可以也是必不可少的,不能照搬教条。

参考文章列表

模板方法模式(模板方法设计模式)详解

秒懂设计模式之模板方法模式(Template Method Pattern)

  • 5
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
模板方法设计模式是一种行为设计模式,它定义了一个算法的骨架,将一些步骤的实现延迟到子类中。这种模式可以确保算法的结构保持不变,但允许子类提供特定的实现细节。在C++中,模板方法设计模式可以通过使用虚函数和继承来实现。 在给出的引用中,我们可以看到一个名为CoffeineBeverage的类,它定义了一个prepare_recipe()方法,并在内部调用了其他几个私有方法。这个类是一个基类,可以被子类继承并重写其中的方法。 具体来说,模板方法设计模式的关键是将算法的骨架定义在基类中,而将可变的实现细节留给子类去实现。在这个例子中,prepare_recipe()方法是算法的骨架,而brew()和add_condiments()方法则是可变的实现细节。 通过将brew()和add_condiments()方法定义为虚函数,基类CoffeineBeverage允许子类去重写这些方法以提供自己的实现。这样,当调用prepare_recipe()方法时,实际执行的是子类中重写后的方法。 使用模板方法设计模式的好处是可以提高代码的复用性和可扩展性。算法的骨架在基类中只需要定义一次,而具体的实现细节可以在不同的子类中灵活变化。 总结起来,C++中的模板方法设计模式通过定义一个算法的骨架并将可变的实现细节留给子类去实现,可以提供代码的复用性和可扩展性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [C++设计模式模板方法模式](https://blog.csdn.net/Long_xu/article/details/127118813)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [C++设计模式-模板方法模式](https://blog.csdn.net/qq78442761/article/details/91990149)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值