範例:動物園管理員(訪問者模式)

Pattern: 訪問者模式

Class Diagram: 動物園管理員


這是一間動物園

  • 定義餵食界面
1
2
3
4
5
6
namespace DotNetCoreKata.DomainModels.ZooKeeper;

public interface IFeedable
{
void Feed();
}
  • 實作猴子
1
2
3
4
5
6
7
8
9
namespace DotNetCoreKata.DomainModels.ZooKeeper;

public class Monkey : IFeedable
{
public void Feed()
{
Console.WriteLine("Monkey eats banana");
}
}
  • 實作乳牛
1
2
3
4
5
6
7
8
9
namespace DotNetCoreKata.DomainModels.ZooKeeper;

public class Cow: IFeedable
{
public void Feed()
{
Console.WriteLine("Cow eats grass");
}
}
  • 實作獅子
1
2
3
4
5
6
7
8
9
namespace DotNetCoreKata.DomainModels.ZooKeeper;

public class Lion: IFeedable
{
public void Feed()
{
Console.WriteLine("Lion eats meat");
}
}
  • 目前的餵食方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using DotNetCoreKata.DomainModels.ZooKeeper;

namespace DotNetCoreKata.Services.ZooKeeper;

public class Zoo
{
public void FeedAnimals()
{
List<IFeedable> animals =
[
new Monkey(),
new Cow(),
new Lion()
];

foreach (var animal in animals)
{
animal.Feed();
}
}
}

目前是將要吃什麼的職責交由每個動物各自管理,
隨著功能變多,動物類別也會越來越肥大。

讓我們嘗試用訪問者模式改造它,
解耦餵食的邏輯。


首先是餵食者的邏輯 (Visitor)

  • 定義餵食者界面
1
2
3
4
5
6
7
8
namespace DotNetCoreKata.DomainModels.ZooKeeper;

public interface IFeeder
{
void Feed(Monkey monkey);
void Feed(Cow cow);
void Feed(Lion lion);
}
  • 實作餵食者與動物互動的邏輯
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace DotNetCoreKata.DomainModels.ZooKeeper;

public class Feeder: IFeeder
{
public void Feed(Monkey monkey)
{
Console.WriteLine("Monkey eats banana");
}

public void Feed(Cow cow)
{
Console.WriteLine("Cow eats grass");
}

public void Feed(Lion lion)
{
Console.WriteLine("Lion eats meat");
}
}

接著改寫動物的邏輯 (Element)

  • 定義被餵食界面
1
2
3
4
5
6
namespace DotNetCoreKata.DomainModels.ZooKeeper;

public interface IFeedable
{
void FeedBy(IFeeder feeder);
}
  • 實作猴子
1
2
3
4
5
6
7
8
9
namespace DotNetCoreKata.DomainModels.ZooKeeper;

public class Monkey : IFeedable
{
public void FeedBy(IFeeder feeder)
{
feeder.Feed(this);
}
}
  • 實作乳牛
1
2
3
4
5
6
7
8
9
namespace DotNetCoreKata.DomainModels.ZooKeeper;

public class Cow : IFeedable
{
public void FeedBy(IFeeder feeder)
{
feeder.Feed(this);
}
}
  • 實作獅子
1
2
3
4
5
6
7
8
9
namespace DotNetCoreKata.DomainModels.ZooKeeper;

public class Lion : IFeedable
{
public void FeedBy(IFeeder feeder)
{
feeder.Feed(this);
}
}

註:這邊也可以將三個動物的餵食邏輯放在一起,
不過要用到繼承跟調整餵食者的程式嗎。


最後改寫動物園的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using DotNetCoreKata.DomainModels.ZooKeeper;

namespace DotNetCoreKata.Services.ZooKeeper;

public class Zoo
{
public void FeedAnimals()
{
List<IFeedable> animals =
[
new Monkey(),
new Cow(),
new Lion()
];

var feeder = new Feeder();

foreach (var animal in animals)
{
animal.FeedBy(feeder);
}
}
}

現在我們將不同動物的餵食邏輯收斂在餵食者身上,
當新增新的動物時,可以修改餵食界面,
並實作相關邏輯。

當有新的訪問需求(比如看診),
我們也可以依樣畫葫蘆實作被看診界面與看診界面,
並實作看診者的邏輯。


ʕ •ᴥ•ʔ:Weija 於讀書會分享這個範例,
完全刷新了我之前用 PHP 寫範例的印象。

原來是在支援多型的語言,訪問者模式可以這麼漂亮。

Share