学设计模式的这段时间:一个管骨架,一个管替换
最近在补设计模式。以前也翻过几回,每次都是看到定义就关了,每个字都认识,连在一起不知道在说什么。这次换了个学法:先找例子,再看定义,然后用自己的话写一遍,写不出来的地方就是没懂的地方,回头再补。这篇是边学边写的记录,写到哪算哪。
为什么突然想学,其实我说不上什么具体契机。就是"模板方法""策略"这两个词老在眼前晃,看别人的代码、看网上的讨论,动不动就冒出来,躲了几次发现躲不掉,索性认真搞一次。先从这两个开始。
没学之前,我对这两个词的第一印象
先说说没学之前的理解,现在回头看挺搞笑的。
“模板方法”,我以为是"模板"。心想是不是像网页模板那样,套一套就能用?那"方法"应该就是函数,合起来大概是个偷懒技巧,把函数做成模板。
“策略"这个词更唬人,听起来像兵法。我猜是"面对不同情况用不同招”。说实话这个直觉方向不算错,但也只是方向对,具体是什么,完全不知道。
第一次认真看定义,看到的是两句话:
模板方法模式:定义一个操作中的算法骨架,而将一些步骤延迟到子类中。
策略模式:定义一族算法,分别封装起来,让它们可以互相替换。
看完就一个感觉:每个字都认识。合上书,问我这俩是干嘛的,一个字都说不出来。那时候我对这两个模式的全部理解就是"都是把变化抽出来",这个理解现在看也不算错,但当时等于没说,因为我不知道"抽"具体指什么、怎么抽、抽完放哪。
那会儿还想过一个问题:设计模式这种东西,是不是得工作好几年才能看懂?我一个代码都还没写利索的人,现在学是不是太早。后来想想,反正躲不掉了,学吧。
模板方法模式:流程固定,步骤可变
先说模板方法。我理解它的套路是这样:一个抽象类里有个"骨架方法",骨架方法把整个流程定死,第一步做什么、第二步做什么、最后做什么,顺序不能乱。但骨架方法自己不干活,它中间会调用几个抽象方法,这些抽象方法留给子类去填。子类能改的是步骤的内容,改不了流程的顺序。
我第一次看懂这个结构的时候,脑子里蹦出来的话是:这不就是继承吗?父类写个方法,子类重写,这不是最普通的用法吗,为什么要单独起个名字。
这个困惑卡了我一阵。后来慢慢想明白区别在哪:普通的继承,父类方法是"你可以重写";模板方法里,骨架方法是"流程我定死了,你只许填我留给你的空"。区别在主动权。骨架方法像个指挥,流程顺序它说了算,子类是被指挥着干活的人。你可以把"洗碗"这步换成机洗,但不能把"先洗碗再擦桌子"改成"先擦桌子再洗碗"。
我给自己总结了一句话:流程固定,步骤可变。这八个字我到现在还记得,因为是我自己琢磨出来的,不是抄的。
再配一个我自己的类比:像考试。卷子的流程是出卷人定死的,先选择、再填空、后大题,答题人改不了。但每道题怎么答,是答题人自己的事。骨架方法就是卷子,抽象方法就是题。
还有个细节我纠结了一下:骨架方法在 Java 里一般写成 final,不让子类重写。我理解这是为了防止子类把流程改乱,既然设计意图就是"流程我定死了",干脆从语法上堵死这条路。不过这个理解我没验证过,只是觉得合理。
这个模式解决什么问题?我现在的理解是,它把重复的流程代码上收到父类,每个子类只写自己不一样的那部分。流程要改的时候,只改骨架那一处,不用每个子类都动。好处挺直观,但我还没在真实项目里用过,只能说到"我理解是"这个程度。
策略模式:给会变的东西立个统一标准
再说策略模式。这个我是从我自己笔记上的一句话开始的,笔记上写着"策略模式统一抽象"这六个字,当时只记了这六个字,现在展开写写我当时到底想记什么。
先说我理解的套路:一个接口,几个实现类,再加一个"上下文"。接口把"做什么"定下来,实现类各自给出"怎么做",上下文只管调用接口,不关心背后具体是谁。要用哪种做法,从外面塞进去就行。
我为什么记"统一抽象"?因为策略模式干的事,在我看就是把"会变的那部分"单独拎出来,给它立一个统一的标准,也就是接口。所有变体都遵守这个标准,调用方看到的永远是标准的样子,不用管背后是哪家。这就是我理解的"统一"和"抽象":用一个接口,把一堆长得不一样的东西,统一成同一个样子给外面用。
类比我想到两个。一个是换轮胎:轮毂上的螺丝规格是统一的,米其林、马牌、朝阳随便换,都能装上。接口就是那个螺丝规格,具体轮胎就是各个实现。另一个是支付:微信、支付宝、银行卡都实现同一个"支付"接口,收银台不用管顾客用什么付,反正都是"支付"。
学到这里,卡点来了:这俩模式也太像了吧。
模板方法:把变化的部分抽出来,让子类实现。
策略:把变化的部分抽出来,让实现类替换。
都是"把变化抽出来",凭什么一个叫模板方法、一个叫策略?这个问题我琢磨了好几天,说下我现在的理解,不一定对。
区别我琢磨出来三个。一个是抽法:模板方法是继承,父类定流程、子类填步骤,变化是往子类里藏的;策略是组合,上下文拿着接口引用,策略从外面塞进去,变化是往外拎的。一个是纵向,一个是横向。
另一个是变的粒度:模板方法变的是流程里的某几步,流程整体还在;策略变的是整个行为,是整块算法换掉。
还有一个是换的方式:模板方法里,用哪个实现是跟着子类走的,子类类型定下来,步骤就跟着定了;策略模式里,同一个对象可以随时换策略,程序跑着跑着换掉,行为就变了。这个"运行时能换"的特点,是我觉得策略和模板方法最本质的区别,也是我反复确认了好几遍才敢写下来的。
对了,还有个一开始误会我的地方:策略模式的定义里说"算法",我差点以为只有排序、搜索这种东西才配叫策略。后来才明白这里的"算法"是泛指,任何一种"做法""方式"都可以是策略。现在想想,定义里"算法"两个字确实容易劝退新人。
试着动手:写一半卡住了
光看不行,我试着写。本来想写一个完整的例子,一个场景分别用两个模式实现一遍,对比着看。结果写到一半就卡住了,代码到现在还没跑通,先不贴了,等跑通了再补。
卡在哪?卡在选场景。想用模板方法,但我脑子里的场景不是流程不够固定,就是一共两步,写个继承纯属折腾。想用策略倒是好找,一个功能有好几种做法、按输入选不同的做法,这种就很自然。但我也只是"想象"这种场景,不是真做过。
这一卡让我对模板方法多了一层理解:它不是随便用的。得流程真的稳定、步骤真的会变,才划算。要是流程三天两头变,或者根本没什么可变步骤,硬套模板方法就是白加一层类,反而更绕。这个想法没有实践验证过,但至少现在写代码之前,我会先问自己一句:这个流程真的固定吗?不固定,模板方法就先别碰。
我还干了一件事,把两个模式的结构用我自己的话各写了一遍,然后互相问"为什么这个场景不用另一个"。模板方法管的是流程不变、细节变,策略管的是整个行为变、随便换。这么一写,边界好像清楚了一点。但真到具体场景,我还是会犹豫。比如一个场景里流程是固定的,但流程里某一步的做法有好几种,这算模板方法还是策略?我现在的答案是看粒度,步骤级的变用模板方法,整个行为级的换用策略。但说实话,这也是我自己的感觉,没有标准答案可查。
另外看例子的时候注意到一个现象:策略模式在真实代码里很少是"一个接口加一堆实现类"这么干净的,经常跟工厂、跟注册表什么的混在一起用。这个我还没搞明白,先记下来,等以后遇到了再说。
现在能说清的和还没搞懂的
学到这儿,让我用一句话说这俩模式,我会说:
模板方法模式:流程骨架定死在父类,子类只填可变步骤。用继承。
策略模式:把整个行为抽象成接口,调用方只认接口,实现随便换。用组合。
跟没学之前比,最大的变化是:以前听到这俩名字就绕道,现在至少知道它们在解决什么问题、大概长什么样。但距离"会用"还差得远。
还没搞懂的,我列一下:
一是"什么时候该用哪个",我还是凭感觉,没有一套自己能说出口的判断标准。
二是钩子方法。模板方法里除了抽象方法,还有一种叫"钩子"的东西,子类可以不覆盖,覆盖了就能控制流程里的可选步骤。抽象方法我搞懂了,钩子方法跟它的边界我还没完全摸清。
三是策略模式混在其他模式里我就认不出来了,真实代码比教程复杂太多。
四是我笔记里那句"统一抽象"是我自己的说法,不是标准术语,如果理解偏了,欢迎指正。
这篇是边学边写的记录,写的时候又卡了几次,卡住的地方反而记得最牢。跟以前比,我学东西的方式变了点:不背定义,先看例子,再用自己的话复述,复述不出来的就是没懂,回头再补。设计模式这名字唬人,拆开了也就是一些常见问题的常见解法,只是没人早点告诉我该从哪头看。
