開放封閉原則

開放封閉原則在 Clean Architecture 一書中是這樣被敘述的

一個軟體製品應該對於擴充是開放的,但對於修改是封閉的。

一個簡單的擴充需求,對於軟體開發上是巨大的改變。這是軟體架構的失敗。一個好的軟體架構可以將修改的程式碼減少到最低程度。在理想情形下這個值是
0

擴展系統功能比較好的方式應該是新增程式碼,並不是去修改既有的程式碼來擴充系統功能。因為修改一個行之有年的系統內部邏輯,可能會發生改
A 壞 B ,如果有自動化測試來驗證既有的程式碼可以在第一時間找出來。
但是在實務上有自動化測試專案真的是少之又少(ps.可能祖上積德才會遇到吧…),如果當初在設計系統的時候能避免的這種改 A 壞 B
的情形,那軟體系統將更容易修改需求及擴充功能。舉例來說,這是一個手機介面。

1
2
3
4
5
6
7
8
9
10
11

public interface IMobilePhone
{

void TurnOn();

void TurnOff();

void Call(string phoneNumber);
}

接下來,我們可以定義不同的手機,並實現IMobilePhone.cs介面:

  • IPhone14.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public class IPhone14 : IMobilePhone
{
public void TurnOn()
{
Console.WriteLine("IPhone14 is turning on.");
}

public void TurnOff()
{
Console.WriteLine("IPhone14 is turning off.");
}

public void Call(string phoneNumber)
{
Console.WriteLine("IPhone14 is calling " + phoneNumber);
}

}

宣告 IMobilePhoneFactory.cs

1
2
3
4
5
6

public interface IMobilePhoneFactory
{
IMobilePhone Create(string mobilePhone);
}

建立 MobilePhoneFactory.cs 實作 IMobilePhoneFactory.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

public class MobilePhoneFactory : IMobilePhoneFactory
{
public IMobilePhone Create(string mobilePhone)
{
var type = typeof(Program).Assembly
.GetExportedTypes()
.SingleOrDefault(t => t.Name.Equals(mobilePhone) && typeof(IMobilePhone).IsAssignableFrom(t));

if (type is null)
{
throw new ArgumentException(nameof(mobilePhone));
}

return (IMobilePhone)Activator.CreateInstance(type);
}
}

  • GooglePixel7.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

public class GooglePixel7 : IMobilePhone
{
public void TurnOn()
{
Console.WriteLine("GooglePixel7 phone is turning on.");
}

public void TurnOff()
{
Console.WriteLine("GooglePixel7 phone is turning off.");
}

public void Call(string phoneNumber)
{
Console.WriteLine("GooglePixel7 phone is calling " + phoneNumber);
}
}

現在,當我們使用不同型號手機,只需要使用IMobilePhone.cs介面,而不需要知道實際的手機型別。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

var mobilePhoneTypes = new List<string>
{
"IPhone14"
};

foreach (var mobilePhoneType in mobilePhoneTypes)
{
var mobilePhoneFactory = new MobilePhoneFactory();

var mobilePhone = mobilePhoneFactory.Create(mobilePhoneType);

mobilePhone.TurnOn();

mobilePhone.Call("1234567890");

mobilePhone.TurnOff();
}

這樣做的好處在於,當我們需要添加新的手機型號時,只需要實現 IMobilePhone.cs 介面即可,不需要修改現有使用端程式碼,只需要建立設定檔將所有手機型號進行註冊動作即可。

總結

開放封閉原則的目標是實現系統的可擴展性和靈活性,使系統易於擴展而不會因為修改而對現有程式碼造成較大的影響。

在類別層次上,建議設計類別時要對擴展開放,對修改封閉。

在架構層次上,建議將系統組織成一系列相互獨立的元件,將這些元件安排到依賴階層中而實現。這樣可以避免高層級元件受到低層級元件變更的影響,同時使系統具有高度的模組化和可擴展性。

總結,適用於類別層次和架構層次的設計原則,皆是實現系統的可擴展性和靈活性,同時保證系統的穩定性和可靠性。