theme: v-green
六大设计原则
评价代码的质量,一般都是从代码是否可维护,是否简单易读,可拓展性,灵活性,简洁性等方面来评价。设计模式是代码设计经验的总结,学习设计模式可以有效提高程序员的coding能力,让你写出质量上乘的代码。设计模式一共有23种,虽然数量不少,但是它们基本上都是这六大设计原则的实现方式。可以说,这六大设计原则就是23种设计模式的基本设计思想。本文简要介绍六种设计原则,并通过实际代码示例加以说明。
1. 单一职责原则(Single Responsibility Principle)
定义:一个类应该只有一个引起它变化的原因,即一个类只负责完成一个职责或者功能。
设计要点:避免设计大而全的类,要设计功能单一,粒度适中的类。
判断标准
- 类中代码行数过多,代码功能职责不清晰;
- 类中包含过多的函数和属性,导致代码维护困难;
- 类的依赖过多,导致程序耦合度过高;
- 类的私有方法过多且这些方法集中操作少数属性。
重构建议
当类变得庞大且功能不单一时,应该把它拆分成多个职责单一的小类。
代码示例
违反单一职责原则的类
// User类同时处理用户信息和用户数据存储
public class User {
private String name;
private String email;
// 获取用户名称
public String getName() {
return name;
}
// 设置用户名称
public void setName(String name) {
this.name = name;
}
// 获取用户邮箱
public String getEmail() {
return email;
}
// 设置用户邮箱
public void setEmail(String email) {
this.email = email;
}
// 保存用户到数据库
public void save() {
// 代码实现保存用户到数据库
}
}
遵循单一职责原则的类
// 仅负责用户信息的User类
public class User {
private String name;
private String email;
// 获取用户名称
public String getName() {
return name;
}
// 设置用户名称
public void setName(String name) {
this.name = name;
}
// 获取用户邮箱
public String getEmail() {
return email;
}
// 设置用户邮箱
public void setEmail(String email) {
this.email = email;
}
}
// 负责用户数据存储的UserRepository类
public class UserRepository {
// 保存用户到数据库
public void save(User user) {
// 代码实现保存用户到数据库
}
}
说明:通过将用户信息和数据存储职责分离,User类仅关注用户数据,而UserRepository类负责数据的持久化操作,提高了代码的可维护性和可扩展性。
2. 开闭原则(Open-Closed Principle)
定义:软件实体(类,模块,函数等)应对拓展开放,对修改封闭。
核心思想
- 对拓展开放:允许通过拓展来增加新的功能。
- 对修改封闭:在需求变化时,不通过直接修改已有功能代码,而是通过增加拓展模块来实现新的功能。
实现方法
使用抽象类或接口来定义稳定的结构,具体功能的拓展通过实现这些抽象类或接口来实现。
重要性
- 符合开闭原则的软件可以更好地适应变化,维护成本更低;
- 所有的设计模式都追求这一原则,以确保系统开发和维护的可靠性。
代码示例
违反开闭原则的类
public class GraphicEditor {
public void drawShape(Shape shape) {
if (shape.type == 1) {
drawCircle((Circle) shape);
} else if (shape.type == 2) {
drawRectangle((Rectangle) shape);
}
// 如果新增形状,需要修改此方法
}
private void drawCircle(Circle circle) {
// 绘制圆形的代码
}
private void drawRectangle(Rectangle rectangle) {
// 绘制矩形的代码
}
}
public class Shape {
int type;
}
public class Circle extends Shape {
public Circle() {
type = 1;
}
}
public class Rectangle extends Shape {
public Rectangle() {
type = 2;
}
}
遵循开闭原则的类
// 抽象类Shape,定义绘制方法
public abstract class Shape {
public abstract void draw();
}
// 圆形类
public class Circle extends Shape {
@Override
public void draw() {
// 绘制圆形的代码
}
}
// 矩形类
public class Rectangle extends Shape {
@Override
public void draw() {
// 绘制矩形的代码
}
}
// 图形编辑器类,不需要修改即可支持新形状
public class GraphicEditor {
public void drawShape(Shape shape) {
shape.draw();
}
}
说明:通过使用抽象类Shape,GraphicEditor类无需修改即可支持新的形状,只需新增继承自Shape的类,实现draw方法即可。
3. 里氏替换原则(Liskov Substitution Principle)
定义:子类对象可以替换父类对象的任何地方,并保证程序的逻辑行为不变。
理解方式
- 替换依赖于面向对象语言中的多态特性,子类对象应该可以在父类出现的任何地方替换父类对象。
- 子类的实现应该遵守接口或父类定义的方法规范,即方法的输入和输出保持一致(保证行为不变)。
代码示例
违反里氏替换原则的类
// 父类鸟
public class Bird {
public void fly() {
// 鸟类飞行的实现
}
}
// 企鹅类继承自鸟,但企鹅不会飞
public class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("企鹅不会飞");
}
}
遵循里氏替换原则的类
// 抽象类动物
public abstract class Animal {
// 一般动物的共同行为
}
// 可以飞行的动物接口
public interface Flyable {
void fly();
}
// 鸟类实现Flyable接口
public class Bird extends Animal implements Flyable {
@Override
public void fly() {
// 鸟类飞行的实现
}
}
// 企鹅类不实现Flyable接口
public class Penguin extends Animal {
// 企鹅的特有行为
}
说明:通过将会飞的行为抽象为Flyable接口,企鹅类不再继承不适用的fly方法,避免了因子类行为不一致导致的问题,符合里氏替换原则。
4. 接口隔离原则(Interface Segregation Principle)
定义:为各个类设计它们需要的专用接口,而不是设计一个庞大的通用接口供所有的类调用。
设计要点
- 避免创建“胖接口”,而是要根据具体需求将接口拆分成对应的多个专一的接口。
- 提高内聚性,降低耦合性,使类与接口之间的依赖关系更清晰。
与单一职责原则的区别:单一职责原则注重类的职责单一,接口隔离原则侧重于接口依赖的隔离。
代码示例
违反接口隔离原则的接口
public interface Worker {
void work();
void eat();
}
public class HumanWorker implements Worker {
@Override
public void work() {
// 人类工作实现
}
@Override
public void eat() {
// 人类吃饭实现
}
}
public class RobotWorker implements Worker {
@Override
public void work() {
// 机器人工作实现
}
@Override
public void eat() {
// 机器人不需要吃饭,但必须实现该方法
throw new UnsupportedOperationException("机器人不需要吃饭");
}
}
遵循接口隔离原则的接口
// 工作接口
public interface Workable {
void work();
}
// 吃饭接口
public interface Eatable {
void eat();
}
public class HumanWorker implements Workable, Eatable {
@Override
public void work() {
// 人类工作实现
}
@Override
public void eat() {
// 人类吃饭实现
}
}
public class RobotWorker implements Workable {
@Override
public void work() {
// 机器人工作实现
}
}
说明:通过将Worker接口拆分为Workable和Eatable,机器人类不再需要实现与其不相关的eat方法,符合接口隔离原则。
5. 依赖倒置原则(Dependency Inversion Principle)
定义:高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖细节,细节应该依赖抽象。
实现方式
- 面向接口编程,而不是面向具体实现。
- 通过依赖注入的方式,将低层模块的实现注入到高层模块的抽象中。
目的
降低模块之间的耦合度,提高系统的灵活性和可维护性。
代码示例
违反依赖倒置原则的类
// 低层模块:数据库日志记录
public class DatabaseLogger {
public void log(String message) {
// 将日志记录到数据库
}
}
// 高层模块:用户服务
public class UserService {
private DatabaseLogger logger = new DatabaseLogger();
public void createUser(String username) {
// 创建用户的逻辑
logger.log("User created: " + username);
}
}
遵循依赖倒置原则的类
// 抽象日志记录接口
public interface Logger {
void log(String message);
}
// 低层模块:数据库日志记录实现
public class DatabaseLogger implements Logger {
@Override
public void log(String message) {
// 将日志记录到数据库
}
}
// 低层模块:文件日志记录实现
public class FileLogger implements Logger {
@Override
public void log(String message) {
// 将日志记录到文件
}
}
// 高层模块:用户服务
public class UserService {
private Logger logger;
// 通过构造函数注入依赖
public UserService(Logger logger) {
this.logger = logger;
}
public void createUser(String username) {
// 创建用户的逻辑
logger.log("User created: " + username);
}
}
使用示例
public class Main {
public static void main(String[] args) {
// 选择具体的日志记录实现
Logger logger = new DatabaseLogger();
// 或者使用文件日志记录
// Logger logger = new FileLogger();
// 通过依赖注入将Logger传递给UserService
UserService userService = new UserService(logger);
userService.createUser("JohnDoe");
}
}
说明:高层模块UserService依赖于抽象Logger接口,而不是具体的DatabaseLogger或FileLogger,从而实现了依赖倒置,增强了系统的灵活性和可扩展性。
6. 迪米特法则(Law of Demeter)
定义:一个类应该尽量少地依赖其他的类,减少类之间的相互依赖关系。
设计要点
- 避免直接与不必要的类进行通信,减少类之间的直接依赖关系。
- 降低类之间的耦合度,提高模块的独立性和可维护性。
代码示例
违反迪米特法则的类
public class OrderService {
private OrderRepository orderRepository = new OrderRepository();
public void processOrder(int orderId) {
Order order = orderRepository.getOrderById(orderId);
Customer customer = order.getCustomer();
Address address = customer.getAddress();
// 直接访问Customer和Address的细节
sendEmail(address.getEmail(), "Your order has been processed.");
}
private void sendEmail(String email, String message) {
// 发送邮件的逻辑
}
}
遵循迪米特法则的类
// Customer类增加发送邮件的方法
public class Customer {
private Address address;
public Address getAddress() {
return address;
}
public void sendEmail(String message) {
// 发送邮件的逻辑,内部获取地址信息
String email = address.getEmail();
// 发送邮件
}
}
// Order类增加获取Customer的方法
public class Order {
private Customer customer;
public Customer getCustomer() {
return customer;
}
}
// OrderRepository类
public class OrderRepository {
public Order getOrderById(int orderId) {
// 获取订单的逻辑
return new Order();
}
}
// OrderService类遵循迪米特法则
public class OrderService {
private OrderRepository orderRepository = new OrderRepository();
public void processOrder(int orderId) {
Order order = orderRepository.getOrderById(orderId);
// 通过Order直接调用Customer的方法,而不需要了解Customer的内部结构
order.getCustomer().sendEmail("Your order has been processed.");
}
}
说明:通过将发送邮件的职责委托给Customer类,OrderService类不再需要了解Customer和Address的内部结构,减少了类之间的耦合,符合迪米特法则。
掌握这些设计原则,并在实际编码中灵活运用,将为后续学习23种设计模式打下坚实的基础,帮助你编写出高质量、可维护、可扩展的代码。