C#/VB.NET 物件導向技巧研究(一)_Chain Constructors_optional parameters

C# /VB.NET  建構子設計中常遇到的冗長設計問題

如下我們建立一個摩托車的類別來詳述一些
還能再改善的程式碼片段徵兆

Code

 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
class Motorcycle
    {
        public int driverIntensity;
        public string driverName;//driver name
        public int driverYear;// driver year

        public void SetDriverName(string name)
        {
            this.driverName = name;
        }

        public void SetDriverYear(int year)
        {
            driverYear = year;
        }

        public Motorcycle(){}

        //冗長的建構子函數.........................
        //確保駕駛安全並限定強度等級不超出10
        public Motorcycle(int intensity)
        {
            if (intensity > 10)
            {
                intensity = 10;
            }
            driverIntensity = intensity;
        }

        public Motorcycle(int intensity , string name)
        {
            if (intensity > 10)
            {
                intensity = 10;
            }
            driverIntensity = intensity;
            this.driverName = name;
        }
        //冗長的建構子函數.........................
        //在兩個overloading建構子中存在著重複多餘的程式碼
        //而且若今天門檻再更換成其他數值則又要額外重複寫。
        
        public void PopAWheely()
        {
            Console.WriteLine("Yeeeeeeeeeee ");
        }
    }


我們可以觀察到  建構子的overloading 函數區塊
一直有重複程式碼出現

就是為了確保機車強度有一定限制而制定的門檻值10該部分判斷流程
但缺乏更改的彈性(比方門檻一變就要改兩函數區塊)
這裡我們進行函數封裝會更加明顯





Code


 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
class Motorcycle2
    {
        public int driverIntensity;
        public string driverName;//driver name
        public int driverYear;// driver year

        public void SetDriverName(string name)
        {
            driverName = name;
        }

        public void SetDriverYear(int year)
        {
            //this.driverYear = year;
            driverYear = year;
        }

        public Motorcycle2() { }

        public Motorcycle2(int intensity)
        {
            SetIntensity(intensity);
        }

        public Motorcycle2(int intensity, string name)
        {
            SetIntensity(intensity);
            driverName = name;
        }

        public void SetIntensity(int intensity)
        {
            if (intensity > 10)
            {
                intensity = 10;
            }
            driverIntensity = intensity;
        }
        public void PopAWheely()
        {
            Console.WriteLine("Yeeeeeeeeeee ");
        }
    }

雖然有比剛剛好一丁點,確實有改善當門檻一變時的多處修改問題。
但若今天又有變多參數時就有一些維護上的困難。

此時就牽涉到「Chain Constructors」(於JAVA也有類似設計技巧的概念)
這個技巧

我們讓一個接受最多參數個數的建構子函數定為「主建構子函數」
並且只在其中實踐必要的業務邏輯流程。

其餘建構子則可透過this關鍵字將對應參數傳遞給主建構子函數中

如此一來,整個類只會有一個建構子需要我們去操心,提升可維護性。





最終優化 Code (此寫法於 .NET 各版本較為廣泛通用)
C#版本

 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
class Motorcycle3
    {
        public int driverIntensity;
        public string driverName;//driver name
        public int driverYear;// driver year

        public void SetDriverName(string name)
        {
            driverName = name;
        }

        public void SetDriverYear(int year)
        {
            driverYear = year;
        }

        public Motorcycle3() { }

        public Motorcycle3(int intensity) : this(intensity , "") { }

        public Motorcycle3(string name) : this(0, name) { }

        //主建構子函數
        public Motorcycle3(int intensity, string name)
        {
            if (intensity > 10)
            {
                intensity = 10;
            }
            driverIntensity = intensity;
            driverName = name;
        }
        public void PopAWheely()
        {
            Console.WriteLine("Yeeeeeeeeeee ");
        }
    }

VB.NET版本


 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
Class Motorcycle3
    Public driverIntensity As Integer
    Public driverName As String
    Public driverYear As Integer

    Public Sub SetDriverName(ByVal name As String)
        driverName = name
    End Sub

    Public Sub SetDriverYear(ByVal year As Integer)
        driverYear = year
    End Sub

    Public Sub New()
    End Sub

    Public Sub New(ByVal intensity As Integer)
        Me.New(intensity, "")
    End Sub

    Public Sub New(ByVal name As String)
        Me.New(0, name)
    End Sub

    Public Sub New(ByVal intensity As Integer, ByVal name As String)
        If intensity > 10 Then
            intensity = 10
        End If

        driverIntensity = intensity
        driverName = name
    End Sub

    Public Sub PopAWheely()
        Console.WriteLine("Yeeeeeeeeeee ")
    End Sub
End 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
class Motorcycle4
    {
        public int driverIntensity;
        public string driverName;//driver name
        public int driverYear;// driver year

        public void SetDriverName(string name)
        {
            driverName = name;
        }

        public void SetDriverYear(int year)
        {
            //this.driverYear = year;
            driverYear = year;
        }

        //The chain of constructor
        public Motorcycle4()
        {
            Console.WriteLine("In default ctor");
        }

        public Motorcycle4(int intensity) : this(intensity, "")
        {
            Console.WriteLine("In ctor with int parameter");
        }

        public Motorcycle4(string name) : this(0, name)
        {
            Console.WriteLine("In ctor with string parameter");
        }

        public Motorcycle4(int intensity, string name)
        {
            Console.WriteLine("In Master ctor");
            if (intensity > 10)
            {
                intensity = 10;
            }
            driverIntensity = intensity;
            driverName = name;
        }

        public void PopAWheely()
        {
            for (int i = 0; i <= driverIntensity; i++)
            {
                Console.WriteLine("Yeeeeeee Haaaaaeewww!");
            }
        }
    }




1.物件實體化(呼叫方)後去呼叫只有單一參數形式的建構子函數
2.該建構子函數把傳入的參數內容透過this先傳遞給主建構子函數
並提供呼叫方並未提供參數部分之初始化內容
3.主建構子函數進行處理與賦值
4.返回至最初調用建構子函數區塊執行接下去程式碼


於.NET4.0以上其實還有提供更加簡潔的建構子多載程式寫法
即「Optional Parameters」


Code

C#版(需.NET4.0以上)


 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
class Motorcycle5
    {
        public int driverIntensity;
        public string driverName;//driver name
        public int driverYear;// driver year

        //可選參數之單一建構子函數(.NET4.0以上)
        //Single constructor using optional args
        public Motorcycle5(int intensity = 0 , string name = "")
        {
            if (intensity > 10)
            {
                intensity = 10;
            }
            driverIntensity = intensity;
            driverName = name;
        }

        public void SetDriverName(string name)
        {
            driverName = name;
        }


    }


VB.NET 版本



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Class Motorcycle5
    Public driverIntensity As Integer
    Public driverName As String
    Public driverYear As Integer

    Public Sub New(ByVal Optional intensity As Integer = 0, ByVal Optional name As String = "")
        If intensity > 10 Then
            intensity = 10
        End If

        driverIntensity = intensity
        driverName = name
    End Sub

    Public Sub SetDriverName(ByVal name As String)
        driverName = name
    End Sub
End Class









外面調用建議用標前綴的寫法(因為建構子參數順序是有差異的!!!會報錯)
還有請記得順序不要錯!!!(怎麼定義就依照一樣型態下去指定)





 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Motorcycle5 mc5_1 = new Motorcycle5();
            Console.WriteLine("Name= {0} , Intensity={1}", mc5_1.driverName, mc5_1.driverIntensity);

            //單一參數寫法1.指定前綴
            Motorcycle5 mc5_2_1 = new Motorcycle5(name: "Jenny");
            Console.WriteLine("Name= {0} , Intensity={1}", mc5_2_1.driverName, mc5_2_1.driverIntensity);

            Motorcycle5 mc5_3_1 = new Motorcycle5(intensity: 7);
            Console.WriteLine("Name= {0} , Intensity={1}", mc5_3_1.driverName, mc5_3_1.driverIntensity);



            //單一參數寫法2.不指定前綴(依資料型態來區別) -->不建議!!!(順序有差!!!!)
            //Motorcycle5 mc5_2_2 = new Motorcycle5("Jenny");
            //Console.WriteLine("Name= {0} , Intensity={1}", mc5_2_2.driverName, mc5_2_2.driverIntensity);

            Motorcycle5 mc5_3_2 = new Motorcycle5(7);
            Console.WriteLine("Name= {0} , Intensity={1}", mc5_3_2.driverName, mc5_3_2.driverIntensity);



            Motorcycle5 mc5_4 = new Motorcycle5(intensity: 8, name: "Mike");
            Console.WriteLine("Name= {0} , Intensity={1}", mc5_4.driverName, mc5_4.driverIntensity);





參考link:
https://www.industriallogic.com/xp/refactoring/chainConstructors.html


留言

這個網誌中的熱門文章

經得起原始碼資安弱點掃描的程式設計習慣培養(五)_Missing HSTS Header

經得起原始碼資安弱點掃描的程式設計習慣培養(三)_7.Cross Site Scripting(XSS)_Stored XSS_Reflected XSS All Clients

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