设计模式之美

思考:

  1. 解决什么问题
  2. 应用场景
  3. 如何权衡,恰当应用于项目 高质量代码长什么样?

为什么用这种设计原则、思想或模式?解决什么编程问题?有哪些应用场景?如何权衡、恰当的在项目中应用?

操作系统、组成原理、编译原理等计算机基础知识在开发中用不上,很难转换成开发“生产力”。但是,它能潜移默化地,间接地提高技术理解。

为什么学习设计模式?

  • 第一个功利性目的:应对面试。平时多积累,面试钱复习即可做到成竹在胸,不再担心设计模式是自己的短板,被问到。
  • 告别烂代码
  • 提高复杂代码设计和开发能力
  • 读源码、学框架事半功倍
  • 职场发展做铺垫

什么才是好代码?
多维度衡量:

  • 笼统概括整体:好,坏,优雅,整洁,清晰
  • 偏重细节、方法论:模块化、高内聚、低耦合、文档详尽、分层清晰
  • 架构设计相关:伸缩性,可用性、稳定性
    几个重要且常用的评论标准:
  • 可维护性,难量化,偏向整体评价。
    1. 受其他因素影响,可读性、简洁、易扩展则可维护性好。也与设计分层,模块化,高内聚低耦合,基于接口,业务复杂度,代码量,文档是否齐全,开发人员水平等因素相关。
    2. 侧面衡量,修改bug难度
    3. 主观性强
  • 可读性,符合编码规范、命名是否达意、注释是否详尽、函数长度模块划分、内聚耦合度,很难覆盖所有指标,因此无法量化。
    侧面衡量: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强制使用类组织数据结构和函数,复杂系统也保持模块清晰

    面向对象编程,以类为思考对象。

    1. 分析设计时不是先思考复杂流程拆分为一个一个方法,而是先思考如何给业务建模,将需求翻译为类
    2. 建立类之间的关系,完成这些不需要考虑错综复杂的处理流程。
    3. 完成类设计之后,按照处理流程,将类组装起来形成程序
  • OOP风格的代码更易复用、易扩展、易维护
    利用面向对象特性更易写出易复用、易扩展、可维护的程序

  • OOP更加人性化、高级、智能
    二进制指令、汇编语言、面向过程 计算机思维方式思考如何设计一组指令,让机器执行指令,操作某些数据,完成某个任务。面向对象编程时思考,如何业务建模,如何将真实的世界映射为类或者对象,更加聚焦到业务,而不是思考跟机器打交道。

哪些代码设计看似是面向对象,实际是面向过程的

  1. 滥用getter setter
    getter返回集合容器,要防范集合内部数据被修改的风险

  2. 滥用全局变量和全局方法
    最好应将全局变量放入业务类中。若不能应细分类,尽量做到职责单一。

    大全局变量类的缺点

    • 影响可维护性(多人开发,容易冲突)
    • 增加编译时间,全局变量一个小改动,所有依赖它的类文件都重新编译。对于大工程一次编译耗费时间几分钟到十几分钟。影响单元测试。
    • 影响复用性,引入此文件时会引入无关变量
  3. 定义数据和方法分离的类
    基于贫血模型的开发模式:后端三层架构VO BO Entity
    为什么这种开发模式如此流行?

面向对象编程中,为什么容易写出面向过程风格的代码?

  1. 人类做事的思路就是思考一步一步做什么才能完成任务。而面向过程需先设计模块(类),再将类组装起来,完成任务。这样适合复杂程序开发,不符合人类习惯。

  2. 面向对象相对难一些。类设计需要技巧与经验,思考设计哪些类,包含哪些数据与方法;思考类间关系;思考类间交互。

基于以上两点,大多数人选择不太需要动脑子的方式实现需求,不由自主的写出面相过程代码了。

面向过程编程及面向过程编程语言就真的无用武之地了吗?

  • 开发的是微小程序,或者是一个数据处理相关的代码,以算法为主,数据为辅,那脚本式的面向过程的编程风格就更适合一些。
  • 面向过程编程是面向对象编程的基础
    类中每个方法的实现逻辑,不就是面向过程风格的代码吗?
  • 两种编程风格不是完全对立的。
    面向对象编程语言开发的软件中,面向过程的代码并不少见,一些标准的开发库(比如 JDK、Apache Commons、Google Guava)中,也有很多面向过程风格的代码。

接口vs抽象类的区别?如何用普通的类模拟抽象类和接口

区别

实现抽象类 说明:is-a关系
实现接口 说明:具有某些功能

抽象类

  • 不可实例化,可继承(复用)
  • 子类必须所有抽象方法
  • 可以包含属性和方法
    接口
  • 不包含属性(成员变量)
  • 只能声明方法
  • 类实现接口,实现所有方法

解决问题

  • 抽象类
    解决复用问题
    不可实例化父类,必须实现抽象方法方法,相比普通类更易用,可维护性高。
  • 接口
    对方法的抽象,解决解耦问题,定义与实现分离,提高扩展性

普通类模拟接口和抽象类

  • 模拟接口
    protected修饰符,方法抛出异常
  • 模拟抽象类
    私有化构造方法,方法抛出异常

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

is-a 的关系,并且是为了解决代码复用问题用抽象类
has-a 关系,并且是为了解决抽象而非代码复用问题就用接口

为什么基于接口而非实现编程?有必要为每个类都定义接口吗?

“基于抽象而非实现编程”的表述方式其实更能体现这条原则的设计初衷。
软件开发时要有抽象意识、封装意识、接口意识。越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性、扩展性、可维护性。

设计接口时思考:

  • 设计是否足够通用,不能包含跟具体实现相关的字眼
  • 与特定实现有关的方法不要定义在接口中

接口和实现分离,封装不稳定的实现,暴露稳定的接口。
降低耦合,提高扩展性,可维护性。

SOLID

单一职责 :模块,接口,类设计职责单一
开放封闭 :扩展开放修改封闭
里氏替换 :子类替换父类
接口隔离 :(接口中不应包含调用者不需要的接口,把不同功能拆分)区别单一职责,它通过调用者如何使用接口来间接地判定
依赖倒置 :依赖抽象不依赖具体,具体实现依赖抽象;高层模块不依赖低层模块,它们共同依赖同一个抽象。

迪米特法则 Law of Demeter 最小知道原则
不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。高内聚,低耦合。

总结

设计模式主要目的:解藕

创建型,将对象的创建和使用解藕
结构型,将不同功能代码解藕
行为型,将不同行为代码解藕
观察者模式将观察者和被观察者的行为解藕