ο缶幊谈呒壧匦耘c設(shè)計(jì)模式實(shí)戰(zhàn))
1. 面向?qū)ο缶幊谈呒壧匦越馕雒嫦驅(qū)ο缶幊?OOP)的高級特性是每個開發(fā)者進(jìn)階必須掌握的技能點(diǎn)。在實(shí)際項(xiàng)目開發(fā)中我們經(jīng)常會遇到需要設(shè)計(jì)復(fù)雜對象關(guān)系、處理特殊場景的情況。這時候基礎(chǔ)的面相對象知識就顯得不夠用了必須深入了解繼承、多態(tài)、封裝等高級特性的實(shí)現(xiàn)原理和應(yīng)用技巧。我見過不少開發(fā)者雖然能寫出符合語法規(guī)范的代碼但在設(shè)計(jì)模式應(yīng)用、抽象層次劃分等高級場景中常常捉襟見肘。這就像會開車但不懂汽車構(gòu)造原理遇到復(fù)雜路況就容易出問題。接下來我將結(jié)合多年開發(fā)經(jīng)驗(yàn)分享面向?qū)ο蟾呒壊糠值暮诵囊c(diǎn)和實(shí)戰(zhàn)技巧。2. 繼承與多態(tài)的深度應(yīng)用2.1 繼承關(guān)系的設(shè)計(jì)原則繼承是OOP三大特性中最容易被誤用的特性。很多新手開發(fā)者喜歡創(chuàng)建過深的繼承層次導(dǎo)致系統(tǒng)難以維護(hù)。在實(shí)踐中我總結(jié)出幾個關(guān)鍵原則優(yōu)先使用組合而非繼承當(dāng)兩個類之間是has-a關(guān)系時應(yīng)該使用組合只有明確的is-a關(guān)系才考慮繼承。比如汽車和引擎應(yīng)該用組合而轎車和汽車可以用繼承。遵循里氏替換原則(LSP)子類必須能夠替換父類而不影響程序正確性。這意味著子類不應(yīng)該修改父類的行為只能擴(kuò)展功能。控制繼承層次深度通常不超過3層過深的繼承會帶來脆弱的基類問題。Java的集合框架就是個反面教材它的繼承層次過深導(dǎo)致了很多設(shè)計(jì)問題。2.2 多態(tài)的實(shí)現(xiàn)機(jī)制多態(tài)是OOP最強(qiáng)大的特性之一它允許我們以統(tǒng)一的方式處理不同類型的對象。理解多態(tài)的實(shí)現(xiàn)機(jī)制對寫出高質(zhì)量的代碼至關(guān)重要// 典型的多態(tài)示例 Animal animal new Dog(); animal.makeSound(); // 實(shí)際調(diào)用的是Dog類的實(shí)現(xiàn)在JVM中多態(tài)是通過虛方法表(vtable)實(shí)現(xiàn)的。每個類都有一個虛方法表存儲著該類所有虛方法的實(shí)際入口地址。當(dāng)調(diào)用虛方法時JVM會根據(jù)對象的實(shí)際類型查找對應(yīng)的虛方法表從而確定要執(zhí)行的方法。重要提示在Java中只有非private、非static、非final的方法才是虛方法才能實(shí)現(xiàn)多態(tài)。理解這點(diǎn)對性能優(yōu)化很重要。3. 接口與抽象類的實(shí)戰(zhàn)選擇3.1 接口的設(shè)計(jì)藝術(shù)現(xiàn)代編程語言中接口已經(jīng)演變成了非常強(qiáng)大的抽象工具。Java 8引入的默認(rèn)方法(default method)更是讓接口如虎添翼。在設(shè)計(jì)接口時我通常會考慮以下幾點(diǎn)單一職責(zé)原則一個接口應(yīng)該只定義一個角色或能力。比如Runnable接口只定義run()方法Comparable接口只定義compareTo()方法。接口隔離原則不應(yīng)該強(qiáng)迫客戶端依賴它們不需要的接口。大而全的接口應(yīng)該拆分成多個小接口。默認(rèn)方法的合理使用默認(rèn)方法適合提供通用實(shí)現(xiàn)但要避免在接口中維護(hù)狀態(tài)。比如Collection接口的stream()方法就是個很好的默認(rèn)方法實(shí)現(xiàn)。3.2 抽象類的適用場景抽象類適合以下場景需要包含一些具體實(shí)現(xiàn)需要定義非public的成員需要定義構(gòu)造方法需要維護(hù)對象狀態(tài)一個典型的抽象類示例public abstract class AbstractListE implements ListE { protected int modCount 0; // 維護(hù)狀態(tài) public abstract E get(int index); public boolean add(E e) { add(size(), e); // 提供部分實(shí)現(xiàn) return true; } }在實(shí)際項(xiàng)目中我通常先用接口定義行為契約當(dāng)發(fā)現(xiàn)有多個實(shí)現(xiàn)類共享代碼時再提取抽象類。這種接口先行的做法能保持設(shè)計(jì)的靈活性。4. 設(shè)計(jì)模式中的面向?qū)ο笏枷?.1 工廠模式與對象創(chuàng)建工廠模式完美體現(xiàn)了面向?qū)ο蟮姆庋b思想。將對象創(chuàng)建邏輯封裝起來客戶端只需要關(guān)心接口不需要知道具體實(shí)現(xiàn)類。Spring框架的BeanFactory就是工廠模式的經(jīng)典應(yīng)用。我經(jīng)常使用的幾種工廠模式變體簡單工廠一個工廠類包含多個創(chuàng)建方法工廠方法每個產(chǎn)品對應(yīng)一個工廠子類抽象工廠創(chuàng)建產(chǎn)品族// 抽象工廠示例 public interface GUIFactory { Button createButton(); Menu createMenu(); } public class WinFactory implements GUIFactory { public Button createButton() { return new WinButton(); } public Menu createMenu() { return new WinMenu(); } }4.2 策略模式與多態(tài)策略模式是多用組合少用繼承原則的完美體現(xiàn)。它通過將算法封裝成獨(dú)立的策略類使得它們可以相互替換。在實(shí)際項(xiàng)目中我常用策略模式來處理業(yè)務(wù)規(guī)則的變化。一個支付策略的示例public interface PaymentStrategy { void pay(int amount); } public class CreditCardStrategy implements PaymentStrategy { public void pay(int amount) { // 信用卡支付邏輯 } } public class ShoppingCart { private PaymentStrategy strategy; public void setPaymentStrategy(PaymentStrategy strategy) { this.strategy strategy; } public void checkout(int amount) { strategy.pay(amount); } }5. 高級面向?qū)ο蠹记膳c陷阱5.1 不可變對象的設(shè)計(jì)不可變對象(Immutable Object)是線程安全的不需要同步可以自由共享。設(shè)計(jì)不可變對象有幾個關(guān)鍵點(diǎn)所有字段設(shè)為final不提供setter方法防止this引用逸出如果包含可變對象需要進(jìn)行防御性拷貝public final class ImmutablePerson { private final String name; private final Date birthDate; public ImmutablePerson(String name, Date birthDate) { this.name name; this.birthDate new Date(birthDate.getTime()); // 防御性拷貝 } public Date getBirthDate() { return new Date(birthDate.getTime()); // 返回拷貝 } }5.2 對象拷貝的注意事項(xiàng)對象拷貝是個容易出錯的地方。淺拷貝(Shallow Copy)只復(fù)制對象本身和其基本類型字段而引用類型字段仍然指向原對象。深拷貝(Deep Copy)則會遞歸復(fù)制所有引用對象。實(shí)現(xiàn)深拷貝的幾種方式手動實(shí)現(xiàn)clone()方法使用序列化/反序列化使用第三方庫如Apache Commons Lang的SerializationUtils實(shí)際經(jīng)驗(yàn)在分布式系統(tǒng)中我更喜歡使用序列化方式實(shí)現(xiàn)深拷貝雖然性能稍差但更可靠能處理復(fù)雜的對象圖。6. 面向?qū)ο笤O(shè)計(jì)原則實(shí)戰(zhàn)6.1 SOLID原則應(yīng)用SOLID原則是面向?qū)ο笤O(shè)計(jì)的基石但在實(shí)際項(xiàng)目中如何應(yīng)用這些原則呢單一職責(zé)原則(SRP)我常用一個類只有一個引起它變化的原因來檢驗(yàn)設(shè)計(jì)。比如訂單類不應(yīng)該包含支付邏輯。開閉原則(OCP)通過抽象和繼承實(shí)現(xiàn)擴(kuò)展開放修改關(guān)閉。策略模式就是OCP的典型應(yīng)用。里氏替換原則(LSP)子類不能破壞父類的契約。比如重寫方法時參數(shù)應(yīng)該更寬松返回值應(yīng)該更嚴(yán)格。接口隔離原則(ISP)不要強(qiáng)迫客戶端依賴它們不需要的接口。大接口要拆分成小接口。依賴倒置原則(DIP)高層模塊不應(yīng)該依賴低層模塊二者都應(yīng)該依賴抽象。Spring的依賴注入就是DIP的實(shí)現(xiàn)。6.2 迪米特法則實(shí)踐迪米特法則(最少知識原則)要求一個對象應(yīng)該對其他對象有最少的了解。在實(shí)踐中我常用以下技巧方法鏈不要過長如a.getB().getC().doSomething()違反了迪米特法則引入中間對象讓a直接調(diào)用c的doSomething()通過b中轉(zhuǎn)使用外觀模式為復(fù)雜子系統(tǒng)提供簡單接口一個違反迪米特法則的例子// 不好的寫法 public void process(Order order) { Customer customer order.getCustomer(); Address address customer.getAddress(); String city address.getCity(); // 使用city... } // 改進(jìn)后的寫法 public void process(Order order) { String city order.getCustomerCity(); // 使用city... }7. 現(xiàn)代面向?qū)ο缶幊腾厔?.1 函數(shù)式編程的影響現(xiàn)代編程語言如Java 8、Kotlin、Scala等都引入了函數(shù)式編程特性這對傳統(tǒng)的面向?qū)ο缶幊坍a(chǎn)生了深遠(yuǎn)影響Lambda表達(dá)式可以更簡潔地實(shí)現(xiàn)策略模式Stream API引入了聲明式的數(shù)據(jù)處理方式Optional類更好地處理null值避免空指針異常// 傳統(tǒng)面向?qū)ο髮懛?ListString names new ArrayList(); for (User user : users) { if (user.isActive()) { names.add(user.getName()); } } // 函數(shù)式風(fēng)格 ListString names users.stream() .filter(User::isActive) .map(User::getName) .collect(Collectors.toList());7.2 響應(yīng)式編程中的對象設(shè)計(jì)響應(yīng)式編程(如Reactor、RxJava)對對象設(shè)計(jì)提出了新要求不可變性更重要因?yàn)閿?shù)據(jù)可能在多個線程間流動更細(xì)粒度的對象便于組合和變換副作用管理明確區(qū)分有副作用和無副作用的方法一個響應(yīng)式風(fēng)格的訂單處理示例public MonoOrder processOrder(Order order) { return validateOrder(order) .flatMap(this::applyDiscount) .flatMap(this::chargePayment) .flatMap(this::sendConfirmation); }在實(shí)際項(xiàng)目中我發(fā)現(xiàn)混合使用面向?qū)ο蠛秃瘮?shù)式風(fēng)格往往能取得最佳效果。面向?qū)ο笊瞄L建模復(fù)雜領(lǐng)域函數(shù)式擅長處理數(shù)據(jù)流和并發(fā)。