问题
简述面向对象的六大原则与一条法则。
答案
一、六大原则(SOLID + 迪米特法则)
面向对象设计的六大原则是软件设计的基石,帮助我们编写可维护、可扩展、低耦合的代码。
1. 单一职责原则(Single Responsibility Principle, SRP)
定义:一个类应该只有一个引起它变化的原因,即一个类只负责一项职责。
核心思想:高内聚,低耦合。每个类专注做好一件事。
反例:
// ❌ 违反 SRP:一个类承担多个职责
public class User {
private String name;
private String email;
// 职责1:用户数据管理
public void setName(String name) {
this.name = name;
}
// 职责2:数据持久化
public void saveToDatabase() {
// 数据库操作
}
// 职责3:邮件发送
public void sendEmail(String message) {
// 发送邮件
}
}
正例:
// ✅ 符合 SRP:职责分离
public class User {
private String name;
private String email;
// 只负责用户数据
}
public class UserRepository {
public void save(User user) {
// 只负责数据持久化
}
}
public class EmailService {
public void sendEmail(String email, String message) {
// 只负责邮件发送
}
}
2. 开闭原则(Open-Closed Principle, OCP)
定义:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
核心思想:通过抽象和多态实现扩展,而不是修改已有代码。
反例:
// ❌ 违反 OCP:每次新增类型都要修改代码
public class PaymentService {
public void pay(String type, double amount) {
if ("alipay".equals(type)) {
System.out.println("支付宝支付: " + amount);
} else if ("wechat".equals(type)) {
System.out.println("微信支付: " + amount);
}
// 新增支付方式需要修改这里
}
}
正例:
// ✅ 符合 OCP:通过抽象扩展
public interface Payment {
void pay(double amount);
}
public class AlipayPayment implements Payment {
@Override
public void pay(double amount) {
System.out.println("支付宝支付: " + amount);
}
}
public class WechatPayment implements Payment {
@Override
public void pay(double amount) {
System.out.println("微信支付: " + amount);
}
}
// 新增支付方式只需新增类,无需修改已有代码
public class UnionPayPayment implements Payment {
@Override
public void pay(double amount) {
System.out.println("银联支付: " + amount);
}
}
3. 里氏替换原则(Liskov Substitution Principle, LSP)
定义:子类对象必须能够替换掉所有父类对象,且程序行为不变。
核心思想:继承必须确保超类所拥有的性质在子类中仍然成立。
反例:
// ❌ 违反 LSP:正方形不是矩形的合理子类
public class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
public class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // 破坏了父类的行为
}
@Override
public void setHeight(int height) {
this.width = height;
this.height = height;
}
}
// 测试代码会失败
public void test(Rectangle rect) {
rect.setWidth(5);
rect.setHeight(4);
assert rect.getArea() == 20; // Square 会失败
}
正例:
// ✅ 符合 LSP:使用组合而非继承
public interface Shape {
int getArea();
}
public class Rectangle implements Shape {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public int getArea() {
return width * height;
}
}
public class Square implements Shape {
private int side;
public Square(int side) {
this.side = side;
}
@Override
public int getArea() {
return side * side;
}
}
4. 接口隔离原则(Interface Segregation Principle, ISP)
定义:客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。
核心思想:接口要小而专,不要设计臃肿的接口。
反例:
// ❌ 违反 ISP:臃肿的接口
public interface Worker {
void work();
void eat();
void sleep();
}
// 机器人不需要 eat 和 sleep
public class Robot implements Worker {
@Override
public void work() {
System.out.println("机器人工作");
}
@Override
public void eat() {
// 机器人不需要吃饭,但被迫实现
}
@Override
public void sleep() {
// 机器人不需要睡觉,但被迫实现
}
}
正例:
// ✅ 符合 ISP:接口隔离
public interface Workable {
void work();
}
public interface Eatable {
void eat();
}
public interface Sleepable {
void sleep();
}
public class Human implements Workable, Eatable, Sleepable {
@Override
public void work() {
System.out.println("人类工作");
}
@Override
public void eat() {
System.out.println("人类吃饭");
}
@Override
public void sleep() {
System.out.println("人类睡觉");
}
}
public class Robot implements Workable {
@Override
public void work() {
System.out.println("机器人工作");
}
}
5. 依赖倒置原则(Dependency Inversion Principle, DIP)
定义:高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。
核心思想:面向接口编程,而不是面向实现编程。
反例:
// ❌ 违反 DIP:高层依赖低层具体实现
public class MySQLDatabase {
public void save(String data) {
System.out.println("保存到 MySQL: " + data);
}
}
public class UserService {
private MySQLDatabase database = new MySQLDatabase();
public void saveUser(String user) {
database.save(user); // 直接依赖具体实现
}
}
正例:
// ✅ 符合 DIP:依赖抽象
public interface Database {
void save(String data);
}
public class MySQLDatabase implements Database {
@Override
public void save(String data) {
System.out.println("保存到 MySQL: " + data);
}
}
public class MongoDatabase implements Database {
@Override
public void save(String data) {
System.out.println("保存到 MongoDB: " + data);
}
}
public class UserService {
private Database database;
// 依赖注入
public UserService(Database database) {
this.database = database;
}
public void saveUser(String user) {
database.save(user);
}
}
6. 迪米特法则(Law of Demeter, LoD)/ 最少知识原则
定义:一个对象应该对其他对象保持最少的了解,只与直接的朋友通信。
核心思想:降低类之间的耦合度,减少类之间的依赖关系。
反例:
// ❌ 违反迪米特法则:过多了解其他对象的内部结构
public class Customer {
public Wallet getWallet() {
return new Wallet();
}
}
public class Wallet {
private double money;
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
public class Cashier {
public void charge(Customer customer, double amount) {
Wallet wallet = customer.getWallet();
if (wallet.getMoney() >= amount) {
wallet.setMoney(wallet.getMoney() - amount);
}
}
}
正例:
// ✅ 符合迪米特法则:只与直接朋友通信
public class Customer {
private Wallet wallet = new Wallet();
public boolean pay(double amount) {
return wallet.deduct(amount);
}
}
public class Wallet {
private double money;
public boolean deduct(double amount) {
if (money >= amount) {
money -= amount;
return true;
}
return false;
}
}
public class Cashier {
public void charge(Customer customer, double amount) {
customer.pay(amount); // 只与 Customer 交互
}
}
二、一条法则:合成复用原则(Composite Reuse Principle, CRP)
定义:尽量使用对象组合/聚合,而不是继承来达到复用的目的。
核心思想:优先使用组合(has-a)而非继承(is-a)。
反例:
// ❌ 滥用继承
public class ArrayList {
// ArrayList 实现
}
public class MyList extends ArrayList {
// 为了复用 ArrayList 的功能而继承
public void customMethod() {
// 自定义方法
}
}
正例:
// ✅ 使用组合
public class MyList {
private List<Object> list = new ArrayList<>();
public void add(Object obj) {
list.add(obj);
}
public void customMethod() {
// 自定义方法
}
}
三、原则之间的关系
单一职责原则 ──┐
开闭原则 ──────┼──→ 高内聚、低耦合
里氏替换原则 ──┤
接口隔离原则 ──┤
依赖倒置原则 ──┤
迪米特法则 ────┘
合成复用原则 ──→ 实现代码复用的最佳方式
四、实际应用场景
Spring 框架中的体现:
- SRP:Controller、Service、Repository 各司其职
- OCP:通过接口和 AOP 实现扩展
- LSP:Bean 的多态替换
- ISP:各种细粒度接口(InitializingBean、DisposableBean)
- DIP:依赖注入(IoC)
- LoD:通过接口隔离实现
- CRP:装饰器模式(BeanWrapper)
五、面试答题要点
- SOLID 五大原则:单一职责、开闭、里氏替换、接口隔离、依赖倒置
- 第六原则:迪米特法则(最少知识原则)
- 一条法则:合成复用原则(组合优于继承)
- 核心目标:高内聚、低耦合、易扩展、易维护
- 实际应用:能结合 Spring、设计模式等框架说明原则的应用
- 权衡取舍:原则不是教条,要根据实际情况灵活运用