设计模式
参考资料
图解设计模式
大话设计模式
设计模式之禅
github我见过最好的设计模式
http://c.biancheng.net/view/1326.html
基本原则
开闭原则
在设计的时候尽可能的考虑,需求的变化,新需求来了尽可能少的改动代码,拥抱变化
定义:指的是软件中一个实体,如类、模块和函数应该对。
面向抽象编程
开闭是对扩展和修改的约束
强调:用抽象构建框架,用实现扩展细节。
优点:提高软件系统的可复用性及可维护性
- 面向对象最基础的设计原则
- 指导我们构建稳定的系统
- 代码不是一次性的,更多时间在维护
- 大多是代码版本的更新迭代
- 我们最好对已有的源码很少修改
- 一般都是新增扩展,类来修改
- 能够降低风险
关于变化
- 逻辑变化
- 比如说算法从变化成其实是可以直接修改的,前提是所有依赖或者关联类都按照相同的逻辑来处理
- 子模块变化
- 子模块变化可能直接引起整体也就是高层的变化
- 可见视图变化
- 如果说需求上多了一些原有逻辑不存在的,可能这种变化是恐怖的,需要我们灵活的设计
例子
- 弹性工作时间,时间是固定的,上下班是可变的
顶层接口
接口是规范,抽象是实现
重点
- 先顶层后细节
- 自顶向下来思考全局不要一开始沉浸于细节
- 高层不依赖于低层,关系应该用抽象来维护
- 针对接口编程不要针对实现编程
以抽象为基准比以细节为基准搭建起来的架构要稳定得多,因此大家在拿到需求之后, 要面向接口编程,先顶层再细节来设计代码结构。
问题
为什么要依赖抽象,抽象表示我还可以扩展还没有具体实现,按照自己的话来解释一遍
- 一般软件中抽象分成两种,接口和抽象类,接口是规范,抽象是模板,我们通过抽象的方式,也就是使用规范和模板这样我们能够使得上层,也就是调用层能够复用逻辑,而我们底层是能够快速更改实现的,例如Spring的依赖注入,Dubbo的SPI,SpringBoot的SPI都如此
依赖的常见写法
- 构造传递依赖对象
- setter方法传递依赖对象
- 接口声明传递对象
最佳实践
- 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备这是依赖倒置的基本要求,接口和抽象类都是属于抽象的,有了抽 象才可能依赖倒置。
- 变量的表面类型尽量是接口或者是抽象类
- 很多书上说变量的类型一定要是接口或者是抽象类,这个有点绝对 化了,比如一个工具类,xxxUtils一般是不需要接口或是抽象类的。还 有,如果你要使用类的clone方法,就必须使用实现类,这个是JDK提供 的一个规范。
- 任何类都不应该从具体类派生
- 如果一个项目处于开发状态,确实不应该有从具体类派生出子类的 情况,但这也不是绝对的,因为人都是会犯错误的,有时设计缺陷是在 所难免的,因此只要不超过两层的继承都是可以忍受的。特别是负责项 目维护的同志,基本上可以不考虑这个规则,为什么工作基本上 都是进行扩展开发,修复行为,通过一个继承关系,覆写一个方法就可 以修正一个很大的Bug,何必去继承最高的基类呢然这种情况尽 量发生在不甚了解父类或者无法获得父类代码的情况下。)
- 尽量不要覆写基类的方法
- 如果基类是一个抽象类,而且这个方法已经实现了,子类尽量不要 覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会产生一 定的影响。
单一职责原则
- 类
- 接口
- 方法
只负责一项职责
如果不是这样设计,一个接口负责两个职责,一旦需求变更,修改其中一个职责的逻辑代码会导致另外一个职责的功能发生故障。
案例
上述图片用户的属性和用户的行为并没有分开
-
下图把
- 用户信息抽成BO(Business Object,业务对象)
- 用户行为抽成Biz(Business Logic 业务逻辑对象)
电话通话会发生下面四个过程
- 拨
- 通话
- 回应
- 挂机
上图的接口做了两个事情
- 协议管理
- dial 拨 接通
- hangup 挂机
- 数据传送
- chat
引起变化的点
- 协议接通会引起会引起变化(连接导致不传输数据)
- 可以有不同的通话方式,
从上面可以看到包含了两个职责,应该考虑拆分成两个接口
问题
为什么要把IAnimal拆分成IFlyAnimal,ISwimAnimal,不拆分会有什么样的问题
- 一个类所提供的功能应该是他所真正具有的,不拆分会导致他不提供的功能但是强行需要实现,而且会有臃肿的类出现
- 可能适配器模式也是为了解决这个问题吧
最佳实践
- 一个接口只服务于一个子模块或者业务逻辑
- 通过业务逻辑压缩接口中的public方法,接口时常去回顾,尽量 让接口达到“满身筋骨肉”,而不是“肥嘟嘟”的一大堆方法;
- 已经被污染了的接口,尽量去修改,若变更的风险较大,则采用进行转化处理;
- 了解环境,拒绝盲从。每个项目或产品都有特定的环境因素,别看到大师是这样做的你就照抄。千万别,环境不同,接口拆分的标准就不同。深入了解业务逻辑,最好的接口设计就出自你的手中!
迪米特法则
一个对象应该对其他对象保证最少的了解,也称,如果两个类不必彼此直接通信,那么这两个类就不应该发生直接的相互作用,如果其中一个类需要调用另外一个类的某个方法的话,可以
能够降低类与类之间的耦合
-
强调只和朋友交流
-
出现在成员变量、方法的输入、输出参数中的类都可以称之为成员朋友类, 而出现在方法体内部的类不属于朋友类。
这里面感觉有点职责分开的感觉,不同的对象应该关注不同的内容,所做的事情也应该是自己所关心的
例子
teamLeader只关心结果,不关心Course
问题
如果以后你要写代码和重构代码你怎么分析怎么重构p>
- 先分析相应代码的职责
- 把不同的对象需要关心的内容抽离出来
- 每个对象应该只创建和关心自己所关心的部分
- 一定要使用的话可以通过三方来使用
- 合适的使用作用域,不要暴露过多的公共方法和非静态的公共方法
注意
迪米特法则要求类“羞涩”一点,尽量不要对外公布太多的 public方法和非静态的public变量,尽量内敛,多使用private、packageprivate、protected等访问权限。
在实际的项目中,需要适度地考虑这个原则,别为了套用原则而做项目。原则只是供参考,如果 违背了这个原则,项目也未必会失败,这就需要大家在采用原则时反复 度量,不遵循是不对的,严格执行就是“过犹不及”。
序列化引起的坑
- 谨慎使用Serializable
- 在一个项目中使用 RMI(Remote Method Invocation,远程方法调用)方式传递一个 VO(Value Object,值对象),这个对象就必须实现Serializable接口 (仅仅是一个标志性接口,不需要实现具体的方法),也就是把需要 络传输的对象进行序列化,否则就会出现NotSerializableException异 常。突然有一天,客户端的VO修改了一个属性的访问权限,从private 变更为public,访问权限扩大了,如果服务器上没有做出相应的变更, 就会 序列化失败,就这么简单。但是这个问题的产生应该属于项目管 理范畴,一个类或接口在客户端已经变更了,而服务器端却没有同步更 新,难道不是项目管理的失职吗li>
遵循的原则
? 如果 一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响, 那就放置在本类中。
里氏替换原则
一个软件实体如果能够适用一个父亲的话,那么一定适用其子类,所有引用父亲的地方必须能透明的使用其子类的对象,子类能够替换父类对象
- 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
- 子类中可以增加自己特有的方法
- 子类的方法重载父类的方法时,入参要比父类的方法输入参数更
- 子类实现父类方法的时候(重写/重载或实现抽象方法),方法的后置条件(方法的输出,返回)要比父类更加
例子
价格重写问题
价格不是直接重写,而是新写一个方法
长方形和正方形问题
问题
为什么要多用组合和聚合少用继承
- 继承是侵入性的
- Java只支持单继承
- 降低了代码的灵活性,子类多了很多约束
- 增强了耦合性,父类修改的时候需要考虑子类的修改
- 会导致关键代码被修改
总结
如果你只有一把铁锤, 那么任何东西看上去都像是钉子。
- 适当的场景使用适当的设计原则
- 需要考虑,人力,成本,时间,质量,不要刻意追求完美
- 需要多思考才能用好工具
我的笔记仓库地址gitee 快来给我点个Star吧
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!