
23種經典設計模式共分為3種型別,分別是創建型、結構型和行為型,
今天,我們把這3種型別分成3個對應的小模塊,逐一帶你回顧一下每一種設計模式的原理、實作、設計意圖和應用場景,
還是那句話,如果你看了之后,感覺都有印象,那就說明學得還不錯;如果還能在腦子里形成自己的知識架構,閉上眼睛都能回憶上來,那說明你學得很好;
如果能有自己的理解,并且在專案開發中,開始思考代碼質量問題,開始用已經學過的設計模式來解決代碼問題,那說明你已經掌握這些內容的精髓,
一、創建型設計模式
創建型設計模式包括:單例模式、工廠模式、建造者模式、原型模式,它主要解決物件的創建問題,封裝復雜的創建程序,解耦物件的創建代碼和使用代碼,
1.單例模式
單例模式用來創建全域唯一的物件,一個類只允許創建一個物件(或者叫實體),那這個類就是一個單例類,這種設計模式就叫作單例模式,單例有幾種經典的實作方式,它們分別是:餓漢式、懶漢式、雙重檢測、靜態內部類、列舉,
盡管單例是一個很常用的設計模式,在實際的開發中,我們也確實經常用到它,但是,有些人認為單例是一種反模式(anti-pattern),并不推薦使用,主要的理由有以下幾點:
- 單例對OOP特性的支持不友好
- 單例會隱藏類之間的依賴關系
- 單例對代碼的擴展性不友好
- 單例對代碼的可測驗性不友好
- 單例不支持有引數的建構式
那有什么替代單例的解決方案呢?如果要完全解決這些問題,我們可能要從根上尋找其他方式來實作全域唯一類,比如,通過工廠模式、IOC容器來保證全域唯一性,
有人把單例當作反模式,主張杜絕在專案中使用,我個人覺得這有點極端,模式本身沒有對錯,關鍵看你怎么用,如果單例類并沒有后續擴展的需求,并且不依賴外部系統,那設計成單例類就沒有太大問題,
對于一些全域類,我們在其他地方new的話,還要在類之間傳來傳去,不如直接做成單例類,使用起來簡潔方便,
除此之外,我們還講到了行程唯一單例、執行緒唯一單例、集群唯一單例、多例等擴展知識點,這一部分在實際的開發中并不會被用到,但是可以擴展你的思路、鍛煉你的邏輯思維,這里我就不帶你回顧了,你可以自己回憶一下,
2.工廠模式
工廠模式包括簡單工廠、工廠方法、抽象工廠這3種細分模式,其中,簡單工廠和工廠方法比較常用,抽象工廠的應用場景比較特殊,所以很少用到,不是我們學習的重點,
工廠模式用來創建不同但是相關型別的物件(繼承同一父類或者介面的一組子類),由給定的引數來決定創建哪種型別的物件,實際上,如果創建物件的邏輯并不復雜,那我們直接通過new來創建物件就可以了,不需要使用工廠模式,
當創建邏輯比較復雜,是一個“大工程”的時候,我們就考慮使用工廠模式,封裝物件的創建程序,將物件的創建和使用相分離,
當每個物件的創建邏輯都比較簡單的時候,我推薦使用簡單工廠模式,將多個物件的創建邏輯放到一個工廠類中,
當每個物件的創建邏輯都比較復雜的時候,為了避免設計一個過于龐大的工廠類,我們推薦使用工廠方法模式,將創建邏輯拆分得更細,每個物件的創建邏輯獨立到各自的工廠類中,
詳細點說,工廠模式的作用有下面4個,這也是判斷要不要使用工廠模式最本質的參考標準,
- 封裝變化:創建邏輯有可能變化,封裝成工廠類之后,創建邏輯的變更對呼叫者透明,
- 代碼復用:創建代碼抽離到獨立的工廠類之后可以復用,
- 隔離復雜性:封裝復雜的創建邏輯,呼叫者無需了解如何創建物件,
- 控制復雜度:將創建代碼抽離出來,讓原本的函式或類職責更單一,代碼更簡潔,
除此之外,我們還講了工廠模式一個非常經典的應用場景:依賴注入框架,比如Spring IOC、Google Guice,它用來集中創建、組裝、管理物件,跟具體業務代碼解耦,讓程式員聚焦在業務代碼的開發上,DI框架已經成為了我們平時開發的必備框架,在專欄中,我還帶你實作了一個簡單的DI框架,你可以再回過頭去看看,
3.建造者模式
建造者模式用來創建復雜物件,可以通過設定不同的可選引數,“定制化”地創建不同的物件,建造者模式的原理和實作比較簡單,重點是掌握應用場景,避免過度使用,
如果一個類中有很多屬性,為了避免建構式的引數串列過長,影響代碼的可讀性和易用性,我們可以通過建構式配合set()方法來解決,但是,如果存在下面情況中的任意一種,我們就要考慮使用建造者模式了,
- 我們把類的必填屬性放到建構式中,強制創建物件的時候就設定,如果必填的屬性有很多,把這些必填屬性都放到建構式中設定,那建構式就又會出現引數串列很長的問題,如果我們把必填屬性通過set()方法設定,那校驗這些必填屬性是否已經填寫的邏輯就無處安放了,
- 如果類的屬性之間有一定的依賴關系或者約束條件,我們繼續使用建構式配合set()方法的設計思路,那這些依賴關系或約束條件的校驗邏輯就無處安放了,
- 如果我們希望創建不可變物件,也就是說,物件在創建好之后,就不能再修改內部的屬性值,要實作這個功能,我們就不能在類中暴露set()方法,建構式配合set()方法來設定屬性值的方式就不適用了,
4.原型模式
如果物件的創建成本比較大,而同一個類的不同物件之間差別不大(大部分欄位都相同),在這種情況下,我們可以利用對已有物件(原型)進行復制(或者叫拷貝)的方式,來創建新物件,以達到節省創建時間的目的,這種基于原型來創建物件的方式就叫作原型模式,
原型模式有兩種實作方法,深拷貝和淺拷貝,淺拷貝只會復制物件中基本資料型別資料和參考物件的記憶體地址,不會遞回地復制參考物件,以及參考物件的參考物件……而深拷貝得到的是一份完完全全獨立的物件,所以,深拷貝比起淺拷貝來說,更加耗時,更加耗記憶體空間,
如果要拷貝的物件是不可變物件,淺拷貝共享不可變物件是沒問題的,但對于可變物件來說,淺拷貝得到的物件和原始物件會共享部分資料,就有可能出現資料被修改的風險,也就變得復雜多了,操作非常耗時的情況下,我們比較推薦使用淺拷貝,否則,沒有充分的理由,不要為了一點點的性能提升而使用淺拷貝,
二、結構型設計模式
結構型模式主要總結了一些類或物件組合在一起的經典結構,這些經典的結構可以解決特定應用場景的問題,結構型模式包括:代理模式、橋接模式、裝飾器模式、配接器模式、門面模式、組合模式、享元模式,
1.代理模式
代理模式在不改變原始類介面的條件下,為原始類定義一個代理類,主要目的是控制訪問,而非加強功能,這是它跟裝飾器模式最大的不同,
一般情況下,我們讓代理類和原始類實作同樣的介面,但是,如果原始類并沒有定義介面,并且原始類代碼并不是我們開發維護的,在這種情況下,我們可以通過讓代理類繼承原始類的方法來實作代理模式,
靜態代理需要針對每個類都創建一個代理類,并且每個代理類中的代碼都有點像模板式的“重復”代碼,增加了維護成本和開發成本,對于靜態代理存在的問題,我們可以通過動態代理來解決,
我們不事先為每個原始類撰寫代理類,而是在運行的時候動態地創建原始類對應的代理類,然后在系統中用代理類替換掉原始類,
代理模式常用在業務系統中開發一些非功能性需求,比如:監控、統計、鑒權、限流、事務、冪等、日志,我們將這些附加功能與業務功能解耦,放到代理類統一處理,讓程式員只需要關注業務方面的開發,除此之外,代理模式還可以用在RPC、快取等應用場景中,
2.橋接模式
橋接模式的代碼實作非常簡單,但是理解起來稍微有點難度,并且應用場景也比較局限,所以,相對來說,橋接模式在實際的專案中并沒有那么常用,你只需要簡單了解,見到能認識就可以了,并不是我們學習的重點,
橋接模式有兩種理解方式,第一種理解方式是“將抽象和實作解耦,讓它們能獨立開發”,這種理解方式比較特別,應用場景也不多,
另一種理解方式更加簡單,等同于“組合優于繼承”設計原則,這種理解方式更加通用,應用場景比較多,不管是哪種理解方式,它們的代碼結構都是相同的,都是一種類之間的組合關系,
對于第一種理解方式,弄懂定義中“抽象”和“實作”兩個概念,是理解它的關鍵,定義中的“抽象”,指的并非“抽象類”或“介面”,而是被抽象出來的一套“類別庫”,它只包含骨架代碼,真正的業務邏輯需要委派給定義中的“實作”來完成,
而定義中的“實作”,也并非“介面的實作類”,而是的一套獨立的“類別庫”,“抽象”和“實作”獨立開發,通過物件之間的組合關系組裝在一起,
3.裝飾器模式
裝飾器模式主要解決繼承關系過于復雜的問題,通過組合來替代繼承,給原始類添加增強功能,這也是判斷是否該用裝飾器模式的一個重要的依據,
除此之外,裝飾器模式還有一個特點,那就是可以對原始類嵌套使用多個裝飾器,為了滿足這樣的需求,在設計的時候,裝飾器類需要跟原始類繼承相同的抽象類或者介面,
4.配接器模式
代理模式、裝飾器模式提供的都是跟原始類相同的介面,而配接器提供跟原始類不同的介面,配接器模式是用來做適配的,它將不兼容的介面轉換為可兼容的介面,讓原本由于介面不兼容而不能一起作業的類可以一起作業,
配接器模式有兩種實作方式:類配接器和物件配接器,其中,類配接器使用繼承關系來實作,物件配接器使用組合關系來實作,
配接器模式是一種事后的補救策略,用來補救設計上的缺陷,應用這種模式算是“無奈之舉”,如果在設計初期,我們就能規避介面不兼容的問題,那這種模式就無用武之地了,在實際的開發中,什么情況下才會出現介面不兼容呢?我總結下了下面這5種場景:
- 封裝有缺陷的介面設計
- 統一多個類的介面設計
- 替換依賴的外部系統
- 兼容老版本介面
- 適配不同格式的資料
5.門面模式
門面模式原理、實作都非常簡單,應用場景比較明確,它通過封裝細粒度的介面,提供組合各個細粒度介面的高層次介面,來提高介面的易用性,或者解決性能、分布式事務等問題,
6.組合模式
組合模式跟我們之前講的面向物件設計中的“組合關系(通過組合來組裝兩個類)”,完全是兩碼事,這里講的“組合模式”,主要是用來處理樹形結構資料,
正因為其應用場景的特殊性,資料必須能表示成樹形結構,這也導致了這種模式在實際的專案開發中并不那么常用,但是,一旦資料滿足樹形結構,應用這種模式就能發揮很大的作用,能讓代碼變得非常簡潔,
組合模式的設計思路,與其說是一種設計模式,倒不如說是對業務場景的一種資料結構和演算法的抽象,其中,資料可以表示成樹這種資料結構,業務需求可以通過在樹上的遞回遍歷演算法來實作,
組合模式,將一組物件組織成樹形結構,將單個物件和組合物件都看作樹中的節點,以統一處理邏輯,并且它利用樹形結構的特點,遞回地處理每個子樹,依次簡化代碼實作,
7.享元模式
所謂“享元”,顧名思義就是被共享的單元,享元模式的意圖是復用物件,節省記憶體,前提是享元物件是不可變物件,
具體來講,當一個系統中存在大量重復物件的時候,我們就可以利用享元模式,將物件設計成享元,在記憶體中只保留一份實體,供多處代碼參考,這樣可以減少記憶體中物件的數量,以起到節省記憶體的目的,
實際上,不僅僅相同物件可以設計成享元,對于相似物件,我們也可以將這些物件中相同的部分(欄位),提取出來設計成享元,讓這些大量相似物件參考這些享元,
三、行為型設計模式
我們知道,創建型設計模式主要解決“物件的創建”問題,結構型設計模式主要解決“類或物件的組合”問題,那行為型設計模式主要解決的就是“類或物件之間的互動”問題,行為型模式比較多,有11種,它們分別是:觀察者模式、模板模式、策略模式、職責鏈模式、迭代器模式、狀態模式、訪問者模式、備忘錄模式、命令模式、解釋器模式、中介模式,
1.觀察者模式
觀察者模式將觀察者和被觀察者代碼解耦,觀察者模式的應用場景非常廣泛,小到代碼層面的解耦,大到架構層面的系統解耦,再或者一些產品的設計思路,都有這種模式的影子,比如,郵件訂閱、RSS Feeds,本質上都是觀察者模式,
不同的應用場景和需求下,這個模式也有截然不同的實作方式:有同步阻塞的實作方式,也有異步非阻塞的實作方式;有行程內的實作方式,也有跨行程的實作方式,
同步阻塞是最經典的實作方式,主要是為了代碼解耦;異步非阻塞除了能實作代碼解耦之外,還能提高代碼的執行效率;行程間的觀察者模式解耦更加徹底,一般是基于訊息佇列來實作,用來實作不同行程間的被觀察者和觀察者之間的互動,
框架的作用有隱藏實作細節,降低開發難度,實作代碼復用,解耦業務與非業務代碼,讓程式員聚焦業務開發,針對異步非阻塞觀察者模式,我們也可以將它抽象成EventBus框架來達到這樣的效果,
EventBus翻譯為“事件總線”,它提供了實作觀察者模式的骨架代碼,我們可以基于此框架非常容易地在自己的業務場景中實作觀察者模式,不需要從零開始開發,
2.模板模式
模板方法模式在一個方法中定義一個演算法骨架,并將某些步驟推遲到子類中實作,模板方法模式可以讓子類在不改變演算法整體結構的情況下,重新定義演算法中的某些步驟,這里的“演算法”,我們可以理解為廣義上的“業務邏輯”,并不特指資料結構和演算法中的“演算法”,
這里的演算法骨架就是“模板”,包含演算法骨架的方法就是“模板方法”,這也是模板方法模式名字的由來,
模板模式有兩大作用:復用和擴展,其中復用指的是,所有的子類可以復用父類中提供的模板方法的代碼,擴展指的是,框架通過模板模式提供功能擴展點,讓框架用戶可以在不修改框架原始碼的情況下,基于擴展點定制化框架的功能,
除此之外,我們還講到回呼,它跟模板模式具有相同的作用:代碼復用和擴展,在一些框架、類別庫、組件等的設計中經常會用到,比如JdbcTemplate就是用了回呼,
相對于普通的函式呼叫,回呼是一種雙向呼叫關系,A類事先注冊某個函式F到B類,A類在呼叫B類的P函式的時候,B類反過來呼叫A類注冊給它的F函式,這里的F函式就是“回呼函式”,A呼叫B,B反過來又呼叫A,這種呼叫機制就叫作“回呼”,
回呼可以細分為同步回呼和異步回呼,從應用場景上來看,同步回呼看起來更像模板模式,異步回呼看起來更像觀察者模式,回呼跟模板模式的區別,更多的是在代碼實作上,而非應用場景上,回呼基于組合關系來實作,模板模式基于繼承關系來實作,回呼比模板模式更加靈活,
3.策略模式
策略模式定義一組演算法類,將每個演算法分別封裝起來,讓它們可以互相替換,策略模式可以使演算法的變化獨立于使用它們的客戶端(這里的客戶端代指使用演算法的代碼),策略模式用來解耦策略的定義、創建、使用,實際上,一個完整的策略模式就是由這三個部分組成的,
策略類的定義比較簡單,包含一個策略介面和一組實作這個介面的策略類,策略的創建由工廠類來完成,封裝策略創建的細節,
策略模式包含一組策略可選,客戶端代碼選擇使用哪個策略,有兩種確定方法:編譯時靜態確定和運行時動態確定,其中,“運行時動態確定”才是策略模式最典型的應用場景,
在實際的專案開發中,策略模式也比較常用,最常見的應用場景是,利用它來避免冗長的if-else或switch分支判斷,不過,它的作用還不止如此,它也可以像模板模式那樣,提供框架的擴展點等等,
實際上,策略模式主要的作用還是解耦策略的定義、創建和使用,控制代碼的復雜度,讓每個部分都不至于過于復雜、代碼量過多,除此之外,對于復雜代碼來說,策略模式還能讓其滿足開閉原則,添加新策略的時候,最小化、集中化代碼改動,減少引入bug的風險,
4.職責鏈模式
在職責鏈模式中,多個處理器依次處理同一個請求,一個請求先經過A處理器處理,然后再把請求傳遞給B處理器,B處理器處理完后再傳遞給C處理器,以此類推,形成一個鏈條,鏈條上的每個處理器各自承擔各自的處理職責,所以叫作職責鏈模式,
在GoF的定義中,一旦某個處理器能處理這個請求,就不會繼續將請求傳遞給后續的處理器了,當然,在實際的開發中,也存在對這個模式的變體,那就是請求不會中途終止傳遞,而是會被所有的處理器都處理一遍,
職責鏈模式常用在框架開發中,用來實作過濾器、攔截器功能,讓框架的使用者在不需要修改框架原始碼的情況下,添加新的過濾、攔截功能,這也體現了之前講到的對擴展開放、對修改關閉的設計原則,
5.迭代器模式
迭代器模式也叫游標模式,它用來遍歷集合物件,這里說的“集合物件”,我們也可以叫“容器”“聚合物件”,實際上就是包含一組物件的物件,比如,陣列、鏈表、樹、圖、跳表,
迭代器模式主要作用是解耦容器代碼和遍歷代碼,大部分編程語言都提供了現成的迭代器可以使用,我們不需要從零開始開發,
遍歷集合一般有三種方式:for回圈、foreach回圈、迭代器遍歷,后兩種本質上屬于一種,都可以看作迭代器遍歷,相對于for回圈遍歷,利用迭代器來遍歷有3個優勢:
- 迭代器模式封裝集合內部的復雜資料結構,開發者不需要了解如何遍歷,直接使用容器提供的迭代器即可;
- 迭代器模式將集合物件的遍歷操作從集合類中拆分出來,放到迭代器類中,讓兩者的職責更加單一;
- 迭代器模式讓添加新的遍歷演算法更加容易,更符合開閉原則,除此之外,因為迭代器都實作自相同的介面,在開發中,基于介面而非實作編程,替換迭代器也變得更加容易,
在通過迭代器來遍歷集合元素的同時,增加或者洗掉集合中的元素,有可能會導致某個元素被重復遍歷或遍歷不到,針對這個問題,有兩種比較干脆利索的解決方案,來避免出現這種不可預期的運行結果,
一種是遍歷的時候不允許增刪元素,另一種是增刪元素之后讓遍歷報錯,第一種解決方案比較難實作,因為很難確定迭代器使用結束的時間點,第二種解決方案更加合理,Java語言就是采用的這種解決方案,增刪元素之后,我們選擇fail-fast解決方式,讓遍歷操作直接拋出運行時例外,
6.狀態模式
狀態模式一般用來實作狀態機,而狀態機常用在游戲、作業流引擎等系統開發中,狀態機又叫有限狀態機,它由3個部分組成:狀態、事件、動作,
其中,事件也稱為轉移條件,事件觸發狀態的轉移及動作的執行,不過,動作不是必須的,也可能只轉移狀態,不執行任何動作,
針對狀態機,我們總結了三種實作方式,
第一種實作方式叫分支邏輯法,利用if-else或者switch-case分支邏輯,參照狀態轉移圖,將每一個狀態轉移原模原樣地直譯成代碼,對于簡單的狀態機來說,這種實作方式最簡單、最直接,是首選,
第二種實作方式叫查表法,對于狀態很多、狀態轉移比較復雜的狀態機來說,查表法比較合適,通過二維陣列來表示狀態轉移圖,能極大地提高代碼的可讀性和可維護性,
第三種實作方式就是利用狀態模式,對于狀態并不多、狀態轉移也比較簡單,但事件觸發執行的動作包含的業務邏輯可能比較復雜的狀態機來說,我們首選這種實作方式,
7.訪問者模式
訪問者模式允許一個或者多個操作應用到一組物件上,設計意圖是解耦操作和物件本身,保持類職責單一、滿足開閉原則以及應對代碼的復雜性,
對于訪問者模式,學習的主要難點在代碼實作,而代碼實作比較復雜的主要原因是,函式多載在大部分面向物件編程語言中是靜態系結的,
也就是說,呼叫類的哪個多載函式,是在編譯期間,由引數的宣告型別決定的,而非運行時,根據引數的實際型別決定的,除此之外,我們還講到Double Disptach,如果某種語言支持Double Dispatch,那就不需要訪問者模式了,
正是因為代碼實作難理解,所以,在專案中應用這種模式,會導致代碼的可讀性比較差,如果你的同事不了解這種設計模式,可能就會讀不懂、維護不了你寫的代碼,所以,除非不得已,不要使用這種模式,
8.備忘錄模式
備忘錄模式也叫快照模式,具體來說,就是在不違背封裝原則的前提下,捕獲一個物件的內部狀態,并在該物件之外保存這個狀態,以便之后恢復物件為先前的狀態,
這個模式的定義表達了兩部分內容:一部分是,存盤副本以便后期恢復;另一部分是,要在不違背封裝原則的前提下,進行物件的備份和恢復,
備忘錄模式的應用場景也比較明確和有限,主要用來防丟失、撤銷、恢復等,它跟平時我們常說的“備份”很相似,兩者的主要區別在于,備忘錄模式更側重于代碼的設計和實作,備份更側重架構設計或產品設計,
對于大物件的備份來說,備份占用的存盤空間會比較大,備份和恢復的耗時會比較長,針對這個問題,不同的業務場景有不同的處理方式,
比如,只備份必要的恢復資訊,結合最新的資料來恢復;再比如,全量備份和增量備份相結合,低頻全量備份,高頻增量備份,兩者結合來做恢復,
9.命令模式
命令模式在平時作業中并不常用,你稍微了解一下就可以,
落實到編碼實作,命令模式用到最核心的實作手段,就是將函式封裝成物件,我們知道,在大部分編程語言中,函式是沒法作為引數傳遞給其他函式的,也沒法賦值給變數,借助命令模式,我們將函式封裝成物件,這樣就可以實作把函式像物件一樣使用,
命令模式的主要作用和應用場景,是用來控制命令的執行,比如,異步、延遲、排隊執行命令、撤銷重做命令、存盤命令、給命令記錄日志等,這才是命令模式能發揮獨一無二作用的地方,
10.解釋器模式
解釋器模式為某個語言定義它的語法(或者叫文法)表示,并定義一個解釋器用來處理這個語法,實際上,這里的“語言”不僅僅指我們平時說的中、英、日、法等各種語言,
從廣義上來講,只要是能承載資訊的載體,我們都可以稱之為“語言”,比如,古代的結繩記事、盲文、啞語、摩斯密碼等,
要想了解“語言”要表達的資訊,我們就必須定義相應的語法規則,這樣,書寫者就可以根據語法規則來書寫“句子”(專業點的叫法應該是“運算式”),閱讀者根據語法規則來閱讀“句子”,這樣才能做到資訊的正確傳遞,
而我們要講的解釋器模式,其實就是用來實作根據語法規則解讀“句子”的解釋器,
解釋器模式的代碼實作比較靈活,沒有固定的模板,我們前面說過,應用設計模式主要是應對代碼的復雜性,解釋器模式也不例外,它的代碼實作的核心思想,就是將語法決議的作業拆分到各個小類中,以此來避免大而全的決議類,
一般的做法是,將語法規則拆分一些小的獨立的單元,然后對每個單元進行決議,最終合并為對整個語法規則的決議,
11.中介模式
中介模式的設計思想跟中間層很像,通過引入中介這個中間層,將一組物件之間的互動關系(或者說依賴關系)從多對多(網狀關系)轉換為一對多(星狀關系),原來一個物件要跟n個物件互動,現在只需要跟一個中介物件互動,從而最小化物件之間的互動關系,降低了代碼的復雜度,提高了代碼的可讀性和可維護性,
觀察者模式和中介模式都是為了實作參與者之間的解耦,簡化互動關系,兩者的不同在于應用場景上,在觀察者模式的應用場景中,參與者之間的互動比較有條理,一般都是單向的,一個參與者只有一個身份,要么是觀察者,要么是被觀察者,
而在中介模式的應用場景中,參與者之間的互動關系錯綜復雜,既可以是訊息的發送者、也可以同時是訊息的接收者,
如果有識訓,歡迎你收藏這篇文章,反復閱讀,并把它分享給你的朋友,
作者:王爭
本文來自博客園,作者:古道輕風,轉載請注明原文鏈接:https://www.cnblogs.com/88223100/p/Have-you-learned-23-design-models.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/540560.html
標籤:其他
上一篇:23種設計模式,你學廢了嘛?
