重溫《深入淺出設計模式》反覆器模式 (Book Review of Head First Design Pattern, Iterator Pattern)
2020-07-07
反覆器已經被實踐在許多當代的語言之中,作為集合類型的低階類別所使用。以往並沒有特別感受到 Iterator 的美好,只覺得高階的語法 for ... in ...
(python) 方便好用,而有時候得到物件回傳 Iterator 反而不知所措 (例如 Beautiful Soup)。但重溫反覆器模式後,才曉得作為底層的反覆器有多重要,同時理解它也有助於自行實踐整合不同型別的集合類別進行迭代。
反覆器模式
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
After Pattern
用來表示菜單項目的類別。
MenuItem.cs
public class MenuItem
{
private string _name;
private string _description;
private bool _vegetarian;
private double _price;
public MenuItem(string name,
string description,
bool vegetarian,
double price)
{
_name = name;
_description = description;
_vegetarian = vegetarian;
_price = price;
}
public string GetName()
{
return _name;
}
public string GetDescription()
{
return _description;
}
public double GetPrice()
{
return _price;
}
public bool IsVegetarian()
{
return _vegetarian;
}
}
IIterator.cs
因為在 .NET 中沒有 Iteraotr Interface 可以被實踐,取而代之的是 IEnumerable,但兩者需要被實踐的方式不同,為貼近書中的範例,於是自己增加一個 IIterator 類別。
public interface IIterator<T>
{
bool HasNext();
T Next();
void Remove();
}
Menu.cs
<- DinerMenu.cs
<- CafeMenu.cs (省略)
<- PancakeHouseMenu.cs (省略)
public interface IMenu
{
IIterator<MenuItem> CreateIterator();
}
public class DinerMenu : IMenu
{
private static readonly int MAX_ITEMS = 6;
private int numberOfItems = 0;
private MenuItem[] menuItems;
public DinerMenu()
{
menuItems = new MenuItem[MAX_ITEMS];
AddItem("Vegetarian BLT",
"(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99);
AddItem("BLT",
"Bacon with lettuce & tomato on whole wheat", false, 2.99);
AddItem("Soup of the day",
"Soup of the day, with a side of potato salad", false, 3.29);
AddItem("Hotdog",
"A hot dog, with saurkraut, relish, onions, topped with cheese",
false, 3.05);
AddItem("Steamed Veggies and Brown Rice",
"A medly of steamed vegetables over brown rice", true, 3.99);
AddItem("Pasta",
"Spaghetti with Marinara Sauce, and a slice of sourdough bread",
true, 3.89);
}
public void AddItem(string name, string description, bool vegetarian, double price)
{
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
if (numberOfItems >= MAX_ITEMS)
{
Console.WriteLine("Sorry, menu is full! Can't add item to menu");
}
else
{
menuItems[numberOfItems] = menuItem;
numberOfItems = numberOfItems + 1;
}
}
public MenuItem[] GetMenuItems()
{
return menuItems;
}
public IIterator<MenuItem> CreateIterator()
{
// 因為 Array 無法提供 Iterator,因此必須自行實作。
return new DinerMenuIterator(menuItems);
}
}
DinerMenuIterator.cs
封裝 DinerMenu 的 CreateIterator 方法演算法,也是對於 IIterator 的實作。
public class DinerMenuIterator : IIterator<MenuItem>
{
private readonly MenuItem[] _list;
private int position = 0;
public DinerMenuIterator(MenuItem[] list)
{
_list = list;
}
public bool HasNext()
{
if (position >= _list.Length || _list[position] == null)
{
return false;
}
else
{
return true;
}
}
public MenuItem Next()
{
MenuItem menuItem = _list[position];
position += 1;
return menuItem;
}
public void Remove()
{
if (position <= 0)
{
throw new Exception("You can't remove an item until you've done at least one next()");
}
if (_list[position - 1] != null)
{
for (int i = position - 1; i < (_list.Length - 1); i++)
{
_list[i] = _list[i + 1];
}
_list[_list.Length - 1] = null;
}
}
}
Waitress.cs
用以整合不同的 Collections (Aggregations) 類別,透過 IIterator 使用多型的方式呈現不同 Collections 的 MenuItem。
public class Waitress
{
IMenu _pancakeHouseMenu;
IMenu _dinerMenu;
IMenu _cafeMenu;
public Waitress(IMenu pancakeHouseMenu, IMenu dinerMenu, IMenu cafeMenu)
{
_pancakeHouseMenu = pancakeHouseMenu;
_dinerMenu = dinerMenu;
_cafeMenu = cafeMenu;
}
public void PrintMenu()
{
IIterator<MenuItem> pancakeIterator = _pancakeHouseMenu.CreateIterator();
IIterator<MenuItem> dinerIterator = _dinerMenu.CreateIterator();
IIterator<MenuItem> cafeIterator = _cafeMenu.CreateIterator();
Console.WriteLine();
Console.WriteLine("MENU\n----\nBREAKFAST");
PrintMenu(pancakeIterator);
Console.WriteLine("\nLUNCH");
PrintMenu(dinerIterator);
Console.WriteLine("\nDINNER");
PrintMenu(cafeIterator);
}
private void PrintMenu(IIterator<MenuItem> iterator)
{
while (iterator.HasNext())
{
MenuItem menuItem = iterator.Next();
Console.WriteLine(menuItem.GetName() + ", ");
Console.WriteLine(menuItem.GetPrice() + " -- ");
Console.WriteLine(menuItem.GetDescription());
}
}
}
.NET IEnumerable, IEnumerator Interface
IEnumerable 僅有一個方法需要實作 GetEnumerator,此方法會回傳 IEnumerator ;而 IEnumerator 則有 Current Properties 以及 MoveNext 、 Reset 方法。
MSDN 上實作 IEnumerable 的範例
namespace System.Collections.Generic
{
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
}
public class People : IEnumerable
{
private Person[] _people;
public People(Person[] pArray)
{
_people = new Person[pArray.Length];
for (int i = 0; i < pArray.Length; i++)
{
_people[i] = pArray[i];
}
}
// Implementation for the GetEnumerator method.
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator) GetEnumerator();
}
public PeopleEnum GetEnumerator()
{
return new PeopleEnum(_people);
}
}
public class PeopleEnum : IEnumerator
{
public Person[] _people;
// Enumerators are positioned before the first element
// until the first MoveNext() call.
int position = -1;
public PeopleEnum(Person[] list)
{
_people = list;
}
public bool MoveNext()
{
position++;
return (position < _people.Length);
}
public void Reset()
{
position = -1;
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public Person Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
}
小結
✔️將變動的部分封裝起來
✔️類別應該對需求保持開放;應該對既有的程式碼修改保持關閉
✔️多用合成,少用繼承
✔️讓需要互動的物件之間的關係鬆綁
✔️針對介面撰寫,而非針對實踐方式撰寫