高质量代码
2020-07-27 23:05:26 0 举报
AI智能生成
Java-如何编写高质量代码
作者其他创作
大纲/内容
设计原则
SOLIDC 原则
S
Single Responsibility Principle,缩写 SRP。单一职责原则。
A class or module should have a single reponsibility
一个类或者模块只负责完成一个职责。
判断类的职责是否足够单一
类中的代码行数
函数或者属性是否过多
类依赖的其他类是否过多
私有方法是否过多
是否比较难给类起一个合适的名字
类中是否有大量的方法都是集中操作类中的某几个属性
单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚性。同时,类职责单一,类依赖的和被依赖的其他类也会变少,减少了代码的耦合性,以此来实现代码的高内聚、低耦合。
类拆分得过细,会适得其反,降低内聚性,也会影响代码的可维护性。
O
Open Closed Principle,缩写 OCP。开闭原则。
software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification.
软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。
开闭原则是解决代码扩展性问题最有效的设计原则。
添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。
开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发;同样的代码改动,在粗代码粒度下,可能被认定为“修改”;在细代码粒度下,可能又被认定为“扩展”。
如何做到?
时刻具备扩展意识、抽象意识、封装意识。
最常用来提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态)。
L
Liskov Substitution Principle,缩写 LSP。里式替换原则。
If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。(1986 年由 Barbara Liskov 提出)
Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。(1996 年,Robert Martin)
子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。
哪些代码明显违背了 LSP?
子类违背父类声明要实现的功能
子类违背父类对输入、输出、异常的约定
子类违背父类注释中所罗列的任何特殊说明
里式替换原则是用来指导,继承关系中子类该如何设计的一个原则。理解里式替换原则,最核心的就是理解“design by contract,按照协议来设计”这几个字。
父类定义了函数的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。这里的约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。
I
Interface Segregation Principle,缩写 ISP。接口隔离原则。
Clients should not be forced to depend upon interfaces that they do not use.
客户端不应该被强迫依赖它不需要的接口。其中的“客户端”,可以理解为接口的调用者或者使用者。
“接口”理解为一组接口集合,如果部分接口只被部分调用者使用,我们就需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口。
“接口”理解为单个 API 接口或函数,部分调用者只需要函数中的部分功能,那我们就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数。
“接口”理解为 OOP 中的接口,也可以理解为面向对象编程语言中的接口语法。那接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。
接口隔离原则与单一职责原则的区别
单一职责原则针对的是模块、类、接口的设计
接口隔离原则相对于单一职责原则,一方面更侧重于接口的设计,另一方面它的思考角度也是不同的
接口隔离原则提供了一种判断接口的职责是否单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。
D
Dependency Inversion Principle,缩写 DIP。依赖反转原则。
High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions.
高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。
所谓高层模块和低层模块的划分,简单来说就是,在调用链上,调用者属于高层,被调用者属于低层。
这条原则主要还是用来指导框架层面的设计,高层模块不依赖低层模块,它们共同依赖同一个抽象,抽象不要依赖具体实现细节。
控制反转
是一个比较笼统的设计思想,并不是一种具体的实现方法,一般用来指导框架层面的设计。
这里所说的“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序员“反转”给了框架。
依赖注入
是一种具体的编码技巧,不通过 new 的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类来使用。
依赖注入框架
依赖注入框架提供的扩展点,简单配置一下所有需要的类及其类与类之间依赖关系,就可以实现由框架来自动创建对象、管理对象的生命周期、依赖注入等原本需要程序员来做的事情。
C
Composite Reuse Principle,缩写 CRP。组合复用原则。
要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
与里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范
分类
继承复用
优点
简单和易实现
缺点
继承复用破坏了类的封装性,因为继承会将父类的实现细节暴露给子类
子类与父类的耦合度高,父类的实现的改变会导致子类的实现发生变化
限制了复用的灵活性,在运行时不可能发生变化
合成复用
维持了类的封装性
新旧类之间的耦合度低
灵活性高,可以运行时动态获取对象
KISS 原则
Keep It Simple and Stupid.
Keep It Short and Simple.
Keep It Simple and Straightforward.
尽量保持简单。
是保持代码可读和可维护的重要手段。
代码越少并非一定满足 KISS 原则;代码逻辑复杂不一定违背 KISS 原则。
手段
不要使用同事可能不懂的技术来实现代码。
不要重复造轮子,要善于使用已经有的工具类库。
不要过度优化,使用一些奇淫巧技(位运算代替算术运算、复杂的条件语句代替 if-else、使用一些过于底层的函数等)
YAGNI 原则
You Ain’t Gonna Need It.
你不会需要它。
不要去设计当前用不到的功能;不要去编写当前用不到的代码。即不要过度设计。
DRY 原则
Don’t Repeat Yourself.
不要写重复的代码。
三种代码重复的情况
实现逻辑重复
功能语义重复
代码执行重复
提高代码复用性
减少代码耦合
满足单一职责原则
业务与非业务逻辑分离
通用代码下沉
继承、多态、抽象、封装
应用模板等设计模式
迪米特法则
Law of Demeter,缩写 LOD
The Least Knowledge Principle,最小知识原则。
Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers.
每个模块(unit)只应该了解那些与它关系密切的模块(units: only units “closely” related to the current unit)的有限知识(knowledge)。或者说,每个模块只和自己的朋友“说话”(talk),不和陌生人“说话”(talk)。
不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口(也就是定义中的“有限知识”)。
高内聚、松耦合
一个非常重要的设计思想,能够有效提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围
“高内聚”用来指导类本身的设计,“松耦合”用来指导类与类之间依赖关系的设计。
相近的功能应该放到同一个类中,类与类之间的依赖关系简单清晰。
Rule of Three 原则
第一次编写代码的时候,我们不考虑复用性;第二次遇到复用场景的时候,再进行重构使其复用。
代码重构
概念
重构是一种对软件内部结构的改善,目的是在不改变软件的可见行为的情况下,使其更易理解,修改成本更低。
在保持功能不变的前提下,利用设计思想、原则、模式、编程规范等理论来优化代码,修改设计上的不足,提高代码质量。
为什么要重构?
重构是时刻保证代码质量的一个极其有效的手段,不至于让代码腐化到无可救药的地步。
项目在演进,代码不停地在堆砌,代码总是会往越来越混乱的方向演进。
优秀的代码或架构不是一开始就能完全设计好的,随着系统的演进,重构代码也是不可避免的。
重构是避免过度设计的有效手段。
将一个比较烂的代码重构成一个比较好的代码,会让你很有成就感。对一个工程师本身技术的成长也有重要的意义。初级工程师在维护代码,高级工程师在设计代码,资深工程师在重构代码。
重构的方式
大型重构指的是对顶层代码设计的重构,包括:系统、模块、代码结构、类与类之间的关系等的重构,重构的手段有:分层、模块化、解耦、抽象可复用组件等。
小型重构指的是对代码细节的重构,主要是针对类、函数、变量等代码级别的重构,比如规范命名、规范注释、消除超大类或函数、提取重复代码等。
对重构的态度
寄希望于在代码烂到一定程度之后,集中重构解决所有问题是不现实的,需保持持续重构。
单元测试
最可落地执行、最有效的保证重构不出错的手段
单元测试是代码层面的测试,由研发自己来编写,用于测试“自己”编写的代码的逻辑的正确性。
写单元测试的过程本身就是代码 Code Review 和重构的过程,能有效地发现代码中的 bug 和代码设计上的问题。对集成测试的有力补充、帮助我们快速熟悉代码、TDD 可落地执行的改进方案。
如何编写单元测试?
写单元测试就是针对代码设计各种测试用例,以覆盖各种输入、异常、边界情况,并将其翻译成代码。
写单元测试尽管繁琐,但并不是太耗时
放低对单元测试代码质量的要求
覆盖率作为衡量单元测试质量的唯一标准是不合理的
单元测试不要依赖被测代码的具体实现逻辑
单元测试框架无法测试,多半是因为代码的可测试性不好
单元测试为何难落地执行?
写单元测试本身比较繁琐,技术挑战不大,很多程序员不愿意去写
开发进度紧,执行虎头蛇尾
代码的可测试性
针对代码编写单元测试的难易程度
依赖注入是编写可测试性代码的最有效手段
常见的测试不友好的代码
代码中包含未决行为逻辑
滥用可变全局变量
滥用静态方法
使用复杂的继承关系
高度耦合的代码
解耦
大型重构的有效手段
解耦保证代码松耦合、高内聚,是控制代码复杂度的有效手段。代码高内聚、松耦合,意味着代码结构清晰、分层模块化合理、依赖关系简单、模块或类之间的耦合小,那代码整体的质量不会差。
是否需要解耦?
修改代码是否牵一发而动全身
模块与模块、类与类之间的依赖关系画出来看复杂性
给代码解耦的方法
封装与抽象
中间层
模块化
设计思想与原则
单一职责原则
基于接口而非实现编程
依赖注入
多用组合少用继承
迪米特法则
设计模式(观察者模式等)
评判标准
描述词语
灵活性(flexibility)
可扩展性(extensibility)
可维护性(maintainability)
可读性(readability)
可理解性(understandability)
易修改性(changeability)
可复用(reusability)
可测试性(testability)
模块化(modularity)
高内聚低耦合(high cohesion loose coupling)
高效(high effciency)
高性能(high performance)
安全性(security)
兼容性(compatibility)
易用性(usability)
整洁(clean)
清晰(clarity)
简单(simple)
直接(straightforward)
少即是多(less code is more)
文档详尽(well-documented)
分层清晰(well-layered)
正确性(correctness、bug free)
健壮性(robustness)
可用性(reliability)
可伸缩性(scalability)
稳定性(stability)
优雅(elegant)
好(good)
坏(bad)
最常用
可维护性(maintainability)
可读性(readability)
可扩展性(extensibility)
灵活性(flexibility)
简洁性(simplicity)
可复用性(reusability)
可测试性(testability)
最重要
可维护性(maintainability)
可读性(readability)
可扩展性(extensibility)
学习资源
菜鸟教程
C语言中文网
极客时间-设计模式之美专栏
设计模式实战全集
书籍
《HEAD_FIRST设计模式》
《Java与模式》
GOF的《设计模式》
代码规范
命名
长度:足够表达含义的情况下,越短越好;公认的、熟悉的单词可以使用缩写
利用上下文简化命名:借助类的信息来简化属性、函数的命名,利用函数的信息来简化函数参数的命名
可读、可搜索
注释
"好的命名完全可以替代注释",观点有点极端,命名不可能做到足够详细
注释的目的就是让代码更容易看懂
写明"做什么"、"怎么做",起到总结性与文档的作用
让代码结构更清晰
在复杂的类或者接口的注释中写明"怎么用"
注释太多会对代码阅读产生干扰,也增加了后期的维护成本
注释太少过一段时间可能自己都忘记了代码的意图与作用
格式
函数的代码行数最好不要超过一屏幕的大小,方便阅读
代码行最好不要超过 IDE 显示的宽度
用空行分割单元块:成员变量、方法、方法内每小部分逻辑处理的代码,用空行进行分割
代码缩紧取决团队规定,两格缩紧更省空间
大括号是否另起一行也取决团队规定,在同一行节省行数,另起一行方便对齐
类中成员的排列顺序:字母顺序,先变量后函数,先静态后普通,方法可以按权限修饰符也可以按一组相关的方法放一起
复杂逻辑的代码提炼类和函数,分割成更小的单元块
参数过多(超过5个):检查函数是否职责单一,进行函数拆分;将函数的参数封装成对象
尽量不要在函数中使用布尔类型的标识参数来控制内部逻辑,可以将函数拆分
嵌套层次不要过深,一般不超过 3 层。可以提取部分逻辑到新的函数;调整执行顺序减少嵌套;使用 continue、break、return 关键字,提前退出嵌套
使用解释性变量:常量取代魔法数字、将复杂的表达式赋值给解释性变量
高质量源码
单例模式的实际应用
工厂模式的实际应用
建造者模式的应用
原型模式的应用
代理模式的应用
适配器模式的应用
桥接模式的应用
装饰器模式的应用
面向对象
面向对象编程
OOP,全称 Object Oriented Programming
两个基础概念:类(class)和对象(object)
一种编程范式或编程风格。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石
面向对象编程语言
OOPL,全称 Object Oriented Programming Language
支持类或对象的语法机制,并有现成的语法机制,能方便地实现面向对象编程四大特性(封装、抽象、继承、多态)的编程语言
对象分析(OOA)Object Oriented Analysis
面向对象设计(OOD)Object Oriented Design
面向对象编程和面向对象编程语言之间的关系
不用面向对象编程语言,照样可以进行面向对象编程
使用面向对象编程语言,写出来的代码也不一定是面向对象编程风格
面向对象编程的优势
更能应对大规模复杂程序的开发,程序的处理流程并非单一的一条主线,而是错综复杂的网状结构
具有更加丰富的特性(封装、抽象、继承、多态)。利用这些特性编写出来的代码,更加易扩展、易复用、易维护
OOP 语言更加人性化、更加高级、更加智能
三大特性 or 四大特性
封装
也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或者数据。它需要编程语言提供权限访问控制语法来支持,例如 Java 中的 private、protected、public 关键字。封装特性存在的意义,一方面是保护数据不被随意修改,提高代码的可维护性;另一方面是仅暴露有限的必要接口,提高类的易用性。
抽象
主要讲如何隐藏信息、保护数据,那抽象就是讲如何隐藏方法的具体实现,让使用者只需要关心方法提供了哪些功能,不需要知道这些功能是如何实现的。抽象可以通过接口类或者抽象类来实现,但也并不需要特殊的语法机制来支持。抽象存在的意义,一方面是提高代码的可扩展性、维护性,修改实现不需要改变定义,减少代码的改动范围;另一方面,它也是处理复杂系统的有效手段,能有效地过滤掉不必要关注的信息。
继承
表示类之间的 is-a 关系,为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持,继承主要是用来解决代码复用的问题。
多态
子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。多态这种特性也需要编程语言提供特殊的语法机制来实现,比如继承、接口类、duck-typing。多态可以提高代码的扩展性和复用性,是很多设计模式、设计原则、编程技巧的代码实现基础。
典型的非面向对象代码
滥用 getter、setter 方法
滥用全局变量和全局方法,Constants 类、Utils 类
将类的数据和方法分离,基于贫血模型的开发模式
为什么容易写出面向过程风格的代码?
完成一个任务,一般都会思考,应该先做什么、后做什么,如何一步一步地顺序执行一系列操作,最后完成整个任务
面向过程编程风格符合这种流程化思维方式
面向对象的编程的底层方法就是面向过程的,去完成什么功能
抽象类和接口存在的意义
抽象类是对成员变量和方法的抽象,是一种 is-a 关系,是为了解决代码复用问题
接口仅仅是对方法的抽象,是一种 has-a 关系,表示具有某一组行为特性,是为了解决解耦问题,隔离接口和具体的实现,提高代码的扩展性
基于接口而非实现编程
意图是将接口和实现相分离,封装不稳定的实现,暴露稳定的接口
不需要把每个类设计成接口
在定义接口的时候,一方面,命名要足够通用,不能包含跟具体实现相关的字眼;另一方面,与特定实现有关的方法不要定义在接口中
多用组合少用继承
继承是面向对象的四大特性之一,用来表示类之间的 is-a 关系,可以解决代码复用的问题。虽然继承有诸多作用,但继承层次过深、过复杂,也会影响到代码的可维护性。在这种情况下,我们应该尽量少用,甚至不用继承
通过组合、接口、委托三个技术手段来达成继承的作用,还能解决层次过深、过复杂的继承关系影响代码可维护性的问题
类之间的继承结构稳定,层次比较浅,关系不复杂,使用继承即可
设计模式
分类
创建型
单例模式
Singleton Design Pattern
一个类只允许创建一个对象(或者实例),这个类就是一个单例类
单例的实现:饿汉式、懒汉式、双重检查、静态内部类、枚举
作用:避免频繁创建和销毁系统全局使用的对象
应用场景:全局唯一类,如 系统配置类、系统硬件资源访问类;公共服务类:如 servlet、springmvc 中的 bean、数据库连接池;Web 计数器、序列号生成器
缺陷:对 OOP 特性的支持不友好、隐藏类之间的依赖关系、对代码的扩展性不友好、对代码的可测试性不友好、不支持有参数的构造函数
替代方案:程序员控制、工厂模式、IOC 容器
扩展:线程纬度的单例、集群环境下的单例、多例模式
工厂模式
Factory Design Pattern
分为:简单工厂、工厂方法和抽象工厂。简单工厂可以看作是工厂方法的一种特例
作用:封装变化(创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明)、代码复用(创建代码抽离到独立的工厂类之后可以复用)、隔离复杂性(封装复杂的创建逻辑,调用者无需了解如何创建对象)、控制复杂度(将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁)
简单工厂适用场景:对象的创建逻辑比较简单,if-else 并不多
工厂方法适用场景:对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作;避免 if-else
抽象工厂适用场景:可以创建多种不同类型的工厂,能够创建一族对象,减少了工厂类
简单工厂、工厂方法、抽象工厂之间的关系与选择:如果实例对象的创建逻辑并发不复杂、实例对象的种类并不多即 if 分支不多,且基本不会有太大变动,使用简单工厂模式;需要创建的实例化对象种类较多、变动可能性大、对象的创建逻辑比较复杂,使用工厂方法,可以去除 if 多分支、将复杂的对象创建逻辑封装到每个具体工厂中;抽象工厂的使用并不多,适用于实例对象的层级逻辑比较复杂,抽象出多个实例对象组,每个工厂创建一组实例对象
建造者
Builder Design Pattern
应用场景:能够解决构造方法的参数很多传错参数的问题;可以保证类属性字段安全,不用提供 public setter 方法;可以进行属性字段间的校验、计算、填充等
缺点:创建对象前必须创建 Builder 对象,多一些性能开销,对性能要求极高的的场景下慎用;代码有点啰嗦
原型模式
Prototype Design Pattern
作用:对象的创建成本比较大,同一个类的不同对象之间的大部分字段相同,可以对已有对象进行拷贝的方式创建新对象,达到节省新对象的创建时间的目的
方式
浅拷贝(仅复制基本类型、引用类型的属性变量、引用类型变量的指针;并不复制引用类型变量指向的堆内存中的对象,而是指向同一个对象)
深拷贝(复制基本类型、应用类型的属性变量、引用类型变量的指针、引用类型指向的堆内存中对象,获得一个全完独立的对象)
实现
深拷贝(实现 Cloneable 接口,递归 clone 引用对象或 new 新对象;借助序列化完成深拷贝)
浅拷贝(实现 Cloneable 接口,重写 clone 方法)
结构型
代理模式
Proxy Design Pattern
作用:在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。使用代理对象完成用户请求,屏蔽用户对真实对象的访问
方式:静态代理、动态代理(JDK 自带、cglib、Javassist 可实现)
应用场景
远程代理、虚拟代理、安全代理...
代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。将这些附加功能与业务功能解耦,放到代理类统一处理,程序员只需要关注业务方面的开发
框架开发,如 Spring AOP 功能(JDK动态代理、cglib)
缺点与注意
静态代理需要一个代理类对应一个被代理类,容易造成类文件增加很多,增加大量的维护工作
JDK 动态代理,需要满足一个条件,就是被代理类需要实现接口,否则会抛出 java.lang.ClassCastException 异常
随着 JVM 的优化,JDK 1.8 开始 JDK 动态代理的性能赶上并超越 cglib
cglib 采用动态创建子类的方法,final 修饰的方法无法进行代理
cglib 创建代理对象时所花费的时间却比 JDK 动态代理要多
桥接模式
Bridge Design Pattern
作用:将抽象和实现解耦,让它们可以独立变化;让一个类拥有两个或多个独立变化的维度,通过组合的方式,让这两个或多个维度可以独立进行扩展
实际应用:JDBC 驱动程序的加载、JVM 在多操作系统间的实现、GUI 在多操作系统间的实现
优点:分离抽象接口与实现、类似多继承但比多继承灵活、可扩展性强、功能对用户透明,隐藏了实现细节
缺点:需要抽象出变化的维度,对业务理解程度要求较高、对系统的设计能力要求较高、代码较难理解
装饰器模式
Decorator Design Pattern
作用
能够动态地给一个对象添加额外的职责,灵活地生成子类
主要解决了原始类在多个方向上功能扩展的问题
原始类可以嵌套使用多个装饰器类,装饰器类需要跟原始类继承相同的抽象类或实现相同的接口
装饰器类具有 is-a 和 has-a 的双重关系
角色
被修饰对象的基类
具体被装饰的类
装饰者的抽象类
具体的装饰类
优点:可以动态扩展一个实现类的功能,装饰类和被装饰类可以独立扩展,不会相互耦合
缺点:层次关系比较复杂
适配器模式
Adapter Design Pattern
作用:适配,将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作
应用场景
对有缺陷的接口设计进行再次封装
统一多个类的接口设计
替换依赖的系统
兼容老版本接口
适配不同格式的数据
实际例子
JDK Iterator 适配 Enumeration
slf4j 对不同的日志框架进行了适配
优点:适配器模式是一种补充模式,可以用来系统的后期扩展与修改
缺点:不适合过多使用,否则代码中的方法调用比较乱
门面模式
Facade Design Pattern
门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用
作用
封装系统的底层实现,隐藏系统的复杂性,提供一组更加简单易用、更高层的接口
客户端原本对多个子系统接口的调用,改为调用门面的一个接口,较少交互,提升性能
解决事务问题
组合模式
Composite Design Pattern
作用
将一组对象组织成树状结构,以表示部分和整体的层次结构,让客户端可以统一单个对象和组合对象的逻辑
更像是一种树形结构数据场景下的数据结构与算法的抽象
使用场景:主要用来处理树形结构的数据。如部门、人员组成树状结构,计算薪资
优点:灵活增加节点、调用简单、代码简洁
缺点:适用场景非常单一
享元模式
Flyweight Design Pattern
作用:复用对象,节省内存。(前提是享元对象是不可变对象)
应用场景:当一个系统中存在大量重复对象的时候,可以利用享元模式,将对象设计成享元,在内存中只保留一份实例,供多处代码引用,减少内存中对象的数量,节省内存。如JDK 中 Integer 类就使用了享元模式缓存了 -128 ~ 127 范围的整数
实现:主要是通过工厂模式,在工厂类中,通过一个 Map 或者 List 来缓存已经创建好的享元对象,以达到复用的目的
行为型
观察者模式
Observer Design Pattern
定义:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知
优点:降低了目标与观察者之间的耦合关系、目标与观察者之间建立了一套触发机制
缺点:当观察者对象很多时,通知的发布会花费很长时间
应用场景:对象间存在一对多关系,一个对象的状态发生改变会影响其他对象
实际应用:邮件订阅、RSS Feeds、MQ 消息发布与订阅
模板模式
Template Method Design Pattern
定义:模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤
实际应用:Java InputStream、Java AbstractList、Java Servlet、JUnit TestCase
作用
代码复用(子类可以复用父类中提供的模板方法的代码)
扩展(提供功能扩展点,可以在不修改框架源码的情况下,基于扩展点定制化框架的功能)
缺点:某种实现都需要子类,类的数量增多;代码可读性变差
策略模式
Strategy Design Pattern
定义:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户
理解:包含策略的定义、实现类的创建、静态或者动态使用不同策略
应用场景:替代根据类型进行多层 if -else 判断
优点
避免使用多重条件语句
避免重复代码
提供相同行为的不同实现
符合开闭原则,灵活增加新算法
缺点:策略实现类会比较多
职责链模式
Chain of Responsibility Design Pattern
作用:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止
优点
将请求的发送者和接收者解耦
增强了系统的可扩展性,根据需要增加新的请求处理类,满足开闭原则
责任分担,每个类只需要处理自己该处理的工作,明确责任范围,符合单一职责原则
缺点:不能保证每个请求一定被处理
使用场景: 可动态指定一组对象处理请求
实际应用:Servlet Filter、Spring Interceptor、Dubbo Filter、 Netty ChannelPipeline
状态模式
State Design Pattern
定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为
应用场景
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为
一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态
实际应用:状态机、游戏中不同人物角色的切换
优点:封装状态变化、减少对象间的相互依赖、利于程序的扩展
缺点:结构与实现都较为复杂
迭代器模式
Iterator Design Pattern
定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示
实际应用:JDK 中 Iterator 接口
优点
封装集合内部的复杂数据结构
将集合对象的遍历操作从集合类中拆分出来,职责单一
添加新的遍历算法更加容易,更符合开闭原则
访问者模式
Visitor Design Pattern
定义:允许一个或者多个操作应用到一组对象上,解耦操作和对象本身
作用:在被访问的类里面加一个对外提供接待访问者的接口,解决稳定的数据结构和易变的操作耦合问题
应用场景:对象结构相对稳定,但其操作算法经常变化;对象提供多种不同且不相关的操作,避免对结构的影响
优点
不修改对象结构,为对象结构中的元素添加新的功能,扩展性好
通过访问者来定义整个对象结构通用的功能,提高复用性
将数据结构与作用于结构上的操作解耦,灵活性好
访问者模式把相关的行为封装在一起,符合单一职责原则
缺点
增加新的元素类很困难,违反开闭原则
具体元素对访问者公布细节,这破坏了对象的封装性
依赖了具体类,违反了依赖倒置原则
难理解、难实现
备忘录模式
Memento Design Pattern
定义:在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态
作用:提供了恢复状态的机制;实现了内部状态的封装,满足单一职责原则
实际场景:游戏存档、编辑器和 IDE 的 ctrl+Z 回滚、数据库回滚
缺点:内容过多或者过频繁备份,资源消耗大;适用场景单一
命令模式
Command Design Pattern
定义:将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等(附加控制)功能
作用:将调用操作的对象与实现该操作的对象解耦;增加或删除命令非常方便,不影响其他类,符合开闭原则
实际应用:游戏开发中将把请求包含的数据和处理逻辑封装为命令对象执行;与备忘录模式组合,实现 redo、undo 功能
缺点:产生大量具体命令类
解释器模式
Interpreter Design Pattern
定义:为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法
作用:构建固定文法的句子的解释器
缺点
通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢
较难调试
包含的文法规则很多时会引起类膨胀
适用场景单一
中介模式
Mediator Design Pattern
定义:定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互
作用:引入中介这个中间层,将对象间的一对多关联转变为一对一的关联,只需要跟中介对象交互即可
优点:降低了代码的复杂度,提高了代码的可读性和可维护性
缺点:中介者的职责很大时,代码将复杂,可维护性降低
清单
单例(Singleton):某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
原型(Prototype):将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
工厂方法(Factory Method):定义一个用于创建产品的接口,由子类决定生产什么产品。
抽象工厂(AbstractFactory):提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
建造者(Builder):将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
代理(Proxy):为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
适配器(Adapter):将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
桥接(Bridge):将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
装饰(Decorator):动态的给对象增加一些职责,即增加其额外的功能。
外观(Facade):为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
享元(Flyweight):运用共享技术来有效地支持大量细粒度对象的复用。
组合(Composite):将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
模板方法(TemplateMethod):定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
策略(Strategy):定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
命令(Command):将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
职责链(Chain of Responsibility):把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
状态(State):允许一个对象在其内部状态发生改变时改变其行为能力。
观察者(Observer):多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
中介者(Mediator):定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
迭代器(Iterator):提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
访问者(Visitor):在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
备忘录(Memento):在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
解释器(Interpreter):提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
0 条评论
下一页