概述
設計模式的六大原則是面向?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());
}
}
問題:客戶端需了解
Company→Employee→Department的鏈式結(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)鍵。