---
# System prepended metadata

title: SOLID 原則
tags: [Design Pattern]

---

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