设计模式学习与实践反思——《learning PHP设计模式》读书笔记

前言

        之所以学习这本书,是因为我明显感觉到,我无法很好的组织复杂逻辑的项目代码——无论是日常项目还是公司msi框架和组件迭代中。尽管学习过工厂模式、状态模式、等诸多设计模式,但在实践中一直未能灵活应用。
        看了这本书之后,对自己过去的实践产生许多反思,并在最近的一个数学游戏『远航时代』中验证实践,收获颇丰,在此分享,期待各位同学指点。
        在这一篇读书笔记中,我更多的是阅读这本书之后,对自己过去实践的反思,并以自己作为案例供大家参照,以便交流学习。而不会对大家都已经熟悉的常用的设计模式作重复描述。

『远航时代』是一个数学课堂的游戏化教学工具,由梅沙书院与梅沙科技合作研发。在这个游戏中,玩家可以运营一家公司,运用数学方法,解决公司业务、经济危机、上市、关联交易等诸多事件中出现的问题。

正文

先提几个问题

  1. 为什么采用OOP(面向对象)?
  2. 即时回报还是长期回报?
  3. 是什么导致了重新设计(或重构)?

为什么采用OOP?

        大家关于这一点,能提出很多,灵活性高、可复用性、可维护性、可扩展性强等等各种优点。但是,综合我大学四年、实习以及毕业后一年的工作经验来看,至少相当一部分人都没有真正了解OOP;或者并不认可OOP;或者不能真正实际应用OOP。一个很常见的实例:大家都在用laravel,都在讲MVC,但是在最终的C层(我视其包含Controller和Logic),我们能看到多数人,其开发思路与顺序编程或过程编程并无本质区别——依然是按照业务逻辑,顺序实现代码——这看起来就像仅仅把POP的思路局限在逻辑层,并未真正由POP转为OOP。尽管如此,显然我们依然能控制函数的单一职责,能保证函数的可复用性——就好像用C语言并不支持OOP的基本特性,仅能以POP编写硬件驱动,但一样能写出高可复用性、高灵活性的代码(模块化)。那么,OOP关于POP的优势到底在哪里?而且,POP的代码,往往更易于理解,从这一点来看,似乎还比OOP更有优势?

        在回顾自己过往的『烂代码』并结合书中描述后,我的答案是,POP对状态变化、事件流程实现往往需要很长的代码,且相互依赖而并没有实现真正意义上的可维护性、可扩展性——回顾自己项目,是否发现总有那么一些方法的实现,十分冗长,想拆解却难以拆解,他们相互依赖,其中一部分逻辑的改变,总是会影响到上下文的正常运行,然后出现改一个位置,其他位置全报错的情况?

案例

        以我最近的版本,远航时代为例。;一个游戏有个很多回合,每回合提交运行后,会自动执行一系列的事件,这些事件是否执行以及执行次序都相互影响,从这点出发来看,我一开始的想法就是一堆方法,每个方法处理一个事件,而最上层的调用者依次调用这些方法传入参数,并将计算结果传入下一事件方法中。这看起来没问题,如果真的实现,也没问题。但是,我们永远不能完全预测未来的版本迭代,这时候如果要新增或移除事件呢?如果『公司』这个角色产生了新的属性呢?如果要改变调用次序呢?

        在POP中,对一个模块的关注点可能在于,这个模块的输入和输出。这看起来没问题,一旦改代码,就会发现,如果增加或移除其中的某个逻辑,我们可能不但要修改这个方法的逻辑、接收和返回,还有修改上下文中的方法的传入参数和返回,甚至可能也要改动逻辑。这就像摞纸牌,摞得很高,看起来很厉害,但每增加或移除一张纸牌,都可能导致整座纸牌塔坍塌。

        而在OOP和设计模式中,我们应该意识到,这就是职责链模式的应用场景——这一系列任务看似相互依赖,实际都仅关注『公司』这一角色的属性变化,其中一个事件发生与否、结果如何都与其他事件相互无关。同时,我们也应该意识到这个需求中,事件列表、执行条件和执行次序都有可能变化,因此,我们可以应用职责链模式,分离各个事件,每个事件的具体实现仅关注『公司』的状态,而不关心其他事件的运行结果。每个事件执行与否由自己决定,而不为调用者所关注,执行后的状态改变立即更新至『公司』实例中。调用者仅关注两点:事件列表;每个事件的后继。如此一来,调用者在启动第一个事件后,无需再关注后续事件执行与否,而仅需关注职责链结束后,『公司』所处的状态并返回最终结果即可。每个事件不需要关注自己的后继是什么,而仅需忠实的调用其后继。这时候我们能体会到,无论新增、移除还是修改什么事件,都能体现『开放封闭原则』,我们在关于事件部分的修改将变的十分轻松——在该版本中,我参考这一模式实践后的直观感受就是,这一部分无论如何修改,我都不必为修改参数传递而操心。

        通过这个案例,相信大家都明白了我想表达的观点:OOP存在诸多优势,这些优势看起来POP都能实现,但实际上,POP相互依赖注定其对于复杂的状态变化、事件流程处理,必然要产生冗长的任务序列,且任何位置的改动都可能无法完全独立。而采用OOP,我们能分解各个角色,明确各自任务,同时相互隔离,当需要修改某个点时,真正体现『开放封闭原则』,并享受其带来的愉悦感。到这里,我想我们也都真正明白了,OOP和POP的真正区别,其实仅在于关注点不同。然而,不同的关注点,带来的变化是很大的。

        上文中,我为什么要把这个需求中可能变化的点作出强调呢?这是我在看了这本书后领悟的一点——不应为了用设计模式而去实现某种代码,而应着眼于需求本身,关注哪些是变化的,哪些是不变的,这或许才是设计模式实践的正确思路。

即时回报还是长期回报?

        上面花了很大的篇幅讲什么是OOP,从而引申了『OOP和POP区别到底在哪里』的问题。但是实际实现中,我们总是有很多的理由拒绝实现。设计模式的圣经,『四人帮』的《设计模式》一书中,也提及:设计面向对象软件很困难;设计可重用的面向对象软件更困难。我们即使看了很多遍各种各样的设计模式,甚至熟悉他们的应用场景,但我们总有各种各样的利用拒绝是用。关于这点,作者也作出了介绍,我认为很有参考意义,因此,这里直接引用,大家可以共同思考。

        有些时候,我们必须放弃原来的老习惯,提升我们的能力。现在花点时间,将来开发项目时就能少花时间,不至于不知所措。另外,你会逐步成长为一个更优秀的程序员,这本身就是学习OOP和设计模式的一个很有说服力的理由。
        总之,学习OOP和涉及模式可以帮助你把事情做得更好,并享受其中的乐趣。

是什么导致了重新设计(或重构)?

        这个问题,其实也是书中提及的问题。因为这个问题我认为十分典型,因此我拿出来与大家讨论。
        以前文的案例为例。我个人认为,很多时候,重新设计的根本原因,都在于我们在某部分代码最初的版本中,没有考虑清楚变与不变性,并提前作出『预防性』的设计,同时,我们又有各种各样的理由没有真正以OOP思维思考、分解并解决问题,也就进一步的导致随着版本迭代,代码越来越烂而难以维护,最终走推倒重构的结局。

设计模式的两个基本原则

        在前文中,我反复提及我对书的理解后,所感悟的『关注变性与不变性』。在这里,我还想再强调设计模式的两个基本原则,因为在这之前,从来没有人和我提及这个概念(包括大学时期,或许是因为我们难以用C语言在写硬件驱动的过程中应用某种设计模式)。

  1. 按接口编程
  2. 优先选择对象组合而不是类继承

        尤其是第二点,我们应该能明显感受到,如果使用类继承,往往导致我们一旦需要修改类的某个方法的输入参数或返回值时,往往要递归遍历修改每一个类的实现。按照我的理解,我们应该从实际从发,依然是分析变性与不变性,慎重选择实现方式。

(这里引用书中一段话)
        这个说法并不是要完全消除继承。实际上,这表示开发程序时如果有机会用组合,就应当优先使用组合而不是继承。这样一来,子类就不会因为继承到大量不用的属性和方法而变得过度膨胀。

小结

        这本书我认为最大的意义不在于其关于设计模式在PHP中的具体应用给出的案例,而在于引发了我对OOP、设计模式的深入思考,并令我真正明白了在实践中如何作出正确的设计,从而保证代码的可扩展性、可复用性、可维护性等。

猜你喜欢

转载自blog.csdn.net/qq_23937195/article/details/94376684