Clean Code_Note9_Replace Switch Statement With Subclasses

 
switch case 應該在寫程式時很常用到
那往往也會讓程式碼看起來比較有結構性但也可能因為太長導致不好維護,甚至很多重複性值的程式。


以下是一個案例,用 switch 來根據類型控制邏輯:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
namespace CleanUpConditionals
{
    public class ReplaceSwitchStatementWithSubclasses
    {
        public ReplaceSwitchStatementWithSubclasses()
        {
            var lowLevelManager = new Manager(Manager.LOW_LEVEL, 30000);
            decimal salary = lowLevelManager.GetMonthlySalary(0);
        }

        class Manager
        {
            public const int LOW_LEVEL = 0;
            public const int MIDDLE_LEVEL = 1;
            public const int TOP_LEVEL = 2;

            private int _managerLevel;
            private decimal _baseSalary;

            public Manager(int level, decimal baseSalary)
            {
                _managerLevel = level;
                _baseSalary = baseSalary;
            }

            public decimal GetMonthlySalary(int numberOfPeopleInDepartment)
            {
                switch (_managerLevel)
                {
                    case LOW_LEVEL:
                        return _baseSalary;
                    case MIDDLE_LEVEL:
                        return _baseSalary * 1.25M ;
                    case TOP_LEVEL:
                        return (_baseSalary * 1.25M) + (decimal)(numberOfPeopleInDepartment * 0.05);
                    default:
                        return 0;
                }
            }
        }
    }
}

類別:Manager
Manager 代表一位主管,具有三種Level:
  1. LOW_LEVEL(低階主管) = 0
    低階主管:只領固定底薪,與部門人數無關。
    例:baseSalary = 30000 → 回傳 30000。
  2. MIDDLE_LEVEL(中階主管) = 1
    中階主管:領底薪 + 25% 獎金(或津貼),不考慮人數,固定加成。
    例:baseSalary = 30000 → 回傳 37500。
  3. TOP_LEVEL(高階主管) = 2
    高階主管:固定底薪+ 25% 獎金(或津貼) + 額外依部門人數計算的補貼
    依部門人數計算的補貼:每多一個部門員工,補貼 0.05。
    例如:baseSalary = 30000
    部門 10 人 → 30000 * 1.25 + 10 * 0.05 = 37500 + 0.5 = 37500.5




封裝方法 GetMonthlySalary(int numberOfPeopleInDepartment),根據等級計算實際薪資。
  • 基礎薪資(_baseSalary):基本的每月薪水。
  • 某些等級會依據部門人數調整薪水。
  • 1.25 是 decimal 型別的常數,尾碼 M 表示這個數值是 decimal 而不是 double。



想像一下今天又再擴充另一個method叫做GetManagerTitle()的method
然後一樣會因為managerLevel有差異又要再次重複寫switch case
Manager 類別使用 switch 判斷管理階層來計算薪資與職稱

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
namespace CleanUpConditionals
{
    public class ReplaceSwitchStatementWithSubclasses
    {
        public ReplaceSwitchStatementWithSubclasses()
        {
            var lowLevelManager = new Manager(Manager.LOW_LEVEL, 30000);
            decimal salary = lowLevelManager.GetMonthlySalary(0);
        }

        class Manager
        {
            public const int LOW_LEVEL = 0;
            public const int MIDDLE_LEVEL = 1;
            public const int TOP_LEVEL = 2;

            private int _managerLevel;
            private decimal _baseSalary;

            public Manager(int level, decimal baseSalary)
            {
                _managerLevel = level;
                _baseSalary = baseSalary;
            }

            public decimal GetMonthlySalary(int numberOfPeopleInDepartment)
            {
                switch (_managerLevel)
                {
                    case LOW_LEVEL:
                        return _baseSalary;
                    case MIDDLE_LEVEL:
                        return _baseSalary * 1.25M ;
                    case TOP_LEVEL:
                        return (_baseSalary * 1.25M) + (decimal)(numberOfPeopleInDepartment * 0.05);
                    default:
                        return 0;
                }
            }

            public string GetManagerTitle()
            {
                switch (_managerLevel)
                {
                    case LOW_LEVEL:
                        return "Low Level Manager";
                    case MIDDLE_LEVEL:
                        return "Middle Level Manager";
                    case TOP_LEVEL:
                        return "Top Level Manager";
                    default:
                        return "";
                }
            }
        }
    }
}


switch case 瘋狂不斷重複的情況,這時可以去新建一個abstract ManagerLevel class
將分支判斷處理移至當中統一實作


簡化GetMonthlySalary之後的程式
處理流程思維:
  • 我們可將不同case處理情境額外抽離出sub class各自都代表某種ManagerLevel
  • 那最上層去繼承自abstract base class也就是ManagerLevel ,ManagerLevel 會去統一實作判定是隸屬哪種階層的判斷。
  • 接著各個sub class只要繼承後依序實作最上層定義要具體實作的method即可。
  • 換言之,每個case 的sub class只要注重要做的事情而判斷誰符合條件交給上一層處理,使關注點分離。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
using System;

namespace CleanUpConditionals
{
    public class ReplaceSwitchStatementWithSubclasses
    {
        public ReplaceSwitchStatementWithSubclasses()
        {
            var lowLevelManager = new Manager(ManagerLevel.LOW_LEVEL, 30000);
            decimal salary = lowLevelManager.GetMonthlySalary(0);
        }

        abstract class ManagerLevel
        {
            public const int LOW_LEVEL = 0;
            public const int MIDDLE_LEVEL = 1;
            public const int TOP_LEVEL = 2;

            public static ManagerLevel GetManagerLevel(int managerLevel)
            {
                switch (managerLevel)
                {
                    case LOW_LEVEL:
                        return new LowManagerLevel();
                    case MIDDLE_LEVEL:
                        return new MiddleManagerLevel();
                    case TOP_LEVEL:
                        return new TopManagerLevel();
                    default:
                        throw new ArgumentException("Level is not supported");
                }
            }
            public abstract decimal GetBaseMonthlySalary(decimal baseSalary,int numberOfPeopleInDepartment);
        }

        class LowManagerLevel : ManagerLevel
        {
            public override decimal GetBaseMonthlySalary(decimal baseSalary, int numberOfPeopleInDepartment)
            {
                return baseSalary;
            }
        }

        class MiddleManagerLevel : ManagerLevel
        {
            public override decimal GetBaseMonthlySalary(decimal baseSalary, int numberOfPeopleInDepartment)
            {
                return baseSalary * 1.25M;
            }
        }

        class TopManagerLevel : ManagerLevel
        {
            public override decimal GetBaseMonthlySalary(decimal baseSalary, int numberOfPeopleInDepartment)
            {
                return (baseSalary * 1.25M) + (decimal)(numberOfPeopleInDepartment * 0.05);
            }
        }

        class Manager
        {
            private int _managerLevel;
            private decimal _baseSalary;
            private ManagerLevel _managerLevelObj;

            public Manager(int level, decimal baseSalary)
            {
                _managerLevel = level;
                _managerLevelObj = ManagerLevel.GetManagerLevel(level);
                _baseSalary = baseSalary;
            }

            public decimal GetMonthlySalary(int numberOfPeopleInDepartment)
            {
                return _managerLevelObj.GetBaseMonthlySalary(_baseSalary, numberOfPeopleInDepartment);
            }

            public string GetManagerTitle()
            {
                switch (_managerLevel)
                {
                    case ManagerLevel.LOW_LEVEL:
                        return "Low Level Manager";
                    case ManagerLevel.MIDDLE_LEVEL:
                        return "Middle Level Manager";
                    case ManagerLevel.TOP_LEVEL:
                        return "Top Level Manager";
                    default:
                        return "";
                }
            }
        }
    }
}


接續處理解化GetManagerTitle()
一樣就是在ManagerLevel Base Class中定義抽象方法
再去每個sub class具體實作回傳。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
using System;

namespace CleanUpConditionals
{
    public class ReplaceSwitchStatementWithSubclasses
    {
        public ReplaceSwitchStatementWithSubclasses()
        {
            var lowLevelManager = new Manager(ManagerLevel.LOW_LEVEL, 30000);
            decimal salary = lowLevelManager.GetMonthlySalary(0);
        }

        abstract class ManagerLevel
        {
            public const int LOW_LEVEL = 0;
            public const int MIDDLE_LEVEL = 1;
            public const int TOP_LEVEL = 2;

            public static ManagerLevel GetManagerLevel(int managerLevel)
            {
                switch (managerLevel)
                {
                    case LOW_LEVEL:
                        return new LowManagerLevel();
                    case MIDDLE_LEVEL:
                        return new MiddleManagerLevel();
                    case TOP_LEVEL:
                        return new TopManagerLevel();
                    default:
                        throw new ArgumentException("Level is not supported");
                }
            }
            public abstract decimal GetBaseMonthlySalary(decimal baseSalary,int numberOfPeopleInDepartment);
            public abstract string GetBaseManagerTitle();

        }

        class LowManagerLevel : ManagerLevel
        {
            public override string GetBaseManagerTitle()
            {
                return "Low Level Manager";
            }

            public override decimal GetBaseMonthlySalary(decimal baseSalary, int numberOfPeopleInDepartment)
            {
                return baseSalary;
            }
        }

        class MiddleManagerLevel : ManagerLevel
        {
            public override string GetBaseManagerTitle()
            {
                return "Middle Level Manager";
            }
            public override decimal GetBaseMonthlySalary(decimal baseSalary, int numberOfPeopleInDepartment)
            {
                return baseSalary * 1.25M;
            }
        }

        class TopManagerLevel : ManagerLevel
        {
            public override string GetBaseManagerTitle()
            {
                return "Top Level Manager";
            }
            public override decimal GetBaseMonthlySalary(decimal baseSalary, int numberOfPeopleInDepartment)
            {
                return (baseSalary * 1.25M) + (decimal)(numberOfPeopleInDepartment * 0.05);
            }
        }

        class Manager
        {
            private int _managerLevel;
            private decimal _baseSalary;
            private ManagerLevel _managerLevelObj;

            public Manager(int level, decimal baseSalary)
            {
                _managerLevel = level;
                _managerLevelObj = ManagerLevel.GetManagerLevel(level);
                _baseSalary = baseSalary;
            }

            public decimal GetMonthlySalary(int numberOfPeopleInDepartment)
            {
                return _managerLevelObj.GetBaseMonthlySalary(_baseSalary, numberOfPeopleInDepartment);
            }

            public string GetManagerTitle()
            {
                return _managerLevelObj.GetBaseManagerTitle();
            }
        }
    }
}

每一個 ManagerLevel(經理等級)都有自己的子類別來處理自己的薪資與職稱邏輯,讓邏輯集中在對應類別中,而不是集中在 Manager 類別內。

優點(與舊版相比)
  • 符合開放封閉原則(OCP):新增階層只需加子類別,不必改動原本的 switch。
  • 邏輯分散到對應類別,讓 Manager 類別更簡潔可讀。
  • 更容易測試與擴充:每個子類別都可單獨單元測試。

留言

這個網誌中的熱門文章

何謂淨重(Net Weight)、皮重(Tare Weight)與毛重(Gross Weight)

(2021年度)駕訓學科筆試準備題庫歸納分析_法規是非題

Architecture(架構) 和 Framework(框架) 有何不同?_軟體設計前的事前規劃的藍圖概念