设计模式学习笔记

524 阅读9分钟

本文主要记录了设计模式的学习笔记以及个人的理解,仅供参考。

设计模式,即Design Patterns,是指在软件设计中,被反复使用的一种代码设计经验。使用设计模式的目的是为了可重用代码,提高代码的可扩展性和可维护性。

一、手中无刀,心中也无刀

上学和工作的这几年,或多或少有听过设计模式这个概念,了解过一些基本的设计模式,比如单例模式、工厂模式,但从来没有认真系统的学习过。工作之后,整个项目的工程还是挺大的,在日常开发需求时,通过同事和历史代码的熏陶,难免会去思考如何做到更好的代码复用、扩展性、维护性、健壮性等问题。在这个过程中,开发过很多需求,修改过很多代码,也设计过一些比较复杂的架构。

最近本着提升技术深度的目标,最近阅读了《设计模式之禅》一书,希望能够更系统化的学习下设计模式。学习了设计模式之后,发现其实自己的很多想法早已经在设计模式中有讲过了(也可以说想法不谋而合)。开头一段对话印象很深刻:

记得《武林外传》中有这样一段对话:

  • 刑捕头:手中无刀,心中有刀。
  • 老白:错了,最高境界是手中无刀,心中也无刀。

体验一下吧,我们的设计模式就是一把刀,极致的境界就是心中无设计模式,代码亦无设计模式——设计模式随处可见,俯拾皆是,已经融入软件设计的灵魂中,这才是高手中的高手,简称高高手。

我们学习设计模式,是学习他的思想,最终的目标是能够将设计模式融入到我们的思想和代码中,设计模式不是万能的,只是达成目标的一种方法,现实的很多场景,需要我们不断的权衡,在完美的设计和有限的资源中找到最佳平衡点,实现我们的代码。

我对设计模式的基本理解如下,拆分设计模式的愿景、战略和战术。

二、六大原则介绍

开闭原则

定义

是一个软件实体如类、模块和函数应该对扩展开发,对修改关闭。

理解

当我们面对需求变化时,应通过扩展来实现变化,而不是通过修改已有的代码来实现。这样的好处非常明显,扩展不会对已有的代码造成影响。

俗话说的好,唯一不变的是变化。日常开发需求中,需求有变化是经常的事情,我们在设计架构时,决不能仅仅考虑当下的场景,需要设计好足够的扩展性,以应对后续的需求变化。如果有需求变化,我们就要修改旧的代码,那每一次都需要将旧代码和新代码都要验证一遍,想一想工作量当然会很大。

当然,实际场景中远没有这么的理想。作为客户端的开发,目前实践下来,发现在一些大的设计上可以尽量满足开闭原则,但在细节的业务场景上,很难做到完全的对扩展开发,修改关闭。

单一职责原则

定义

应该有且仅有一个原因引起类的变更。

理解

在我刚进入工作时做的第一个新人项目中,就写了一个Manager类,它的能力非常强大,能够处理网络、本地数据、各种业务逻辑,使用也非常方便,所有地方只要调用这个类接口就可以。结果这个Manager类代码有上千行,几乎所有的业务调整修改,都会涉及到这个类的修改,这种类一般俗称超级类,能力很强,但非常难以维护。

更好的做法,就是将这个Manager类拆分成不同的Mananger类,每一个类只负责一个业务或功能,比如网络Manager类、数据存储Manager类等等,这样每个类只需要维护负责的逻辑就可以了。

但具体如何去拆分和组织,没有一个绝对管用的方法,需要根据具体业务来评估,同时呢也不是时时刻刻都要遵守这个原则,假如项目时间非常紧,需要快速上线去赚钱,那么哪有那么多时间等你来做完善的方案设计,只要能实现功能就行了,真的等项目上线了,再花时间重构的来得及。商业项目,赚钱永远是第一位的。

切记,作为技术人员,一定要把代码优化时时刻刻放在心上,哪怕因为项目资源的原因,暂时不能很好的实现代码,但从长期维护角度来看,一定是有必要去重构不好的代码的,不然最后吃亏的第一是自己。

里氏替换原则

定义

只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常。

理解

里氏替换原则主要为了满足程序健壮性,保证在版本升级、增加子类的一些情况下,程序仍然可以正常运行或保持良好的兼容性,其中有以下几个点需要关注:

  • 子类必须完全实现父类的方法

    • 面向对象的三大特征之一就是继承,子类可以继承父类的方法,并且扩展自己的特性。但有一点很重要,一般我们在方法传参时,会使用父类定义,但在运行时期,真正传递的对象,可能是任意一个子类的对象。这时候里氏替换原则就非常重要,比如新增的子类代码不会影响到旧的代码。
  • 子类可以拥有自己的个性

    • 当然,这里不能反过来使用,子类出现的地方,父类不一定合适。因为子类能够扩展自己的特性。
  • 覆盖或实现父类的方法时输入参数可以被放大、输出结果可以被缩小

    • 输入参数:子类方法的输入参数应该和父类方法的输入参数相同或是其子类,这样输入参数可以满足里氏替换原则

    • 返回结果:子类方法的返回结果应该和父类方法的返回结果相同或是其父类,这样返回结果可以满足里氏替换原则

继承是一个很好用的特性,但同时它的侵入性非常的高,增加了很多的耦合性,所以在实际工程中,我们可以灵活的使用继承、聚合、组合、依赖等关系,在很多情况下,继承是最简单的那种方式,但不一定是最好的选择。

依赖倒置原则

定义

  • 高层模块不应该依赖底层模块,两者都应该依赖期抽象

  • 抽象不应该依赖细节

  • 细节应该依赖抽象

理解

依赖倒置这个名字不是非常容易理解,换一个说法,叫面向接口编程,看起来就比较容易理解了。在平时工作开发时,会设计一些接口,让类和类之间不直接依赖,这样可以很大程度上实现解耦。比如工程中不同业务线之间的解耦方案,就会非常大量的使用到接口。亦或者在并行开发时,提前设计好接口,可以让双方独立开始开发,不需要互相等待。

依赖倒置原则是一个不错的原则,但在一些中小型项目中不太容易体现出它的优势,反倒会使得实现成本提高。在维护时间较长的大中型项目中采用依赖倒置原则可以让维护人员轻松地扩展和维护。

接口隔离原则

定义

  • 客户端不应该依赖它不需要的接口
  • 类间的依赖关系应该建立在最小的接口上

理解

接口隔离原则和单一职责原则很像,但它们所针对的角度不同,单一职责原则要求职责统一,比如有10个方法都属于同一个职责的,都在同一个接口中声明,那么它们就是符合单一职责原则。但如果这10个接口,其中有5个只有业务A会用到,另外5个只有业务B会用到,那他们放到同一个接口中声明,就不符合接口隔离原则了。正确的做法应该是拆分成两个接口。

核心点就是接口要尽量小,但也还是前边说的,根据实际情况来决定到底小到什么程度,否则代码过于复杂,维护成本反而会增加。

迪米特法则

定义

一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道的最少。

理解

迪米特法则的目的是减少类间的耦合。可使用的方法有

  • 只和朋友交流,出现在成员变量、方法的输入输出参数中的类称为朋友,而出现在方法体内部的类不属于朋友类,尽量减少和这样的类交流。因为一旦有这样的情况,必然会增加耦合。
  • 朋友间有距离:将细节封装,不要将自己的太多内部实现方法暴露给外部,暴露的越多,会增加越多的耦合。

迪米特法则在平时写代码中挺容易考虑到和实现,但若按照前边说的,只和朋友交流,那可能会出现比较多的中转或跳转类,导致系统复杂性提高,维护难度也增加,所以在实际执行中,也要仔细考虑如何才能做到高内聚低耦合。

三、总结

在实际工作中,设计模式六大原则是一个很好的指引,可以帮我们更好的做到高重用性、扩展性、维护性。但同时我们也要了解,设计模式不是万能的,在实际工程中,需要我们根据当时的情况来权衡如何使用,在这个过程中,希望可以使用设计模式来牵引我们,设计出更好更适合的架构,能够符合现有的需求,也能够应对可能发生的变化。

本文引用书籍---《设计模式之禅》