系统设计 基础知识
2024-03-24 21:39:01 0 举报
AI智能生成
系统设计 基础知识
作者其他创作
大纲/内容
RESTful API
API
API(Application Programming Interface) 翻译过来是应用程序编程接口的意思。
把 API 理解为程序与程序之间通信的桥梁,其本质就是一个函数而已。
定义
RESTful API 经常也被叫做 REST API,它是基于 REST 构建的 API。
REST
REST 的全称是 Resource Representational State Transfer ,直白翻译就是 “资源”在网络传输中以某种“表现形式”进行“状态转移” 。
资源(Resource):我们可以把真实的对象数据称为资源。一个资源既可以是一个集合,也可以是单个个体。
表现形式(Representational):"资源"是一种信息实体,它可以有多种外在表现形式。
状态转移(State Transfer):REST 中的状态转移更多地描述的服务器端资源的状态。
RESTful 架构
每一个 URI 代表一种资源。
客户端和服务器之间,传递这种资源的某种表现形式比如 json,xml,image,txt 等等。
客户端通过特定的 HTTP 动词,对服务器端资源进行操作,实现"表现层状态转化"。
动作
GET:请求从服务器获取特定资源。举个例子:GET /classes(获取所有班级)
POST:在服务器上创建一个新的资源。举个例子:POST /classes(创建班级)
PUT:更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:PUT /classes/12(更新编号为 12 的班级)
DELETE:从服务器删除特定的资源。举个例子:DELETE /classes/12(删除编号为 12 的班级)
PATCH:更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),使用的比较少,这里就不举例子了。
路径(接口命名)
路径又称"终点"(endpoint),表示 API 的具体网址。
规范
网址中不能有动词,只能有名词,API 中的名词也应该使用复数。
不用大写字母,建议用中杠 - 不用下杠 _ 。
善用版本化 API。
接口尽量使用名词,避免使用动词。
过滤信息(Filtering)
如果我们在查询的时候需要添加特定条件的话,建议使用 url 参数的形式。
状态码(Status Codes)
2xx:成功
200 成功
201 创建
3xx:重定向
301 永久重定向
304 资源未修改
4xx:客户端错误
400 错误请求
401 未授权
403 禁止访问
404 未找到
405 请求方法不对
5xx:服务器错误
500 服务器错误
502 网关错误
504 网关超时
HATEOAS
返回结果中提供链接,连向其他 API 方法,使得用户不查文档,也知道下一步应该做什么。即 Hypermedia API 设计,也被称为 HATEOAS。
软件工程
在更少资源消耗的情况下,创造出更好、更容易维护的软件。
开发过程
软件开发过程(software development process)或软件过程(software process)是软件开发的开发生命周期(software development life cycle),
其各个阶段实现了软件的需求定义与分析、设计、实现、测试、交付和维护。软件过程是在开发与构建系统时应遵循的步骤,是软件开发的路线图。
其各个阶段实现了软件的需求定义与分析、设计、实现、测试、交付和维护。软件过程是在开发与构建系统时应遵循的步骤,是软件开发的路线图。
需求分析:分析用户的需求,建立逻辑模型。
软件设计:根据需求分析的结果对软件架构进行设计。
编码:编写程序运行的源代码。
测试 : 确定测试用例,编写测试报告。
交付:将做好的软件交付给客户。
维护:对软件进行维护比如解决 bug,完善功能。
开发模型
瀑布模型(Waterfall Model)、快速原型模型(Rapid Prototype Model)、V 模型(V-model)、W 模型(W-model)、敏捷开发模型。
敏捷开发核心
持续集成、重构、小版本发布、低文档、站会、结对编程、测试驱动开发。
基本策略
软件复用
通过复用已有的一些轮子(框架、第三方库等)、设计模式、设计原则等等现成的物料,可以更快地构建出一个满足要求的软件。
分而治之
将一些比较复杂的问题拆解为一些小问题,然后,一一攻克。
在领域驱动(Domain Driven Design,简称 DDD)设计中,很重要的一个概念就是领域(Domain),它就是我们要解决的问题。
在领域驱动设计中,我们要做的就是把比较大的领域(问题)拆解为若干的小领域(子域)。
在领域驱动设计中,我们要做的就是把比较大的领域(问题)拆解为若干的小领域(子域)。
逐步演进
软件开发是一个逐步演进的过程,我们需要不断进行迭代式增量开发,最终交付符合客户价值的产品。
MVP(Minimum Viable Product,最小可行产品),刚好能够满足客户需求的产品。
优化折中
软件开发是一个不断优化改进的过程,要学会折中,在有限的投入内,以最有效的方式提高现有软件的质量。
代码命名
好的代码本身就是注释,我们要尽量规范和美化自己的代码来减少不必要的注释。
规范
驼峰命名法(CamelCase)
使用大小写混合的格式来区别各个单词,并且单词之间不使用空格隔开或者连接字符连接的命名方式。
大驼峰命名法(UpperCamelCase)
类名
小驼峰命名法(lowerCamelCase)
方法名、参数名、成员变量、局部变量
蛇形命名法(snake_case)
各个单词之间通过下划线“_”连接。
优点:命名所需要的单词比较多时,更易读。
应用:测试方法名、常量、枚举名称。
串式命名法(kebab-case)
各个单词之间通过连接符“-”连接。
应用:项目文件夹名称。
基本命名规范
类名需要使用大驼峰命名法(UpperCamelCase)风格。方法名、参数名、成员变量、局部变量需要使用小驼峰命名法(lowerCamelCase)。
测试方法名、常量、枚举名称需要使用蛇形命名法(snake_case)。并且,测试方法名称要求全部小写,常量以及枚举名称需要全部大写。
项目文件夹名称使用串式命名法(kebab-case),比如dubbo-registry。
包名统一使用小写,尽量使用单个名词作为包名,各个单词通过 "." 分隔符连接,并且各个单词必须为单数。
抽象类命名使用 Abstract 开头。
异常类命名使用 Exception 结尾。
测试类命名以它要测试的类的名称开始,以 Test 结尾。
POJO 类中布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。
如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
命名易读性规范
为了能让命名更加易懂和易读,尽量不要缩写/简写单词,除非这些单词已经被公认可以被这样缩写/简写。
命名不像函数一样要尽量追求短,可读性强的名字优先于简短的名字,虽然可读性强的名字会比较长一点。
避免无意义的命名,你起的每一个名字都要能表明意思。
避免命名过长(50 个字符以内最好),过长的命名难以阅读并且丑陋。
不要使用拼音,更不要使用中文。
代码重构
定义
重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
利用设计模式(如组合模式、策略模式、责任链模式)、
软件设计原则(如 SOLID 原则、YAGNI 原则、KISS 原则)和重构手段(如封装、继承、构建测试体系)来让代码更容易理解,更易于修改。
软件设计原则(如 SOLID 原则、YAGNI 原则、KISS 原则)和重构手段(如封装、继承、构建测试体系)来让代码更容易理解,更易于修改。
核心
步子一定要小,每一步的重构都不会影响软件的正常运行,可以随时停止重构。
常见的设计模式
单例模式
⼀种确保⼀个类只有⼀个实例,并提供⼀个全局访问点来访问该实例的创建模式。
原理
⼀个私有构造函数(确保只能单例类⾃⼰创建实例):单例类通常会将其构造函数设为私有,以防⽌外部代码直接实例化对象。
⼀个私有静态变量(确保只有⼀个实例):单例类通常包含⼀个私有的静态变量,⽤于保存该类的唯⼀实例。
⼀个公有静态函数(给使⽤者提供调⽤⽅法)。
优点
使⽤单例模式就可以避免⼀个全局使⽤的类,频繁的创建与销毁,耗费系统资源。
分类
懒汉式(线程不安全)
先不创建实例,当第⼀次被调⽤时,再创建实例,所以被称为懒汉式。
优点
延迟了实例化,如果不需要使⽤该类,就不会被实例化,只有在需要时才创建实例,避免了资源浪费。
缺点
线程不安全,多线程环境下会实例化多个实例。
饿汉式(线程安全)
先不管需不需要使⽤这个实例,直接先实例化好实例(饿死⻤⼀样,所以称为饿汉式),然后当需要使⽤的时候,直接调⽅法就可以使⽤了。
优点
提起实例化好了⼀个实例,避免了线程不安全问题的出现。
缺点
直接实例化了实例,不再延迟实例化。若系统没有使⽤这个实例,或者系统运⾏很久之后才需要使⽤这个实例,都会使操作系统的资源浪费。
懒汉式(线程安全)
实现和线程不安全的懒汉式⼏乎⼀样,唯⼀不同的点是,在 get ⽅法上加了⼀把锁(synchronized)。
优点
延迟实例化,节约了资源,并且是线程安全的。
缺点
虽然解决了线程安全问题,但是因为有锁导致性能降低了。
双重检查锁实现(线程安全)
双重检查锁相当于是改进了线程安全的懒汉式。获取锁之前增加空判断,并且增加 volatile 关键字修饰静态实例。
volatile 作用
禁⽌ JVM 的指令重排,可以保证多线程环境下的安全运⾏。
优点
延迟实例化,节约了资源;线程安全;并且相对于线程安全的懒汉式,性能提⾼了。
缺点
volatile 关键字,对性能也有⼀些影响。
静态内部类实现(线程安全)
新增静态内部类持有实例,外部类提供静态方法返回内部类持有的实例。仅在获取实例时,才会被初始化,⽽且 JVM 会确保只被实例化⼀次。
优点
延迟实例化,节约了资源,且线程安全,性能也提⾼了。
枚举类实现(线程安全)
默认枚举实例的创建就是线程安全的,且在任何情况下都是单例。
优点
写法简单,线程安全,天然防⽌反射和反序列化调⽤。
序列化
把 java 对象转换为字节序列的过程。
反序列化
通过这些字节序列在内存中新建 java 对象的过程。
将⼀个单例实例对象写到磁盘再读回来,从⽽获得了⼀个新的实例。枚举类天然防⽌反序列化。
其他单例模式 可以通过 重写 readResolve() ⽅法,从⽽防⽌反序列化,使实例唯⼀重写。
应用
资源共享:当多个模块或系统需要共享某⼀资源时,可以使⽤单例模式确保该资源只被创建⼀次,避免重复创建和浪费资源。
控制资源访问:单例模式可以⽤于控制对特定资源的访问,例如数据库连接池、线程池等。
配置管理器:当整个应⽤程序需要共享⼀些配置信息时,可以使⽤单例模式将配置信息存储在单例类中,⽅便全局访问和管理。
⽇志记录器:单例模式可以⽤于创建⼀个全局的⽇志记录器,⽤于记录系统中的⽇志信息。
线程池:在多线程环境下,使⽤单例模式管理线程池,确保线程池只被创建⼀次,提⾼线程池的利⽤率。
缓存:单例模式可以⽤于实现缓存系统,确保缓存只有⼀个实例,避免数据不⼀致性和内存浪费。
⼯⼚模式
定义
⼯⼚模式是⼀种⾮常常⽤的创建型设计模式,其提供了创建对象的最佳⽅式。
在创建对象时,不会对客户端暴露对象的创建逻辑,⽽是通过使⽤共同的接⼝来创建对象。
原理
⽤来封装和管理类的创建,本质是对获取对象过程的抽象。
优点
解耦:将对象的创建和使⽤进⾏分离,客户端代码与具体产品类的实例化过程解耦,客户端只需知道⼯⼚和抽象产品的接⼝,⽽不需要关⼼具体的实现类。
可复⽤:对于创建过程⽐较复杂且在很多地⽅都使⽤到的对象,通过⼯⼚模式可以提⾼对象创建的代码的复⽤性。
易于扩展:添加新的产品类时,只需要扩展相应的具体产品和具体⼯⼚,⽽不需要修改已有的代码,符合开闭原则。
更符合⾯向对象的设计原则:通过⼯⼚模式,将对象的创建封装在⼯⼚类中,使得系统更符合单⼀职责原则。
分类
简单⼯⼚
定义
简单⼯⼚模式也被称为静态⼯⼚⽅法模式。
简单⼯⼚模式并不是⼀个标准的设计模式,更像是⼀种编程习惯。
在简单⼯⼚模式中,⼀个⼯⼚类负责创建多个产品类的实例,通过传⼊不同的参数来决定创建哪种产品。
同时在简单⼯⼚模式中会定义⼀个类负责创建其他类的实例,被创建的实例也通常具有共同的⽗类。
缺点
虽实现了对象的创建和使⽤的分离,但不够灵活,⼯⼚类集合了所有产品的创建逻辑,职责过重。
新增⼀个产品就需要在原⼯⼚类内部添加⼀个分⽀,违反了开闭原则。
若是有多个判断条件共同决定创建对象,则后期修改会越来越复杂。
应用
JDK 中的 DateFormate、Calendar 类都有使⽤,通过不同参数返回我们需要的对象。
⼯⼚⽅法
定义
将简单⼯⼚中的⼯⼚类变为⼀个抽象接⼝。
负责给出不同⼯⼚应该实现的⽅法,⾃身不再负责创建各种产品,⽽是将具体的创建操作交给实现该接⼝的⼦⼯⼚类来做。
优点
通过多态的形式解决了简单⼯⼚模式过多的分⽀问题。
虽然在新增产品时不仅要新增⼀个产品类还要实现与之对应的⼦⼯⼚,但是相较于简单⼯⼚模式更符合开闭原则。
应用
JDK 中的 Collection 接⼝中 Iterator 的实现。
抽象⼯⼚
定义
提供⼀个创建⼀系列相关或相互依赖对象的接⼝,⽽⽆需指定它们的具体类。
通常涉及多个抽象产品、多个具体产品和多个具体⼯⼚。
缺点
虽然对于新增⼀个产品族很方便,并且也符合开闭原则,但是新增⼀个产品等级结构,会对整个⼯⼚结构进⾏⼤改。
应用
Spring 中的 BeanFactory。
条件
当⼀个类不知道它所需要的类的时候。
当⼀个类希望通过其⼦类来指定创建对象的时候。
当类将创建对象的职责委托给多个帮助⼦类中的某⼀个,并且希望将哪⼀个帮助⼦类是代理者的信息局部化时。
应用
在数据库操作中,通过⼯⼚模式可以根据不同的数据库类型(MySQL、Oracle等)创建对应的数据库连接对象。
通过⼯⼚模式可以根据配置⽂件或其他条件选择不同类型的⽇志记录器,如⽂件⽇志记录器、数据库⽇志记录器等。
在图形⽤户界⾯(GUI)库中,可以使⽤⼯⼚模式创建不同⻛格或主题的界⾯元素,如按钮、⽂本框等。
在加密算法库中,可以使⽤⼯⼚模式根据需要选择不同的加密算法,例如对称加密、⾮对称加密等。
在⽂件解析过程中,可以使⽤⼯⼚模式根据⽂件类型选择不同的解析器,如XML解析器、JSON解析器等。
在⽹络通信库中,可以使⽤⼯⼚模式创建不同类型的⽹络连接对象,如TCP连接、UDP连接等。
观察者模式
定义
属于⾏为型模式的⼀种,它定义了⼀种⼀对多的依赖关系,让多个观察者对象同时监听某⼀个主题对象。
这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够⾃动更新⾃⼰。
构成
Subject(主题)
主题是被观察的对象,它包含了⼀组观察者对象,并提供了添加、删除和通知观察者的⽅法。主题通常有⼀个状态,当状态改变时,通知所有观察者。
Observer(观察者)
观察者是依赖于主题的对象,当主题的状态发⽣改变时,观察者得到通知并进⾏相应的更新。观察者的具体实现类需要实现更新的⽅法。
ConcreteSubject(具体主题)
具体主题是主题的具体实现类,它维护了⼀个状态,并在状态改变时通知观察者。
ConcreteObserver(具体观察者)
具体观察者是观察者的具体实现类,实现了更新的⽅法,以便在接收到通知时进⾏相应的处理。
优点
解除耦合,让耦合的双⽅都依赖于抽象,从⽽使得各⾃的变换都不会影响另⼀边的变换。
缺点
调试复杂,⽽且在Java中消息的通知⼀般是顺序执⾏,那么⼀个观察者卡顿,会影响整体的执⾏效率,在这种情况下,⼀般会采⽤异步实现。
实现
定义主题接⼝(Subject),包括注册、移除和通知观察者的⽅法。
定义观察者接⼝(Observer),包括更新的⽅法。
创建具体主题类(Concrete Subject),维护⼀组观察者对象,并在状态改变时通知观察者。
创建具体观察者类(Concrete Observer),实现更新的⽅法。
客户端代码中创建主题对象和观察者对象,注册观察者到主题中,然后通过主题改变状态,观察者得到通知并进⾏更新。
应用
事件处理:当⼀个对象的状态发⽣改变时,观察者模式可以⽤于通知和处理与该对象相关的事件。
这在图形⽤户界⾯(GUI)开发中是很常⻅的,例如按钮点击事件、⿏标移动事件等。
这在图形⽤户界⾯(GUI)开发中是很常⻅的,例如按钮点击事件、⿏标移动事件等。
发布订阅:观察者模式可以⽤于实现发布-订阅模型,其中⼀个主题(发布者)负责发送通知,
⽽多个观察者(订阅者)监听并响应这些通知。这种模型在消息队列系统、事件总线等场景中经常使⽤。
⽽多个观察者(订阅者)监听并响应这些通知。这种模型在消息队列系统、事件总线等场景中经常使⽤。
MVC架构:观察者模式常被⽤于实现MVC架构中的模型和视图之间的通信。
当模型的状态发⽣改变时,所有相关的视图都会得到通知并更新显示。
当模型的状态发⽣改变时,所有相关的视图都会得到通知并更新显示。
异步编程:观察者模式可以⽤于处理异步任务的完成事件。任务完成时,通知所有相关的观察者进⾏后续处理。
代理模式
定义
代理模式(Proxy Pattern)是⼀种结构型设计模式,其主要⽬的是在访问某个对象时引⼊⼀种代理对象,通过代理对象控制对原始对象的访问。
结构
抽象主题(Subject):定义了代理对象和真实对象的共同接⼝,使得代理对象能够替代真实对象。
真实主题(Real Subject):是实际执⾏业务逻辑的对象,是代理模式中的被代理对象。
代理(Proxy):包含⼀个指向真实主题的引⽤,提供与真实主题相同的接⼝,可以控制对真实主题的访问,并在需要时负责创建或删除真实主题的实例。
作用
实现懒加载、控制访问、监控对象等场景。
策略模式
定义
策略模式(Strategy Pattern)是⼀种⾏为设计模式,它定义了⼀系列算法,把它们单独封装起来并可以互相替换,使算法独⽴于使⽤它的客户端⽽变化。
这些算法所完成的功能类型是⼀样的,对外接⼝也是⼀样的,只是不同的策略为引起环境⻆⾊表现出不同的⾏为。
相⽐于使⽤⼤量的if...else,使⽤策略模式可以降低复杂度,使得代码更容易维护。
构成
策略接⼝(Stragety): 策略接⼝定义了算法的抽象,具体的策略类实现了这个接⼝。
具体策略类(ConcreteStragety): 具体策略类实现了策略接⼝,封装了具体的算法。
环境类(Context):⽤来操作策略的上下⽂环境类,环境类的构造函数包含了 Strategy 类,
通过多态传进来不同的具体策略(ConcreteStrategyA。ConcreteStrategyB)来调⽤不同策略的⽅法。
通过多态传进来不同的具体策略(ConcreteStrategyA。ConcreteStrategyB)来调⽤不同策略的⽅法。
实现
定义⼀个策略接⼝,声明算法的抽象⽅法。
创建具体的策略类,实现策略接⼝,封装具体的算法。
创建环境类,包含对策略接⼝的引⽤,以及⼀个⽤于设置具体策略对象的⽅法。
在客户端中创建环境类的对象,并调⽤其⽅法来执⾏具体的算法。
优点
使⽤策略模式可以避免使⽤多重条件转移语句。多重转移语句将算法或⾏为的逻辑混合在⼀起,不易维护。
缺点
可能需要定义⼤量的策略类,并且这些策略类都要提供给客户端。
客户端必须知道所有的策略类,并⾃⾏决定使⽤哪⼀个策略类,策略模式只适⽤于客户端知道所有的算法或⾏为的情况。
应用
个商场销售系统根据不同的促销策略计算最终价格。
装饰模式
定义
装饰模式(Decorator Pattern)是⼀种结构型设计模式,它允许在不改变原始类接⼝的情况下,动态地添加功能或责任。
装饰模式通过创建⼀个装饰类,包裹原始类的实例,并在保持原始类接⼝不变的情况下,提供额外的功能。就增加功能来说,装饰模式⽐⽣成⼦类更为灵活。
结构
组件接⼝:定义了具体组件和装饰器共同的接⼝,确保它们可以互相替换。
具体组件:实现了组件接⼝,是被装饰的具体对象。
装饰器:持有⼀个组件对象的引⽤,并实现了组件接⼝。装饰器通常是⼀个抽象类,它的具体⼦类实现具体的装饰逻辑。
具体装饰器:继承⾃装饰器,实现了具体的装饰逻辑,并调⽤⽗类的⽅法以保持接⼝⼀致。
应用
装饰模式把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象。
因此,当需要执⾏特殊⾏为时,客户代码就可以在运⾏时根据需要有选择地、按顺序地使⽤装饰功能包装对象了。
因此,当需要执⾏特殊⾏为时,客户代码就可以在运⾏时根据需要有选择地、按顺序地使⽤装饰功能包装对象了。
23种设计模式
创建型模式
⼯⼚⽅法(factory method)模式
定义⼀个创建对象的接⼝,但由⼦类决定需要实例化哪⼀个类。⼯⼚⽅法使得⼦类实例化的过程推迟。
抽象⼯⼚(abstract factory)模式
提供⼀个接⼝,可以创建⼀系列相关或相互依赖的对象,⽽⽆需指定他们具体的类。
原型(prototype)模式
⽤原型实例指定创建对象的类型,并且通过拷⻉这个原型来创建新的对象。
单例(singleton)模式
保证⼀个类只有⼀个实例,并提供⼀个访问它的全局访问点。
构建器(builder)模式
将⼀个复杂类的表示与其构造相分离,使得相同的构建过程能够得出不同的表示。
结构型模式
适配器(adapter)模式
将⼀个类的接⼝转换成⽤户希望得到的另⼀个接⼝。它使原本不相容的接⼝得以协同⼯作——速记关键字:转换接⼝。
桥接(bridge)模式
将类的抽象部分和它的实现部分分离开来,使它们可以独⽴地变化——速记关键字:继承树拆分。
组合(composite)模式
将对象组合成树型结构以表示“整体-部分”的层次结构,使得⽤户对单个对象和组合对象的使⽤具有⼀致性——速记关键字:树形⽬录结构。
装饰(decorator)模式
动态地给⼀个对象添加⼀些额外的职责。它提供了⽤⼦类扩展功能的⼀个灵活的替代,⽐派⽣⼀个⼦类更加灵活——速记关键字:附加职责。
外观(facade)模式
定义⼀个⾼层接⼝,为⼦系统中的⼀组接⼝提供⼀个⼀致的外观,从⽽简化了该⼦系统的使⽤——速记关键字:对外统⼀接⼝。
享元(flyweight)模式
提供⽀持⼤量细粒度对象共享的有效⽅法。
代理(proxy)模式
为其他对象提供⼀种代理以控制这个对象的访问。
⾏为型模式
职责链(chain of responsibility)模式
通过给多个对象处理请求的机会,减少请求的发送者与接收者之间的耦合。
将接收对象链接起来,在链中传递请求,直到有⼀个对象处理这个请求——速记关键字:传递职责。
将接收对象链接起来,在链中传递请求,直到有⼀个对象处理这个请求——速记关键字:传递职责。
命令(command)模式
将⼀个请求封装为⼀个对象,从⽽可⽤不同的请求对客户进⾏参数化,将请求排队或记录请求⽇志,⽀持可撤销的操作——速记关键字:⽇志记录,可撤销。
解释器(interpreter)模式
给定⼀种语⾔,定义它的⽂法表示,并定义⼀个解释器,该解释器⽤来根据⽂法表示来解释语⾔中的句⼦。
迭代器(iterator)模式
提供⼀种⽅法来顺序访问⼀个聚合对象中的各个元素⽽不需要暴露该对象的内部表示。
中介者(mediator)模式
⽤⼀个中介对象来封装⼀系列的对象交互。
它使各对象不需要显式地相互调⽤,从⽽达到低耦合,还可以独⽴地改变对象间的交互——速记关键字:不直接引⽤。
它使各对象不需要显式地相互调⽤,从⽽达到低耦合,还可以独⽴地改变对象间的交互——速记关键字:不直接引⽤。
备忘录(memento)模式
在不破坏封装性的前提下,捕获⼀个对象的内部状态,并在该对象之外保存这个状态,从⽽可⽤在以后将该对象恢复到原先保存的状态。
观察者(observer)模式
定义对象间的⼀种⼀对多的依赖关系,当⼀个对象的状态发⽣改变时,所有依赖于它的对象都得到通知并⾃动更新。
状态(state)模式
允许⼀个对象在其内部状态改变时改变它的⾏为——速记关键字:状态变成类。
策略(strategy)模式
定义⼀系列算法,把它们⼀个个封装起来,并且使它们之间可互相替换,从⽽让算法可以独⽴于使⽤它的⽤户⽽变化。
模板⽅法(template method)模式
定义⼀个操作中的算法⻣架,⽽将⼀些步骤延迟到⼦类中,使得⼦类可以不改变⼀个算法的结构即可重新定义算法的某些特定步骤。
访问者(visitor)模式
表示⼀个作⽤于某对象结构中的各元素的操作,使得在不改变各元素的类的前提下定义作⽤于这些元素的新操作。
常见的软件设计原则
单⼀职责
在设计类的时候要尽量缩⼩粒度,使功能明确、单⼀,不要做多余的事情(⾼内聚,低耦合)。
开闭原则
软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着可以通过扩展来添加新功能,⽽不必修改现有代码。
⾥⽒替换
⼦类型必须能够替换掉它们的基类型。在程序中,如果有⼀个基类和⼀个⼦类,那么可以⽤⼦类对象替换基类对象,⽽程序的⾏为仍然是正确的。
接⼝隔离
不应该强迫⼀个类实现它不需要的接⼝。⼀个类不应该对它⽤不到的⽅法负责。
依赖倒置
⾼层模块不应该依赖于低层模块,⽽是应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
⽐如Java的操作数据库,Java定义了⼀组接⼝,由各个数据库去实现它,Java不依赖于他们,数据库依赖于Java。
⽐如Java的操作数据库,Java定义了⼀组接⼝,由各个数据库去实现它,Java不依赖于他们,数据库依赖于Java。
组合优于继承
继承耦合度⾼,组合耦合度低。继承基类是固定好的,但是组合通过组合类的指针,可以传⼊不同的类,避免⾼耦合。
组合复⽤原则
概念
优先使⽤组合 contains a(聚合 has a),⽽不是继承 is a 来达到⽬的。
原因
继承会将实现细节暴露给⼦类,继承复⽤破坏了封装性,是⽩箱复⽤。使⽤继承时需要考虑⾥⽒替换原则。
优点
新类对象存取成员对象只通过成员对象的接⼝,是⿊箱复⽤,系统更灵活,降低耦合度。
可以在运⾏时动态进⾏,新对象可动态引⽤与成员对象类型相同的对象。
可以在运⾏时动态进⾏,新对象可动态引⽤与成员对象类型相同的对象。
缺点
需要管理较多对象。
迪⽶特法则
概念
⼀个对象应当对其他对象有尽可能少的了解,即不和陌⽣⼈说话,“朋友圈”概念。
this
该对象⽅法中的参数。
实例变量直接引⽤的对象。
实例变量如果是⼀个聚集(聚合对象),聚集中的元素。
该对象⽅法中创建的变量。
要求
优先考虑将⼀个类设计成不变类。
尽量降低⼀个类的访问权限。
谨慎使⽤ Serializable(持久化,通过序列化⼀个对象,将其写⼊磁盘,以后程序调⽤时重新恢复该对象)。
尽量降低成员的访问权限。
优点
降低类之间的耦合。
缺点
过多使⽤迪⽶特法则,会产⽣⼤量中介类,设计变复杂。
YAGNI(你不需要它原则)
You Aren't Gonna Need It 的缩写。
极限编程原则告诫开发人员,他们应该只实现当前所需的功能,并避免实现未来需要的功能,仅在必要时才实现。
遵守这一原则可以减小代码库大小,同时避免时间和生产力浪费在没有价值的功能上。
DRY(不要重复你自己原则)
系统中,每一块知识都必须是单一、明确而权威的。
DRY 是 Do not Repeat Yourself 的缩写。这个原则旨在帮助开发人员减少代码的重复性,并将公共代码保存在一个地方。
与 DRY 相反的是 WET(功能实现两次或者喜欢打字 Write Everything Twice or We Enjoy Typing)。
实际上,如果你在两个或更多的地方有相同的功能,你可以使用 DRY 原则将它们合并为一个,并在任何你需要的地方重复使用。
KISS 原则
保持简单和直白。
KISS 原则指明了如果大多数的系统能够保持简单而非复杂化,那么他们便能够工作在最佳状态。
因此,简单性应该是设计时的关键指标,同时也要避免不必要的复杂度。
因此,简单性应该是设计时的关键指标,同时也要避免不必要的复杂度。
目的
主要目的主要是提升代码 & 架构的灵活性 / 可扩展性以及复用性。最终目标是提高软件开发速度和质量 。
让代码更容易理解:通过添加注释、命名规范、逻辑优化等手段可以让我们的代码更容易被理解。
避免代码腐化:通过重构干掉坏味道代码。
加深对代码的理解:重构代码的过程会加深你对某部分代码的理解。
发现潜在 bug:是这样的,很多潜在的 bug ,都是我们在重构的过程中发现的。
时机
提交代码之前
营地法则:保证你离开时的代码库一定比来时更健康。
当我们离开营地(项目代码)的时候,请不要留下垃圾(代码坏味道)!尽量确保营地变得更干净了!
开发一个新功能之后&之前
Code Review 之后
捡垃圾式重构
阅读理解代码的时候
注意事项
单元测试是重构的保护网。
不要为了重构而重构。
遵循方法。
单元测试
单元测试(Unit Testing)是针对程序模块(软件设计的最小单位)进行的正确性检验测试工作。
作用
为重构保驾护航、提高代码质量、减少 bug、快速定位 bug、持续集成依赖单元测试
TDD
Test-Driven Development( 测试驱动开发),这是敏捷开发的一项核心实践和技术,也是一种设计方法论。
原理
开发功能代码之前,先编写测试用例代码,然后针对测试用例编写功能代码,使其能够通过。
优点
帮你整理需求,梳理思路。
帮你设计出更合理的接口(空想的话很容易设计出屎)。
减小代码出现 bug 的概率。
提高开发效率(前提是正确且熟练使用 TDD)。
缺点
能用好 TDD 的人非常少,看似简单,实则门槛很高。
投入开发资源(时间和精力)通常会更多。
由于测试用例在未进行代码设计前写,很有可能限制开发者对代码整体设计。
可能引起开发人员不满情绪,这点很严重,毕竟不是人人都喜欢单元测试,尽管单元测试会带给我们相当多的好处。
单测框架
JUnit、Mockito、Spock、PowerMock、JMockit、TestableMock 等等。
JUnit 几乎是默认选择,但是其不支持 Mock,因此还需要选择一个 Mock 工具。Mockito 和 Spock 是最主流的两款 Mock 工具,一般都是在这两者中选择。
Mockito 和 Spock
Spock 没办法 Mock 静态方法和私有方法 ,Mockito 3.4.0 以后,支持静态方法的 Mock。
Spock 基于 Groovy,写出来的测试代码更清晰易读,比较规范(自带 given-when-then 的常用测试结构规范)。
Mockito 没有具体的结构规范,需要项目组自己约定一个或者遵守比较好的测试代码实践。
Mockito 没有具体的结构规范,需要项目组自己约定一个或者遵守比较好的测试代码实践。
Mockito 使用的人群更广泛,稳定可靠。并且,Mockito 是 SpringBoot Test 默认集成的 Mock 工具。
0 条评论
下一页