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就是基本方法中的钩子方法。
但注意,我们在使用设计模式的时候,做出适当的变化是可以也是必不可少的,不能照搬教条。