设计模式之美
思考:
- 解决什么问题
- 应用场景
- 如何权衡,恰当应用于项目 高质量代码长什么样?
为什么用这种设计原则、思想或模式?解决什么编程问题?有哪些应用场景?如何权衡、恰当的在项目中应用?
操作系统、组成原理、编译原理等计算机基础知识在开发中用不上,很难转换成开发“生产力”。但是,它能潜移默化地,间接地提高技术理解。
为什么学习设计模式?
- 第一个功利性目的:应对面试。平时多积累,面试钱复习即可做到成竹在胸,不再担心设计模式是自己的短板,被问到。
- 告别烂代码
- 提高复杂代码设计和开发能力
- 读源码、学框架事半功倍
- 职场发展做铺垫
什么才是好代码?
多维度衡量:
- 笼统概括整体:好,坏,优雅,整洁,清晰
- 偏重细节、方法论:模块化、高内聚、低耦合、文档详尽、分层清晰
- 架构设计相关:伸缩性,可用性、稳定性
几个重要且常用的评论标准: - 可维护性,难量化,偏向整体评价。
- 受其他因素影响,可读性、简洁、易扩展则可维护性好。也与设计分层,模块化,高内聚低耦合,基于接口,业务复杂度,代码量,文档是否齐全,开发人员水平等因素相关。
- 侧面衡量,修改bug难度
- 主观性强
- 可读性,符合编码规范、命名是否达意、注释是否详尽、函数长度模块划分、内聚耦合度,很难覆盖所有指标,因此无法量化。
侧面衡量:code review其他人容易读懂 - 可扩展性 尽量少修改源代码,以扩展方式添加新功能。
- 灵活性
- 简洁
- 可复用
- 可测试
如何写出高质量代码:
掌握可落地的编程方法论,包括:设计思想、设计原则、设计模式、编码规范、重构技巧等
面向对象、设计原则、设计模式、编程规范、重构的关系
- 面向对象 是实现设计思想、原则、模式的基础
- 设计原则 代码设计的经验总结
- 设计模式 总结开发中经常遇到的一些设计问题,形成的解决方案或思路
- 编程规范 主要解决可读性问题,偏重代码细节,持续的小重构依赖的理论基础主要就是编程规范
- 重构 保持代码质量不下降的有效手段,利用的就是面向对象、设计原则、设计模式、编码规范这些理论。
当谈论面向对象的时候,我们到底在谈论什么
面向对象编程语言
支持类和对象的语法机制,且语法机制实现了面向对象(封装、抽象、继承、多态)特性。
面向对象编程
一种编程风格(范式),以类和对象为基本单元,并将封装、抽象、继承、多态作为代码的设计与实现的基石。
理解每种特性讲的是什么内容、解决什么问题、存在的意义
面向对象分析和面向对象设计
OOA Object Oriented Analysis :分析做什么
OOD Object Oriented Design :怎么做
OOP :将分析和设计的的结果翻译成代码的过程
OOA、OOD围绕着对象或类做需求分析和设计。分析和设计最终产出类的设计,包括程序拆解为哪些类,类有哪些属性方法,类之间如何交互等等。OOA、AAD产出更加具体、更加落地、更加贴近编码,更能够顺利地过渡到面向对象编程环节。
UML
Unified Model Language 统一建模语言
封装、抽象、继承、多态解决什么问题
不同语言实现此四特性的语法机制有所不同
封装 Encapsulation
定义
封装也叫作信息隐藏与数据访问保护(隐藏信息、保护数据)。类暴漏有限的访问接口,授权外部通过类提供的方式(或函数)访问内部信息(或数据)
语法机制支持:访问权限控制(private,public…)
解决问题(意义)
- 保护数据不被随意修改,提高可维护性;增加访问限制(控制灵活性)减少属性随意修改,导致逻辑混乱,而影响可读、维护性
- 暴露有限的必要接口,提高类的易用性暴漏必要的方法,让使用更加简单,不必理解业务细节,减少用错概率。
抽象 Abstraction
定义
隐藏方法实现,只需直到提供了此功能而不必了解具体实现
语法机制支持: 利用interface 和Abstract 两种语法机制,实现抽象特性
为什么抽象有时不被认为是面向对象的特性
不需要必须依靠接口类或者抽象类这些特殊语法机制来支持,因为函数本身就是一种抽象(通过函数包裹具体的实现逻辑),不需要了解内部实现。所以只提供函数语法机制,即可实现抽象特性,所以,没有很强的“特异性”,有时不被看作面向对象编程的特性。
解决问题(意义)
- 提高代码的可扩展性、维护性,修改实现不需要改变定义,减少代码的改动范围;
- 处理复杂系统的有效手段,能有效地过滤掉不必要关注的信息。
继承 Inheritance
表示类之间的 is-a 关系,分为两种模式:单继承和多继承
语法机制支持: java是使用extends关键字实现继承
解决问题(意义)
- 代码复用(重用父类代码),但可通过组合关系实现
- 反应现实世界is-a的关系,符合人类认知
多态 Polymorphism
子类可替换父类,在运行时调用子类方法实现。
其他两种实现多态的方式:利用接口(C++不支持),duck-typing 语法(动态语言py,js支持)
语法机制支持:
- 支持父类对象可以引用子类对象
- 支持继承
- 支持子类可以重写父类中的方法
解决问题(意义)
- 提高代码的可扩展性和复用性
- 是许多设计原则、模式、编程技巧的基础(比如策略模式、基于接口而非实现编程、依赖倒置原则、里式替换原则、利用多态去掉冗长的 if-else 语句等等)
面向对象相比面向过程有哪些优势?面向过程真的过时了吗?
面向对象的优势
OOP更好应对大规模复杂程序开发
复杂程序处理流程是复杂的网状结构,用面向过程线性的思维方式,把程序拆解为顺序执行的方法很吃力。
OOP强制使用类组织数据结构和函数,复杂系统也保持模块清晰面向对象编程,以类为思考对象。
- 分析设计时不是先思考复杂流程拆分为一个一个方法,而是先思考如何给业务建模,将需求翻译为类
- 建立类之间的关系,完成这些不需要考虑错综复杂的处理流程。
- 完成类设计之后,按照处理流程,将类组装起来形成程序
OOP风格的代码更易复用、易扩展、易维护
利用面向对象特性更易写出易复用、易扩展、可维护的程序OOP更加人性化、高级、智能
二进制指令、汇编语言、面向过程 计算机思维方式思考如何设计一组指令,让机器执行指令,操作某些数据,完成某个任务。面向对象编程时思考,如何业务建模,如何将真实的世界映射为类或者对象,更加聚焦到业务,而不是思考跟机器打交道。
哪些代码设计看似是面向对象,实际是面向过程的
滥用
getter
setter
getter返回集合容器,要防范集合内部数据被修改的风险滥用全局变量和全局方法
最好应将全局变量放入业务类中。若不能应细分类,尽量做到职责单一。大全局变量类的缺点
- 影响可维护性(多人开发,容易冲突)
- 增加编译时间,全局变量一个小改动,所有依赖它的类文件都重新编译。对于大工程一次编译耗费时间几分钟到十几分钟。影响单元测试。
- 影响复用性,引入此文件时会引入无关变量
定义数据和方法分离的类
基于贫血模型的开发模式:后端三层架构VO BO Entity
为什么这种开发模式如此流行?
面向对象编程中,为什么容易写出面向过程风格的代码?
人类做事的思路就是思考一步一步做什么才能完成任务。而面向过程需先设计模块(类),再将类组装起来,完成任务。这样适合复杂程序开发,不符合人类习惯。
面向对象相对难一些。类设计需要技巧与经验,思考设计哪些类,包含哪些数据与方法;思考类间关系;思考类间交互。
基于以上两点,大多数人选择不太需要动脑子的方式实现需求,不由自主的写出面相过程代码了。
面向过程编程及面向过程编程语言就真的无用武之地了吗?
- 开发的是微小程序,或者是一个数据处理相关的代码,以算法为主,数据为辅,那脚本式的面向过程的编程风格就更适合一些。
- 面向过程编程是面向对象编程的基础
类中每个方法的实现逻辑,不就是面向过程风格的代码吗? - 两种编程风格不是完全对立的。
面向对象编程语言开发的软件中,面向过程的代码并不少见,一些标准的开发库(比如 JDK、Apache Commons、Google Guava)中,也有很多面向过程风格的代码。
接口vs抽象类的区别?如何用普通的类模拟抽象类和接口
区别
实现抽象类 说明:is-a关系
实现接口 说明:具有某些功能
抽象类
- 不可实例化,可继承(复用)
- 子类必须所有抽象方法
- 可以包含属性和方法
接口 - 不包含属性(成员变量)
- 只能声明方法
- 类实现接口,实现所有方法
解决问题
- 抽象类
解决复用问题
不可实例化父类,必须实现抽象方法方法,相比普通类更易用,可维护性高。 - 接口
对方法的抽象,解决解耦问题,定义与实现分离,提高扩展性
普通类模拟接口和抽象类
- 模拟接口
protected修饰符,方法抛出异常 - 模拟抽象类
私有化构造方法,方法抛出异常
抽象类和接口的应用场景区别
is-a 的关系,并且是为了解决代码复用问题用抽象类
has-a 关系,并且是为了解决抽象而非代码复用问题就用接口
为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
“基于抽象而非实现编程”的表述方式其实更能体现这条原则的设计初衷。
软件开发时要有抽象意识、封装意识、接口意识。越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性、扩展性、可维护性。
设计接口时思考:
- 设计是否足够通用,不能包含跟具体实现相关的字眼
- 与特定实现有关的方法不要定义在接口中
接口和实现分离,封装不稳定的实现,暴露稳定的接口。
降低耦合,提高扩展性,可维护性。
SOLID
单一职责 :模块,接口,类设计职责单一
开放封闭 :扩展开放修改封闭
里氏替换 :子类替换父类
接口隔离 :(接口中不应包含调用者不需要的接口,把不同功能拆分)区别单一职责,它通过调用者如何使用接口来间接地判定
依赖倒置 :依赖抽象不依赖具体,具体实现依赖抽象;高层模块不依赖低层模块,它们共同依赖同一个抽象。
迪米特法则 Law of Demeter 最小知道原则
不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。高内聚,低耦合。
总结
设计模式主要目的:解藕
创建型,将对象的创建和使用解藕
结构型,将不同功能代码解藕
行为型,将不同行为代码解藕
观察者模式将观察者和被观察者的行为解藕