重修设计模式-行为型-模板模式
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
模板模式(Template Method Pattern)主要用来解决复用和扩展两个问题。通常用于那些步骤基本相同,但某些步骤的具体实现需要改变的场景。模板模式的原理和实现非常简单,在平时的编码中,可能不经意间就已经用到了模板模式。
模板方法只有两个角色,抽象类和具体子类:
抽象类(Abstract Class)
:定义了一个模板方法,其内部调用了一组抽象方法;也可以包含一些复用的具体方法和默认实现。具体子类(Concrete Subclass)
:继承抽象类,并提供某些步骤的具体实现(重写抽象方法)
钩子方法(Hook Method):由子类的一个方法返回值决定公共部分的执行结果。下面是模板模式的通用代码:
kotlin
//定义模板:
abstract class AbstractClass {
protected abstract fun step1()
open fun step2() {}
//钩子方法
open fun needStep2(): Boolean = true
fun start() {
this.step1()
if (needStep2()) {
this.step2()
}
}
}
//具体实现1:
class ConcreteClass1: AbstractClass() {
override fun step1() {
println("把冰箱门打开...")
}
override fun step2() {
println("把大象装进去...")
}
}
//具体实现2:
class ConcreteClass2: AbstractClass() {
override fun step1() {
println("洗衣机太小,装不了大象...")
}
override fun needStep2(): Boolean = false
override fun step2() {
//step2不会得到执行
}
}
fun main() {
val template1 = ConcreteClass1()
val template2 = ConcreteClass2()
//调用模板方法
template1.start()
template2.start()
}
模板方法的目的就是加强代码复用和方便扩展,原理和实现简单,应用也非常广泛,比如 Java 的 AbstractList、AbstractMap;在移动端开发中也经常定义出 BaseActivity/BaseFragment
等来封装通用逻辑。
模板方法的优点是让公共代码复用,方便维护,同时符合开闭原则(封装不变部分,扩展可变部分);缺点是某些场景可能会有很多子类,且如果抽象类设计不当还会起到反效果。
反模式:BaseActivity
以上面的 BaseActivity 举例,移动端项目中一般都会定义一个页面基类,将页面的通用逻辑封装起来,比如标题栏的处理逻辑、页面停留时间的上报逻辑等,常规写法如下:
kotlin
abstract class BaseActivity : AppCompatActivity() {
private var startTime = 0L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getContentView())
initToolbar()
}
abstract fun getContentView(): View
//1.标题栏初始化逻辑
open fun initToolbar() {
val toolbar = findViewById<Toolbar?>(R.id.toolbar) ?: throw Throwable("xml file need include toolbar!")
toolbar.setNavigationOnClickListener { finish() }
}
//2.页面停留时间上报逻辑
override fun onResume() {
super.onResume()
startTime = System.currentTimeMillis()
}
override fun onPause() {
super.onPause()
if (startTime > 0) {
val reportTime = System.currentTimeMillis() - startTime
ReportUtil.report(this.javaClass.simpleName, reportTime)
}
}
}
//3.继承BaseActivity获得埋点上报和标题栏处理能力
class MainActivity : BaseActivity() {
override fun getContentView(): View {
return View(this)
}
}
细看这个例子,如果页面只需要埋点逻辑,不需要标题栏逻辑,这种写法就不能支持了。而且 BaseActivity 承担太多的职责了,各种职责混杂在一起,让代码难以阅读和维护,如果将上面两个功能拆分成单独的类,那继承关系又不好确定:
kotlin
//1.时间上报封装
abstract class ReportShowTimeActivity: AppCompatActivity() {
private var startTime = 0L
override fun onResume() {
super.onResume()
startTime = System.currentTimeMillis()
}
override fun onPause() {
super.onPause()
if (startTime > 0) {
val reportTime = System.currentTimeMillis() - startTime
ReportUtil.report(this.javaClass.simpleName, reportTime)
}
}
}
//2.标题栏处理封装
abstract class ToolbarActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getContentView())
initToolbar()
}
abstract fun getContentView(): View
open fun initToolbar() {
val toolbar = findViewById<Toolbar?>(R.id.toolbar) ?: throw Throwable("xml file need include toolbar!")
toolbar.setNavigationOnClickListener { finish() }
}
}
//3.使用处:Java/Kotlin是单继承的,只能继承一个类
class MainActivity : ReportShowTimeActivity(), ToolbarActivity() {
override fun getContentView(): View {
return View(this)
}
}
继承层次复杂、BaseActivity 职责混乱,所以有人认为 BaseActivity 写法是反模式。不过在 Kotlin 语言中,类委托特性可以避免这种代码,继续上面的例子,使用类委托来实现,代码如下:
kotlin
//1.时间上报封装
interface ReportShowTime {
fun registerLifecycle(owner: LifecycleOwner)
}
class ReportShowTimeImpl : ReportShowTime {
override fun registerLifecycle(owner: LifecycleOwner) {
owner.lifecycle.addObserver(object : DefaultLifecycleObserver {
private var startTime = 0L
override fun onResume(owner: LifecycleOwner) {
startTime = System.currentTimeMillis()
}
override fun onPause(owner: LifecycleOwner) {
if (startTime > 0) {
val reportTime = System.currentTimeMillis() - startTime
ReportUtil.report(this.javaClass.simpleName, reportTime)
}
}
})
}
}
//2.标题栏处理封装
interface ToolbarHandler {
fun initToolbar(activity: Activity)
}
class ToolbarHandlerImpl : ToolbarHandler {
override fun initToolbar(activity: Activity) {
val toolbar = activity.findViewById<Toolbar?>(R.id.toolbar)
?: throw Throwable("xml file need include toolbar!")
toolbar.setNavigationOnClickListener { activity.finish() }
}
}
//3.调用处:继承接口并委托给具体实现类
class Activity2 : AppCompatActivity(),
ReportShowTime by ReportShowTimeImpl(),
ToolbarHandler by ToolbarHandlerImpl() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initToolbar(this)
registerLifecycle(this)
}
}
可以看见,通过类委托可以让每个职责更加独立,并且使用处也可以非常灵活的选择需要的代码封装来实现。类委托其实是通过静态代理实现的,之所以代码能如此简介,其实都是编译器的功劳。反编译 Kotlin 代码如下:
kotlin
public final class Activity2 extends AppCompatActivity implements ReportShowTime, ToolbarHandler {
//1.时间上报代理对象
private final ReportShowTimeImpl $$delegate_0 = new ReportShowTimeImpl();
//2.标题栏处理代理对象
private final ToolbarHandlerImpl $$delegate_1 = new ToolbarHandlerImpl();
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131558428);
this.initToolbar((Activity)this);
this.registerLifecycle((LifecycleOwner)this);
}
public void registerLifecycle(@NotNull LifecycleOwner owner) {
//3.委托给代理对象实现
this.$$delegate_0.registerLifecycle(owner);
}
public void initToolbar(@NotNull Activity activity) {
//3.委托给代理对象实现
this.$$delegate_1.initToolbar(activity);
}
}
代理模式可以参考这篇文章,回到模板方法模式,要注意封装类设计的合理性,避免职责混杂,具体设计要根据场景而定。
总结
模板模式是经常使用的设计模式,它使得算法的结构在高层得到定义,而具体的实现细节则可以在低层通过子类来实现,符合开闭原则,让代码可复用,扩展方便。