[UE5 设计模式] 迭代器模式Iterator Pattern


参考材料
1. 【UE4 设计模式】策略模式 Strategy Pattern
3. 迭代器模式
3. 游戏开发设计模式之迭代器模式
4. C++ STL迭代器原理和实现

1. 概述

1.1 描述

$\cdot$ 迭代器模式(Iterator Pattern) 是编程环境中非常常用的设计模式.
$\\$ 迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素, 而又不暴露其内部的表示.
$\\$ 迭代器模式属于行为型模式.

1.2 套路

$\cdot$ 实现方式:
$\\$ 1) 定义迭代器接口: 包含HasNext()和Next()等方法, 用于遍历元素.
$\\$ 2) 创建具体迭代器: 实现迭代器接口, 定义如何遍历特定的聚合对象.
$\\$ 3) 聚合类: 定义一个接口用于返回一个迭代器对象.

$\cdot$ 关键代码:
$\\$ 1) 迭代器接口: 规定了遍历元素的方法.
$\\$ 2) 具体迭代器: 实现了迭代器接口, 包含遍历逻辑.

$\cdot$ 结构:
$\\$ 迭代器模式包含以下几个主要角色:
$\\$ 1) 迭代器接口(Iterator): 定义了访问和遍历聚合对象中各个元素的方法, 通常包括获取下一个元素, 判断是否还有元素, 获取当前位置等方法.
$\\$ 2) 具体迭代器(Concrete Iterator): 实现了迭代器接口, 负责对聚合对象进行遍历和访问, 同时记录遍历的当前位置.
$\\$ 3) 聚合对象接口(Aggregate): 定义了创建迭代器对象的接口, 通常包括一个工厂方法用于创建迭代器对象.
$\\$ 4) 具体聚合对象(Concrete Aggregate): 实现了聚合对象接口, 负责创建具体的迭代器对象, 并提供需要遍历的数据.

1.3 使用场景

$\cdot$ 当需要遍历一个聚合对象, 而又不希望暴露其内部结构时.

1.4 优缺点

$\cdot$ 优点:
$\\$ 1) 支持多种遍历方式: 不同的迭代器可以定义不同的遍历方式.
$\\$ 2) 简化聚合类: 聚合类不需要关心遍历逻辑.
$\\$ 3) 多遍历支持: 可以同时对同一个聚合对象进行多次遍历.
$\\$ 4) 扩展性: 增加新的聚合类和迭代器类都很方便, 无需修改现有代码.

$\cdot$ 缺点:
$\\$ 1) 系统复杂性: 每增加一个聚合类, 就需要增加一个对应的迭代器类, 增加了类的数量.

2. UE5实践


#pragma once

template
class TNode
{
public:
	TNode() : Next(nullptr)
	{
	}

	TNode(T Val, TNode* P = nullptr) : Value(Val), Next(P)
	{
	}

public:
	T Value;

	TNode* Next;
};

template
class TMyList
{
private:
	class TListIterator
	{
	public:
		TListIterator(TNode* P = nullptr) : Ptr(P)
		{
		}

		// 重载++, --, *, ->等基本操作
		// 返回引用, 方便通过*It来修改对象
		T& operator*() const
		{
			return Pt->Value;
		}

		TNode* operator->() const
		{
			return Ptr;
		}

		// 前置++
		TListIterator& operator++()
		{
			Ptr = Ptr->Next;
			return *this;
		}

		// 后置++
		TListIterator operator++(int32)
		{
			TNode* Tmp = Ptr;
			// this 是指向TListIterator的常量指针, 因此*this就是TListIterator对象, 前置++已经被重载过
			++(*this);
			return TListIterator(Tmp);
		}

		bool operator==(const TListIterator& ListIterator) const
		{
			return ListIterator.Ptr == this->Ptr;
		}

		bool operator!=(const TListIterator& ListIterator) const
		{
			return ListIterator.Ptr != this->Ptr;
		}

	private:
		TNode* Ptr; // 指向List容器中的某个元素的指针
	};

public:
	typedef TListIterator TIterator;

	TMyList()
	{
		Head = nullptr;
		Tail = nullptr;
		Size = 0;
	}

	// 从链表尾部插入元素
	void PushBack(const T& Value)
	{
		if (Head == nullptr)
		{
			Head = new TNode(Value);
			Tail = Head;
		}
		else
		{
			Tail->Next = new TNode(Value);
			Tail = Tail->Next;
		}

		++Size;
	}

	// 打印链表元素
	void Print() const
	{
		for (TNode* Ptr = Head; Ptr != Tail->Next; Ptr = Ptr->Next)
		{
			UE_LOG(LogTemp, Log, TEXT("%s"), *(Ptr->Value).ToString());
		}
	}

	// 操作迭代器的方法
	// 返回链表头部指针
	TIterator Begin() const
	{
		return TListIterator(Head);
	}

	// 返回链表尾部指针
	TIterator End() const
	{
		return TListIterator(Tail->Next);
	}

	// 其它成员函数Insert/Erase/Emplace

private:
	TNode Head;

	TNode Tail;

	int32 Size;
};


#include "MyList.h"


struct FStudent
{
	FStudent(const FString& N, int32 A) : Name(N), Age(A)
	{
	}

	FString ToString()
	{
		return FString::Printf(TEXT("%s %d"), *Name, Age);
	}
	
	FString Name;

	int32 Age;
};

int main()
{
	TMyList L;
	L.PushBack(FStudent(TEXT("Bob"), 1)); // 临时量作为实参传递给PushBack方法
	L.PushBack(FStudent(TEXT("Allen"), 2));
	L.PushBack(FStudent(TEXT("Anna"), 3));
	L.Print();

	for (TMyList::TIterator It = L.Begin(); It != L.End(); ++It)
	{
		UE_LOG(LogTemp, Log, TEXT("%s"), *(*It).ToString());
		*It = FStudent(TEXT("Wengle"), 18);
	}
	
	return 0;
}

3. 迭代器模式与开闭原则的关系及其对游戏开发的影响是什么?

迭代器模式与开闭原则的关系及其对游戏开发的影响可以从以下几个方面进行详细阐述:

3.1 迭代器模式与开闭原则的关系

迭代器模式提供了一种方法, 以顺序访问一个聚合对象中的各个元素, 而不需要暴露该对象的内部表示. 这种模式通过将遍历算法代码抽取为独立的类, 从而简化了聚合类, 并且不需要改动原有的代码就可以很方便地增加新的聚合类和迭代器类.
$\\$ 开闭原则(Open-Closed Principle, OCP) 的核心思想是软件实体(类, 模块, 函数等) 应该对扩展开放, 对修改关闭. 迭代器模式正是符合这一原则的典型例子. 它可以在不修改集合对象的情况下新增不同类型的迭代器对象, 从而扩展了集合对象的功能, 这种设计使得软件在面对新的需求时, 能够通过增加新的迭代器类来扩展系统的功能, 而无需修改现有的代码.

3.2 对游戏开发的影响

在游戏开发中, 通常需要处理大量的数据结构, 如角色, 物品, 任务等. 通过使用迭代器模式, 开发者可以方便地遍历这些数据结构, 而无需暴露其内部表示. 这不仅提高了代码的可维护性, 还增强了系统的可扩展性. 例如, 在角色扮演游戏中, 角色具有不同的技能和属性, 通过迭代器模式, 开发者可以方便地遍历这些角色, 并为每个角色添加新的技能或属性.
$\\$ 游戏中的数据结构往往非常复杂, 如角色的装备系统, 任务的完成情况等. 迭代器模式可以简化这些复杂数据结构的遍历过程. 例如. 通过定义一个通用的迭代器接口, 开发者可以为不同的数据结构(如装备列表, 任务列表等) 提供统一的遍历接口, 从而避免重复编写遍历代码.
$\\$ 迭代器模式支持以不同的方式遍历一个聚合对象, 在不改变聚合对象结构的前提下, 可以定义新的迭代器类来支持新的遍历方式. 这对于游戏开发尤为重要, 因为不同的游戏场景可能需要不同的遍历方式. 例如, 在探索游戏中, 玩家可能需要以不同的顺序访问地图上的不同地点, 通过迭代器模式, 开发者可以轻松实现这种需求.
$\\$ 迭代器模式不仅符合开闭原则, 而且在游戏开发中具有重要的应用价值.

3.3 在使用迭代器模式时, 如何避免性能问题并确保系统的稳定性?

在使用迭代器模式时, 避免性能问题并确保系统稳定性需要综合考虑多个方面. 以下是详细的建议:
$\\$ 1) 延迟加载: 为了提高迭代器的效率, 可以采用延迟加载的方式. 即在需要遍历集合元素时才对集合进行遍历, 从而减少初始化所需的时间和内存开销.
$\\$ 2) 考虑数据库性能和并发访问: 在使用JDBC进行数据查询时, 结果集通常是一次性全部读取到内存中的, 这可能会引起内存溢出. 因此, 需要特别注意数据库的性能和并发访问的影响, 避免出现数据库锁和性能问题.
$\\$ 3) 多线程环境下的安全性: 在多线程环境中, 使用迭代器模式可以实现安全的遍历操作, 避免多个线程同时访问集合造成的问题.
$\\$ 4) 减少内存消耗: 迭代器和生成器是提高性能和减少内存消耗的重要工具. 它们不仅简化了代码结构, 而且在处理大型数据集时具有明显的优势.
$\\$ 5) 避免代码复杂性增加: 虽然迭代器模式可以简化集合遍历, 但也会增加代码的复杂度和维护成本. 因此, 在使用迭代器模式时, 需要权衡其带来的便利性和潜在的复杂性.
$\\$ 6) 性能优化: 在某些情况下, 迭代器模式可能会引起性能问题. 因此, 需要对迭代器的实现进行优化, 以确保其在实际应用中的性能.
$\\$ 7) 可靠性问题: 迭代器模式可能存在一些可靠性问题, 因此在使用时需要特别注意其稳定性和可靠性.

总结来说, 在游戏开发中选择哪种设计模式取决于具体的需求和场景. 迭代器模式适合需要统一遍历方式的场景; 观察者模式适合需要高模块化和低耦合度的场景; 而状态模式则适合需要管理复杂状态转换的场景.

————————————————
版权声明:本小节内容来源于CSDN博主「数学小师Yq」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2302_80644606/article/details/141462740

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注