设计原则,编程规范的总结
本文作为一个对于整理过的设计原则和思想的总结,包括:
- 面向对象
- 封装、继承、抽象、多态
- 面向对象编程 vs 面向过程编程
- 面向对象分析、设计、编程
- 接口 vs 抽象类
- 基于接口而非实现编程
- 多用组合少用继承
- 贫血模式 vs 充血模式
- 设计原则
- 单一职责原则
- 开闭原则
- 里氏替换原则
- 接口隔离原则
- 依赖倒置原则
- DRY
- KISS
- YAGNI
- LOD
- 规范与重构
- 目的,对象,时机,方法
- 单元测试和代码的可测试性
- 大重构
- 小重构
1. 代码质量的评判标准
- 常用评价标准
- 最常用
- 可维护性
- 可读性
- 可扩展性
- 其他
- 灵活性
- 简洁性
- 可复用性
- 可测试性
- 最常用
- 如何写出高质量代码?
- 设计思想
- 设计原则
- 设计模式
- 编码规范
- 重构技巧
2. 面向对象
特性
- 封装
- 隐藏信息,数据访问保护
- 继承
- is a
- 多态
- 子类可以替代父类的模式
- 在实际代码运行当中,通过调用子类的方法来实现
- 抽象
- 隐藏类的具体实现方法
- 使得修改实现不需要改变定义
- 封装
面向对象设计 – 如何设计出具体的类
- 划分职责
- 定义类及其属性和方法
- 定义类和类之间的交互关系
- 将类组装起来并提供执行入口
接口 vs 抽象类
接口
- 对方法的抽象
- 是一种has a的关系
- 表示具有某一组行为特性
- 为了解决解耦问题,隔离接口和具体实现,提高代码扩展性
抽象类
- 对成员变量和方法的抽象
- 是一种is a的关系
- 为了解决代码复用的问题
贫血模型 vs 充血模型
- MVC 贫血模型
- 充血模型的设计
- 与贫血模型的区别在于Service层
- 在基于充血模型的开发模式下,将service类中的业务逻辑移动到一个充血的domain领域模型当中
- 让Service类的实现依赖这个domain类
3. 设计原则
单一职责原则
- 一个类只负责一个职责或者功能
开闭原则
- 对扩展开放,对修改关闭
- 添加一个新的功能,应该是通过在已有的代码基础上扩展代码(新增模块,类,方法,属性),而非修改已有的代码的方式来完成的
- 指的是以最小的修改代码的代价来完成新功能的开发
- 对扩展开放,对修改关闭
里氏替代原则
- 子类对象能够替代程序当中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。
- 理解 Design by contract 按照协议来设计
- 父类定义函数的约定/协议
- 子类可以改变函数的内部实现逻辑,但不能改变函数的原有约定
- 约定包括
- 函数声明要实现的功能
- 对输入 输出 异常的约定
- 注释中罗列的特殊说明
- 约定包括
接口隔离原则
- 客户端不应该强迫依赖它不需要的接口
- 将接口理解为一组接口集合
- 如果部分接口只被部分调用者使用,应该将这部分接口隔离起来,单独给他们使用
- 理解为单个API接口或函数
- 部分调用者只需要函数的部分功能,那我们就应该将函数拆分为粒度更细的多个函数,让调用者只依赖它需要的那个细粒度的函数
- 理解为OOP中的接口
- 接口的设计需要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数
- 将接口理解为一组接口集合
- 客户端不应该强迫依赖它不需要的接口
YAGNI - you ain’t gonna need it
LOD - 高内聚,低耦合
迪米特法则
- 不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口
4. 相关的文章
- 架构学习-general
- 架构学习-原则
- 架构学习-可扩展架构模式
- 架构学习-复杂度来源
- 架构学习 - 实战
- 架构学习 - 架构设计文档模板
- 架构学习-架构设计流程
- 架构学习-高可用架构模式
- 架构学习-高性能架构模式
- 基于充血模型的DDD开发模型
- SOLID-单一职责原则
- SOLID - 开闭原则
- SOLID - 里氏替换原则
- SOLID - 接口隔离原则
- SOLID - 依赖反转原则
- KISS and YAGNI原则
- DRY 原则
- 迪米特法则 (LOD) — 高内聚,低耦合
- 应用设计 Practice
- 提高代码质量的Tips
5. 实战:ID生成器
使用ID来做服务内部的请求追踪,因为在日志文件当中,不同请求的日志是会交织到一起的。我们需要使用ID来标识哪些日志属于同一个请求。
因此我们需要做的事情就是给每个请求分配一个唯一的ID,并且保存在请求的上下文当中。Java当中可以将ID存储在ThreadLocal当中,或者使用Slf4j的MDC(Mapped Diagnostic Contexts)来实现。每次打印日志的时候,我们就可以从请求上下文当中取出请求ID,跟日志一块输出。
5.1 原始的生成ID的代码
public class IdGenerator {
private static final Logger logger = LoggerFactory.getLogger(IdGenerator.class);
public static String generate() {
String id = "";
try {
String hostName = InetAddress.getLocalHost().getHostName();
String[] tokens = hostName.split("\\.");
if (tokens.length > 0) {
hostName = tokens[tokens.length - 1];
}
char[] randomChars = new char[8];
int count = 0;
Random random = new Random();
while (count < 8) {
int randomAscii = random.nextInt(122);
if (randomAscii >= 48 && randomAscii <= 57) {
randomChars[count] = (char)('0' + (randomAscii - 48));
count++;
} else if (randomAscii >= 65 && randomAscii <= 90) {
randomChars[count] = (char)('A' + (randomAscii - 65));
count++;
} else if (randomAscii >= 97 && randomAscii <= 122) {
randomChars[count] = (char)('a' + (randomAscii - 97));
count++;
}
}
id = String.format("%s-%d-%s", hostName,
System.currentTimeMillis(), new String(randomChars));
} catch (UnknownHostException e) {
logger.warn("Failed to get the host name.", e);
}
return id;
}
}
- 上述代码存在的问题
- static 方法可测试性太低
- generate函数的代码实现依赖运行环境,时间函数以及随机函数,本身的可测试性也不强
- 随机字符串生成代码难以看懂
- 有太多的魔法数,需要告诉读代码的人这些都是什么意思才可以的
5.2 完善后的代码
public interface IdGenerator {
String generate();
}
public interface LogTraceIdGenerator extends IdGenerator {
}
public class RandomIdGenerator implements IdGenerator {
private static final Logger logger = LoggerFactory.getLogger(RandomIdGenerator.class);
@Override
public String generate() {
String substrOfHostName = getLastfieldOfHostName();
long currentTimeMillis = System.currentTimeMillis();
String randomString = generateRandomAlphameric(8);
String id = String.format("%s-%d-%s",
substrOfHostName, currentTimeMillis, randomString);
return id;
}
private String getLastfieldOfHostName() {
String substrOfHostName = null;
try {
String hostName = InetAddress.getLocalHost().getHostName();
String[] tokens = hostName.split("\\.");
substrOfHostName = tokens[tokens.length - 1];
return substrOfHostName;
} catch (UnknownHostException e) {
logger.warn("Failed to get the host name.", e);
}
return substrOfHostName;
}
private String generateRandomAlphameric(int length) {
char[] randomChars = new char[length];
int count = 0;
Random random = new Random();
while (count < length) {
int maxAscii = 'z';
int randomAscii = random.nextInt(maxAscii);
boolean isDigit= randomAscii >= '0' && randomAscii <= '9';
boolean isUppercase= randomAscii >= 'A' && randomAscii <= 'Z';
boolean isLowercase= randomAscii >= 'a' && randomAscii <= 'z';
if (isDigit|| isUppercase || isLowercase) {
randomChars[count] = (char) (randomAscii);
++count;
}
}
return new String(randomChars);
}
}
//代码使用举例
LogTraceIdGenerator logTraceIdGenerator = new RandomIdGenerator();
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 stone2paul@gmail.com
文章标题:设计原则,编程规范的总结
文章字数:1.8k
本文作者:Leilei Chen
发布时间:2020-06-08, 02:12:00
最后更新:2020-06-11, 12:21:56
原始链接:https://www.llchen60.com/%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99%EF%BC%8C%E7%BC%96%E7%A8%8B%E8%A7%84%E8%8C%83%E7%9A%84%E6%80%BB%E7%BB%93/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。