範例:世紀帝國(簡單工廠模式)

Pattern: 簡單工廠模式

Class Diagram: 世紀帝國


情境:根據用戶端對兵種的需求,我們要生產對應的軍事單位

  • 定義兵種類型
1
2
3
4
5
6
7
8
9
namespace DotNetCoreKata.Enums;

public enum UnitCategory
{
Unknown = 0,
Military = 1,
Archer = 2,
Knight = 3,
}

兵種有民兵弓兵騎士三種類型。


  • 定義軍事單位
1
2
3
4
5
6
7
namespace DotNetCoreKata.DomainModels.AgeOfEmpires;

public interface IUnit
{
string Attack();
string Move();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using DotNetCoreKata.DomainModels.AgeOfEmpires.Transportation;
using DotNetCoreKata.DomainModels.AgeOfEmpires.Weapon;

namespace DotNetCoreKata.DomainModels.AgeOfEmpires;

public class Unit(IWeapon weapon, ITransportation transportation) : IUnit
{
public string Attack()
{
return $"attacks with {weapon.Name()}";
}

public string Move()
{
return $"moves using {transportation.Mode()}";
}
}

軍事單位會使用武器進攻,用對應的移動方式移動。


  • 定義武器
1
2
3
4
5
6
namespace DotNetCoreKata.DomainModels.AgeOfEmpires.Weapon;

public interface IWeapon
{
string Name();
}
1
2
3
4
5
6
7
8
9
namespace DotNetCoreKata.DomainModels.AgeOfEmpires.Weapon;

public class Stick : IWeapon
{
public string Name()
{
return "stick";
}
}
1
2
3
4
5
6
7
8
9
namespace DotNetCoreKata.DomainModels.AgeOfEmpires.Weapon;

public class Bow : IWeapon
{
public string Name()
{
return "bow";
}
}
1
2
3
4
5
6
7
8
9
namespace DotNetCoreKata.DomainModels.AgeOfEmpires.Weapon;

public class Sword : IWeapon
{
public string Name()
{
return "sword";
}
}

武器有木棒弓箭長劍


  • 定義移動方式
1
2
3
4
5
6
namespace DotNetCoreKata.DomainModels.AgeOfEmpires.Transportation;

public interface ITransportation
{
string Mode();
}
1
2
3
4
5
6
7
8
9
namespace DotNetCoreKata.DomainModels.AgeOfEmpires.Transportation;

public class Legs : ITransportation
{
public string Mode()
{
return "legs";
}
}
1
2
3
4
5
6
7
8
9
namespace DotNetCoreKata.DomainModels.AgeOfEmpires.Transportation;

public class Horse : ITransportation
{
public string Mode()
{
return "horse";
}
}

  • 客戶端
1
2
3
4
5
6
7
8
9
using DotNetCoreKata.DomainModels.AgeOfEmpires;
using DotNetCoreKata.Enums;

namespace DotNetCoreKata.Services.AgeOfEmpires;

public interface IClient
{
IUnit Train(UnitCategory unitCategory);
}
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
using DotNetCoreKata.DomainModels.AgeOfEmpires;
using DotNetCoreKata.DomainModels.AgeOfEmpires.Transportation;
using DotNetCoreKata.DomainModels.AgeOfEmpires.Weapon;
using DotNetCoreKata.Enums;

namespace DotNetCoreKata.Services.AgeOfEmpires;

public class Client : IClient
{
public IUnit Train(UnitCategory unitCategory)
{
Console.Write($"Ask resources to build unit: {unitCategory}.");

if (unitCategory == UnitCategory.Military)
{
return new Unit(new Stick(), new Legs());
}
else if (unitCategory == UnitCategory.Archer)
{
return new Unit(new Bow(), new Legs());
}
else
{
return new Unit(new Sword(), new Horse());
}
}
}

使用簡單工廠模式改造

  • 新增簡單工廠,將創建軍事單位的邏輯從客戶端抽離
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using DotNetCoreKata.Enums;

namespace DotNetCoreKata.DomainModels.AgeOfEmpires;

public static class TrainingCenter
{
public static IUnit Create(UnitCategory unitCategory)
{
return unitCategory switch
{
UnitCategory.Military => new Unit(new Stick(), new Legs()),
UnitCategory.Archer => new Unit(new Bow(), new Legs()),
UnitCategory.Knight => new Unit(new Sword(), new Horse()),
_ => new Unit(new Stick(), new Legs())
};
}
}

  • 修改原本客戶端的程式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using DotNetCoreKata.DomainModels.AgeOfEmpires;
using DotNetCoreKata.Enums;

namespace DotNetCoreKata.Services.AgeOfEmpires;

public class Client
{
public IUnit Train(UnitCategory unitCategory)
{
Console.Write($"Ask resources to build unit: {unitCategory}.");

return TrainingCenter.Create(unitCategory);
}
}

[單一職責原則]
我們將 創建類別 (Creator)產品類別 (Product) 視作兩種不同的職責。

[開放封閉原則]

  1. 新增新的產品時,我們僅需新增產品類別修改創建類別
  2. 修改既有產品時,我們僅需修改其產品類別創建類別
    不會影響到其他的產品類別。
  3. 無論是新增/修改產品,我們都不用再去修改到客戶端 (Client)

[依賴反轉原則]

  1. 運輸類別依賴抽象的機型介面。
  2. 產品類別實作抽象的機型介面。

透過簡單工廠模式,客戶端減少了改變的機會,
但創建類別依然常常需要變更。

ʕ •ᴥ•ʔ:重讀一次 Design Pattern,改用 C# 來寫範例。

Share