Java 面向对象设计原则 + 综合实战:写出“好代码“的底
Java 面向对象设计原则 + 综合实战:写出"好代码"的底层逻辑
学习日期:2026-06-08 难度:⭐⭐⭐⭐⭐ 高阶
前言:为什么学设计原则?
前面四天,我们学了继承、多态、抽象类、接口——工具有了,但怎么用才是"好设计"?
同样的需求,100 个人写出 100 种代码。设计原则就是帮你判断:哪种写法改起来不痛苦、扩起来不费劲。
这七个原则不是教条,是前辈踩了无数坑总结出来的经验。记住核心思想比记住名字重要。
一、单一职责原则(SRP):一个类只做一件事
核心思想
一个类应该只有一个引起它变化的原因。说白了:别把什么都塞到一个类里。
反面教材
java
99
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ❌ 一个类管了用户数据、数据库操作、邮件通知——三件事
class UserService {
void register(String name, String email) {
// 1. 校验用户数据
if (name == null || name.isEmpty()) throw new RuntimeException("用户名不能为空");
// 2. 保存到数据库
System.out.println("INSERT INTO users VALUES('" + name + "', '" + email + "')");
// 3. 发欢迎邮件
System.out.println("发送邮件给 " + email + ":欢迎注册!");
}
}
问题:数据库要换 MySQL → 改这个类;邮件模板要改 → 还改这个类;校验规则要变 → 又改这个类。一个类改三件事,牵一发动全身。
正确做法
java
99
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// ✅ 拆成三个类,各管各的
class UserValidator {
boolean validate(String name, String email) {
return name != null && !name.isEmpty() && email.contains("@");
}
}
class UserRepository {
void save(String name, String email) {
System.out.println("INSERT INTO users VALUES('" + name + "', '" + email + "')");
}
}
class EmailService {
void sendWelcome(String email) {
System.out.println("发送邮件给 " + email + ":欢迎注册!");
}
}
// 组装者
class UserService {
private UserValidator validator = new UserValidator();
private UserRepository repo = new UserRepository();
private EmailService emailService = new EmailService();
void register(String name, String email) {
if (!validator.validate(name, email)) {
throw new RuntimeException("用户信息不合法");
}
repo.save(name, email);
emailService.sendWelcome(email);
}
}
好处:改数据库不影响邮件,改邮件不影响校验。各改各的,互不干扰。
⚠️ 别过度拆! 如果类就 20 行代码,没必要拆成 5 个类。职责拆到"变化的原因"这个粒度就够了。
二、开闭原则(OCP):对扩展开放,对修改关闭
核心思想
加新功能时,应该加新代码,而不是改旧代码。
反面教材
// ❌ 每增加一种折扣,就要改这个方法
class PriceCalculator {
double calculate(String type, double price) {
if (type.equals("normal")) {
return price;
} else if (type.equals("vip")) {
return price * 0.8;
} else if (type.equals("svip")) {
return price * 0.6;
}
// 每加一种会员,就要加一个 else if...改旧代码,风险大
return price;
}
}
正确做法
// ✅ 定义折扣策略接口,新增类型只需新增类
interface DiscountStrategy {
double apply(double price);
}
class NormalDiscount implements DiscountStrategy {
public double apply(double price) { return price; }
}
class VipDiscount implements DiscountStrategy {
public double apply(double price) { return price * 0.8; }
}
class SvipDiscount implements DiscountStrategy {
public double apply(double price) { return price * 0.6; }
}
// 未来要加超级VIP?新建一个类就行,PriceCalculator 一行不改
class PriceCalculator {
private DiscountStrategy strategy;
PriceCalculator(DiscountStrategy strategy) {
this.strategy = strategy;
}
double calculate(double price) {
return strategy.apply(price);
}
}
// 使用
new PriceCalculator(new VipDiscount()).calculate(100); // 80.0
new PriceCalculator(new SvipDiscount()).calculate(100); // 60.0
💡 这就是策略模式的雏形——用接口+多态替代 if-else 分支,是 OCP 最经典的落地方式。
三、里氏替换原则(LSP):子类必须能替代父类
核心思想
所有用到父类的地方,换成子类应该依然正常工作,不能出 bug。
经典反面教材:正方形继承矩形?
// ❌ 看似合理,实际违反 LSP
class Rectangle {
int width;
int height;
void setWidth(int w) { this.width = w; }
void setHeight(int h) { this.height = h; }
int getArea() { return width * height; }
}
class Square extends Rectangle {
@Override
void setWidth(int w) {
this.width = w;
this.height = w; // 正方形:宽高必须一致
}
@Override
void setHeight(int h) {
this.width = h;
this.height = h;
}
}
// 灾难现场
Rectangle r = new Square(); // 用子类替换父类
r.setWidth(5);
r.setHeight(10);
System.out.println(r.getArea());
// 期望 5 * 10 = 50,实际 10 * 10 = 100 ← 行为不一致!
教训:正方形 IS-A 矩形在数学上成立,但在程序中行为不一致。继承不光看现实关系,更要看行为是否兼容。
正确做法
// ✅ 正方形和矩形都继承自 Shape,各管各的
abstract class Shape {
abstract int getArea();
}
class Rectangle extends Shape {
int width, height;
Rectangle(int w, int h) { this.width = w; this.height = h; }
int getArea() { return width * height; }
}
class Square extends Shape {
int side;
Square(int s) { this.side = s; }
int getArea() { return side * side; }
}
LSP 实践要点
- 子类不要"破坏"父类的行为约定
- 子类重写方法时,前置条件不能更严格,后置条件不能更宽松
- 如果子类导致程序行为异常,说明继承关系设计有问题
四、接口隔离原则(ISP):接口要小而专
核心思想
不要强迫类实现它用不到的方法。接口要拆细,一个接口只服务一类客户。
反面教材
// ❌ 胖接口:所有能力塞一起
interface AnimalAction {
void fly();
void swim();
void run();
}
class Dog implements AnimalAction {
public void fly() { /* 狗不会飞,空实现 */ }
public void swim() { System.out.println("狗刨式游泳"); }
public void run() { System.out.println("狂奔"); }
}
// Dog 被迫实现了它根本不需要的 fly()——这就是"接口污染"
正确做法
// ✅ 拆成小接口,按需实现
interface Flyable { void fly(); }
interface Swimable { void swim(); }
interface Runnable { void run(); }
class Dog implements Swimable, Runnable {
public void swim() { System.out.println("狗刨式游泳"); }
public void run() { System.out.println("狂奔"); }
}
class Eagle implements Flyable, Runnable {
public void fly() { System.out.println("翱翔天空"); }
public void run() { System.out.println("地上走两步"); }
}
class Duck implements Flyable, Swimable, Runnable {
public void fly() { System.out.println("低空飞行"); }
public void swim() { System.out.println("水面游"); }
public void run() { System.out.println("摇摇摆摆"); }
}
💡 和 SRP 的区别:SRP 关注类的职责单一,ISP 关注接口的方法精简。一个是对内,一个是对外。
五、依赖倒置原则(DIP):面向接口编程
核心思想
- 高层模块不应该依赖低层模块,两者都应该依赖抽象
- 抽象不应该依赖细节,细节应该依赖抽象
大白话:别 new 具体类,用接口/抽象类对接。
反面教材
// ❌ 高层直接依赖低层具体类
class MySQLDatabase {
void save(String data) {
System.out.println("MySQL存储:" + data);
}
}
class OrderService {
private MySQLDatabase db = new MySQLDatabase(); // 死绑 MySQL
void createOrder(String order) {
db.save(order);
}
}
// 要换 PostgreSQL?改 OrderService 源码。要单元测试?没法 mock 数据库。
正确做法
// ✅ 依赖抽象(接口),不依赖具体
interface Database {
void save(String data);
}
class MySQLDatabase implements Database {
public void save(String data) {
System.out.println("MySQL存储:" + data);
}
}
class PostgreSQLDatabase implements Database {
public void save(String data) {
System.out.println("PostgreSQL存储:" + data);
}
}
class MockDatabase implements Database {
public void save(String data) {
System.out.println("[测试]模拟存储:" + data);
}
}
// 高层模块通过构造器注入依赖
class OrderService {
private Database db;
OrderService(Database db) { // 依赖接口,不关心具体实现
this.db = db;
}
void createOrder(String order) {
db.save(order);
}
}
// 使用:灵活切换
OrderService service1 = new OrderService(new MySQLDatabase());
OrderService service2 = new OrderService(new PostgreSQLDatabase());
OrderService testService = new OrderService(new MockDatabase());
💡 这就是依赖注入(DI) 的基本思想——不自己创建依赖,让外部传进来。Spring 框架的核心就是这个。
六、合成复用原则(CRP):优先用组合,少用继承
核心思想
如果想复用代码,优先用"组合"(把对象当成员变量),而不是"继承"。
为什么继承要慎用?
// ❌ 为了复用 ArrayList 的功能而继承——脆弱的继承
class MyStack<T> extends ArrayList<T> {
public void push(T item) { add(item); }
public T pop() { return remove(size() - 1); }
}
MyStack<Integer> stack = new MyStack<>();
stack.push(1);
stack.push(2);
stack.add(0, 99); // 继承来了 ArrayList 的方法,破坏了栈的语义!
// 栈应该是后进先出,但 add(index, item) 可以随便插入
正确做法
// ✅ 组合:把 ArrayList 当内部工具用,只暴露栈该有的方法
class MyStack<T> {
private ArrayList<T> list = new ArrayList<>(); // 组合
public void push(T item) { list.add(item); }
public T pop() { return list.remove(list.size() - 1); }
public T peek() { return list.get(list.size() - 1); }
public boolean isEmpty() { return list.isEmpty(); }
}
MyStack<Integer> stack = new MyStack<>();
stack.push(1);
stack.push(2);
// stack.add(0, 99); // 编译报错!外部根本看不到 ArrayList 的方法
继承 vs 组合速判
表格
| 场景 | 选择 |
|---|---|
| 子类是父类的一种(Dog is an Animal) | 继承 |
| 只是想复用父类的功能 | 组合 |
| 子类需要重写父类行为 | 继承 |
| 想控制暴露哪些方法 | 组合 |
🎯 口诀: "Has-a" 用组合,"Is-a" 用继承。 不确定时,先想组合。
七、迪米特法则(LoD):最少知道原则
核心思想
一个对象应该对其他对象有最少的了解。只跟直接朋友说话,不跟陌生人说话。
反面教材
// ❌ 链式调用:知道太多不该知道的细节
class Company {
Department department;
Department getDepartment() { return department; }
}
class Department {
Manager manager;
Manager getManager() { return manager; }
}
class Manager {
String name;
String getName() { return name; }
}
// 调用方:跟 Company、Department、Manager 三个类都耦合了
Company company = new Company();
String managerName = company.getDepartment().getManager().getName();
// 如果 Department 去掉了 manager 字段?整条链全得改。
正确做法
// ✅ Company 只暴露调用方真正需要的信息
class Company {
private Department department;
String getManagerName() { // 封装内部细节,只返回结果
return department.getManagerName();
}
}
class Department {
private Manager manager;
String getManagerName() {
return manager.getName();
}
}
class Manager {
private String name;
String getName() { return name; }
}
// 调用方:只跟 Company 打交道
Company company = new Company();
String name = company.getManagerName();
// 内部结构怎么变都不影响调用方
八、七大原则总览
表格
| 原则 | 一句话 | 关键词 |
|---|---|---|
| 单一职责 SRP | 一个类只做一件事 | 拆 |
| 开闭原则 OCP | 加功能不改旧代码 | 扩展 |
| 里氏替换 LSP | 子类能完美替代父类 | 兼容 |
| 接口隔离 ISP | 接口要小而专 | 精简 |
| 依赖倒置 DIP | 面向接口编程 | 抽象 |
| 合成复用 CRP | 优先组合少继承 | 组合 |
| 迪米特 LoD | 少知道别人的事 | 封装 |
💡 记住口诀:拆、扩、兼、精、抽、组、封。七个字概括七个原则。
九、综合实战:简易电商系统
把前五天学的一切串起来,用设计原则重构一个电商订单系统。
9.1 需求
- 商品有不同类型(普通/折扣/限时秒杀),价格计算方式不同
- 订单支持多种支付方式(支付宝/微信/银行卡)
- 订单创建后发通知(邮件/短信),且通知方式可扩展
- 支持多种物流方式(顺丰/中通/自提)
9.2 设计分析
表格
| 需求 | 用到的原则 | 设计方式 |
|---|---|---|
| 商品定价策略不同 | OCP + DIP | 策略接口 |
| 多种支付方式 | OCP + ISP | 支付接口 |
| 通知方式可扩展 | OCP + DIP | 通知接口 |
| 物流方式不同 | OCP + ISP | 物流接口 |
| 订单类不该管价格计算 | SRP | 职责拆分 |
9.3 完整代码
// ========== 1. 定价策略(OCP:新增策略不改旧代码) ==========
interface PricingStrategy {
double calculate(double originalPrice);
}
class NormalPricing implements PricingStrategy {
public double calculate(double price) { return price; }
}
class DiscountPricing implements PricingStrategy {
private double discount;
DiscountPricing(double discount) { this.discount = discount; }
public double calculate(double price) { return price * discount; }
}
class FlashSalePricing implements PricingStrategy {
private double flashPrice;
FlashSalePricing(double flashPrice) { this.flashPrice = flashPrice; }
public double calculate(double price) { return flashPrice; }
}
// ========== 2. 支付方式(ISP:支付接口只管支付) ==========
interface PaymentMethod {
boolean pay(double amount);
}
class Alipay implements PaymentMethod {
public boolean pay(double amount) {
System.out.println("支付宝支付:" + amount + "元");
return true;
}
}
class WechatPay implements PaymentMethod {
public boolean pay(double amount) {
System.out.println("微信支付:" + amount + "元");
return true;
}
}
class BankCardPay implements PaymentMethod {
private String cardNo;
BankCardPay(String cardNo) { this.cardNo = cardNo; }
public boolean pay(double amount) {
System.out.println("银行卡(" + cardNo + ")支付:" + amount + "元");
return true;
}
}
// ========== 3. 通知(DIP:依赖接口,不依赖具体实现) ==========
interface Notifier {
void notify(String message);
}
class EmailNotifier implements Notifier {
private String email;
EmailNotifier(String email) { this.email = email; }
public void notify(String message) {
System.out.println("[邮件→" + email + "] " + message);
}
}
class SmsNotifier implements Notifier {
private String phone;
SmsNotifier(String phone) { this.phone = phone; }
public void notify(String message) {
System.out.println("[短信→" + phone + "] " + message);
}
}
// ========== 4. 物流 ==========
interface ShippingMethod {
double getFee();
String getName();
}
class SFShipping implements ShippingMethod {
public double getFee() { return 20; }
public String getName() { return "顺丰速运"; }
}
class ZTOShipping implements ShippingMethod {
public double getFee() { return 8; }
public String getName() { return "中通快递"; }
}
class SelfPickup implements ShippingMethod {
public double getFee() { return 0; }
public String getName() { return "门店自提"; }
}
// ========== 5. 商品类(SRP:商品只管自己的属性和定价) ==========
class Product {
private String name;
private double price;
private PricingStrategy pricingStrategy;
Product(String name, double price, PricingStrategy strategy) {
this.name = name;
this.price = price;
this.pricingStrategy = strategy;
}
String getName() { return name; }
double getFinalPrice() { return pricingStrategy.calculate(price); }
}
// ========== 6. 订单项 ==========
class OrderItem {
Product product;
int quantity;
OrderItem(Product product, int quantity) {
this.product = product;
this.quantity = quantity;
}
double getSubtotal() {
return product.getFinalPrice() * quantity;
}
@Override
public String toString() {
return product.getName() + " x" + quantity + " = " + getSubtotal() + "元";
}
}
// ========== 7. 订单类(SRP:订单只管流程编排) ==========
class Order {
private String orderId;
private List<OrderItem> items = new ArrayList<>();
private PaymentMethod payment;
private ShippingMethod shipping;
private List<Notifier> notifiers = new ArrayList<>();
Order(String orderId) {
this.orderId = orderId;
}
void addItem(Product product, int quantity) {
items.add(new OrderItem(product, quantity));
}
void setPayment(PaymentMethod payment) { this.payment = payment; }
void setShipping(ShippingMethod shipping) { this.shipping = shipping; }
void addNotifier(Notifier notifier) { notifiers.add(notifier); }
double getTotalAmount() {
double total = 0;
for (OrderItem item : items) {
total += item.getSubtotal();
}
return total + shipping.getFee();
}
void checkout() {
// 1. 计算总金额
double total = getTotalAmount();
System.out.println("=== 订单 " + orderId + " ===");
for (OrderItem item : items) {
System.out.println(" " + item);
}
System.out.println(" 物流:" + shipping.getName() + " " + shipping.getFee() + "元");
System.out.println(" 总计:" + total + "元");
// 2. 支付
if (!payment.pay(total)) {
System.out.println("支付失败!");
return;
}
// 3. 通知
String message = "订单" + orderId + "已支付" + total + "元," + shipping.getName() + "配送";
for (Notifier n : notifiers) {
n.notify(message);
}
System.out.println("下单成功!\n");
}
}
// ========== 8. 运行 ==========
public class ECommerceDemo {
public static void main(String[] args) {
// 创建商品(不同定价策略)
Product phone = new Product("手机", 3999, new NormalPricing());
Product shirt = new Product("衬衫", 299, new DiscountPricing(0.7));
Product earbuds = new Product("耳机", 499, new FlashSalePricing(199));
// 创建订单
Order order = new Order("ORD-20260608-001");
order.addItem(phone, 1); // 手机 3999
order.addItem(shirt, 2); // 衬衫 299*0.7*2 = 418.6
order.addItem(earbuds, 1); // 耳机秒杀价 199
// 设置支付、物流、通知(DIP:注入接口实现)
order.setPayment(new Alipay());
order.setShipping(new SFShipping());
order.addNotifier(new EmailNotifier("zhangsan@example.com"));
order.addNotifier(new SmsNotifier("138 ****1234"));
order.checkout();
// 换个支付方式?换行代码就行
Order order2 = new Order("ORD-20260608-002");
order2.addItem(phone, 1);
order2.setPayment(new WechatPay());
order2.setShipping(new SelfPickup());
order2.addNotifier(new SmsNotifier("138****1234"));
order2.checkout();
}
}
输出:
=== 订单 ORD-20260608-001 ===
手机 x1 = 3999.0元
衬衫 x2 = 418.6元
耳机 x1 = 199.0元
物流:顺丰速运 20.0元
总计:4636.6元
支付宝支付:4636.6元
[邮件→zhangsan@example.com] 订单ORD-20260608-001已支付4636.6元,顺丰速运配送
[短信→138****1234] 订单ORD-20260608-001已支付4636.6元,顺丰速运配送
下单成功!
=== 订单 ORD-20260608-002 ===
手机 x1 = 3999.0元
物流:门店自提 0.0元
总计:3999.0元
微信支付:3999.0元
[短信→138****1234] 订单ORD-20260608-002已支付3999.0元,门店自提配送
下单成功!
9.4 设计原则落地对照
表格
| 原则 | 在项目中的体现 |
|---|---|
| SRP | Product 管定价、Order 管流程、Notifier 管通知,各司其职 |
| OCP | 新增定价策略/支付方式/通知方式,只需新建类,不改旧代码 |
| LSP | 所有策略实现都可以替换接口使用,行为一致 |
| ISP | PaymentMethod 只有 pay(),Notifier 只有 notify(),精简不臃肿 |
| DIP | Order 依赖接口(PaymentMethod/ShippingMethod/Notifier),不依赖具体类 |
| CRP | Order 组合了 PaymentMethod、ShippingMethod 等对象,没继承任何东西 |
| LoD | Order 只跟接口交互,不关心 Alipay/WechatPay 的内部实现 |
十、总结速查表
表格
| 原则 | 一句话 | 怎么做 |
|---|---|---|
| 单一职责 SRP | 一个类一个变化原因 | 大类拆小类 |
| 开闭原则 OCP | 加新不改旧 | 用接口+多态替代 if-else |
| 里氏替换 LSP | 子类能替代父类 | 别写破坏行为的重写 |
| 接口隔离 ISP | 接口小而专 | 胖接口拆成多个小接口 |
| 依赖倒置 DIP | 面向接口编程 | 构造器注入,不 new 具体类 |
| 合成复用 CRP | 组合优于继承 | Has-a 用组合,Is-a 才继承 |
| 迪米特 LoD | 最少知道 | 封装内部细节,减少链式调用 |
核心心法 :设计原则不是要求你每次七个全用,而是遇到痛点时,回头对照——哪个原则能帮你解决当前的问题。先写出能跑的代码,再按原则重构,循序渐进。
从流程控制到数组,从类与对象到继承多态,再到今天的七大设计原则和综合实战——Java OOP 的地图你已经走完了。工具在手,原则在心,下一步:异常处理、集合框架、IO 流,实战能力再上一个台阶!🚀
更多推荐
所有评论(0)