2020年11月1日星期日

面向对象23种设计模式系列(四)

本系列将和大家分享面向对象23种设计模式中常用的几种设计模式,本章主要简单介绍下迭代器模式。

迭代器模式(Iterator Pattern)

  1、迭代器模式是设计模式中行为型模式(behavioral pattern)的一个例子,他是一种简化对象间通讯的模式,也是一种非常容易理解和使用的模式。简单来说,迭代器模式使得你能够获取到序列中的所有元素而不用关心是其类型是array,list,linked list或者是其他什么序列结构。这一点使得能够非常高效的构建数据处理通道(data pipeline)--即数据能够进入处理通道,进行一系列的变换,或者过滤,然后得到结果。事实上,这正是Linq的核心模式。

  2、在.NET中,迭代器模式被IEnumerator和IEnumerable及其对应的泛型接口所封装。如果一个类实现了IEnumerable接口,那么就能够被迭代;调用GetEnumerator方法将返回IEnumerator接口的实现,它就是迭代器本身。迭代器类似数据库中的游标,他是数据序列中的一个位置记录。迭代器只能向前移动,同一数据序列中可以有多个迭代器同时对数据进行操作。

  3、含有yield的函数说明它是一个生成器,而不是普通的函数。当程序运行到yield这一行时,该函数会返回值,并保存当前域的所有变量状态;等到该函数下一次被调用时,会从上一次中断的地方开始执行,一直遇到下一个yield,程序返回值,并在此保存当前状态; 如此反复,直到函数正常执行完成。

  4、yield是语法糖,编译时由编译器生成Iterrator的代码,包括MoveNext、Current、Reset等。

一、迭代器模式的实现原理

首先我们先来看个例子:

/// <summary>/// 食物/// </summary>public class Food{ public int Id { get; set; } public string Name { get; set; } public int Price { get; set; }}/// <summary>/// 肯德基菜单/// </summary>public class KFCMenu{ private Food[] _foodList = new Food[3]; public KFCMenu() {  this._foodList[0] = new Food()  {   Id = 1,   Name = "汉堡包",   Price = 15  };  this._foodList[1] = new Food()  {   Id = 2,   Name = "可乐",   Price = 10  };  this._foodList[2] = new Food()  {   Id = 3,   Name = "薯条",   Price = 8  }; } public Food[] GetFoods() {  return this._foodList; }}/// <summary>/// 麦当劳菜单/// </summary>public class MacDonaldMenu{ private List<Food> _foodList = new List<Food>(); public MacDonaldMenu() {  this._foodList.Add(new Food()  {   Id = 1,   Name = "鸡肉卷",   Price = 15  });  this._foodList.Add(new Food()  {   Id = 2,   Name = "红豆派",   Price = 10  });  this._foodList.Add(new Food()  {   Id = 3,   Name = "薯条",   Price = 9  }); } public List<Food> GetFoods() {  return this._foodList; }}
class Program{ static void Main(string[] args) {  {   KFCMenu kfcMenu = new KFCMenu();   Food[] foodCollection = kfcMenu.GetFoods();   for (int i = 0; i < foodCollection.Length; i++)   {    Console.WriteLine("KFC: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price);   }  }  {   MacDonaldMenu macDonaldMenu = new MacDonaldMenu();   List<Food> foodCollection = macDonaldMenu.GetFoods();   for (int i = 0; i < foodCollection.Count(); i++)   {    Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price);   }  }  Console.ReadKey(); }}

从上面的例子可以发现肯德基菜单和麦当劳菜单差不多,但是呢一个是数组存放一个是集合存放,这就导致了它们两者的访问方式不太一样。

那么从某种角度上看我们当然希望它们两者能有一个统一的访问方式。

class Program{ static void Main(string[] args) {  {   KFCMenu kfcMenu = new KFCMenu();   Food[] foodCollection = kfcMenu.GetFoods();   for (int i = 0; i < foodCollection.Length; i++)   {    Console.WriteLine("KFC: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price);   }   foreach (var item in foodCollection)   {    Console.WriteLine("KFC: Id={0} Name={1} Price={2}", item.Id, item.Name, item.Price);   }  }  {   MacDonaldMenu macDonaldMenu = new MacDonaldMenu();   List<Food> foodCollection = macDonaldMenu.GetFoods();   for (int i = 0; i < foodCollection.Count(); i++)   {    Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price);   }   foreach (var item in foodCollection)   {    Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", item.Id, item.Name, item.Price);   }  }  Console.ReadKey(); }}

可以发现使用foreach后它们两者的访问方式就统一了。那么这个foreach是怎么设计出来的呢?其实这就用到了迭代器,迭代器可以为不同的数据结构提供一个通用的访问方式。

下面我们直接通过代码来看下迭代器的实现原理:

/// <summary>/// 迭代器抽象类(模拟IEnumerator)/// </summary>public interface IIterator<T>{ /// <summary> /// 当前的对象 /// </summary> T Current { get; } /// <summary> /// 移动到下一个对象,是否存在。 /// </summary> /// <returns></returns> bool MoveNext(); /// <summary> /// 重置 /// </summary> void Reset();}/// <summary>/// 抽象聚合类(模拟IEnumerable)/// </summary>public interface IAggregate<T>{ IIterator<T> GetEnumerator();}
/// <summary>/// 迭代器具体类/// 肯德基菜单迭代器/// </summary>public class KFCMenuIterator : IIterator<Food>{ private Food[] _foodList = null; public KFCMenuIterator(KFCMenu kfcMenu) {  this._foodList = kfcMenu.GetFoods(); } private int _currentIndex = -1; public Food Current {  get  {   return this._foodList[_currentIndex];  } } public bool MoveNext() {  return this._foodList.Length > ++this._currentIndex; //此处判断方式是.Length } public void Reset() {  this._currentIndex = -1; }}/// <summary>/// 迭代器具体类/// 麦当劳菜单迭代器/// </summary>public class MacDonaldIterator : IIterator<Food>{ private List<Food> _foodList = null; public MacDonaldIterator(MacDonaldMenu macDonaldMenu) {  this._foodList = macDonaldMenu.GetFoods(); } private int _currentIndex = -1; public Food Current {  get  {   return this._foodList[_currentIndex];  } } public bool MoveNext() {  return this._foodList.Count > ++this._currentIndex; //此处判断方式是.Count } public void Reset() {  this._currentIndex = -1; }}
/// <summary>/// 肯德基菜单/// 实现IAggregate/// </summary>public class KFCMenu : IAggregate<Food>{ private Food[] _foodList = new Food[3]; public KFCMenu() {  this._foodList[0] = new Food()  {   Id = 1,   Name = "汉堡包",   Price = 15  };  this._foodList[1] = new Food()  {   Id = 2,   Name = "可乐",   Price = 10  };  this._foodList[2] = new Food()  {   Id = 3,   Name = "薯条",   Price = 8  }; } public Food[] GetFoods() {  return this._foodList; } public IIterator<Food> GetEnumerator() {  return new KFCMenuIterator(this); }}/// <summary>/// 麦当劳菜单/// 实现IAggregate/// </summary>public class MacDonaldMenu : IAggregate<Food>{ private List<Food> _foodList = new List<Food>(); public MacDonaldMenu() {  this._foodList.Add(new Food()  {   Id = 1,   Name = "鸡肉卷",   Price = 15  });  this._foodList.Add(new Food()  {   Id = 2,   Name = "红豆派",   Price = 10  });  this._foodList.Add(new Food()  {   Id = 3,   Name = "薯条",   Price = 9  }); } public List<Food> GetFoods() {  return this._foodList; } public IIterator<Food> GetEnumerator() {  return new MacDonaldIterator(this); }}

使用如下(红色字体部分):

using System;using System.Collections.Generic;using System.Linq;using IteratorPattern.Iterator;using IteratorPattern.Menu;namespace IteratorPattern{ /// <summary> /// 迭代器模式(yield return) ///  1、迭代器模式是设计模式中行为型模式(behavioral pattern)的一个例子,他是一种简化对象间通讯的模式,也是一种非常容易理解和使用的模式。 ///  简单来说,迭代器模式使得你能够获取到序列中的所有元素而不用关心是其类型是array,list,linked list或者是其他什么序列结构。 ///  这一点使得能够非常高效的构建数据处理通道(data pipeline)--即数据能够进入处理通道,进行一系列的变换,或者过滤,然后得到结果。 ///  事实上,这正是LINQ的核心模式。Linq to object的延迟查询,按需获取。 ///  2、在.NET中,迭代器模式被IEnumerator和IEnumerable及其对应的泛型接口所封装。如果一个类实现了IEnumerable接口,那么就能够被迭代; ///  调用GetEnumerator方法将返回IEnumerator接口的实现,它就是迭代器本身。迭代器类似数据库中的游标,他是数据序列中的一个位置记录。 ///  迭代器只能向前移动,同一数据序列中可以有多个迭代器同时对数据进行操作。 ///  3、含有yield的函数说明它是一个生成器,而不是普通的函数。当程序运行到yield这一行时,该函数会返回值,并保存当前域的所有变量状态; ///  等到该函数下一次被调用时,会从上一次中断的地方开始执行,一直遇到下一个yield,程序返回值,并在此保存当前状态; 如此反复,直到函数正常执行完成。 ///  4、yield是语法糖,编译时由编译器生成Iterrator的代码,包括MoveNext、Current、Reset等。 /// </summary> class Program {  static void Main(string[] args)  {   {    KFCMenu kfcMenu = new KFCMenu();    Food[] foodCollection = kfcMenu.GetFoods();    for (int i = 0; i < foodCollection.Length; i++)    {     Console.WriteLine("KFC: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price);    }    foreach (var item in foodCollection)    {     Console.WriteLine("KFC: Id={0} Name={1} Price={2}", item.Id, item.Name, item.Price);    }    IIterator<Food> foodIterator = kfcMenu.GetEnumerator();    while (foodIterator.MoveNext())    {     Food food = foodIterator.Current;     Console.WriteLine("KFC: Id={0} Name={1} Price={2}", food.Id, food.Name, food.Price);    }   }   {    MacDonaldMenu macDonaldMenu = new MacDonaldMenu();    List<Food> foodCollection = macDonaldMenu.GetFoods();    for (int i = 0; i < foodCollection.Count(); i++)    {     Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price);    }    foreach (var item in foodCollection)    {     Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", item.Id, item.Name, item.Price);    }    IIterator<Food> foodIterator = macDonaldMenu.GetEnumerator();    while (foodIterator.MoveNext())    {     Food food = foodIterator.Current;     Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", food.Id, food.Name, food.Price);    }   }   Console.ReadKey();  } }}

可以发现使用迭代器模式后我们做到了两者访问方式的统一。

在C# 1.0中我们经常使用foreach来遍历一个集合中的元素,然而一个类型要能够使用foreach关键字来对其进行遍历必须实现IEnumerable或IEnumerable<T>接口

之所以必须要实现IEnumerable这个接口,是因为foreach是迭代语句,要使用foreach就必须要有一个迭代器才行。

IEnumerable接口中就有IEnumerator GetEnumerator()方法是返回迭代器的,实现了IEnumerable接口就必须实现GetEnumerator()这个方法来返回迭代器,有了迭代器自然就可以使用foreach语句了。

在C# 1.0中要实现一个迭代器就必须实现IEnumerator接口中的bool MoveNext()和void Reset()方法。

而在C# 2.0中提供了yield关键字来简化迭代器的实现,这样在C# 2.0中如果我们要自定义一个迭代器就容易多了。

二、在C#1.0中实现迭代器

在C# 1.0 中实现一个迭代器必须实现IEnumerator接口,下面代码演示了传统方式来实现一个自定义的迭代器:

using System;namespace IteratorPattern.IteratorImpl{ /// <summary> /// 朋友类 /// </summary> public class Friend {  private string _name;  public string Name { get => _name; set => _name = value; }  public Friend(string name)  {   this._name = name;  } }}
using System.Collections;namespace IteratorPattern.IteratorImpl.Demo1{ /// <summary> /// 自定义迭代器,必须实现IEnumerator接口 /// </summary> public class FriendIterator : IEnumerator {  private readonly Friends _friends;  private int _index;  private Friend _current;  internal FriendIterator(Friends friends)  {   this._friends = friends;   _index = 0;  }  #region 实现IEnumerator接口中的方法  public object Current  {   get   {    return this._current;   }  }  public bool MoveNext()  {   if (_index + 1 > _friends.Count)   {    return false;   }   else   {    this._current = _friends[_index];    _index++;    return true;   }  }  public void Reset()  {   _index = 0;  }  

没有评论:

发表评论