重修设计模式-行为型-模板模式

重修设计模式-行为型-模板模式

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);
   }
}

代理模式可以参考这篇文章,回到模板方法模式,要注意封装类设计的合理性,避免职责混杂,具体设计要根据场景而定。

总结

模板模式是经常使用的设计模式,它使得算法的结构在高层得到定义,而具体的实现细节则可以在低层通过子类来实现,符合开闭原则,让代码可复用,扩展方便。

相关推荐
小白不太白95014 分钟前
设计模式之 责任链模式
python·设计模式·责任链模式
吾与谁归in43 分钟前
【C#设计模式(13)——代理模式(Proxy Pattern)】
设计模式·c#·代理模式
吾与谁归in44 分钟前
【C#设计模式(14)——责任链模式( Chain-of-responsibility Pattern)】
设计模式·c#·责任链模式
闲人一枚(学习中)1 小时前
设计模式-创建型-原型模式
设计模式
Iced_Sheep1 小时前
干掉 if else 之策略模式
后端·设计模式
哪 吒9 小时前
最简单的设计模式,抽象工厂模式,是否属于过度设计?
设计模式·抽象工厂模式
Theodore_10229 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
转世成为计算机大神12 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
小乖兽技术13 小时前
23种设计模式速记法
设计模式
小白不太白95014 小时前
设计模式之 外观模式
microsoft·设计模式·外观模式