# SOLID 原則 ###### tags: `Design Pattern` ## Single Responsibility Principle > 當需要修改類別(Class)時,原因只會有一個 盡可能的讓類別負責一個功能,並且完全封裝在這個類別當中。 ```cpp= class Employee { public: String getName(){}; void printTimeSheetReport()P{}; private: String name ; }; ``` 在上面這個員工的類別當中,含有了獲取員工名字與列印報告的功能,而列印報告可能會時常需要修改,所以我們應該把他獨立出來。如下: ```cpp= class Employee { public: void getName(){}; private: String name; }; class TimeSheetReport { public: print(employee); }; ``` 透過這個方法我們將TimeSheetReport依賴於Employee類別,我們只需要將員工傳入這個函數中,就可以去列印該員工的報表,同時如果需要更改列印的訊息,我們可以不動到Employee的類別。 ## Open/Closed Principle >對於擴展類別應該要是開放的 ; 對於修改類別應該是封閉的 主要的概念是為了新增新功能時,可以同時保持舊的程式碼不被改變。 下方是一個有關於電子商務平台的範例,假如需要新增一個運輸方式,我們會需要直接對Order類別來做修改,有可能造成無法使用的問題產生: ```cpp= class Order { public: int getTotal(){}; int getTotalWeight(){}; void SetShippingType(String str){}; int getShippingCost() { if(shipping == "ground") { if(getTotal() > 100) { return 0; } return max(10 , getTotalWeight()*1.5); } if (shipping == "air") { return max(20,getTotalWeight()*3); } } int getShippingDate(){}; private: int lineItems ; String shipping; }; ``` 可以修改為如下: ```cpp= class Order { public: int getToatl(){} int getTotalWeight(){} void setShippingType(Shipping ship) { shipping = ship; } int getShippingCost(){} int getShippingDate(){} private: int lineitems; std::shared_ptr<Shipping> shipping; }; class Shipping { public: virtual int getCost(Order o){}; virtual int getDate(Order o){}; }; class Ground : public Shipping { int getCost(Order o) { if(order.getTotal()>100) { return 0; } return max(10 , order.getTotalWeight()*1.5); } int getDate() { } }; class Air : public Shipping { int getCost(Order o) { if(order.getTotal()>100) { return 0; } return max(10 , order.getTotalWeight()*1); } int getDate() { } }; ``` ## Liskov Substitution Principle >當擴充某個類別時,應該要在能不修改客戶端的代碼前提下,將子類的對象作為父類對象進行傳遞。 * 子類函數的參數類型必須與父類的參數類型相同or更抽象 下方的方法都要餵食的時候必須傳入Cat的物件 ```cpp= class Animal { void feed (Cat c) { .... } }; ``` 比較好的作法: ```cpp= class Animal { public: virtual void feed() = 0 ; }; class Cat : public Animal { public: void feed() override { //feed cat } }; void feed_aniaml(std::shared_ptr<Animal> ptr) { ptr->feed(); } int main() { std::shared_ptr<Animal> ptr = std::make_shared<Cat>(); feed_aniaml(ptr); } ``` 這邊先創造了一個Animal的類別,並且創立了Cat作為子類,在feed_animal傳入參數中傳入的是Animal這個父類的指針,這個作法不會限制feed_animal這個函數可以餵食的動物。 比較不好的方式: ```cpp= class Animal { public: virtual void feed() = 0 ; }; class Cat : public Animal { public: void feed() override { //feed cat } }; class BengalCat : public Animal { public: void feed() override { //feed cat } }; void feed_aniaml(std::shared_ptr<BengalCat> ptr) { ptr->feed(); } int main() { // std::shared_ptr<Animal> ptr = std::make_shared<Cat>(); //傳入會報錯 std::shared_ptr<Animal> ptr = std::make_shared<BengalCat>(); feed_aniaml(ptr); } ``` 這邊與上方架構相同,新增了孟加拉貓這個類,但我們修改了feed_animal的傳入參數,只有孟加拉貓能能餵食,在之後如果要傳入Cat類的話就會報錯,只可以傳入孟加拉貓。 ## Interface Segregation Principle > 客戶端不應該強迫依賴於其不使用的方法 盡量縮小介面的範圍,讓繼承的類不需要實現他不需要的行為。 先來看看較不好的作法: ```cpp= class CloudProvider { public: virtual void storeFile(name) = 0; virtual bool getFile(name) = 0; virtual void createServer(region) = 0 ; virtual void listServers(region) = 0; virtual int getCDNAddress() = 0; }; class AlibabaCloud : public CloudProvider { public: void storeFile(name){} bool getFile(name){} void createServer(region){} void listServers(region){} int getCDNAddress(){} }; class TencentCloud : public CloudProvider { public: void storeFile(name){} bool getFile(name){} void createServer(region){} ///沒有實現 void listServers(region){} ///沒有實現 int getCDNAddress(){} ///沒有實現 }; ``` 上面程式碼中的 TencentCloud類別,儘管沒有使用到後三個的函數,但仍然需要實現他。 可以將介面再拆程更小更細節的多個介面: ```cpp= class CloudHostingProvider { public: virtual void createServer(region r) = 0; virtual void listServers(region r) = 0; }; class CDNProvider { public: virtual void getCDNAddress() = 0; }; class CoudStorageProvider { public: virtual void storeFile(name) = 0; virtual void getFile(name) = 0; }; class AlibabaCloud : public CloudHostingProvider , public CDNProvider , public CoudStorageProvider { public: void storeFile(name){} void getFile(name){} void createServer(name){} void listServers(region){} void getCDNAdress(){} }; class TencentCloud : public CoudStorageProvider { public: void storeFile(name){} void getFile(name){} }; ``` 上述將原先複雜的介面拆分成多個更小的介面,但同樣的過度的拆分會造成程式碼越複雜。 ## Dependency Inversion Principle > 高層次的類別不應該依賴於低層次的類別,他們應該依賴介面。 > 介面也不應該依賴於實做 , 而是實做依賴於介面 * 低層次的類別 用來完成基礎操作的類 (Socket \ 通訊 \ TCPIP等) * 高層次的類別 複雜的運算或是操作邏輯 , 主要藉由低層次的類來組合實現 ```cpp= class MySQLDatabase //低層次的類 { public: void insert(){} void update(){} void delete(){} }; class BudgetReport //高層次的類 { public: void open(date){} void save(){} private: MySQLDatabase database; }; ``` 在上述的作法,我們有兩個類別分別是預算報告(BudgetReport)與資料庫(MySQLDatabase),高層次的類會使用低層次的類來讀取與保存資料,這會變成假設低層次的類改變,都有可能會影響到高層次的類,但高層次的類不應該需要注意資料儲存的細節問題。 可以將上述改為如下程式碼 ```cpp= //interface class Database { public: virtual void insert() = 0; virtual void update() = 0; virtual void delete() = 0; }; class MySQL : public Database //低層次的類 { public: void insert() override {} void update() override {} void delete() override {} }; class MongoDB : public Database { public: void insert() override {} void update() override {} void delete() override {} }; class BudgetReport //高層次的類 { public: void open(date){} void save(){} private: Database database; }; ```