2018年7月5日 星期四

C# Switch 重構 Refactoring 2 : 使用資料字典與多型或委派方法或 Lambda

在上一篇文章中, 
C# Switch 重構 Refactoring 1 : 使用資料字典
,我們嘗試要將複雜的 Switch 敘述,透過資料字典的方式,重構這個 Switch 需要做到的功能,我們可以看得出來,當我們透過了 資料字典 Data Dictionary 的方式重構之後,因為透過了資料字典,可以快速的找到需要進行顏色轉換的方法 (為什麼會比較快呢?);現在,當我們在每個 case 中需要處理的工作,不是短短的一行程式碼就可以解決的,我們該如何處理這樣的問題呢?在這裡,我們將嘗試透過重構的方法,搭配使用資料字典與委派方法或者 Lambda 匿名委派方法,將可以解決此一問題。
在這裡,我會建立 RefactorSwitchDelegate 專案來進行說明 (這裡的練習專案,將會建立一個 .NET Core 主控制台應用程式專案)
首先,我們原來使用 Switch 關鍵字的程式碼如下,我們有個類別 StringToColor,裡面僅有一個方法 Transfer,在此方法內,使用 Switch 來判斷傳入的字串顏色名稱,產生出該顏色的 Color 類別物件。
C Sharp / C#
public class StringToColor
{
    public Color Transfer(string name)
    {
        Color transferResult;

        switch (name.ToLower())
        {
            case "red":
                transferResult = Color.FromArgb(0xFF, 0xFF, 0x00, 0x00);
                break;
            case "green":
                transferResult = Color.FromArgb(0xFF, 0x80, 0x80, 0x80);
                break;
            case "blue":
                transferResult = Color.FromArgb(0xFF, 0x00, 0x00, 0xFF);
                break;
            default:
                throw new ArgumentException("不正確的顏色名稱");
        }
        return transferResult;
    }
}
在這裡,我們來看看如何使用這個支援類別的將字串轉換成為 Color 類別物件。
首先,我們建立一個 StringToColor 類別的物件,緊接著呼叫 Transfer 方法,我們在這個方法傳入一個字串 (Red) 進去,當然,我們將會取得一個 Color 物件,並且把這個物件的顏色屬性顯示在螢幕上。
C Sharp / C#
class Program
{
    static void Main(string[] args)
    { 
        string MyColorName = "Blue";

        Console.WriteLine("使用 Swith 來設計方法");

        StringToColor foo = new StringToColor();
        Color fooColor = foo.Transfer(MyColorName);

        Console.WriteLine($"{MyColorName} =  A:{fooColor.A} R:{fooColor.R} G:{fooColor.G} B:{fooColor.B}");
        Console.WriteLine("Press any key for continuing...");
    }
}
這裡是執行結果
Console
使用 Swith 來設計方法
Blue =  A:255 R:0 G:0 B:255
Press any key for continuing...
現在,我們要將顏色轉換的敘述,分別產生一個類別,將這些敘述轉移到那哩,例如,想要取得紅色顏,建立一個 TransferToRed 類別執行個體,透過該執行個體中的 MakeTransfer 方法,就可以取得 紅色的 Color 物件。
不過,當我們需要依據各種不同顏色文字,自己判斷要呼叫哪個類別的執行個體,這樣的話,似乎又回到了 if ... else if ... / switch 的作法,因此,在這裡,我們將要透過繼承的方式來進行實作,所以,在這裡,我們建立了一個抽象類別 ColorTransfer,這個抽象類別中,僅有一個抽象方法 Transfer,所以,在我們的用戶端中,我們只需要面對 ColorTransfer 這個型別的變數,便可以套用多型的規則,自動產生出相對應的顏色物件。
經過這樣的重構,下次當我們有新的顏色文字要對應產生 Color 物件的時候,我們僅需要建立一個 TransferToNewColor 這樣的類別,並且要繼承抽象類別 ColorTransfer與實作該抽象類別中的抽象方法即可,如此,便又有新的顏色轉換規則可以使用了。
C Sharp / C#
namespace RefactorSwitchDictionary.Refactoring
{
    #region 將不同顏色名稱轉換成為顏色物件的過程,設計成為不同的類別
    public abstract class ColorTransfer
    {
        public abstract Color Transfer();
    }
    public class TransferToRed : ColorTransfer
    {
        public override Color Transfer()
        {
            return Color.FromArgb(0xFF, 0xFF, 0x00, 0x00);
        }
    }
    public class TransferToGreen : ColorTransfer
    {
        public override Color Transfer()
        {
            return Color.FromArgb(0xFF, 0x80, 0x80, 0x80);
        }
    }
    public class TransferToBlue : ColorTransfer
    {
        public override Color Transfer()
        {
            return Color.FromArgb(0xFF, 0x00, 0x00, 0xFF);
        }
    }
    #endregion
}
好的,讓我們回到我們最初的類別 StringToColor,我們要如何套用剛剛寫好的抽象類別與顏色轉換具體實作類別到這個 StringToColor 類別內呢?在這裡,我們先使用相同 資料字典 的模式,不過,在這裡,我們定義的資料字典為 Dictionary ,鍵值 Key 一樣是 字串,也就是要轉換的顏色文字敘述,而 值 Value 部分,我們將會變更成為各個顏色轉換的類別而產生的物件,在這裡,我們使用了 ColorTransfer 這個抽象類別,因為,所有的顏色轉換實作類別,都會繼承與實作這個抽象類別,透過多型的特色,我們可以依據當時真正物件的型別,來呼叫 Transfer 方法,進行顏色物件的產生。
C Sharp / C#
namespace RefactorSwitchDictionary.Refactoring
{
    public class StringToColor
    {
        Dictionary<string, ColorTransfer> _colorMaps;
        public StringToColor()
        {
            _colorMaps = new Dictionary<string, ColorTransfer>()
            {
                {"Red", new TransferToRed() },
                {"Green", new TransferToGreen() },
                {"Blue", new TransferToBlue() },
            };
        }
        public Color Transfer(string name)
        {
            if (!_colorMaps.ContainsKey(name))
                throw new ArgumentException("不正確的顏色名稱");
            return _colorMaps[name].Transfer();
        }
    }
    #endregion
}
我們來看看重構之後對於 StringToColor 這個類別的用法,有沒有甚麼不同;由於我們將重構完成的 StringToColor 類別設計在 RefactorSwitchDictionary.Refactoring 命名空間之內,因此,在這裡,我們引用 StringToColor 類別的時候,將會加入 RefactorSwitchDictionary.Refactoring 命名空間到這個類別前。
您將會發現到,我們重構後的類別用法,與重構前的用法沒有不同,不過,是不是程式碼變成更加精簡與容易閱讀和維護了呢?
C Sharp / C#
class Program
{
    static void Main(string[] args)
    { 
        string MyColorName = "Blue";

            Console.WriteLine("使用 資料字典與多型 來重構方法,進行 Swith 需求設計");

            RefactorSwitchDictionary.Refactoring.StringToColor fooRefactoring = new RefactorSwitchDictionary.Refactoring.StringToColor();
            Color fooRefactoringColor = fooRefactoring.Transfer(MyColorName);

            Console.WriteLine($"{MyColorName} =  A:{fooRefactoringColor.A} R:{fooRefactoringColor.R} G:{fooRefactoringColor.G} B:{fooRefactoringColor.B}");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
    }
}
執行結果內容
Console
使用 資料字典與多型 來重構方法,進行 Swith 需求設計
Blue =  A:255 R:0 G:0 B:255
Press any key for continuing...

關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程



2018年7月4日 星期三

C# Switch 重構 Refactoring 1 : 使用資料字典

在這篇文章中,我要來嘗試透過重構的方法,將原來使用 C# Switch 寫的程式碼,重構成為比較清爽與好閱讀和好維護的程式碼,首先,我們來看看原先使用 Switch 所寫的程式碼。
在這裡,我會建立 RefactorSwitchDictionary 專案來進行說明 (這裡的練習專案,將會建立一個 .NET Core 主控制台應用程式專案)
在這個範例中,我們要設計一個支援類別,在這個支援類別中僅有一個方法,這個方法將會接受一個字串的傳入,接著,根據字串的內容與意義,產生相對應的顏色 Color 類別物件。其實,任何會寫 C# 程式碼的人,應該對於這樣的需求並不陌生,我們可以寫很多 if ... else if ... else if ... else ... 的連貫敘述程式碼,或者,使用 Switch 關鍵字,搭配許多的 case 關鍵字來寫出這樣的方法,如下列程式碼。
在底下的程式碼看似十分完美,但是,若您可以支援的顏色名稱,超過了 30 個以上,那麼,這個方法將會變成十分的長,而且,說實在的,要維護起來,首先要看過這些程式碼,依序看完這些程式碼後,接著要進行增加新的顏色名稱或者修改錯誤,也不是這麼的方便。(我們在這裡,對於每個 case 關鍵字後要執行的工作,設定為僅有一行敘述,而且每個 case 關鍵字之後要執行的功能,都十分類似;但是,若在這個 switch ... case ... case .. 這樣的程式碼中,每個 case 要處理的動作皆完全不相同,對於這樣的情況,我們可以使用其他的重構技巧來進行解決,我們並不在這裡進行討論)。
C Sharp / C#
public class StringToColor
{
    public Color Transfer(string name)
    {
        Color transferResult;

        switch (name.ToLower())
        {
            case "red":
                transferResult = Color.FromArgb(0xFF, 0xFF, 0x00, 0x00);
                break;
            case "green":
                transferResult = Color.FromArgb(0xFF, 0x80, 0x80, 0x80);
                break;
            case "blue":
                transferResult = Color.FromArgb(0xFF, 0x00, 0x00, 0xFF);
                break;
            default:
                throw new ArgumentException("不正確的顏色名稱");
        }
        return transferResult;
    }
}
在這裡,我們來看看如何使用這個支援類別的將字串轉換成為 Color 類別物件。
首先,我們建立一個 StringToColor 類別的物件,緊接著呼叫 Transfer 方法,我們在這個方法傳入一個字串 (Red) 進去,當然,我們將會取得一個 Color 物件,並且把這個物件的顏色屬性顯示在螢幕上。
C Sharp / C#
class Program
{
    static void Main(string[] args)
    { 
        string MyColorName = "Blue";

        Console.WriteLine("使用 Swith 來設計方法");

        StringToColor foo = new StringToColor();
        Color fooColor = foo.Transfer(MyColorName);

        Console.WriteLine($"{MyColorName} =  A:{fooColor.A} R:{fooColor.R} G:{fooColor.G} B:{fooColor.B}");
        Console.WriteLine("Press any key for continuing...");
    }
}
這裡是執行結果
Console
使用 Swith 來設計方法
Blue =  A:255 R:0 G:0 B:255
Press any key for continuing...
剛剛的轉換字串到顏色物件的方法程式碼,似乎還滿簡單的,但試想,若隨著需求增加,顏色文字不斷的增加,您將會看到長長的 Switch ... case 敘述。現在,讓我們使用資料字典的方法,將原有 19 行的方法程式碼,瞬間重構成為只有 6 行,而且讓這個類別內的程式碼變成很容易閱讀與了解他們在做甚麼,並且容易進行擴充,最重要的是,以往需要花時間來閱讀與理解的長長 Switch 敘述程式碼,也就重構不見了。
現在,我們在 StringToColor 類別中,加入一個建構函式、加入一個資料字典的欄位 ColorMaps,ColorMaps 的 是由 字串與 Color類別所組成。接著,我們會在建構式內進行 ColorMaps 這個資料字典物件的初始化動作,請把所有顏色的名稱與相對應要產生的 Color 物件敘述式,加入到這個 ColorMaps 資料字典內。
我們要重構原有的 Transfer 方法,如下列程式碼所示,在這個方法內,我們先檢查這個傳入進來的顏色字串名稱是否存在於 ColorMaps 資料字典內,若有存在的話,我們就會回傳這個顏色物件。
C Sharp / C#
namespace RefactorSwitchDictionary.Refactoring
{
    public class StringToColor
    {
        Dictionary<string, Color> ColorMaps;
        public StringToColor()
        {
            ColorMaps = new Dictionary<string, Color>()
            {
                {"Red",  Color.FromArgb(0xFF, 0xFF, 0x00, 0x00)},
                {"Green",  Color.FromArgb(0xFF, 0x80, 0x80, 0x80) },
                {"Blue",  Color.FromArgb(0xFF, 0x00, 0x00, 0xFF)}
            };
        }
        public Color Transfer(string name)
        {
            if(!ColorMaps.ContainsKey(name))
                throw new ArgumentException("不正確的顏色名稱");
            return ColorMaps[name];
        }
    }
}
我們來看看重構之後對於 StringToColor 這個類別的用法,有沒有甚麼不同;由於我們將重構完成的 StringToColor 類別設計在 RefactorSwitchDictionary.Refactoring 命名空間之內,因此,在這裡,我們引用 StringToColor 類別的時候,將會加入 RefactorSwitchDictionary.Refactoring 命名空間到這個類別前。
您將會發現到,我們重構後的類別用法,與重構前的用法沒有不同,不過,是不是程式碼變成更加精簡與容易閱讀和維護了呢?
C Sharp / C#
class Program
{
    static void Main(string[] args)
    { 
        string MyColorName = "Blue";

        Console.WriteLine("使用 資料字典 來重構方法,進行 Swith 需求設計");

        RefactorSwitchDictionary.Refactoring.StringToColor fooRefactoring = new RefactorSwitchDictionary.Refactoring.StringToColor();
        Color fooRefactoringColor = fooRefactoring.Transfer(MyColorName);

        Console.WriteLine($"{MyColorName} =  A:{fooRefactoringColor.A} R:{fooRefactoringColor.R} G:{fooRefactoringColor.G} B:{fooRefactoringColor.B}");
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
}
執行結果內容
Console
使用 資料字典 來重構方法,進行 Swith 需求設計
Blue =  A:255 R:0 G:0 B:255
Press any key for continuing...

關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程



2018年5月21日 星期一

在 .NET Core 專案中,第一次體驗 Firebase 資料庫的使用

因為進行 Xamarin 教學課程上的需要,因此,最近特別來研究與體驗如何使用 Firebase 的資料庫功能。

了解更多關於 [.NET Core 3.0 的新功能



首先,建立起一個 .NET Core 主控制台應用專案,讓我們來體驗一下在 C# 中,使用 Firebase 來存取資料庫的應用。
  • 在這個專案中,加入 FirebaseDatabase.net NuGet 套件 FirebaseDatabase.net NuGet Package
  • 接著,我們建立一個資料模型類別,用來宣告要儲存在 Firebase 資料庫內的內容。在這裡,我們宣告一個 MyMoney 類別,用來記錄每筆消費紀錄項目。
public class MyMoney
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public string InvoiceNo { get; set; }
    public int Cost { get; set; }
}
  • 現在,讓我們開始來使用 Firebase,在這個測試範例程式碼中,我們將會做到:將整個資料表刪除、查詢現在所有的紀錄、刪除指定的紀錄、找出特定的紀錄、修改某筆紀錄、新增10筆紀錄等功能。

完整的測試程式碼

class Program
{
    static async Task Main(string[] args)
    {
        var client = new Firebase.Database.FirebaseClient("https://xamarindb-3408d.firebaseio.com");
        var child = client.Child("MyMoneys");

        Console.WriteLine("刪除掉所有的資料");
        await child.DeleteAsync();

        Console.WriteLine("產生 10 筆購物紀錄");
        for (int i = 1; i < 10; i++)
        {
            await child.PostAsync<MyMoney>(new MyMoney()
            {
                Id = Guid.NewGuid(),
                Title = $"冷泡茶 {i} 瓶",
                InvoiceNo = $"0000 {i}",
                Cost = 20 * i,
            });
        }

        Console.WriteLine("列出 Firebase 中所有的紀錄");
        var fooPosts = await child.OnceAsync<MyMoney>();
        foreach (var item in fooPosts)
        {
            Console.WriteLine($"購買商品:{item.Object.Title} 價格:{item.Object.Cost}");
        }

        Console.WriteLine("查詢購物價格小於 90 的紀錄");
        var fooRec = fooPosts.Where(x => x.Object.Cost <= 90);
        foreach (var item in fooRec)
        {
            Console.WriteLine($"購買商品:{item.Object.Title} 價格:{item.Object.Cost}");
        }

        Console.WriteLine("刪除購物價格小於 90 的紀錄");
        var fooRecDeleted = fooPosts.Where(x => x.Object.Cost <= 90);
        foreach (var item in fooRecDeleted)
        {
            await child.Child(item.Key).DeleteAsync();
            Console.WriteLine($"購買商品:{item.Object.Title} 價格:{item.Object.Cost} 已經被刪除");
        }


        Console.WriteLine("列出 Firebase 中所有的紀錄");
        fooPosts = await child.OnceAsync<MyMoney>();
        foreach (var item in fooPosts)
        {
            Console.WriteLine($"購買商品:{item.Object.Title} 價格:{item.Object.Cost}");
        }

        Console.WriteLine("查詢購物價格等於 140 的紀錄");
        var foo140Rec = fooPosts.FirstOrDefault(x => x.Object.Cost == 140);
        foo140Rec.Object.Cost = 666;
        await child.Child(foo140Rec.Key).PutAsync(foo140Rec.Object);
        Console.WriteLine($"購買商品:{foo140Rec.Object.Title} 的價格已經修正為 價格:{foo140Rec.Object.Cost}");

        Console.WriteLine("列出 Firebase 中所有的紀錄");
        fooPosts = await child.OnceAsync<MyMoney>();
        foreach (var item in fooPosts)
        {
            Console.WriteLine($"購買商品:{item.Object.Title} 價格:{item.Object.Cost}");
        }

        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();

    }
}

執行結果


執行這個測試程式,其輸出結果如下所示:
刪除掉所有的資料
產生 10 筆購物紀錄
列出 Firebase 中所有的紀錄
購買商品:冷泡茶 1 瓶 價格:20
購買商品:冷泡茶 2 瓶 價格:40
購買商品:冷泡茶 3 瓶 價格:60
購買商品:冷泡茶 4 瓶 價格:80
購買商品:冷泡茶 5 瓶 價格:100
購買商品:冷泡茶 6 瓶 價格:120
購買商品:冷泡茶 7 瓶 價格:140
購買商品:冷泡茶 8 瓶 價格:160
購買商品:冷泡茶 9 瓶 價格:180
查詢購物價格小於 90 的紀錄
購買商品:冷泡茶 1 瓶 價格:20
購買商品:冷泡茶 2 瓶 價格:40
購買商品:冷泡茶 3 瓶 價格:60
購買商品:冷泡茶 4 瓶 價格:80
刪除購物價格小於 90 的紀錄
購買商品:冷泡茶 1 瓶 價格:20 已經被刪除
購買商品:冷泡茶 2 瓶 價格:40 已經被刪除
購買商品:冷泡茶 3 瓶 價格:60 已經被刪除
購買商品:冷泡茶 4 瓶 價格:80 已經被刪除
列出 Firebase 中所有的紀錄
購買商品:冷泡茶 5 瓶 價格:100
購買商品:冷泡茶 6 瓶 價格:120
購買商品:冷泡茶 7 瓶 價格:140
購買商品:冷泡茶 8 瓶 價格:160
購買商品:冷泡茶 9 瓶 價格:180
查詢購物價格等於 140 的紀錄
購買商品:冷泡茶 7 瓶 的價格已經修正為 價格:666
列出 Firebase 中所有的紀錄
購買商品:冷泡茶 5 瓶 價格:100
購買商品:冷泡茶 6 瓶 價格:120
購買商品:冷泡茶 7 瓶 價格:666
購買商品:冷泡茶 8 瓶 價格:160
購買商品:冷泡茶 9 瓶 價格:180
Press any key for continuing...

了解更多關於 [.NET Core 3.0 的新功能