17. Iterator


Posted by WayneCheng on 2021-01-24

關於 Iterator 本篇將討論以下幾個問題

1. 關於 Iterator

2. UML

3. 將 UML 轉為程式碼

4. 情境


測試環境:

OS:Windows 10
IDE:Visual Studio 2019


1. 關於 Iterator

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

by Gang of Four

  • 提供一種在不暴露集合底層形式(Array、Queue、Stack等)的情況下遍歷集合中元素的方法

Iterator(迭代器)屬於行為型(Behavioral Patterns),當遇到對外部隱藏複雜結構集合的實作達到特定目的,並提供遍歷接口時,可以使用 Iterator 來做為外部取用集合的接口,以提高安全和便利性。

優點:

  • 符合 單一職責原則(Single Responsibility Principle)
  • 符合 開閉原則(Open Closed Principle)

缺點:

  • 在特定集合類型使用迭代器可能會有效能問題
  • 一般不會在迭代器中處理 新增/刪除,因維護上較麻煩

2. UML

Class 間關聯:

  • Client 關聯 Iterator & Aggregate
  • ConcreteAggregate 繼承 Aggregate
  • ConcreteIterator 繼承 Iterator
  • ConcreteAggregate 可包含 ConcreteIterator
  • ConcreteIterator 關聯 ConcreteAggregate

Class:

  • Client:呼叫端
  • Iterator:迭代器抽象類別或介面,定義訪問或遍歷元素的接口
  • ConcreteIterator:迭代器實作
  • Aggregate:包含迭代器的集合的抽象類別或介面
  • ConcreteAggregate:包含迭代器的集合實作

3. 將 UML 轉為程式碼

包含迭代器的集合的介面

/// <summary>
/// 包含迭代器的集合的介面
/// </summary>
public interface IAggregate
{
    IIterator CreateIterator();
}

包含迭代器的集合實作

/// <summary>
/// 包含迭代器的集合實作
/// </summary>
public class ConcreteAggregate : IAggregate
{
    private ArrayList _items = new ArrayList();

    public IIterator CreateIterator()
    {
        return new ConcreteIterator(this);
    }

    public int ElementCount => _items.Count;

    // Indexer
    public object this[int index]
    {
        get => _items[index];
        set => _items.Insert(index, value);
    }
}

迭代器介面

/// <summary>
/// 迭代器介面
/// </summary>
public interface IIterator
{
    object First();
    object Next();
    bool IsDone();
    object CurrentItem();
}

迭代器實作

/// <summary>
/// 迭代器實作
/// </summary>
public class ConcreteIterator : IIterator
{
    private ConcreteAggregate _aggregate { get; }
    private int _current = 0;

    public ConcreteIterator(ConcreteAggregate aggregate)
    {
        _aggregate = aggregate;
    }

    // 取得第一個元素
    public object First()
    {
        return _aggregate[0];
    }

    // 取得接續元素
    public object Next()
    {
        object ret = null;
        if (_current < _aggregate.ElementCount - 1)
        {
            ret = _aggregate[++_current];
        }

        return ret;
    }

    // 取得當前元素
    public object CurrentItem()
    {
        return _aggregate[_current];
    }

    // 是否為最後一個元素
    public bool IsDone()
    {
        return _current >= _aggregate.ElementCount;
    }
}
  1. 建立集合 aggregate
  2. 呼叫aggregate.CreateIterator()建立迭代器
  3. 使用迭代器遍歷集合
static void Main(string[] args)
{
    Default.ConcreteAggregate aggregate = new Default.ConcreteAggregate
    {
        [0] = "Item A", [1] = "Item B", [2] = "Item C", [3] = "Item D"
    };

    Default.IIterator iterator = aggregate.CreateIterator();

    Console.WriteLine("遍歷 ConcreteAggregate:");

    var item = iterator.First();
    while (item != null)
    {
        Console.WriteLine(item);
        item = iterator.Next();
    }

    Console.ReadLine();
}

執行結果

遍歷 ConcreteAggregate:
Item A
Item B
Item C
Item D

4. 情境

我們接到了一個取得員工資料的需求

  • 員工資料中包含榮譽員工鎮店貓貓、鎮店狗狗
  • 取得員工資料時,只需取得一般員工資料

※ 在高階程式語言中有更簡單的作法(e.g. foreach + where),此情境範例僅是為了更加深 Iterator 使用方式的印象

員工資料類與員工類型

/// <summary>
/// 員工類型分為 一般員工 & 榮譽員工
/// </summary>
public enum EmployeeType
{
    General,
    Honours
}

/// <summary>
/// 員工資料
/// </summary>
public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public EmployeeType EmployeeType { get; set; }
}

包含迭代器的員工的介面

/// <summary>
/// 包含迭代器的員工的介面
/// </summary>
public interface IEmployee
{
    IIterator CreateIterator();
}

包含迭代器的員工實作

/// <summary>
/// 包含迭代器的員工實作
/// </summary>
public class Employees : IEmployee
{
    // 此處並非範例重點,故直接指定容量為 10
    private Employee[] _items = new Employee[10];

    public IIterator CreateIterator()
    {
        return new ConcreteIterator(this);
    }

    public int ElementCount => _items.Length;

    // Indexer
    public Employee this[int index]
    {
        get => _items[index];
        set =>_items.SetValue(value, index);
    }
}

迭代器介面

/// <summary>
/// 迭代器介面
/// </summary>
public interface IIterator
{
    Employee First();
    Employee Next();
    bool IsDone();
    Employee CurrentItem();
}

迭代器實作,取得資料時排除榮譽員工

/// <summary>
/// 迭代器實作
/// </summary>
public class ConcreteIterator : IIterator
{
    private Employees _aggregate { get; }
    private int _current = 0;

    public ConcreteIterator(Employees aggregate)
    {
        _aggregate = aggregate;
    }

    // 取得第一個元素
    public Employee First()
    {
        var employee = _aggregate[0];

        if (employee != null && employee.EmployeeType == EmployeeType.Honours)
        {
            Next();
        }

        return employee;
    }

    // 取得接續元素
    public Employee Next()
    {
        Employee employee = null;
        if (_current < _aggregate.ElementCount - 1)
        {
            employee = _aggregate[++_current];
        }

        if (employee != null && employee.EmployeeType == EmployeeType.Honours)
        {
            return Next();
        }

        return employee;
    }

    // 取得當前元素
    public Employee CurrentItem()
    {
        return _aggregate[_current];
    }

    // 是否為最後一個元素
    public bool IsDone()
    {
        return _current >= _aggregate.ElementCount;
    }
}
  1. 建立員工集合
  2. 呼叫employees.CreateIterator()建立迭代器
  3. 使用迭代器遍歷員工集合
static void Main(string[] args)
{
    Situation.Employees employees = new Situation.Employees
    {
        [0] = new Situation.Employee { Id = 1, Name = "Wayne", EmployeeType = Situation.EmployeeType.General},
        [1] = new Situation.Employee { Id = 2, Name = "Dog", EmployeeType = Situation.EmployeeType.Honours },
        [2] = new Situation.Employee { Id = 3, Name = "Andy", EmployeeType = Situation.EmployeeType.General },
        [3] = new Situation.Employee { Id = 4, Name = "Cat", EmployeeType = Situation.EmployeeType.Honours },
    };

    Situation.IIterator iterator = employees.CreateIterator();

    Console.WriteLine("Iterating over collection:");

    var item = iterator.First();
    while (item != null)
    {
        Console.WriteLine($"Id: {item.Id}, Name: {item.Name}");
        item = iterator.Next();
    }

    Console.ReadLine();
}

執行結果

Iterating over collection:
Id: 1, Name: Wayne
Id: 3, Name: Andy

完整程式碼

GitHub:Behavioral_04_Iterator


總結

其實在高階程式語言中的集合都已經在底層實作 Iterator 了,所以我們才能夠直接對集合進行 foreach 操作,除了特定目的(e.g. 情境範例中的排除特定目標)之外,基本上也很少有機會需要自己實作 Iterator 了。


參考資料

  1. Design Patterns
  2. 大話設計模式
  3. dofactory
  4. Refactoring.Guru

新手上路,若有錯誤還請告知,謝謝


#designpattern #CSharp







Related Posts

Go 起手式之二

Go 起手式之二

Day 3 | Enter max-width

Day 3 | Enter max-width

邁入第三年的回顧

邁入第三年的回顧


Comments