【架构】面向对象六大设计原则
面向对象六大设计原则
在软件开发中,代码量随着项目规模的增长而膨胀,如果没有良好的架构设计,项目就会变得难以维护。面向对象六大设计原则 是指导我们编写高质量代码的重要法则。它们并不是晦涩的规则,而是帮助我们在实际开发中 降低耦合、增强复用、提高可维护性 的经验总结。
单一职责原则 SRP
一个类只做一件事。
如果一个类承担了过多功能,它的变化就会牵一发而动全身。例如一个类既负责用户登录,又负责日志记录,那么登录逻辑变更时,日志模块也可能被意外影响。
我们应尽量把不同的功能拆分成多个独立的类或模块,使得每个类有且只有一个导致它变化的原因。
UserService
既处理业务又负责日志与持久化
public class UserService {public void register(String name) {// 业务逻辑System.out.println("Validate and create user: " + name);// 持久化System.out.println("Saving user to DB: " + name);// 日志System.out.println("Log: user registered " + name);}
}
职责拆分
public class UserService {private final UserRepository userRepository;private final Logger logger;public UserService(UserRepository userRepository, Logger logger) {this.userRepository = userRepository;this.logger = logger;}public void register(String name) {userRepository.save(new User(name));logger.log("User registered: " + name);}
}interface UserRepository {void save(User user);
}interface Logger {void log(String message);
}class User { String name;User(String name) { this.name = name; }
}
开闭原则 OCP
对扩展开放,对修改关闭。
我们希望在新增功能时,不要去修改已有的类,而是通过扩展(继承、接口、多态等方式)来实现。这样做的好处是,原有逻辑不被破坏,风险大大降低。
假如有一个图形绘制系统,最开始只有 Circle
,后来要增加 Rectangle
。如果按照开闭原则,我们不去修改绘制类内部,而是通过新增 Rectangle
类并实现绘制接口即可。
支付方法硬编码到 PaymentProcessor
public class PaymentProcessor {public void pay(String type, double amount) {if ("WeChat".equals(type)) {} else if ("AliPay".equals(type)) {}}
}
对新增支付方式开放实现类
public interface PaymentStrategy {void pay(double amount);
}public class WeChatPay implements PaymentStrategy {public void pay(double amount) { }
}public class AliPay implements PaymentStrategy {public void pay(double amount) { }
}public class PaymentProcessor {public void process(PaymentStrategy strategy, double amount) {strategy.pay(amount);}
}
用策略扩展新支付方式时,不需要改 PaymentProcessor
,只需新增实现类。
里氏替换原则 LSP
子类必须能够替换其基类出现的地方,且程序行为不被破坏。
也就是说,如果父类能做的事情,子类也必须能做;子类不能违背父类的契约。
如果 Bird
有 fly()
方法,而我们让 Penguin
继承 Bird
却无法飞,这就破坏了里氏替换。正确做法是把 Bird
抽象成 Animal
,把会飞的鸟作为 FlyingBird
,避免不合理的继承。
方圆问题
class Rectangle {protected int width, height;public void setWidth(int w) { width = w; }public void setHeight(int h) { height = h; }public int area() { return width * height; }
}class Square extends Rectangle {public void setWidth(int w) {super.width = w;super.height = w; // 覆盖行为:修改一个属性影响另一个}public void setHeight(int h) {super.width = h;super.height = h;}
}
若把 Square
当作 Rectangle
使用,会破坏对 setWidth
/setHeight
的预期。
更合理的抽象
interface Shape {int area();
}class Rectangle implements Shape {private int width, height;public Rectangle(int w, int h) { this.width = w; this.height = h; }public int area() { return width * height; }
}class Square implements Shape {private int side;public Square(int side) { this.side = side; }public int area() { return side * side; }
}
用更合适的抽象(Shape
)来避免不恰当的继承,保证替换安全。
依赖倒置原则 DIP
高层模块不依赖底层模块,二者共同依赖抽象;抽象不依赖细节,细节依赖抽象。
简单说,就是我们应该面向接口编程,而不是面向实现编程。
支付系统中不应该直接依赖 WeChatPay
或 AliPay
,而是依赖一个通用的 PayInterface
。这样在切换或新增支付方式时,只需实现接口,而不用修改业务逻辑。
违反示例(高层依赖具体实现)
public class OrderService {private MySqlOrderRepository repository = new MySqlOrderRepository();// 直接new导致OrderService强耦合于MySQL实现
}
改进示例(依赖抽象并注入)
public interface OrderRepository {void save(Order order);
}public class MySqlOrderRepository implements OrderRepository {public void save(Order order) { }
}public class OrderService {private final OrderRepository repository;public OrderService(OrderRepository repository) {this.repository = repository; // 依赖注入,灵活注入OrderRepository的实现类}public void place(Order order) {repository.save(order);}
}
OrderService
只依赖 OrderRepository
抽象,测试时可注入 mock,切换实现也方便。
接口隔离原则 ISP
一个接口应该只包含客户需要的方法。
如果接口过于臃肿,会迫使实现类去实现一些它们根本不需要的方法,造成冗余。
例如,如果我们定义了一个 Animal
接口,里面有 fly()
、run()
、swim()
,那么企鹅实现它时就会很尴尬。正确的做法是把大接口拆成更小、更专注的接口,比如 Flyable
、Runnable
、Swimmable
。
臃肿接口 Printer
interface Printer {void print(Document d);void scan(Document d);void fax(Document d);
}class SimplePrinter implements Printer {public void print(Document d) {// all right}public void scan(Document d) { throw new UnsupportedOperationException(); }public void fax(Document d) { throw new UnsupportedOperationException(); }
}
SimplePrinter
被迫实现不需要的方法,产生空实现或异常。
接口拆分
interface Printable { void print(Document d); }
interface Scannable { void scan(Document d); }
interface Faxable { void fax(Document d); }class SimplePrinter implements Printable {public void print(Document d) { }
}
客户端只依赖它需要的接口,职责更清晰也更灵活。
迪米特法则 LoD
也叫最少知道原则,强调对象之间应尽量少地相互了解。
简单说就是不要和陌生人说话。一个对象只与直接朋友(自己创建的对象、参数、成员变量)通信,而不是跨层级去操作别的对象的内部细节。
例如:A.getB().getC().doSomething()
就违反了迪米特法则,因为 A 不应该知道 C 的存在,最好通过 B 提供一个方法来间接完成操作。
链式调用的违反示例
class Engine {public OilTank getOilTank() { return new OilTank(); }
}
class Car {private Engine engine;public Engine getEngine() { return engine; }
}
Car car = new Car();
car.getEngine().getOilTank().drain(); // 违反LoD:car不该知道oilTank的存在
通过封装提供高层方法以改进
class Engine {private OilTank oilTank;public void drainOil() { oilTank.drain(); }
}
class Car {private Engine engine;public void drainEngineOil() { engine.drainOil(); }
}Car car = new Car();
car.drainEngineOil(); // Car的调用只涉及自己的直接朋友
把对内部组件的操作通过高层方法封装,减少耦合,便于演化与测试。
六大设计原则并不是孤立存在的,而是相互联系、共同作用的。掌握并灵活运用这六大原则,就能在项目中写出更清晰、更健壮、更易扩展的代码。