設計模式六大原則 — 列舉反例詳解各個原則的核心思想和意義

概述

設計模式的六大原則是面向?qū)ο笤O計的基石,遵循這些原則可以提升代碼的可維護性、可擴展性和可讀性。下面對六大原則進行詳細解析。

一、單一職責原則 (Single Responsibility Principle, SRP)

1. 定義

一個類應該只有一個引起變化的原因,即:一個類只負責一項職責

2. 核心思想

  • 高內(nèi)聚:類的功能要集中
  • 低耦合:類之間的依賴要少

3. 代碼示例

違反 SRP 的例子

// 違反單一職責原則
public class UserService {
    public void registerUser(String username, String password) {
        if (username == null || username.isEmpty()) {
            throw new IllegalArgumentException("用戶名不能為空");
        }
        if (password == null || password.length() < 6) {
            throw new IllegalArgumentException("密碼長度不能少于6位");
        }
        
        saveUserToDatabase(username, password);
        sendWelcomeEmail(username);
    }
    
    private void saveUserToDatabase(String username, String password) {
        System.out.println("用戶保存到數(shù)據(jù)庫: " + username);
    }
    
    private void sendWelcomeEmail(String username) {
        System.out.println("發(fā)送歡迎郵件給: " + username);
    }
}

問題:UserService 同時承擔了驗證、持久化、郵件發(fā)送三種職責,職責不單一。

遵循 SRP 的改進

public class User {
    private String username;
    private String password;
    // getter/setter
}

// 只負責驗證
public class UserValidator {
    public void validateUser(User user) {
        if (user.getUsername() == null || user.getUsername().isEmpty()) {
            throw new IllegalArgumentException("用戶名不能為空");
        }
        if (user.getPassword() == null || user.getPassword().length() < 6) {
            throw new IllegalArgumentException("密碼長度不能少于6位");
        }
    }
}

// 只負責數(shù)據(jù)持久化
public class UserRepository {
    public void saveUser(User user) {
        System.out.println("用戶保存到數(shù)據(jù)庫: " + user.getUsername());
    }
}

// 只負責郵件發(fā)送
public class EmailService {
    public void sendWelcomeEmail(String username) {
        System.out.println("發(fā)送歡迎郵件給: " + username);
    }
}

// 協(xié)調(diào)各服務
public class UserService {
    private UserValidator validator;
    private UserRepository repository;
    private EmailService emailService;
    
    public UserService() {
        this.validator = new UserValidator();
        this.repository = new UserRepository();
        this.emailService = new EmailService();
    }
    
    public void registerUser(User user) {
        validator.validateUser(user);
        repository.saveUser(user);
        emailService.sendWelcomeEmail(user.getUsername());
    }
}

4. 優(yōu)點

  • 降低類的復雜度
  • 提高類的可讀性
  • 提高系統(tǒng)的可維護性
  • 降低變更引起的風險

二、開閉原則 (Open-Closed Principle, OCP)

1. 定義

軟件實體(類、模塊、函數(shù)等)應該對擴展開放,對修改關(guān)閉。

2. 核心思想

  • 通過抽象和接口實現(xiàn)擴展性
  • 新增功能時不需要修改現(xiàn)有代碼

3. 代碼示例

違反 OCP 的例子

public class ShapeCalculator {
    public double calculateArea(String shapeType, double... params) {
        if ("circle".equals(shapeType)) {
            return Math.PI * params[0] * params[0];
        } else if ("rectangle".equals(shapeType)) {
            return params[0] * params[1];
        } else if ("triangle".equals(shapeType)) {
            return params[0] * params[1] / 2;
        }
        throw new IllegalArgumentException("不支持的形狀類型");
    }
}

問題:每新增一種圖形,都需要修改 calculateArea 方法,違反了“對修改關(guān)閉”。

遵循 OCP 的改進

public interface Shape {
    double calculateArea();
}

public class Circle implements Shape {
    private double radius;
    public Circle(double radius) { this.radius = radius; }
    @Override public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

public class Rectangle implements Shape {
    private double width, height;
    public Rectangle(double width, double height) {
        this.width = width; this.height = height;
    }
    @Override public double calculateArea() {
        return width * height;
    }
}

public class Triangle implements Shape {
    private double base, height;
    public Triangle(double base, double height) {
        this.base = base; this.height = height;
    }
    @Override public double calculateArea() {
        return base * height / 2;
    }
}

// 擴展無需修改
public class Ellipse implements Shape {
    private double a, b;
    public Ellipse(double a, double b) { this.a = a; this.b = b; }
    @Override public double calculateArea() {
        return Math.PI * a * b;
    }
}

// 面積計算器:對擴展開放,對修改關(guān)閉
public class AreaCalculator {
    public double calculateTotalArea(List<Shape> shapes) {
        return shapes.stream().mapToDouble(Shape::calculateArea).sum();
    }
}

4. 優(yōu)點

  • 提高代碼的可擴展性
  • 使軟件更易于維護
  • 提高代碼的穩(wěn)定性

三、里氏替換原則 (Liskov Substitution Principle, LSP)

1. 定義

所有引用基類的地方必須能透明地使用其子類的對象

2. 核心思想

  • 子類可以擴展父類功能,但不能改變原有功能
  • 子類不應重寫父類的非抽象方法,破壞行為一致性

3. 代碼示例

違反 LSP 的例子

class Rectangle {
    protected double width, height;
    public void setWidth(double width) { this.width = width; }
    public void setHeight(double height) { this.height = height; }
    public double getArea() { return width * height; }
}

class Square extends Rectangle {
    @Override
    public void setWidth(double width) {
        super.setWidth(width);
        super.setHeight(width); // 強制同步
    }
    
    @Override
    public void setHeight(double height) {
        super.setWidth(height);
        super.setHeight(height);
    }
}

// 測試
public class Test {
    public static void main(String[] args) {
        Rectangle rect = new Square();
        rect.setWidth(5);
        rect.setHeight(10);
        System.out.println("面積: " + rect.getArea()); // 輸出 100,期望 50
    }
}

問題:子類改變了父類的行為,導致多態(tài)失效。

遵循 LSP 的改進

abstract class Shape {
    public abstract double getArea();
}

class Rectangle extends Shape {
    private double width, height;
    public Rectangle(double width, double height) {
        this.width = width; this.height = height;
    }
    @Override public double getArea() { return width * height; }
}

class Square extends Shape {
    private double side;
    public Square(double side) { this.side = side; }
    @Override public double getArea() { return side * side; }
}

// 使用工廠創(chuàng)建
class ShapeFactory {
    public static Shape createRectangle(double w, double h) {
        return new Rectangle(w, h);
    }
    public static Shape createSquare(double s) {
        return new Square(s);
    }
}

4. 優(yōu)點

  • 保證繼承關(guān)系的正確性
  • 提高代碼健壯性
  • 增強程序可靠性

四、接口隔離原則 (Interface Segregation Principle, ISP)

1. 定義

客戶端不應該依賴它不需要的接口,一個類對另一個類的依賴應建立在最小接口上。

2. 核心思想

  • 接口要小而專,避免“胖接口”
  • 為不同客戶端提供專用接口

3. 代碼示例

違反 ISP 的例子

interface Animal {
    void eat();
    void sleep();
    void fly();   // 并非所有動物都會飛
    void swim();  // 并非所有動物都會游泳
}

class Bird implements Animal {
    public void eat() { System.out.println("鳥在吃東西"); }
    public void sleep() { System.out.println("鳥在睡覺"); }
    public void fly() { System.out.println("鳥在飛翔"); }
    public void swim() { throw new UnsupportedOperationException("鳥不會游泳"); }
}

class Fish implements Animal {
    public void eat() { System.out.println("魚在吃東西"); }
    public void sleep() { System.out.println("魚在睡覺"); }
    public void fly() { throw new UnsupportedOperationException("魚不會飛"); }
    public void swim() { System.out.println("魚在游泳"); }
}

問題:實現(xiàn)類被迫實現(xiàn)無意義的方法。

遵循 ISP 的改進

interface BasicAnimal { void eat(); void sleep(); }
interface Flyable { void fly(); }
interface Swimmable { void swim(); }
interface Runnable { void run(); }

class Bird implements BasicAnimal, Flyable {
    public void eat() { System.out.println("鳥在吃東西"); }
    public void sleep() { System.out.println("鳥在睡覺"); }
    public void fly() { System.out.println("鳥在飛翔"); }
}

class Fish implements BasicAnimal, Swimmable {
    public void eat() { System.out.println("魚在吃東西"); }
    public void sleep() { System.out.println("魚在睡覺"); }
    public void swim() { System.out.println("魚在游泳"); }
}

class Duck implements BasicAnimal, Flyable, Swimmable, Runnable {
    public void eat() { System.out.println("鴨子在吃東西"); }
    public void sleep() { System.out.println("鴨子在睡覺"); }
    public void fly() { System.out.println("鴨子在飛翔"); }
    public void swim() { System.out.println("鴨子在游泳"); }
    public void run() { System.out.println("鴨子在奔跑"); }
}

4.優(yōu)點

  • 降低接口復雜度
  • 提高系統(tǒng)靈活性
  • 保證接口純潔性

五、依賴倒置原則 (Dependency Inversion Principle, DIP)

1. 定義

  • 高層模塊不應依賴低層模塊,二者都應依賴抽象
  • 抽象不應依賴細節(jié),細節(jié)應依賴抽象

2. 核心思想

  • 面向接口編程,而非面向?qū)崿F(xiàn)
  • 使用依賴注入實現(xiàn)解耦

3. 代碼示例

違反 DIP 的例子

class MySQLDatabase {
    public void connect() { System.out.println("連接MySQL數(shù)據(jù)庫"); }
    public void query(String sql) { System.out.println("執(zhí)行MySQL查詢: " + sql); }
}

class UserService {
    private MySQLDatabase database = new MySQLDatabase(); // 緊耦合
    
    public void getUserById(int id) {
        database.connect();
        database.query("SELECT * FROM users WHERE id = " + id);
    }
}

問題:UserService 直接依賴具體實現(xiàn),難以替換數(shù)據(jù)庫。

遵循 DIP 的改進

interface Database {
    void connect();
    void query(String sql);
}

class MySQLDatabase implements Database {
    @Override public void connect() { System.out.println("連接MySQL數(shù)據(jù)庫"); }
    @Override public void query(String sql) { System.out.println("執(zhí)行MySQL查詢: " + sql); }
}

class PostgreSQLDatabase implements Database {
    @Override public void connect() { System.out.println("連接PostgreSQL數(shù)據(jù)庫"); }
    @Override public void query(String sql) { System.out.println("執(zhí)行PostgreSQL查詢: " + sql); }
}

class UserService {
    private Database database;
    
    public UserService(Database database) { // 依賴注入
        this.database = database;
    }
    
    public void getUserById(int id) {
        database.connect();
        database.query("SELECT * FROM users WHERE id = " + id);
    }
}

// 使用示例
public class DIPExample {
    public static void main(String[] args) {
        Database mysql = new MySQLDatabase();
        Database postgresql = new PostgreSQLDatabase();
        
        UserService userService1 = new UserService(mysql);
        userService1.getUserById(1);
        
        UserService userService2 = new UserService(postgresql);
        userService2.getUserById(1);
    }
}

4. 優(yōu)點

  • 降低類間耦合度
  • 提高系統(tǒng)穩(wěn)定性
  • 增強可維護性和可測試性

六、迪米特法則 (Law of Demeter, LoD) / 最少知識原則

1. 定義

一個對象應該對其他對象保持最少的了解,只與直接朋友通信。

2. 核心思想

  • 降低類之間的耦合
  • 提高模塊獨立性

2. 代碼示例

違反迪米特法則的例子

class Company {
    private Employee manager;
    public Employee getManager() { return manager; }
}

class Employee {
    private Department department;
    public Department getDepartment() { return department; }
}

class Department {
    private String address;
    public String getAddress() { return address; }
}

class Client {
    public void printCompanyAddress(Company company) {
        // 違反:鏈式調(diào)用,了解過多內(nèi)部結(jié)構(gòu)
        System.out.println(company.getManager().getDepartment().getAddress());
    }
}

問題:客戶端需了解 CompanyEmployeeDepartment 的鏈式結(jié)構(gòu)。

遵循迪米特法則的改進

class Company {
    private Employee manager;
    public String getCompanyAddress() {
        return manager.getDepartmentAddress(); // 隱藏細節(jié)
    }
}

class Employee {
    private Department department;
    public String getDepartmentAddress() {
        return department.getAddress();
    }
}

class Department {
    private String address;
    public String getAddress() { return address; }
}

class Client {
    public void printCompanyAddress(Company company) {
        // 只與 Company 通信
        System.out.println(company.getCompanyAddress());
    }
}

更完整的例子:電腦啟動

class Computer {
    private CPU cpu;
    private Memory memory;
    private HardDisk hardDisk;
    
    public Computer(CPU cpu, Memory memory, HardDisk hardDisk) {
        this.cpu = cpu;
        this.memory = memory;
        this.hardDisk = hardDisk;
    }
    
    public void start() {
        cpu.start();
        memory.check();
        hardDisk.read();
        System.out.println("電腦啟動成功");
    }
    
    public void shutdown() {
        cpu.shutdown();
        memory.clear();
        hardDisk.write();
        System.out.println("電腦關(guān)閉成功");
    }
}

// 用戶只需調(diào)用 Computer,無需了解內(nèi)部組件
class User {
    public void useComputer() {
        Computer computer = new Computer(new CPU(), new Memory(), new HardDisk());
        computer.start();
        computer.shutdown();
    }
}

4. 優(yōu)點

  • 降低類之間的耦合度
  • 提高模塊的相對獨立性
  • 提高系統(tǒng)的可維護性

七、六大原則總結(jié)

原則 核心思想 關(guān)鍵點
單一職責原則 (SRP) 一個類只負責一項職責 高內(nèi)聚、低耦合
開閉原則 (OCP) 對擴展開放,對修改關(guān)閉 抽象、多態(tài)、接口
里氏替換原則 (LSP) 子類可替換父類 繼承的正確使用
接口隔離原則 (ISP) 使用多個專門的接口 接口細化、避免冗余
依賴倒置原則 (DIP) 依賴抽象而非實現(xiàn) 面向接口編程、依賴注入
迪米特法則 (LoD) 最少知識原則 降低耦合、封裝細節(jié)

八、綜合應用示例

// 1. 單一職責 + 接口隔離
interface Payment { void pay(double amount); }
interface Refund { void refund(double amount); }

// 2. 開閉原則 + 依賴倒置
class PaymentProcessor {
    private Payment payment;
    public PaymentProcessor(Payment payment) {
        this.payment = payment;
    }
    public void processPayment(double amount) {
        payment.pay(amount);
    }
}

// 3. 里氏替換原則
class CreditCardPayment implements Payment, Refund {
    public void pay(double amount) { System.out.println("信用卡支付: " + amount); }
    public void refund(double amount) { System.out.println("信用卡退款: " + amount); }
}

class PayPalPayment implements Payment {
    public void pay(double amount) { System.out.println("PayPal支付: " + amount); }
}

// 4. 迪米特法則
class OrderService {
    private PaymentProcessor paymentProcessor;
    public OrderService(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }
    public void checkout(double amount) {
        paymentProcessor.processPayment(amount); // 客戶端無需知道細節(jié)
    }
}

總結(jié)
這六大原則是構(gòu)建高質(zhì)量、可維護、可擴展軟件系統(tǒng)的基礎。在實際開發(fā)中,應靈活運用這些原則,避免過度設計,做到“恰到好處的抽象”。


提示:原則是指導,不是教條。結(jié)合業(yè)務場景合理使用才是關(guān)鍵。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容