跳转至

01-设计原则

设计模式核心原则关系图

学习时间: 约3-4小时 难度级别: ⭐⭐ 初中级 前置知识: 面向对象编程基础(类、继承、多态、封装) 学习目标: 深入理解SOLID五大原则和其他核心设计原则,为学习设计模式打下坚实基础


🎯 学习目标

  1. 理解并能举例说明SOLID五大原则
  2. 掌握DRY、KISS、YAGNI、LoD等实用原则
  3. 理解"组合优于继承"的设计思想
  4. 理解"针对接口编程"的含义
  5. 能识别代码中违反设计原则的"坏味道"

目录


1. SOLID原则概述

SOLID是由Robert C. Martin(Uncle Bob)提出的五个面向对象设计原则的首字母缩写:

字母 原则 核心思想
S Single Responsibility Principle 一个类只做一件事
O Open/Closed Principle 对扩展开放,对修改关闭
L Liskov Substitution Principle 子类可以替换父类
I Interface Segregation Principle 接口要精简专一
D Dependency Inversion Principle 依赖抽象,不依赖具体

这五个原则不是孤立的,它们相互支持、互为补充。遵循SOLID原则的代码通常具有高内聚、低耦合的特点,易于维护和扩展。


2. 单一职责原则 (SRP)

定义

一个类应该只有一个引起它变化的原因。

换句话说,每个类应该只负责一个功能领域中的工作。

❌ 坏例子

Python
class Employee:
    """一个类承担了太多职责"""
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def calculate_pay(self):
        """计算薪资 — 财务部门的职责"""
        return self.salary * 1.1

    def save_to_database(self):
        """保存到数据库 — IT部门的职责"""
        db.execute(f"INSERT INTO employees VALUES ('{self.name}', {self.salary})")

    def generate_report(self):
        """生成报告 — HR部门的职责"""
        return f"Employee Report: {self.name}, Salary: {self.salary}"

问题:如果数据库架构变了,要修改 Employee 类;如果报告格式变了,还要修改这个类。三个不同的变化原因影响同一个类。

✅ 好例子

Python
class Employee:
    """只负责员工数据"""
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

class PayCalculator:
    """只负责薪资计算"""
    def calculate_pay(self, employee: Employee):
        return employee.salary * 1.1

class EmployeeRepository:
    """只负责数据持久化"""
    def save(self, employee: Employee):
        db.execute(f"INSERT INTO employees VALUES ('{employee.name}', {employee.salary})")

class ReportGenerator:
    """只负责报告生成"""
    def generate(self, employee: Employee):
        return f"Employee Report: {employee.name}, Salary: {employee.salary}"

改进:每个类只有一个变化原因,修改一个不会影响其他。

Java示例

Java
// 好的设计:每个类单一职责
public class Employee {
    private String name;
    private double salary;
    // getter/setter
}

public class PayCalculator {
    public double calculatePay(Employee employee) {
        return employee.getSalary() * 1.1;
    }
}

public class EmployeeRepository {
    public void save(Employee employee) {
        // 数据库操作
    }
}

3. 开闭原则 (OCP)

定义

软件实体应该对扩展开放,对修改关闭。

添加新功能时,应该通过增加新代码来实现,而不是修改已有代码。

❌ 坏例子

Python
class DiscountCalculator:
    def calculate(self, customer_type, amount):
        """每增加一种客户类型,就要修改这个方法"""
        if customer_type == "regular":
            return amount * 0.95
        elif customer_type == "vip":
            return amount * 0.8
        elif customer_type == "super_vip":
            return amount * 0.7
        # 新增类型?继续加 elif...
        else:
            return amount

✅ 好例子

Python
from abc import ABC, abstractmethod

class DiscountStrategy(ABC):
    @abstractmethod
    def calculate(self, amount: float) -> float:
        pass

class RegularDiscount(DiscountStrategy):
    def calculate(self, amount):
        return amount * 0.95

class VIPDiscount(DiscountStrategy):
    def calculate(self, amount):
        return amount * 0.80

class SuperVIPDiscount(DiscountStrategy):
    def calculate(self, amount):
        return amount * 0.70

# 新增客户类型?添加新类即可,不修改已有代码
class EmployeeDiscount(DiscountStrategy):
    def calculate(self, amount):
        return amount * 0.60

class DiscountCalculator:
    def __init__(self, strategy: DiscountStrategy):
        self.strategy = strategy

    def calculate(self, amount):
        return self.strategy.calculate(amount)

改进:新增折扣类型只需创建新类,无需修改 DiscountCalculator。这实际上就是策略模式的应用。


4. 里氏替换原则 (LSP)

定义

子类对象必须能替换父类对象,而程序的行为不受影响。

子类可以扩展父类功能,但不能改变父类原有的行为契约。

❌ 坏例子:经典的正方形/矩形问题

Python
class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height

    @property  # @property将方法变为属性访问
    def width(self):
        return self._width

    @width.setter
    def width(self, value):
        self._width = value

    @property
    def height(self):
        return self._height

    @height.setter
    def height(self, value):
        self._height = value

    def area(self):
        return self._width * self._height

class Square(Rectangle):
    """正方形是矩形?数学上是,但OOD中不是!"""
    @Rectangle.width.setter
    def width(self, value):
        self._width = value
        self._height = value  # 违反了矩形"宽高独立"的隐含契约

    @Rectangle.height.setter
    def height(self, value):
        self._width = value
        self._height = value

# 使用者期望矩形行为
def test_rectangle(rect: Rectangle):
    rect.width = 5
    rect.height = 4
    assert rect.area() == 20  # 如果传入Square,面积是16!❌

✅ 好例子

Python
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2

# 任何Shape子类都可以安全替换
def print_area(shape: Shape):
    print(f"Area: {shape.area()}")  # 总是正确的 ✅

5. 接口隔离原则 (ISP)

定义

客户端不应该被迫依赖它不使用的方法。

大而全的接口应该拆分为多个小而专的接口。

❌ 坏例子

Python
from abc import ABC, abstractmethod

class Worker(ABC):
    """一个臃肿的接口"""
    @abstractmethod
    def work(self): pass

    @abstractmethod
    def eat(self): pass

    @abstractmethod
    def sleep_in_office(self): pass

class HumanWorker(Worker):
    def work(self): print("Working...")
    def eat(self): print("Eating lunch...")
    def sleep_in_office(self): print("Taking a nap...")

class RobotWorker(Worker):
    def work(self): print("Working efficiently...")
    def eat(self): raise NotImplementedError("Robots don't eat!")  # ❌ 被迫实现不需要的方法
    def sleep_in_office(self): raise NotImplementedError("Robots don't sleep!")

✅ 好例子

Python
class Workable(ABC):
    @abstractmethod
    def work(self): pass

class Eatable(ABC):
    @abstractmethod
    def eat(self): pass

class Sleepable(ABC):
    @abstractmethod
    def sleep_in_office(self): pass

class HumanWorker(Workable, Eatable, Sleepable):
    def work(self): print("Working...")
    def eat(self): print("Eating lunch...")
    def sleep_in_office(self): print("Taking a nap...")

class RobotWorker(Workable):  # 只实现需要的接口 ✅
    def work(self): print("Working efficiently...")

Java示例

Java
// 拆分接口
interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

public class HumanWorker implements Workable, Eatable {
    public void work() { System.out.println("Working..."); }
    public void eat() { System.out.println("Eating..."); }
}

public class RobotWorker implements Workable {
    public void work() { System.out.println("Working efficiently..."); }
}

6. 依赖倒置原则 (DIP)

定义

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

❌ 坏例子

Python
class MySQLDatabase:
    def query(self, sql):
        return f"MySQL executing: {sql}"

class UserService:
    def __init__(self):
        self.db = MySQLDatabase()  # ❌ 直接依赖具体实现

    def get_user(self, user_id):
        return self.db.query(f"SELECT * FROM users WHERE id = {user_id}")

# 如果要换成PostgreSQL?必须修改UserService内部代码

✅ 好例子

Python
from abc import ABC, abstractmethod

class Database(ABC):
    """抽象层:定义接口"""
    @abstractmethod
    def query(self, sql: str) -> str:
        pass

class MySQLDatabase(Database):
    def query(self, sql):
        return f"MySQL: {sql}"

class PostgreSQLDatabase(Database):
    def query(self, sql):
        return f"PostgreSQL: {sql}"

class MongoDatabase(Database):
    def query(self, sql):
        return f"MongoDB: {sql}"

class UserService:
    def __init__(self, db: Database):  # ✅ 依赖抽象,通过构造函数注入
        self.db = db

    def get_user(self, user_id):
        return self.db.query(f"SELECT * FROM users WHERE id = {user_id}")

# 使用时注入具体实现
service = UserService(PostgreSQLDatabase())  # 切换数据库无需修改UserService

关键:控制反转(IoC)和依赖注入(DI)是实现DIP的常用手段。

Java示例

Java
// 依赖抽象接口
public interface Database {  // interface定义类型契约
    String query(String sql);
}

public class MySQLDatabase implements Database {  // extends继承;implements实现接口
    public String query(String sql) {
        return "MySQL: " + sql;
    }
}

public class UserService {
    private final Database db;

    public UserService(Database db) {  // 构造器注入
        this.db = db;
    }

    public String getUser(int userId) {
        return db.query("SELECT * FROM users WHERE id = " + userId);
    }
}

7. 其他重要原则

7.1 DRY(Don't Repeat Yourself)

不要重复自己 — 每一个知识点在系统中只应有一个、明确的、权威的表示。

Python
# ❌ 违反DRY:验证逻辑重复
def create_user(email):
    if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', email):
        raise ValueError("Invalid email")
    # ...

def update_user(email):
    if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', email):  # 重复!
        raise ValueError("Invalid email")
    # ...

# ✅ 遵循DRY:提取通用函数
def validate_email(email):
    if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', email):
        raise ValueError("Invalid email")

def create_user(email):
    validate_email(email)
    # ...

7.2 KISS(Keep It Simple, Stupid)

保持简单 — 简单的设计比复杂的设计更容易理解、测试和维护。

Python
# ❌ 过度设计:为了"灵活"写了一堆抽象
class AbstractStrategyFactoryProviderSingleton:
    ...

# ✅ KISS:简单直接地解决问题
def calculate_discount(price, discount_rate):
    return price * (1 - discount_rate)

7.3 YAGNI(You Aren't Gonna Need It)

你不会需要它 — 不要为"将来可能"的需求提前编写代码。

Python
# ❌ YAGNI违反:项目只需要JSON,但"以防万一"支持了5种格式
class DataExporter:
    def export_json(self, data): ...
    def export_xml(self, data): ...     # 没人用
    def export_csv(self, data): ...     # 没人用
    def export_yaml(self, data): ...    # 没人用
    def export_protobuf(self, data): ...# 没人用

# ✅ YAGNI:只实现当前需要的
class DataExporter:
    def export_json(self, data): ...
    # 将来需要其他格式时再加

7.4 迪米特法则 / 最少知识原则(Law of Demeter, LoD)

一个对象应该对其他对象有最少的了解。 只与直接朋友通信。

Python
# ❌ 违反LoD:链式调用暴露了内部结构
customer.get_wallet().get_credit_card().charge(100)

# ✅ 遵循LoD:封装内部细节
customer.charge(100)
# Customer内部:
# def charge(self, amount):
#     self.wallet.charge(amount)

8. 组合优于继承

8.1 为什么继承有问题

  • 强耦合:子类与父类紧密绑定,父类改变可能破坏所有子类
  • 脆弱基类:修改基类可能导致子类行为异常
  • 单继承限制:大多数语言只支持单继承(Java),限制了灵活性
  • 层次膨胀:功能组合导致继承层次爆炸

8.2 组合的优势

Python
# ❌ 继承方式:功能组合导致类爆炸
class Dog: pass
class SwimmingDog(Dog): pass
class FlyingDog(Dog): pass       # 真的有
class SwimmingFlyingDog(Dog): pass  # 组合爆炸!

# ✅ 组合方式:通过组装能力来构建
class SwimAbility:
    def swim(self):
        print("Swimming!")

class FlyAbility:
    def fly(self):
        print("Flying!")

class BarkAbility:
    def bark(self):
        print("Woof!")

class Dog:
    def __init__(self):
        self.bark = BarkAbility()
        self.swim = None
        self.fly = None

    def add_ability(self, ability_name, ability):
        setattr(self, ability_name, ability)  # hasattr/getattr/setattr动态操作对象属性

# 灵活组合
super_dog = Dog()
super_dog.add_ability('swim', SwimAbility())
super_dog.add_ability('fly', FlyAbility())

8.3 何时使用继承

继承并非完全不能用,以下场景仍适合: - "is-a"关系明确且稳定:Cat is an Animal - 模板方法模式:定义算法骨架 - 抽象基类:定义接口契约

经验法则:优先考虑组合,只在"is-a"关系明确时使用继承。


9. 针对接口编程

9.1 核心思想

面向接口(抽象)编程,而非面向实现编程。

Python
from abc import ABC, abstractmethod  # ABC抽象基类;abstractmethod强制子类实现
from typing import List

class Notifier(ABC):
    """接口:通知能力"""
    @abstractmethod
    def send(self, message: str): pass

class EmailNotifier(Notifier):
    def send(self, message):
        print(f"📧 Email: {message}")

class SMSNotifier(Notifier):
    def send(self, message):
        print(f"📱 SMS: {message}")

class SlackNotifier(Notifier):
    def send(self, message):
        print(f"💬 Slack: {message}")

class AlertService:
    """依赖接口,不依赖具体实现"""
    def __init__(self, notifiers: List[Notifier]):
        self.notifiers = notifiers

    def alert(self, message):
        for notifier in self.notifiers:
            notifier.send(message)

# 灵活配置通知渠道
service = AlertService([EmailNotifier(), SlackNotifier()])
service.alert("Server is down!")

9.2 Python中的"接口"

Python没有Java那样的 interface 关键字,但有多种实现方式:

  1. 抽象基类(ABC):最正式的方式
  2. Protocol(Python 3.8+):结构化子类型(鸭子类型+类型检查)
  3. 鸭子类型:只要有需要的方法就行
Python
from typing import Protocol

class Renderable(Protocol):
    """Protocol: 结构化子类型检查"""
    def render(self) -> str: ...

class HTMLRenderer:
    def render(self) -> str:
        return "<html>...</html>"

class JSONRenderer:
    def render(self) -> str:
        return '{"key": "value"}'

def display(renderer: Renderable):
    """不需要任何继承关系,只要有render方法即可"""
    print(renderer.render())

display(HTMLRenderer())  # ✅
display(JSONRenderer())  # ✅

10. 练习与自我检查

✏️ 练习题

  1. 识别违反:以下代码违反了哪些SOLID原则?如何重构?
Python
class OrderService:
    def create_order(self, items, user):
        # 验证库存
        for item in items:
            if item.stock <= 0:
                raise ValueError(f"{item.name} out of stock")
        # 计算价格
        total = sum(item.price * item.qty for item in items)
        # 应用折扣
        if user.is_vip:
            total *= 0.8
        # 保存到数据库
        db.save_order(user.id, items, total)
        # 发送邮件
        smtp.send_email(user.email, f"Order confirmed: ${total}")
        # 发送短信
        sms.send(user.phone, f"Order confirmed: ${total}")
  1. 改造代码:将上面的代码重构为符合SOLID原则的设计(至少拆分为3个类)。

  2. LSP判断:下面的继承关系合理吗?为什么?

  3. Penguin extends Bird(企鹅是鸟)—— 如果 Birdfly() 方法? > 不合理。企鹅不能飞,违反LSP——子类无法替代父类使用。解决:将fly()提取到Flyable接口,Bird不强制包含fly()
  4. Stack extends ArrayList —— 栈"是一个"列表吗? > 不合理。栈只允许LIFO操作,但继承ArrayList会暴露get(i)/add(i,e)等随机访问方法,违反LSP。应该用组合:Stack内部持有List,只暴露push/pop/peek。
  5. FileLogger extends Logger —— Logger 接口有 log(message) 方法 > 合理。FileLogger可以完全实现Logger的log()契约,调用方无需知道具体实现,符合LSP。

  6. 组合重构:将以下继承层次重构为组合方式:

Text Only
Vehicle
├── ElectricCar
├── GasCar
├── ElectricSelfDrivingCar
├── GasSelfDrivingCar
└── HybridSelfDrivingCar  (混合动力+自动驾驶)
  1. SOLID选择题:对于以下场景,应该应用哪条原则?
  2. 一个类同时处理用户认证和日志记录 → SRP(单一职责原则)——拆分为AuthService和LogService
  3. 更换短信服务商需要修改业务逻辑代码 → DIP(依赖倒置原则)——业务代码依赖SMSService接口,而非具体供应商实现
  4. 某个接口有15个方法,但大多数实现者只用其中3个 → ISP(接口隔离原则)——拆分为多个小接口,实现者只需实现需要的

面试要点

Q1: 解释SOLID原则中的"O"? A: 开闭原则——对扩展开放(可以添加新功能),对修改关闭(不需要改已有代码)。通过抽象和多态实现,如策略模式用接口定义行为,新增策略只需添加新类。

Q2: 组合优于继承是什么意思? A: 优先使用对象组合(has-a)而非类继承(is-a)来实现代码复用。继承会导致强耦合和类层次膨胀,组合更灵活——可以在运行时动态组装功能。

Q3: 依赖倒置和依赖注入的关系? A: 依赖倒置是原则(高层不应依赖低层,两者都依赖抽象),依赖注入是实现手段(通过构造函数/setter/接口将依赖从外部传入,而非内部创建)。

Q4: DRY原则的适用范围? A: DRY不仅指不要复制粘贴代码,更重要的是"知识不要重复"——同一个业务规则只在一处定义。但不要为了DRY而强行合并相似但实际无关的逻辑。

自我检查清单

  • 能解释SOLID五个原则各自的含义
  • 能识别代码中违反SOLID原则的问题
  • 理解DRY、KISS、YAGNI的实际应用
  • 理解为什么"组合优于继承"
  • 知道Python中实现"接口"的多种方式
  • 能区分"针对接口编程"和"针对实现编程"

下一章: 02-创建型模式 — 学习如何优雅地创建对象