Java设计模式
2021-06-27 07:50:19 1 举报
AI智能生成
Java设计模式及设计原则
作者其他创作
大纲/内容
规范与重构
重构的目的(why)、对象(what)、时机(when)、方法(how)
重构的定义
在保持功能不变的前提下,利用设计思想、原则、模式、编程规范等理论来优化代码,修改设计上的不足,提高代码质量。
重构的目的
重构是时刻保证代码质量的一个极其有效的手段
重构的对象
大规模高层次重构(以下简称为“大型重构”)
定义
对顶层代码设计的重构,包括:系统、模块、代码结构、类与类之间的关系等的重构
手段
分层、模块化、解耦、抽象可复用组件等等
小规模低层次的重构(以下简称为“小型重构”)。
对代码细节的重构,主要是针对类、函数、变量等代码级别的重构,比如规范命名、规范注释、消除超大类或函数、提取重复代码等等
重构的时机
持续重构。时刻具有持续重构意识,才能避免开发初期就过度设计,避免代码维护的过程中质量的下降
重构的方法
大型重构
提前做好完善的重构计划,有条不紊地分阶段来进行。每个阶段完成一小部分代码的重构,然后提交、测试、运行,发现没有问题之后,再继续进行下一阶段的重构,保证代码仓库中的代码一直处于可运行、逻辑正确的状态
小型重构
保证重构不出错的手段,单元测试和代码的可测试性;
单元测试
什么是单元测试?
为什么要写单元测试?
如何编写单元测试?
子主题
不同规模的重构,重点讲解大规模高层次重构(比如系统、模块、代码结构、类与类之间的交互等的重构)和小规模低层次重构(类、函数、变量等的重构)。
代码的可测试性
如果代码中依赖了外部系统或者不可控组件,比如,需要依赖数据库、网络通信、文件系统等,那我们就需要将被测代码与外部系统解依赖,而这种解依赖的方法就叫作“mock”
依赖注入是实现代码可测试性的最有效的手段
Anti-Patterns
未决行为
代码的输出是随机或者说不确定的,比如,跟时间、随机数有关的代码
滥用可变全局变量
全局变量是一种面向过程的编程风格,有种种弊端。实际上,滥用全局变量也让编写单元测试变得困难
滥用静态方法
只有在这个静态方法执行耗时太长、依赖外部资源、逻辑复杂、行为未决等情况下,我们才需要在单元测试中 mock 这个静态方法
复杂继承
相比组合关系,继承关系的代码结构更加耦合、不灵活,更加不易扩展、不易维护。实际上,继承关系也更加难测试
高耦合代码
改善代码质量的20条编程规范
命名
1. 命名多长最合适?
对于作用域比较小的变量,我们可以使用相对短的命名,比如一些函数内的临时变量。相反,对于类名这种作用域比较大的,我更推荐用长的命名方式。
2. 利用上下文简化命名
除了类之外,函数参数也可以借助函数这个上下文来简化命名
3. 命名要可读、可搜索
4. 如何命名接口和抽象类?
注释
1. 注释到底该写什么?
做什么
为什么
怎么做
2. 注释是不是越多越好?
代码风格
1. 类、函数多大才合适?
对于函数代码行数的最大限制,不要超过一个显示屏的垂直高度
当一个类的代码读起来让你感觉头大了,实现某个功能时不知道该用哪个函数了,想用哪个函数翻半天都找不到了,只用到一个小功能要引入整个类(类中包含很多无关此功能实现的函数)的时候,这就说明类的行数过多
2. 一行代码多长最合适?
3. 善用空行分割单元块
4. 四格缩进还是两格缩进?
一定不要用 tab 键缩进。因为在不同的 IDE 下,tab 键的显示宽度不同
5. 大括号是否要另起一行?
6. 类中成员的排列顺序
编程技巧
1. 把代码分割成更小的单元块
2. 避免函数参数过多
大于等于 5 个的时候,我们就觉得参数有点过多了
3. 勿用函数参数来控制逻辑
4. 函数设计要职责单一
5. 移除过深的嵌套层次
去掉多余的 if 或 else 语句
使用编程语言提供的 continue、break、return 关键字,提前退出嵌套
调整执行顺序来减少嵌套
将部分嵌套逻辑封装成函数调用
6. 学会使用解释性变量
常量取代魔法数字
使用解释性变量来解释复杂表达式
创建型
单例模式
用处
处理资源访问冲突
表示全局唯一类
实现方式
懒汉式
饿汉式
双重检测
静态内部类
枚举
存在问题
1. 单例对 OOP 特性的支持不友好
2. 单例会隐藏类之间的依赖关系
3. 单例对代码的扩展性不友好
4. 单例对代码的可测试性不友好
5. 单例不支持有参数的构造函数
工厂模式
简单工厂
工厂方法
工厂方法模式比起简单工厂模式更加符合开闭原则
抽象工厂
作用
封装变化
代码复用
隔离复杂性
控制复杂度
DI容器
配置解析
对象创建
对象生命周期管理
建造者模式(builder模式)
与工厂模式有何区别
原型模式
实现方式
深拷贝
浅拷贝
结构型
代理模式
代理模式的原理与实现
动态代理的原理与实现
应用场景
业务系统的非功能性需求开发
在 RPC、缓存中的应用
桥接模式
装饰器模式
适配器模式
类适配器
使用继承关系来实现
对象适配器
使用组合关系来实现
应用场景
1. 封装有缺陷的接口设计
2. 统一多个类的接口设计
3. 替换依赖的外部系统
4. 兼容老版本接口
5. 适配不同格式的数据
门面模式
应用场景
1. 解决易用性问题
2. 解决性能问题
3. 解决分布式事务问题
组合模式
享元模式
享元模式的实现
主要是通过工厂模式
享元模式 VS 单例、缓存、对象池
应用单例模式是为了保证对象全局唯一。应用享元模式是为了实现对象复用
缓存是为了提高访问效率,而非复用
池化技术中的“复用”理解为“重复使用”,主要是为了节省时间。而享元主要是节省空间
行为型
观察者模式
模版方法模式
模板模式作用一:复用
模板模式作用二:扩展
应用举例一:JdbcTemplate
应用举例二:setClickListener()
应用举例三:addShutdownHook()
模板模式 VS 回调
子主题
策略模式
责任链模式
应对代码的复杂性
让代码满足开闭原则,提高代码的扩展性
学习导读
评判代码质量好坏
可维护性(maintainability)
可读性(readability)
可扩展性(extensibility)
灵活性(flexibility)
简洁性(simplicity)
可复用性(reusability)
可测试性(testability)
面向对象、设计原则、设计模式、编程规范、重构
面向对象
面向对象的四大特性:封装、抽象、继承、多态
子主题
面向对象编程与面向过程编程的区别和联系
面向对象分析、面向对象设计、面向对象编程
接口和抽象类的区别以及各自的应用场景
基于接口而非实现编程的设计思想
多用组合少用继承的设计思想
面向过程的贫血模型和面向对象的充血模型
设计原则
SOLID 原则 -SRP 单一职责原则
SOLID 原则 -OCP 开闭原则
SOLID 原则 -LSP 里式替换原则
SOLID 原则 -ISP 接口隔离原则
SOLID 原则 -DIP 依赖倒置原则
DRY 原则、KISS 原则、YAGNI 原则、LOD 法则
设计模式
创建型
单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式。
不常用的有:原型模式。
结构型
代理模式、桥接模式、装饰者模式、适配器模式。
不常用的有:门面模式、组合模式、享元模式。
行为型
观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式。
不常用的有:访问者模式、备忘录模式、命令模式、解释器模式、中介模式。
编程规范
代码重构
重构的目的(why)、对象(what)、时机(when)、方法(how);
保证重构不出错的技术手段:单元测试和代码的可测试性;
两种不同规模的重构:大重构(大规模高层次)和小重构(小规模低层次)
设计原则与思想:面向对象
封装、抽象、继承、多态分别可以解决哪些编程问题
封装(Encapsulation)
访问权限控制
提高类的易用性
增加可控性
抽象(Abstraction)
抽象作为一种只关注功能点不关注实现的设计思路,正好帮我们的大脑过滤掉许多非必要的信息。
抽象作为一个非常宽泛的设计思想,在代码设计中,起到非常重要的指导作用
在定义(或者叫命名)类的方法的时候,也要有抽象思维,不要在方法定义中,暴露太多的实现细节,以保证在某个时间点需要改变方法的实现逻辑的时候,不用去修改其定义
继承(Inheritance)
继承最大的一个好处就是代码复用
多态(Polymorphism)
继承实现多态
接口实现多态
提高代码的可扩展性和复用性
哪些代码设计看似是面向对象,实际是面向过程的
1. 滥用 getter、setter 方法
2. 滥用全局变量和全局方法
3. 定义数据和方法分离的类
接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?
抽象类
特性
抽象类不允许被实例化,只能被继承
抽象类可以包含属性和方法。方法既可以包含代码实现,也可以不包含代码实现。不包含代码实现的方法叫作抽象方法。
子类继承抽象类,必须实现抽象类中的所有抽象方法
解决问题
代码复用
多态
子主题
为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
如何解读原则中的“接口”二字?
“基于接口而非实现编程”
函数的命名不能暴露任何实现细节
封装具体的实现细节
为实现类定义抽象的接口
基于抽象而非实现编程
是否需要为每个类定义接口?
为何说要多用组合少用继承?如何决定该用组合还是继承?
为什么不推荐使用继承?
2. 组合相比继承有哪些优势?
3. 如何判断该用组合还是继承?
设计原则
单一职责原则
Single Responsibility Principle,缩写为 SRP。那就是:一个类或者模块只负责完成一个职责(或者功能)
描述的对象,类(class)
描述的对象,模块(module)
如何判断类的职责是否足够单一?
类中的代码行数、函数或属性过多,会影响代码的可读性和可维护性,我们就需要考虑对类进行拆分;
类依赖的其他类过多,或者依赖类的其他类过多,不符合高内聚、低耦合的设计思想,我们就需要考虑对类进行拆分;
私有方法过多,我们就要考虑能否将私有方法独立到新的类中,设置为 public 方法,供更多的类使用,从而提高代码的复用性;
比较难给类起一个合适名字,很难用一个业务名词概括,或者只能用一些笼统的 Manager、Context 之类的词语来命名,这就说明类的职责定义得可能不够清晰;
类中大量的方法都是集中操作类中的某几个属性,比如,在 UserInfo 例子中,如果一半的方法都是在操作 address 信息,那就可以考虑将这几个属性和对应的方法拆分出来。
类的职责是否设计得越单一越好?
开闭原则,对扩展开放、修改关闭
如何理解“对扩展开放、修改关闭”?
添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。
修改代码就意味着违背开闭原则吗?
如何做到“对扩展开放、修改关闭”?
为了尽量写出扩展性好的代码,我们要时刻具备扩展意识、抽象意识、封装意识。这些“潜意识”可能比任何开发技巧都重要。
将可变部分封装起来,隔离变化,提供抽象化的不可变接口,给上层系统使用。
支持开闭原则的一些方法论
如何在项目中灵活应用开闭原则?
里式替换原则
定义
子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。
跟多态的区别
违背了 LSP
1. 子类违背父类声明要实现的功能
2. 子类违背父类对输入、输出、异常的约定
3. 子类违背父类注释中所罗列的任何特殊说明
接口隔离原则
定义
“Clients should not be forced to depend upon interfaces that they do not use。”直译成中文的话就是:客户端不应该被强迫依赖它不需要的接口。其中的“客户端”,可以理解为接口的调用者或者使用者。“接口”理解为下面三种东西:
一组 API 接口集合
单个 API 接口或函数
OOP 中的接口概念
与单一职责原则的区别
依赖反转原则
1. 控制反转
2. 依赖注入
3. 依赖注入框架
4. 依赖反转原则
KISS 原则和 YAGNI 原则
KISS 原则
Keep It Simple and Stupid.
Keep It Short and Simple.
Keep It Simple and Straightforward.
如何写出满足 KISS 原则的代码?
不要使用同事可能不懂的技术来实现代码。比如前面例子中的正则表达式,还有一些编程语言中过于高级的语法等。
不要重复造轮子,要善于使用已经有的工具类库。经验证明,自己去实现这些类库,出 bug 的概率会更高,维护的成本也比较高。
不要过度优化。不要过度使用一些奇技淫巧(比如,位运算代替算术运算、复杂的条件语句代替 if-else、使用一些过于底层的函数等)来优化代码,牺牲代码的可读性。
YAGNI 原则
DRY 原则
三种典型的代码重复情况
实现逻辑重复
功能语义重复
代码执行重复
代码复用性(Code Reusability)
怎么提高代码复用性?
减少代码耦合
满足单一职责原则
模块化
业务与非业务逻辑分离
通用代码下沉
继承、多态、抽象、封装
应用模板等设计模式
迪米特法则
辩证思考和灵活应用
Rule of Three
分支主题
0 条评论
下一页