# 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;
};
```