记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步
投资要趁早,才能尽早享受复利。早点学习,以后的项目都可以拿来锻炼,每写一行代码,都是对内功的利用和加深,受益整个职业生涯。
灵活性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…
维护无非就是修改bug、修改老的代码、添加新的代码之类的工作。代码易维护是指,在不破坏原有代码设计、不引入新的bug的情况下,能快速修改或添加代码。
侧面评价,如果bug容易修复,修改、添加功能能够轻松完成,可以主观的认为代码对我们来说易维护;如果修改一个bug,修改、添加一个功能,需要耗费很长时间,可以认为不易维护。
代码被阅读的次数远超被编写和执行的次数。可读性在非常大的程度上影响代码的可维护性,首先要读懂代码,否则很容易引入新bug。
需要看是否符合编码规范、命名是否达意、注释是否详尽、函数是否长短合适、模块划分是否清晰、是否符合高内聚低耦合等。
code review是个很好的检查代码可读性的手段,如果同事可轻松的读懂你写的代码,说明可读性很好;如果读你的代码时,有很多疑问,说明可读性有待提高。
代码预留了一些功能扩展点,可以把新功能代码,直接插到扩展点,而不需要为了添加一个功能大动干戈,改动大量的原始代码。
几个场景:
当添加一个新的功能代码时,原有的代码已经预留好了扩展点,不需要修改原有的代码,只要在扩展点上添加新的代码即可,可以说代码写的很灵活。
当要实现一个功能时,发现原有代码中,已经抽象出了很多底层可复用的模块、类等代码,直接拿来用,除了说代码易复用外,可以说代码写的很灵活。
当使用某组接口时,如果这组接口可应对各种使用场景,满足各种不同的需求,除了说接口易用,还可以说这个接口设计的很灵活。
KISS原则:keep it simple,stupid
。尽量保持代码简单。代码简单、逻辑清晰意味着易读易维护。
思从深而行从简,真正的高手能云淡风轻的用最简单的方法解决最复杂的问题,这也是编程老手跟编程新手的本质区别之一。
尽量减少重复代码的编写,复用已有的代码。继承、多态存在的目的之一,就是为了提高代码的可复用性;单一职责原则也跟代码复用性有关;重构技巧中解耦、高内聚、模块化等都能提高代码的可复用性。可复用性是很多设计原则、思想、模式所要达到的最终效果。
代码的可复用性跟DRY(Don’t Repeat Yourself)这个设计原则的关系很紧密。
代码的可测试性差,比较难写单元测试,基本上说明代码设计有问题。
可维护性、可读性、可扩展性是最重要的三个评价标准。
还有一个易定位,是否有打印详细的日志,是否有可快速定位问题的debug点
是指导我们代码设计的一些经验总结,需要掌握设计初衷,能解决哪些编程问题,有哪些应用场景。
常用的
大部分要解决的都是代码的可扩展问题,要了解都能解决哪些问题,掌握典型的应用场景,懂的不过度使用。
23种三大类
常用:单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式
不常用:原型模式
常用的:代理模式、桥接模式、装饰者模式、适配器模式
不常用:门面模式、组合模式、享元模式
常用的:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式
不常用:访问者模式、备忘录模式、命令模式、解释器模式、中介模式
如代码大全等书籍,参考阿里巴巴编程规范即可
只要软件不断迭代,需求的变化,代码的堆砌,原有的设计必定存在各种问题,就需要持续的重构,保持代码质量不下降,避免代码腐化。
重构的工具就是前面的面向对象设计思想、设计原则、设计模式、编码规范。其实,设计思想、设计原则、设计模式一个最重要的应用场景就是重构的时候。
重构要掌握的:
面向对象编程因为其具有丰富的特性(封装抽象继承多态),可实现很多复杂的设计思路,是很多设计原则、设计模式等编码实现的基础。
设计原则是指导代码设计的一些经验总结,对某些场景下,是否应用某种设计模式,有指导意义,如开闭原则是很多设计模式(策略、模板等)的指导原则
设计模式是针对开发中经常遇到的问题,总结的解决方案或设计思路。目的是提高代码的可扩展性。抽象程度上,设计原则比设计模式更抽象,设计模式更具体、更加可执行。
编程规范解决代码的可读性问题,相对设计原则、设计模式更加具体、更偏重代码细节、更能落地。小重构的理论基础就是编程规范。
重构用到的就是上述的理论。
面向对象编程,OOP,Object Oriented Programming,是一种编程范式或编程风格,以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石。
面向对象编程语言,OOPL,Object Oriented Programming Language,是支持类或对象的语法机制,并有现成的语法机制,能方便的实现面向对象编程四大特性的编程语言。
面向对象分析就是要搞清楚做什么,面向对象设计就是搞清楚怎么做,两个阶段最终的产出是类的设计,包括程序被拆解为哪些类,每个类有哪些属性方法、类与类之间如何交互等。
封装也叫信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或数据。
需要编程语言提供权限访问控制语法来支持,如java的private、protected、public关键字。
封装存在的意义:
抽象是讲如何隐藏方法的具体实现,让使用者只需要关心方法提供了哪些功能,不需要知道这些功能是如何实现的。抽象可通过接口类或抽象类实现,但并不需要特殊的语法机制来支持。
抽象存在的意义:
用来表示类之间的is-a关系,分为两种模式:单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可继承多个父类。为实现继承的特性,编程语言提供特殊的语法机制来支持。
继承主要用来解决代码复用的问题。
多态是指子类可替换父类,在实际的代码运行过程中,调用子类的方法实现。多态需要编程语言提供特殊的语法机制实现,如继承、接口类、duck-typing。
多态可提高代码的可扩展性和复用性,是很多设计模式、设计原则、编程技巧的代码实现基础。
在设计实现类的时候,除非真的需要,否则尽量不要给属性定义setter方法,此外,尽管getter方法相对setter安全,但如果返回的是集合容器,也要防范集合内部数据被修改的风险
如java的Collections.unmodifiableList()方法,让getter返回一个不可被修改的unmodifiableList容器,但仍可修改内部的对象
尽量做到职责单一、定义一些细化的小类,如RedisConstants、FileUtils,而不是一个大而全的Constants类。此外,如果能将这些类的属性和方法,划分归并到其他业务中,最好不过,提高内聚性和代码的可复用性。
MVC模式,数据和操作分开定义在VO/BO/Entity和Controller/Service/Repository中。
抽象类不允许被实例化,只能被继承。可以包含属性和方法。方法既可以包含代码实现,也可以不包含代码实现。不包含代码实现的方法叫抽象方法。子类继承抽象类,必须实现抽象类的所有抽象方法。接口不能包含属性,只能声明方法,方法不能包含代码实现。类实现接口的时候,必须实现接口声明的所有方法。
抽象类是对成员变量和方法的抽象,是一种is-a的关系,是为了解决代码复用问题。接口仅仅是对方法的抽象,是一种has-a的关系,表示具有一组行为特征,是为了解决解耦问题,隔离接口和具体的实现,提高代码的扩展性。
什么时候用抽象类,什么时候用接口?判断标准:如果表示一种is-a关系,并且为了解决代码复用问题,用抽象类;如果表示has-a关系,并且为了解决抽象而非代码复用问题,用接口
相比于has-a,bahave-like更形象
继承是面向对象的四大特性之一,用来表示类之间的is-a关系,解决代码复用问题。虽然继承有诸多作用,但继承层次过深、过复杂,也会影响到代码的可维护性。这种情况下,应尽量少用,甚至不用继承。
经典案例就是鸭子问题,会飞、会叫,木头鸭子
继承主要有三个作用:表示is-a关系,支持多态特性,代码复用。而这三个作用都可以通过组合、接口、委托三个技术手段达成。此外,组合还能解决层次过深、过复杂的继承关系影响代码可维护性的问题。
如果类之间的继承结构稳定,层次比较浅,关系不复杂,大胆的使用继承。反之,尽量使用组合。
基于MVC架构开发web应用时,经常在dao层定义entity,service层定义BO(Business Object),在controller层定义vo(view object)。大部分情况下,这三者代码有很大重复,但又不完全相同,如何处理代码重复问题?
UserBo是一个纯粹的数据结构,只包含数据,不包含任何业务逻辑。业务逻辑集中在UserService中,通过UserService来操作UserBo。也就是说,service层的数据和业务逻辑,被分割为BO和service两个类中。像UserBo这样,只包含数据,不包含业务逻辑的类,叫做贫血模型(Anemic Domain Model)。这种贫血模型将数据和操作分离,破坏了面向对象的封装特性,是一种典型的面向过程的编程风格。
充血模型(Rich Domain Model),数据和对应的业务逻辑被封装到同一个类中。
领域驱动设计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之类的监控系统即可。