在一个方法中定义一个算法的骨架,而将一些步骤的实现延迟到子类中,使得子类可以在不改变一个算法的结构前提下即可重定义该算法的某些特定步骤
使用场景
这个设计模式一般在最初写代码的时候基本上是不会预先想到的,都是在后期不断重构的过程中被提炼出来的。简单来说就是:当你发现你写了两个或者多个几乎一毛一样的类,只是有些方法的实现方式不一致的时候,你就要停下来想想了,如果你又发现在使用这些类的地方调用方法顺序都一样,不要犹豫请使用模板方法模式重构吧。
具体实现
第一步:先定义一个模板类LivePlay
,如下代码片段所示,其中的seeLivePlay()
就是所谓的模板方法,为了不被子类overwrite
,它被设置为final
的,其定义了一个算法骨架。 其中的login()
是一个实体方法,里面是通用逻辑,所有的子类都是一样的。四个被abstract
修饰的是抽象方法,这些方法是需要子类去根据自己的实际算法实现的,而pushVideoStream()
方法有一个默认的空实现,这个一般称为钩子方法,设计用来被其中部分需要的子类overwrite
. 例如腾讯直播SDK提供了旁路推流的功能,而金山的没有提供,那么腾讯直播类就可以选择overwrite这个钩子方法,提供自己的实现。
public abstract class LivePlay {
//模板方法
public final void seeLivePlay() {
login();
openRoom();
startAudioAndVideoStream();
pushVideoStream();
stopAudioAndVideoStream();
closeRoom();
}
//实体方法,这个方法实现通用的业务逻辑
private void login() {
System.out.println("用户登录");
}
/*抽象方法*/
//打开房间
public abstract void openRoom();
//打开音视频流
public abstract void startAudioAndVideoStream();
//关闭音视频流
public abstract void stopAudioAndVideoStream();
//关闭房间
public abstract void closeRoom();
/*钩子方法,可以被需要的子类overwrite*/
//旁路推流,可以通过视频链接在浏览器中查看视频
public void pushVideoStream() {
}
}
第二步:定义具体的实体类,根据情况overwrite
相应的抽象方法和钩子方法。
//腾讯直播类
public class TencentLivePlay extends LivePlay {
@Override
public void openRoom() {
System.out.println("腾讯打开房间");
}
@Override
public void startAudioAndVideoStream() {
System.out.println("腾讯打开音视频流");
}
@Override
public void stopAudioAndVideoStream() {
System.out.println("腾讯关闭音视频流");
}
@Override
public void closeRoom() {
System.out.println("腾讯关闭房间");
}
//覆写钩子方法,提供旁路推流功能
@Override
public void pushVideoStream() {
super.pushVideoStream();
System.out.println("腾讯进行旁路推流");
}
}
值得注意的是,由于腾讯SDK提供了旁路推流的功能,所以它overwrite
了pushVideoStream()
这个钩子方法.
//金山直播类
public class JinShanLivePlay extends LivePlay {
@Override
public void openRoom() {
System.out.println("金山打开房间");
}
@Override
public void startAudioAndVideoStream() {
System.out.println("金山打开音视频流");
}
@Override
public void stopAudioAndVideoStream() {
System.out.println("金山关闭音视频流");
}
@Override
public void closeRoom() {
System.out.println("金山关闭房间");
}
}
由于金山SDK没有提供了旁路推流的功能,所以它不用覆写pushVideoStream()
这个钩子方法,而只需要overwrite
抽象方法即可。
第三步:客户端调用 我们根据后端返回的结果来决定使用哪家的SDK
public static void main(String[] args) {
//此处省略若干代码
...
LivePlay tencentLive=new TencentLivePlay();
tencentLive.seeLivePlay();
System.out.println("");
LivePlay jinShanLive=new JinShanLivePlay();
jinShanLive.seeLivePlay();
}
输出为:
用户登录
腾讯打开房间
腾讯打开音视频流
腾讯进行旁路推流
腾讯关闭音视频流
腾讯关闭房间
用户登录
金山打开房间
金山打开音视频流
金山关闭音视频流
金山关闭房间
从输出可以清楚的看到,要使用哪家的服务就实例化哪家的对象,然后调用开始观看直播即可。
优缺点
优点
主要是提高了代码的复用度,而且很好的符合的“开闭原则”。
缺点
- 设计模式的通病:类增多了
- 调用控制反转:一般情况下,程序的执行流是子类调用父类的方法,模板方法模式使得程序流程变成了父类调用子类方法,这个使得程序比较难以理解和跟踪。
实例:CAS