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):面向接口编程

核心思想

  1. 高层模块不应该依赖低层模块,两者都应该依赖抽象
  2. 抽象不应该依赖细节,细节应该依赖抽象

大白话:别 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 流,实战能力再上一个台阶!🚀

更多推荐