迪米特法则
一个对象应该对其他对象保持最少的了解
合成复用原则
尽量使用合成/聚合的方式,而不是使用继承。
单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,告诉我们要对扩展开放,对修改关闭。
设计模式
设计模式:软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案。
-
创建型:主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码
-
结构型:主要通过类或对象的不同组合,解耦不同功能的耦合
-
行为型:主要解决的是类或对象之间的交互行为的耦合
| 类型 | 模式 | 说明 | 适用场景 |
| — | — | — | — |
| 创建型 | 单例 | 一个类只允许创建一个实例或对象,并为其提供一个全局的访问点 | 无状态/全局唯一/控制资源访问 |
|
| 工厂 | 创建一个或者多个相关的对象,而使用者不用关心具体的实现类 | 分离对象的创建和使用 |
|
| 建造者 | 用于创建一种类型的复杂对象,通过设置不同的可选参数进行“定制化” | 对象的构造参数较多且多数可选 |
|
| 原型 | 通过复制已有对象来创建新的对象 | 对象的创建成本较大且同一类的不同对象之前差别不大 |
| 结构型 | 代理 | 不改变原始类和不使用继承的情况下,通过引入代理类来给原始类附加功能 | 增加代理访问,比如监控、缓存、限流、事务、RPC |
|
| 装饰者 | 不改变原始类和不使用继承的情况下,通过组合的方式动态扩展原始类的功能 | 动态扩展类的功能 |
|
| 适配器 | 不改变原始类的情况下,通过组合的方式使其适配新的接口 | 复用现有类,但与期望接口不适配 |
|
| 桥接 | 当类存在多个独立变化的维度时,通过组合的方式使得其可以独立进行扩展 | 存在多个维度的继承体系时 |
|
| 门面 | 为子系统中一组接口定义一个更高层的接口,使得子系统更加容易使用 | 解决接口复用性(细粒度)与接口易用性(粗粒度)的矛盾 |
|
| 组合 | 将对象组合成树形结构以表示部分-整体的层次结构,统一单个对和组合对象的处理逻辑 | 满足部分与整体这种树形结构 |
|
| 享元 | 运用共享技术有效地支持大量细粒度的对象 | 当系统存在大量的对象,这些对象的很多字段取值范围固定 |
| 行为型 | 观察者 | 多个观察者监听同一主题对象,当主题对象状态发生变化时通知所有观察者,使它们能够自动更新自己 | 解耦事件创建者与接收者 |
|
| 模板 | 定义一个操作中算法的骨架,将某些步骤实现延迟到子类中 | 解决复用与扩展问题 |
|
| 策略 | 定义一组算法类,将每个算法分别封装起来,使得它们可以互相替换 | 消除各种if-else分支判断 解耦策略的定义、创建、使用 |
|
| 状态 | 允许一个对象在其内部状态改变的时候改变其行为 | 分离对象的状态与行为 |
|
| 职责链 | 将一组对象连成一条链,请求沿着该链传递,直到某个对象能够处理它为止 | 解耦请求的发送者与接收者 |
|
| 迭代器 | 提供一种方法顺序访问一个集合对象的各个元素,但不暴露该对象的内部表示 | 解耦集合对象的内部表示与遍历访问 |
|
| 访问者 | 封装一些作用于某种数据结构中各元素的操作,在不改变数据结构的前提下,定义作用于这些元素的新操作。 | 分离对象的数据结构与行为 |
|
| 备忘录 | 在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态 | 用于对象的备份与恢复 |
|
| 命令 | 将不同的请求封装成对应的命令对象,对命令的执行进行控制且对使用方透明 | 用于控制命令的执行,比如异步、延迟、排队、撤销、存储与撤销 |
|
| 解释器 | 为某个语言定义它的语法表示,并定义一个解释器来处理这个语法 | 用于编译器、规则引擎、正则表达式等特定场景 |
|
| 中介 | 定义一个单独的中介对象,来封装一组对象之间的交互,避免对象之间的直接交互 | 使各个对象不需要显式地相互引用,从而使其耦合松散 |
代码分层
模块结构说明
-
server_main:配置层,负责整个项目的module管理,maven配置管理、资源管理等;
-
server_application:应用接入层,承接外部流量入口,例如:RPC接口实现、消息处理、定时任务等;不要在此包含业务逻辑;
-
server_biz:核心业务层,用例服务、领域实体、领域事件等
-
server_irepository:资源接口层,负责资源接口的暴露
-
server_repository:资源层,负责资源的proxy访问,统一外部资源访问,隔离变化。注意:这里强调的是弱业务性,强数据性;
-
server_common:公共层,vo、工具等
代码开发要遵守各层的规范,并注意层级之间的依赖关系。
命名规范
一个好的命名应该要满足以下两个约束:
- 准确描述所做得事情
- 格式符合通用的惯例
如果你觉得一个类或方法难以命名的时候,可能是其承载的功能太多了,需要进一步拆分。
约定俗称的惯例
| 场景 | 强约束 | 示例 |
| — | — | — |
| 项目名 | 全部小写,多个单词用中划线分隔‘-’ | spring-cloud |
| 包名 | 全部小写 | com.alibaba.fastjson |
| 类名/接口名 | 单词首字母大写 | ParserConfig,DefaultFieldDeserializer |
| 变量名 | 首字母小写,多个单词组成时,除首个单词,其他单词首字母都要大写 | password, userName |
| 常量名 | 全部大写,多个单词,用’_'分隔 | CACHE_EXPIRED_TIME |
| 方法 | 同变量 | read(), readObject(), getById() |
类命名
类名使用大驼峰命名形式,类命通常使用名词或名词短语。接口名除了用名词和名词短语以外,还可以使用形容词或形容词短语,如 Cloneable,Callable 等,表示实现该接口的类有某种功能或能力。
| 场景 | 约束 | 示例 |
| — | — | — |
| 抽象类 | Abstract 或者 Base 开头 | BaseUserService |
| 枚举类 | Enum 作为后缀 | GenderEnum |
| 工具类 | Utils 作为后缀 | StringUtils |
| 异常类 | Exception 结尾 | RuntimeException |
| 接口实现类 | 接口名+ Impl | UserServiceImpl |
| 设计模式相关类 | Builder,Factory 等 | 当使用到设计模式时,需要使用对应的设计模式作为后缀,如 ThreadFactory |
| 处理特定功能的类 | Handler,Predicate, Validator | 表示处理器,校验器,断言,这些类工厂还有配套的方法名如 handle,predicate,validate |
| 特定层级的类 | Controller,Service,ServiceImpl,Dao 后缀 | UserController, UserServiceImpl,UserDao |
| 特定层级的值对象 | Ao, Param, Vo,Config, Message | Param调用入参;Ao为thrift返回结果;Vo通用值对象;Config配置类;Message为MQ消息 |
| 测试类 | Test 结尾 | UserServiceTest, 表示用来测试 UserService 类的 |
方法命名
方法命名采用小驼峰的形式,首字小写,往后的每个单词首字母都要大写。和类名不同的是,方法命名一般为动词或动词短语,与参数或参数名共同组成动宾短语,即动词 + 名词。一个好的函数名一般能通过名字直接获知该函数实现什么样的功能。
| 场景 | 约束 | 示例 |
| — | — | — |
| 返回真伪值 | is/can/has/needs/should | isValid/canRemove |
| 用于检查 | ensure/validate | ensureCapacity/validateInputs |
| 按需执行 | IfNeeded/try/OrDefault/OrElse | drawIfNeeded/tryCreate/getOrDefault |
| 数据相关 | get/search/save/update/batchSave/ batchUpdate/saveOrUpdateselect /insert/update/delete | getUserById/searchUsersByCreateTime |
| 生命周期 | initialize/pause/stop/destroy | initialize/pause/onPause/stop/onStop |
| 常用动词对 | split/join、inject/extract、bind/seperate、 increase/decrease、lanch/run、observe/listen、build/publish、 encode/decode、submit/commit、push/pull、enter/exit、 expand/collapse、encode/decode |
|
重构技巧
提炼方法
多个方法代码重复、方法中代码过长或者方法中的语句不在一个抽象层级。方法是代码复用的最小粒度,方法过长不利于复用,可读性低,提炼方法往往是重构工作的第一步。
意图导向编程:把处理某件事的流程和具体做事的实现方式分开。
-
把一个问题分解为一系列功能性步骤,并假定这些功能步骤已经实现
-
我们只需把把各个函数组织在一起即可解决这一问题
-
在组织好整个功能后,我们在分别实现各个方法函数
/**
-
1、交易信息开始于一串标准ASCII字符串。
-
2、这个信息字符串必须转换成一个字符串的数组,数组存放的此次交易的领域语言中所包含的词汇元素(token)。
-
3、每一个词汇必须标准化。
-
4、包含超过150个词汇元素的交易,应该采用不同于小型交易的方式(不同的算法)来提交,以提高效率。
-
5、如果提交成功,API返回”true”;失败,则返回”false”。
*/
public class Transaction {
public Boolean commit(String command) {
Boolean result = true;
String[] tokens = tokenize(command);
normalizeTokens(tokens);
if (isALargeTransaction(tokens)) {
result = processLargeTransaction(tokens);
} else {
result = processSmallTransaction(tokens);
}
return result;
}
}
复制代码
以函数对象取代函数
将函数放进一个单独对象中,如此一来局部变量就变成了对象内的字段。然后你可以在同一个对象中将这个大型函数分解为多个小型函数。
引入参数对象
方法参数比较多时,将参数封装为参数对象
移除对参数的赋值
public int discount(int inputVal, int quantity, int yearToDate) {
if (inputVal > 50) inputVal -= 2;
if (quantity > 100) inputVal -= 1;
if (yearToDate > 10000) inputVal -= 4;
return inputVal;
}
public int discount(int inputVal, int quantity, int yearToDate) {
int result = inputVal;
if (inputVal > 50) result -= 2;
if (quantity > 100) result -= 1;
if (yearToDate > 10000) result -= 4;
return result;
}
复制代码
将查询与修改分离
任何有返回值的方法,都不应该有副作用
-
不要在convert中调用写操作,避免副作用
-
常见的例外:将查询结果缓存到本地
移除不必要临时变量
临时变量仅使用一次或者取值逻辑成本很低的情况下
引入解释性变量
将复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途
if ((platform.toUpperCase().indexOf(“MAC”) > -1)
&& (browser.toUpperCase().indexOf(“IE”) > -1) && wasInitialized() && resize > 0) {
// do something
}
final boolean isMacOs = platform.toUpperCase().indexOf(“MAC”) > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf(“IE”) > -1;
final boolean wasResized = resize > 0;
if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
// do something
}
复制代码
使用卫语句替代嵌套条件判断
把复杂的条件表达式拆分成多个条件表达式,减少嵌套。嵌套了好几层的if - then-else语句,转换为多个if语句
//未使用卫语句
public void getHello(int type) {
if (type == 1) {
return;
} else {
if (type == 2) {
return;
} else {
if (type == 3) {
return;
} else {
setHello();
}
}
}
}
//使用卫语句
public void getHello(int type) {
if (type == 1) {
return;
}
if (type == 2) {
return;
}
if (type == 3) {
return;
}
setHello();
}
复制代码
使用多态替代条件判断断
当存在这样一类条件表达式,它根据对象类型的不同选择不同的行为。可以将这种表达式的每个分支放进一个子类内的复写函数中,然后将原始函数声明为抽象函数。
public int calculate(int a, int b, String operator) {
int result = Integer.MIN_VALUE;
if (“add”.equals(operator)) {
result = a + b;
} else if (“multiply”.equals(operator)) {
result = a * b;
} else if (“divide”.equals(operator)) {
result = a / b;
} else if (“subtract”.equals(operator)) {
result = a - b;
}
return result;
}
复制代码
当出现大量类型检查和判断时,if else(或switch)语句的体积会比较臃肿,这无疑降低了代码的可读性。另外,if else(或switch)本身就是一个“变化点”,当需要扩展新的类型时,我们不得不追加if else(或switch)语句块,以及相应的逻辑,这无疑降低了程序的可扩展性,也违反了面向对象的开闭原则。
基于这种场景,我们可以考虑使用“多态”来代替冗长的条件判断,将if else(或switch)中的“变化点”封装到子类中。这样,就不需要使用if else(或switch)语句了,取而代之的是子类多态的实例,从而使得提高代码的可读性和可扩展性。很多设计模式使用都是这种套路,比如策略模式、状态模式。
public interface Operation {
int apply(int a, int b);
}
public class Addition implements Operation {
@Override
public int apply(int a, int b) {
return a + b;
}
}
public class OperatorFactory {
private final static Map<String, Operation> operationMap = new HashMap<>();
static {
operationMap.put(“add”, new Addition());
operationMap.put(“divide”, new Division());
// more operators
}
public static Operation getOperation(String operator) {
return operationMap.get(operator);
}
}
public int calculate(int a, int b, String operator) {
if (OperatorFactory .getOperation == null) {
throw new IllegalArgumentException(“Invalid Operator”);
}
return OperatorFactory .getOperation(operator).apply(a, b);
}
复制代码
使用异常替代返回错误码
非正常业务状态的处理,使用抛出异常的方式代替返回错误码
-
不要使用异常处理用于正常的业务流程控制
-
- 异常处理的性能成本非常高
-
尽量使用标准异常
-
避免在finally语句块中抛出异常
-
- 如果同时抛出两个异常,则第一个异常的调用栈会丢失
-
finally块中应只做关闭资源这类的事情
//使用错误码
public boolean withdraw(int amount) {
if (balance < amount) {
return false;
} else {
balance -= amount;
return true;
}
}
//使用异常
public void withdraw(int amount) {
if (amount > balance) {
throw new IllegalArgumentException(“amount too large”);
}
balance -= amount;
}
复制代码
引入断言
某一段代码需要对程序状态做出某种假设,以断言明确表现这种假设。
-
不要滥用断言,不要使用它来检查“应该为真”的条件,只使用它来检查“一定必须为真”的条件
-
如果断言所指示的约束条件不能满足,代码是否仍能正常运行?如果可以就去掉断言
引入Null对象或特殊对象
当使用一个方法返回的对象时,而这个对象可能为空,这个时候需要对这个对象进行操作前,需要进行判空,否则就会报空指针。当这种判断频繁的出现在各处代码之中,就会影响代码的美观程度和可读性,甚至增加Bug的几率。
空引用的问题在Java中无法避免,但可以通过代码编程技巧(引入空对象)来改善这一问题。
//空对象的例子
public class OperatorFactory {
static Map<String, Operation> operationMap = new HashMap<>();
static {
operationMap.put(“add”, new Addition());
operationMap.put(“divide”, new Division());
// more operators
}
public static Optional getOperation(String operator) {
return Optional.ofNullable(operationMap.get(operator));
}
}
public int calculate(int a, int b, String operator) {
Operation targetOperation = OperatorFactory.getOperation(operator)
.orElseThrow(() -> new IllegalArgumentException(“Invalid Operator”));
return targetOperation.apply(a, b);
}
//特殊对象的例子
public class InvalidOp implements Operation {
@Override
public int apply(int a, int b) {
throw new IllegalArgumentException(“Invalid Operator”);
}
}
复制代码
提炼类
根据单一职责原则,一个类应该有明确的责任边界。但在实际工作中,类会不断的扩展。当给某个类添加一项新责任时,你会觉得不值得分离出一个单独的类。于是,随着责任不断增加,这个类包含了大量的数据和函数,逻辑复杂不易理解。
此时你需要考虑将哪些部分分离到一个单独的类中,可以依据高内聚低耦合的原则。如果某些数据和方法总是一起出现,或者某些数据经常同时变化,这就表明它们应该放到一个类中。另一种信号是类的子类化方式:如果你发现子类化只影响类的部分特性,或者类的特性需要以不同方式来子类化,这就意味着你需要分解原来的类。
//原始类
public class Person {
private String name;
private String officeAreaCode;
private String officeNumber;
public String getName() {
return name;
}
public String getTelephoneNumber() {
return (“(” + officeAreaCode + “)” + officeNumber);
}
public String getOfficeAreaCode() {
return officeAreaCode;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
总结
其他的内容都可以按照路线图里面整理出来的知识点逐一去熟悉,学习,消化,不建议你去看书学习,最好是多看一些视频,把不懂地方反复看,学习了一节视频内容第二天一定要去复习,并总结成思维导图,形成树状知识网络结构,方便日后复习。
这里还有一份很不错的《Java基础核心总结笔记》,特意跟大家分享出来
目录:
部分内容截图:
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
712422528725)]
[外链图片转存中…(img-8bHGPsGu-1712422528726)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-CHCX2tB9-1712422528726)]
总结
其他的内容都可以按照路线图里面整理出来的知识点逐一去熟悉,学习,消化,不建议你去看书学习,最好是多看一些视频,把不懂地方反复看,学习了一节视频内容第二天一定要去复习,并总结成思维导图,形成树状知识网络结构,方便日后复习。
这里还有一份很不错的《Java基础核心总结笔记》,特意跟大家分享出来
目录:
[外链图片转存中…(img-WEsEMsPL-1712422528727)]
部分内容截图:
[外链图片转存中…(img-RjGTJS64-1712422528727)]
[外链图片转存中…(img-HScjzCoi-1712422528727)]
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算