设计模式之美笔记1

记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步

1. 为什么要学习设计模式

  • 应对面试中的设计模式相关问题
  • 告别写被人吐槽的烂代码
  • 提高复杂代码的设计和开发能力
  • 让读源码、学框架事半功倍
  • 为职场发展做铺垫

投资要趁早,才能尽早享受复利。早点学习,以后的项目都可以拿来锻炼,每写一行代码,都是对内功的利用和加深,受益整个职业生涯。

2. 如何评价代码质量的高低

1. 简单的描述词汇

灵活性flexibility、可扩展性extensibility、可维护性maintainability、可读性readability、可测试性特testability、模块化modularity、高内聚低耦合high cohesion loose coupling、高效high efficiency、高性能high performance、安全性security、兼容性compatibility、易用性usability、整洁clean、清晰clarity、简单simple、直接straightforward、少即是多less code is more、文档详尽well-documented、分层清晰well-layerd、正确性correctness、bug free、健壮性robustness、鲁棒性robustness、可用性reliability、可伸缩性scalability、稳定性stability、优雅elegant…

2. 最常用的评价标准

1. 可维护性maintainability

维护无非就是修改bug、修改老的代码、添加新的代码之类的工作。代码易维护是指,在不破坏原有代码设计、不引入新的bug的情况下,能快速修改或添加代码。

侧面评价,如果bug容易修复,修改、添加功能能够轻松完成,可以主观的认为代码对我们来说易维护;如果修改一个bug,修改、添加一个功能,需要耗费很长时间,可以认为不易维护。

2. 可读性readability

代码被阅读的次数远超被编写和执行的次数。可读性在非常大的程度上影响代码的可维护性,首先要读懂代码,否则很容易引入新bug。

需要看是否符合编码规范、命名是否达意、注释是否详尽、函数是否长短合适、模块划分是否清晰、是否符合高内聚低耦合等。

code review是个很好的检查代码可读性的手段,如果同事可轻松的读懂你写的代码,说明可读性很好;如果读你的代码时,有很多疑问,说明可读性有待提高。

3. 可扩展性extensibility

代码预留了一些功能扩展点,可以把新功能代码,直接插到扩展点,而不需要为了添加一个功能大动干戈,改动大量的原始代码。

扫描二维码关注公众号,回复: 11579923 查看本文章

4. 灵活性 flexibility

几个场景:

当添加一个新的功能代码时,原有的代码已经预留好了扩展点,不需要修改原有的代码,只要在扩展点上添加新的代码即可,可以说代码写的很灵活。

当要实现一个功能时,发现原有代码中,已经抽象出了很多底层可复用的模块、类等代码,直接拿来用,除了说代码易复用外,可以说代码写的很灵活。

当使用某组接口时,如果这组接口可应对各种使用场景,满足各种不同的需求,除了说接口易用,还可以说这个接口设计的很灵活。

5. 简洁性 simplicity

KISS原则:keep it simple,stupid。尽量保持代码简单。代码简单、逻辑清晰意味着易读易维护。

思从深而行从简,真正的高手能云淡风轻的用最简单的方法解决最复杂的问题,这也是编程老手跟编程新手的本质区别之一。

6. 可复用性reusability

尽量减少重复代码的编写,复用已有的代码。继承、多态存在的目的之一,就是为了提高代码的可复用性;单一职责原则也跟代码复用性有关;重构技巧中解耦、高内聚、模块化等都能提高代码的可复用性。可复用性是很多设计原则、思想、模式所要达到的最终效果。

代码的可复用性跟DRY(Don’t Repeat Yourself)这个设计原则的关系很紧密。

7. 可测试性testability

代码的可测试性差,比较难写单元测试,基本上说明代码设计有问题。

3. 最重要的三条

可维护性、可读性、可扩展性是最重要的三个评价标准。

还有一个易定位,是否有打印详细的日志,是否有可快速定位问题的debug点

3. 面向对象、设计原则、设计模式、编程规范、重构五者的关系

1. 面向对象

  • 面向对象的四大特性:封装、抽象、继承和多态
  • 面向对象编程和面向过程编程的区别和联系
  • 面向对象分析、面向对象设计、面向对象编程
  • 接口和抽象类的区别和各自的应用场景
  • 基于接口而非实现编程的设计思想
  • 多用组合少用继承的设计思想
  • 面向过程的贫血模型和面向对象的充血模型

2. 设计原则

是指导我们代码设计的一些经验总结,需要掌握设计初衷,能解决哪些编程问题,有哪些应用场景。

常用的

  • SOLID原则-SRP单一职责原则
  • SOLID原则-OCP开闭原则
  • SOLID原则-LSP里式替换原则
  • SOLID原则-ISP接口隔离原则
  • SOLID原则-DIP依赖倒置原则
  • DRY原则
  • KISS原则
  • YAGNI原则
  • LOD原则

3. 设计模式

大部分要解决的都是代码的可扩展问题,要了解都能解决哪些问题,掌握典型的应用场景,懂的不过度使用。

23种三大类

1. 创建型

常用:单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式

不常用:原型模式

2. 结构型

常用的:代理模式、桥接模式、装饰者模式、适配器模式

不常用:门面模式、组合模式、享元模式

3. 行为型

常用的:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式

不常用:访问者模式、备忘录模式、命令模式、解释器模式、中介模式

4. 编程规范

如代码大全等书籍,参考阿里巴巴编程规范即可

5. 代码重构

只要软件不断迭代,需求的变化,代码的堆砌,原有的设计必定存在各种问题,就需要持续的重构,保持代码质量不下降,避免代码腐化。

重构的工具就是前面的面向对象设计思想、设计原则、设计模式、编码规范。其实,设计思想、设计原则、设计模式一个最重要的应用场景就是重构的时候。

重构要掌握的:

  • 重构的目的why、对象what、时机when、方法how
  • 保证重构不出错的技术手段:单元测试和代码的可测试性
  • 两种不同规模的重构:大重构(大规模高层次)和小重构

6. 关系

面向对象编程因为其具有丰富的特性(封装抽象继承多态),可实现很多复杂的设计思路,是很多设计原则、设计模式等编码实现的基础。

设计原则是指导代码设计的一些经验总结,对某些场景下,是否应用某种设计模式,有指导意义,如开闭原则是很多设计模式(策略、模板等)的指导原则

设计模式是针对开发中经常遇到的问题,总结的解决方案或设计思路。目的是提高代码的可扩展性。抽象程度上,设计原则比设计模式更抽象,设计模式更具体、更加可执行。

编程规范解决代码的可读性问题,相对设计原则、设计模式更加具体、更偏重代码细节、更能落地。小重构的理论基础就是编程规范。

重构用到的就是上述的理论。

4. 面向对象

1. 概念

  • 面向对象编程

面向对象编程,OOP,Object Oriented Programming,是一种编程范式或编程风格,以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石。

  • 面向对象编程语言

面向对象编程语言,OOPL,Object Oriented Programming Language,是支持类或对象的语法机制,并有现成的语法机制,能方便的实现面向对象编程四大特性的编程语言。

  • 面向对象分析和面向对象设计

面向对象分析就是要搞清楚做什么,面向对象设计就是搞清楚怎么做,两个阶段最终的产出是类的设计,包括程序被拆解为哪些类,每个类有哪些属性方法、类与类之间如何交互等。

2. 封装、抽象、继承和多态分别解决的编程问题

1. 封装特性

封装也叫信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或数据。

需要编程语言提供权限访问控制语法来支持,如java的private、protected、public关键字。

封装存在的意义:

  • 保护数据不被随意修改,提高代码的可维护性
  • 只暴露有限的必要接口,提高类的易用性

2. 抽象特性

抽象是讲如何隐藏方法的具体实现,让使用者只需要关心方法提供了哪些功能,不需要知道这些功能是如何实现的。抽象可通过接口类或抽象类实现,但并不需要特殊的语法机制来支持。

抽象存在的意义:

  • 提高代码的可扩展性、可维护性,修改实现不需要改变定义,减少代码的改动范围
  • 处理复杂系统的有效手段,能有效地过滤不必要关注的信息

3. 继承特性

用来表示类之间的is-a关系,分为两种模式:单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可继承多个父类。为实现继承的特性,编程语言提供特殊的语法机制来支持。

继承主要用来解决代码复用的问题。

4. 多态特性

多态是指子类可替换父类,在实际的代码运行过程中,调用子类的方法实现。多态需要编程语言提供特殊的语法机制实现,如继承、接口类、duck-typing。

多态可提高代码的可扩展性和复用性,是很多设计模式、设计原则、编程技巧的代码实现基础。

3. 面向对象编程相比面向过程编程的优势

  • 对大规模复杂程序的开发,程序的处理流程并非单一的一条主线,而是错综复杂的网状结构。面向对象编程比起面向过程编程,更能应对这种复杂类型的程序开发
  • 面向对象编程相比面向过程编程,具有更加丰富的特性(封装、抽象、继承、多态),利用这些特性编写的代码,更加易扩展、易复用、易维护
  • 从编程语言跟机器打交道的方式的演进规律可以总结:面向对象编程语言比起面向过程编程语言,更加人性化、更加高级、更加智能

4. 三种违反面向对象编程风格的典型代码设计

1. 滥用getter、setter方法

在设计实现类的时候,除非真的需要,否则尽量不要给属性定义setter方法,此外,尽管getter方法相对setter安全,但如果返回的是集合容器,也要防范集合内部数据被修改的风险

如java的Collections.unmodifiableList()方法,让getter返回一个不可被修改的unmodifiableList容器,但仍可修改内部的对象

2. Constants类、Utils类的设计问题

尽量做到职责单一、定义一些细化的小类,如RedisConstants、FileUtils,而不是一个大而全的Constants类。此外,如果能将这些类的属性和方法,划分归并到其他业务中,最好不过,提高内聚性和代码的可复用性。

3. 基于贫血模型的开发模式

MVC模式,数据和操作分开定义在VO/BO/Entity和Controller/Service/Repository中。

5. 接口和抽象类

1. 抽象类和接口的语法特性

抽象类不允许被实例化,只能被继承。可以包含属性和方法。方法既可以包含代码实现,也可以不包含代码实现。不包含代码实现的方法叫抽象方法。子类继承抽象类,必须实现抽象类的所有抽象方法。接口不能包含属性,只能声明方法,方法不能包含代码实现。类实现接口的时候,必须实现接口声明的所有方法。

2. 抽象类和接口的存在的意义

抽象类是对成员变量和方法的抽象,是一种is-a的关系,是为了解决代码复用问题。接口仅仅是对方法的抽象,是一种has-a的关系,表示具有一组行为特征,是为了解决解耦问题,隔离接口和具体的实现,提高代码的扩展性。

3. 抽象类和接口的应用场景区别

什么时候用抽象类,什么时候用接口?判断标准:如果表示一种is-a关系,并且为了解决代码复用问题,用抽象类;如果表示has-a关系,并且为了解决抽象而非代码复用问题,用接口

6. 基于接口而非实现编程

  1. 基于接口而非实现编程,这条原则的另一个表述方式,是基于抽象而非实现编程。后者的标书方式更能体现这条原则的设计初衷。做软件开发时,一定要有抽象意识、封装意识、接口意识。越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性、扩展性、可维护性。
  2. 定义接口的时候,一方面,命名要足够通用,不能包含跟具体实现相关的字眼;另一方面,与特定实现有关的方法不要定义在接口中。
  3. 这条原则,不仅可指导非常细节的编程开发,还能指导更上层的架构设计、系统设计等。如服务器和客户端之间的“接口”设计、类库的“接口”设计。

相比于has-a,bahave-like更形象

7. 继承和组合

1. 为什么不推荐使用继承

继承是面向对象的四大特性之一,用来表示类之间的is-a关系,解决代码复用问题。虽然继承有诸多作用,但继承层次过深、过复杂,也会影响到代码的可维护性。这种情况下,应尽量少用,甚至不用继承。

经典案例就是鸭子问题,会飞、会叫,木头鸭子

2. 组合相比继承的优势

继承主要有三个作用:表示is-a关系,支持多态特性,代码复用。而这三个作用都可以通过组合、接口、委托三个技术手段达成。此外,组合还能解决层次过深、过复杂的继承关系影响代码可维护性的问题。

3. 如何判断该用组合还是继承

如果类之间的继承结构稳定,层次比较浅,关系不复杂,大胆的使用继承。反之,尽量使用组合。

4. 思考

基于MVC架构开发web应用时,经常在dao层定义entity,service层定义BO(Business Object),在controller层定义vo(view object)。大部分情况下,这三者代码有很大重复,但又不完全相同,如何处理代码重复问题?

8. 贫血模型和充血模型

1. 贫血模型

UserBo是一个纯粹的数据结构,只包含数据,不包含任何业务逻辑。业务逻辑集中在UserService中,通过UserService来操作UserBo。也就是说,service层的数据和业务逻辑,被分割为BO和service两个类中。像UserBo这样,只包含数据,不包含业务逻辑的类,叫做贫血模型(Anemic Domain Model)。这种贫血模型将数据和操作分离,破坏了面向对象的封装特性,是一种典型的面向过程的编程风格。

2. 充血模型

充血模型(Rich Domain Model),数据和对应的业务逻辑被封装到同一个类中。

3. 领域驱动设计

领域驱动设计DDD(Domain Driven Design),主要是用来指导如何解耦业务系统,划分业务模块,定义业务领域模型及其交互。

领域驱动设计用来指导划分服务,因此微服务的兴起,加速了领域驱动设计的盛行。

基于充血模型的DDD开发模式实现的代码,也是MVC三层架构分层,相较传统的MVC,区别在于service层。传统的service层包含service类和bo类,在DDD开发模式中,service层包含service类和Domain类两部分。Domain和Bo的区别在于它是基于充血模型开发的,既包含数据,也包含业务逻辑。而service类变得非常单薄。

总结就是,基于贫血模型的传统的开发模式,重service轻bo;基于充血模型的DDD开发模式,轻service重domain。

DDD开发模式,开发流程,需要事先理清所有的业务,定义领域模型所包含的属性和方法,领域模型相当于可复用的业务中间层。新功能需求的开发,都是基于之前定义好的这些领域模型完成。

感觉充血模型很鸡肋,其实想要解决代码复用的问题,有很多思路,如SQL只定义基本的,用mybatis之类的或在service层磊积木;此外,时间成本很重要,程序员水平参差不齐,主流的MVC完全可以满足业务,如果想快速定位问题,也有对策,如重点代码部分写好日志,搭建日志中心,以及pinpoint之类的监控系统即可。

猜你喜欢

转载自blog.csdn.net/wjl31802/article/details/107589206