设计模式之模板方法模式

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)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值