Design Pattern_Skill_6_建造者模式(Builder Design Pattern)

最近在和同事合作過程,就時常會跟SD 合作。
SD主要工作會負責設計一些DB 的Schema  , 撰寫系統預期流程和規格書、畫面操作互動

SD通常就會向我們這些PG規劃和分配任務、負責監工等等

SD通常會跟院內User 一起討論瞭解需求後
在經過自己的統整、文件撰寫來和PG溝通User需要的是捨麼請幫忙開發

PG可能在開發階段只負責程式撰寫工作而不會cover到系統設計規劃

這之間的關係就好比
找多個工人施工蓋房子和一個監工分配工作任務的工頭一樣







這裡以生產產品為例
一個產品從製造、加工都會有不同製程步驟
如下是要取得待在生產線上工作的特定認證(必須精熟和落實會這些工法)

1
2
3
4
5
6
7
8
public interface IBuilder
{
    void BuildPartA();

    void BuildPartB();

    void BuildPartC();
}

一個產品中會區分不同的組件在製造生產(需透過不同組件組合而成)
不同產品會有需要對應生產不同的組件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Product
{
    private List<object> lsParts = new List<object>();

    public void Add(string part)
    {
        this.lsParts.Add(part);
    }

    public string ListParts()
    {
        return "Product Parts:" + string.Join(",", lsParts.ToArray()) + "\n";
    }
}

負責在產線中製造、加工的作業工程人員
會實踐具體的特定產品規格生產工法流程

 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
public class ConcreteBuilder : IBuilder
{
    private Product _product = new Product();

    public ConcreteBuilder()
    {
        this.Reset();
    }

    public void Reset()
    {
        this._product = new Product();
    }

    public void BuildPartA()
    {
        this._product.Add("PartA");
    }

    public void BuildPartB()
    {
        this._product.Add("PartB");
    }

    public void BuildPartC()
    {
        this._product.Add("PartC");
    }

    public Product GetProduct()
    {
        Product result = this._product;
        this.Reset();
        return result;
    }

}

一個產線上可能都會有一些負責特定產品的組長
會知曉特定產品的流程
再去負責監工指派給底下作業人員

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class Director
{
    private IBuilder _builder;

    public IBuilder Builder { get => _builder; set => _builder = value; }

    //minimal valuable product
    public void buildMinimalValuableProduct()
    {
        this._builder.BuildPartA();
    }

    public void builderFeatureProduct()
    {
        this._builder.BuildPartA();
        this._builder.BuildPartB();
        this._builder.BuildPartC();
    }
}



完整的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
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern
{
    public interface IBuilder
    {
        void BuildPartA();

        void BuildPartB();

        void BuildPartC();
    }

    public class Product
    {
        private List<object> lsParts = new List<object>();

        public void Add(string part)
        {
            this.lsParts.Add(part);
        }

        public string ListParts()
        {
            return "Product Parts:" + string.Join(",", lsParts.ToArray()) + "\n";
        }
    }

    public class ConcreteBuilder : IBuilder
    {
        private Product _product = new Product();

        public ConcreteBuilder()
        {
            this.Reset();
        }

        public void Reset()
        {
            this._product = new Product();
        }

        public void BuildPartA()
        {
            this._product.Add("PartA");
        }

        public void BuildPartB()
        {
            this._product.Add("PartB");
        }

        public void BuildPartC()
        {
            this._product.Add("PartC");
        }

        public Product GetProduct()
        {
            Product result = this._product;
            this.Reset();
            return result;
        }

    }

    public class Director
    {
        private IBuilder _builder;

        public IBuilder Builder { get => _builder; set => _builder = value; }

        //minimal valuable product
        public void buildMinimalValuableProduct()
        {
            this._builder.BuildPartA();
        }

        public void builderFeatureProduct()
        {
            this._builder.BuildPartA();
            this._builder.BuildPartB();
            this._builder.BuildPartC();
        }
    }
}


呼叫端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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var builder = new ConcreteBuilder();
            var director = new Director();
            director.Builder = builder;

            Console.WriteLine("Standard basic product:");
            director.buildMinimalValuableProduct();
            Console.WriteLine(builder.GetProduct().ListParts());

            Console.WriteLine("Standard full featured product:");
            director.builderFeatureProduct();
            Console.WriteLine(builder.GetProduct().ListParts());

            // Remember, the Builder pattern can be used without a Director class
            Console.WriteLine("Custom product:");
            builder.BuildPartA();
            builder.BuildPartC();
            Console.WriteLine(builder.GetProduct().ListParts());
            Console.ReadKey();
        }
    }
}


演示結果
我們可以直接指定建構標準最小可行性產品
也可以建構比較多工序的較複雜特色產品
更可以去動態調整不同流程來創建包含不同工序的產品



這裡演示的是所謂「由 Client、Director、Builder 和 Product 形成的」建造者模式

Client就是所謂程式實際呼叫端(就是主程式中間接呼叫的地方)
Builder 是所謂的公產生產可上線負責產品的認證
實作了該介面(認證)代表是具有資格的作業員也就是ConcreteBuilder
Product 就是我們的產品(通常是類似加裝許多部份的集中產物)
Director則是一個監工者(有點類似導演、主管的角色...)




這裡再用一個寫程式工作情境來舉例
Builder模式第一種.
「由 Client、Director、Builder 和 Product 形成的」


一個需求資訊是由多種不同規格、時程要求等組建而成

 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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern
{
    public class WorkInfo
    {
        private List<string> lsWorkItem = new List<string>();

        public void AddWorkInfo(string workItem)
        {
            lsWorkItem.Add(workItem);
        }

        public IEnumerable<string> GetDetail()
        {
            foreach(var item in lsWorkItem)
            {
                yield return item;
            }
        }
    }
}



在日常需求程式開發過程難免會需要做到
有明確任務需求名稱、時數、備註等等
來對每一個工程師進行需求開發控管

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern
{
    public interface IDevelopmentBuilder
    {
        void Reset();
        void SetTaskName(string _task_title);
        void SetWorkHour(double _work_hour);
        void SetRemark();
        WorkInfo GetTaskDetail();
    }
}


SD或者PM是負責監工指派的角色

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern
{
    //SD
    public class WorkInfoDirector
    {

        public WorkInfo CreateWorkInfo(IDevelopmentBuilder developmentBuilder, string task_name,double workHour)
        {
            developmentBuilder.SetTaskName(task_name);
            developmentBuilder.SetWorkHour(workHour);
            developmentBuilder.SetRemark();
            return developmentBuilder.GetTaskDetail();
        }
    }
}



而對應去落實需求資訊紀錄和管理的則是一些PG
這裡我們假設ProgrammerA 負責的都是固定備註
然而可能後續備註會因為不同需求功能、平台、時程而有不同變化
這裡只是暫時做一個簡單演示

 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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern
{   
    public class ProgrammerA : IDevelopmentBuilder
    {
        private WorkInfo _work_info;
        private string _remark = "要使用C# 跟 MS SQL";

        public ProgrammerA()
        {
            Reset();
        }

        public void Reset()
        {
            _work_info = new WorkInfo();
        }

        public void SetTaskName(string _task_title)
        {
            _work_info.AddWorkInfo(_task_title);
        }

        public void SetWorkHour(double _work_hour)
        {
            string workTime = string.Format("工時:{0}小時", _work_hour);
            _work_info.AddWorkInfo(workTime);
        }

        public void SetRemark()
        {
            string remark = string.Format("備註:{0}",this._remark);
            _work_info.AddWorkInfo(remark);
        }

        public WorkInfo GetTaskDetail()
        {
            return _work_info;
        }
    }
}


呼叫端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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern
{
    class Program
    {
        static void PrintWorkInfoDetail(IEnumerable<object> workDetail)
        {
            foreach(var item in workDetail)
            {
                Console.WriteLine(item);
            }
            Console.WriteLine("= = = = = = = =");
        }

        static void Main(string[] args)
        {
            var workInfoDirector = new WorkInfoDirector();
            Console.WriteLine("ProgrammerA 工作任務列表");
            var workInfo1 = workInfoDirector.CreateWorkInfo(new ProgrammerA(), "訂單管理報表微調", 2.5);
            var workDetail_1 = workInfo1.GetDetail();
            PrintWorkInfoDetail(workDetail_1);

            var workInfo2 = workInfoDirector.CreateWorkInfo(new ProgrammerA(), "文管系統簽核流程更改", 4);
            var workDetail_2 = workInfo2.GetDetail();
            PrintWorkInfoDetail(workDetail_2);
            Console.ReadKey();
        }
    }
}

在此就能打印出
因應不同需求和相應備註、時程產生的需求開發資訊
(各個實體就這樣被創造出來)



Builder模式第二種.藉由靜態內部類方式實踐
(少監工、派工角色)

將IDevelopmentBuilder裡面定義要實踐的method
改為統一回傳IDevelopmentBuilder本身的函數
具體去落實的ProgrammerA則會在設置完相應返回IDevelopmentBuilder的介面
使後期能夠產生鏈結風格( someBuilder->setValueA(1)->setValueB(2)->create() )
於最後定義返回WorkInfo的單一集中物的型別


WorkInfo則和上述例子不同在於裡面By特定欄位串值一一去實作
而最終一樣加到大的List中給予函數返回

和第一種Builder模式最大差異在於沒有Director的角色
而是可由具體的ProgrammerA , ProgrammerB ,....去自行控制增減調整一些屬性和工作流程

第二種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
 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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern2
{
    public interface IDevelopmentBuilder
    {
        IDevelopmentBuilder SetTaskName(string _task_title);
        IDevelopmentBuilder SetWorkHour(double _work_hour);
        IDevelopmentBuilder SetRemark(string _remark);
        WorkInfo Build();
    }


    public class WorkInfo
    {
        private string _task_tile;
        private string _work_hour;
        private string _remark;
        private List<string> lsWorkItem = new List<string>();        

        public IEnumerable<string> GetDetail()
        {
            foreach (var item in lsWorkItem)
            {
                yield return item;
            }
        }

        public void SetTaskName(string _task_title)
        {
            this._task_tile = _task_title;
            lsWorkItem.Add(_task_title);
        }

        public void SetWorkHour(double _work_hour)
        {
            this._work_hour = _work_hour.ToString();
            lsWorkItem.Add(string.Format("工時:{0}小時", _work_hour));
        }

        public void SetRemark(string _remark)
        {
            this._remark = _remark;
            lsWorkItem.Add(string.Format("備註:{0}", this._remark));
        }

    }

    public class ProgrammerA : IDevelopmentBuilder
    {
        private WorkInfo workInfo;
        public ProgrammerA()
        {
            workInfo = new WorkInfo();
        }
       
        public IDevelopmentBuilder SetTaskName(string _task_title)
        {
            workInfo.SetTaskName(_task_title);
            return this;
        }

        public IDevelopmentBuilder SetWorkHour(double _work_hour)
        {
            workInfo.SetWorkHour(_work_hour);
            return this;
        }

        public IDevelopmentBuilder SetRemark(string _remark)
        {
            workInfo.SetRemark(_remark);
            return this;
        }

        public WorkInfo Build()
        {
            return workInfo;
        }
    }


    class Program
    {
        static void PrintWorkInfoDetail(IEnumerable<object> workDetail)
        {
            foreach (var item in workDetail)
            {
                Console.WriteLine(item);
            }
            Console.WriteLine("= = = = = = = =");
        }

        static void Main(string[] args)
        {
            Console.WriteLine("ProgrammerA 工作任務列表");
            WorkInfo workInfo = new ProgrammerA()
                                .SetTaskName("文管系統權限調整")
                                .SetWorkHour(1.5)
                                .SetRemark("需要在11/20之前完成").Build();
            PrintWorkInfoDetail(workInfo.GetDetail());

            Console.WriteLine("ProgrammerA 工作任務列表");
            WorkInfo workInfo_2 = new ProgrammerA()
                                .SetTaskName("業管系統邏輯調整")
                                .SetWorkHour(3.5)
                                .SetRemark("需要在12/10之前完成").Build();
            PrintWorkInfoDetail(workInfo_2.GetDetail());

            WorkInfo workInfo_3 = new ProgrammerA()
                                  .SetTaskName("商標管理系統查詢報錯問題修正").Build();
            PrintWorkInfoDetail(workInfo_3.GetDetail());
            Console.ReadKey();
        }
    }
}






Ref:

Builder in C#
https://refactoring.guru/design-patterns/builder/csharp/example

Builder Pattern In C#
https://www.c-sharpcorner.com/article/builder-pattern-in-c-sharp/

Hierarchically Implementing the Bloch's Builder Pattern in C#
https://www.codeproject.com/Articles/240756/Hierarchically-Implementing-the-Bolchs-Builder-Pat




留言

這個網誌中的熱門文章

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

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

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