iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 13
1

剛學寫程式時,每個人都會學到建構子,知道可以透過類別中的建構子來初始化類別的狀態。當一個類別需要依照不同情境來初始化類別時,就會需要多載許多不同的建構子,每個建構子分別帶入不同參數來初始化類別。

假設有一個我們用一個Point類別來表示二維空間中的某一個點

public class Point
{
    public double X { get; }
    public double Y { get; }
    public Point(string coordinate)
    {
        var axis = coordinate.Split(',');
        X = double.Parse(axis[0]);
        Y = double.Parse(axis[1]);
    }
}

和一個計算兩點距離的類別

public double Calculate2dDistance(string coordinate1, string coordinate2)
{
    var point1 = new Point(coordinate1);
    var point2 = new Point(coordinate2);
    return Math.Sqrt(Math.Pow(Math.Abs(point1.X - point2.X), 2) +
                     Math.Pow(Math.Abs(point1.Y - point2.Y), 2));
}

一切看似都非常合理,直到需求發生了變化。

有一天世界忽然從二維空間變成三維空間了,我們將傳入三維坐標,計算三維空間的距離。

public double Calculate3dDistance(string coordinate1, string coordinate2)
{
    ...
}

我們想透過多載Point建構子讓他可以支援三維空間,卻發現新的建構子參數與二維座標的建構子長的一樣,造成編譯結果有錯誤。

public class Point
{
    public double X { get; }
    public double Y { get; }
    public Point(string coordinate)
    {
        var axis = coordinate.Split(',');
        X = double.Parse(axis[0]);
        Y = double.Parse(axis[1]);
    }

    public Point(string coordinate)
    {
        //出現了問題
    } 
}

假設我們想辦法透過修改原本的建構子,讓他透過一個flag判斷是否輸入為三圍座標

public class Point
{
    public double X { get; }
    public double Y { get; }
    public double Z { get; }
    public Point(string coordinate, bool is3d = false)
    {
        var axis = coordinate.Split(',');
        X = double.Parse(axis[0]);
        Y = double.Parse(axis[1]);
        if (is3d)
        {
            Z = double.Parse(axis[2]);
        }
    }
}

然後新增計算三維座標距離的方法

public double Calculate3dDistance(string coordinate1, string coordinate2)
{
    var point1 = new Point(coordinate1, true);
    var point2 = new Point(coordinate2, true);
    return Math.Sqrt(Math.Pow(Math.Abs(point1.X - point2.X), 2) +
                     Math.Pow(Math.Abs(point1.Y - point2.Y), 2) +
                     Math.Pow(Math.Abs(point1.Z - point2.Z), 2));
}

可以發現雖然Point可以支援三維座標了,但是建構子參數也變得複雜了,如果不了解Point建構子實作去看,一眼看下去會不知道座標參數後面的flag表示什麼意思,造成閱讀代碼上的不便。

由此可以發現,根據不同使用情境,運用多載建構子初始化類別有時會造成一些問題

  1. 當不同情境但是建構子參數型別卻一樣,會造成編譯有錯誤
  2. 如果建構子的參數比較複雜,或者是有很多不同多載建構子時,不容易知道每個建構子分別代表什麼使用情境,增加閱讀代碼的困難。

為了解決上面的問題,可以在製造一些Point工廠方法,就可以省去不必要的flag參數。

public class Point
{
    public double X { get; private set; }
    public double Y { get; private set; }
    public double Z { get; private set; }

    public static Point Create2d(string coordinate)
    {
        var axis = coordinate.Split(',');
        return new Point()
        {
            X = double.Parse(axis[0]),
            Y = double.Parse(axis[1]),
        };
    }
    
    public static Point Create3d(string coordinate)
    {
        var axis = coordinate.Split(',');
        return new Point()
        {
            X = double.Parse(axis[0]),
            Y = double.Parse(axis[1]),
            Z = double.Parse(axis[2]),
        };
    }
}

在使用端也能從方法名稱了解其使用情境。

public double Calculate2dDistance(string coordinate1, string coordinate2)
{
    var point1 = Point.Create2d(coordinate1);
    var point2 = Point.Create2d(coordinate2);
    return Math.Sqrt(Math.Pow(Math.Abs(point1.X - point2.X), 2) +
                     Math.Pow(Math.Abs(point1.Y - point2.Y), 2));
}

public double Calculate3dDistance(string coordinate1, string coordinate2)
{
    var point1 = Point.Create3d(coordinate1);
    var point2 = Point.Create3d(coordinate2);
    return Math.Sqrt(Math.Pow(Math.Abs(point1.X - point2.X), 2) +
                     Math.Pow(Math.Abs(point1.Y - point2.Y), 2) +
                     Math.Pow(Math.Abs(point1.Z - point2.Z), 2));
}

透過上面的例子可以知道,工廠方法的好處

  1. 可以使用相同參數型別
  2. 可以跟使用情境給予符合使用情境的方法名稱,增加代碼的可讀性。

上一篇
Day 12 這個方法掛羊頭賣狗肉
下一篇
Day 14 一些Paging LINQ的使用情境
系列文
在Kata中尋找Clean Code是否搞錯了什麼30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言