Unity 设计模式 之 结构型模式 -【装饰者模式】【外观模式】【享元模式】【代理模式】
目录
[Unity 设计模式 之 结构型模式 -【装饰者模式】【外观模式】【享元模式】【代理模式】](#Unity 设计模式 之 结构型模式 -【装饰者模式】【外观模式】【享元模式】【代理模式】)
[二、装饰者模式(Decorator Pattern)](#二、装饰者模式(Decorator Pattern))
[三、在 Unity 中使用 装饰者模式](#三、在 Unity 中使用 装饰者模式)
[1、定义组件接口 ICharacter](#1、定义组件接口 ICharacter)
[2、实现具体角色类 PlayerCharacter](#2、实现具体角色类 PlayerCharacter)
[3、实现装饰者基类 CharacterDecorator](#3、实现装饰者基类 CharacterDecorator)
[4.1 添加盔甲功能的装饰者 ArmorDecorator](#4.1 添加盔甲功能的装饰者 ArmorDecorator)
[4.2 添加武器功能的装饰者 WeaponDecorator](#4.2 添加武器功能的装饰者 WeaponDecorator)
[5、在 Unity 中使用装饰者模式](#5、在 Unity 中使用装饰者模式)
[四、外观模式(Facade Pattern)](#四、外观模式(Facade Pattern))
[五、在 Unity 中使用 外观模式](#五、在 Unity 中使用 外观模式)
[1.1 子系统 1:3D 对象管理(ObjectManager)](#1.1 子系统 1:3D 对象管理(ObjectManager))
[1.2 子系统 2:对象移动管理(MovementManager)](#1.2 子系统 2:对象移动管理(MovementManager))
[1.3 子系统 3:音频管理(AudioManager)](#1.3 子系统 3:音频管理(AudioManager))
[2、实现外观类 GameManagerFacade](#2、实现外观类 GameManagerFacade)
[六、享元模式(Flyweight Pattern)](#六、享元模式(Flyweight Pattern))
[七、在 Unity 中使用 享元模式](#七、在 Unity 中使用 享元模式)
[1. 定义享元类](#1. 定义享元类)
[2. 创建享元工厂](#2. 创建享元工厂)
[3. 客户端代码](#3. 客户端代码)
[4. 场景设置](#4. 场景设置)
[八、代理模式(Proxy Pattern)](#八、代理模式(Proxy Pattern))
[九、在 Unity 中使用 代理模式](#九、在 Unity 中使用 代理模式)
[1、定义组件接口 IModel](#1、定义组件接口 IModel)
[5、 运行示例](#5、 运行示例)
一、简单介绍
设计模式 是指在软件开发中为解决常见问题而总结出的一套可复用的解决方案 。这些模式是经过长期实践证明有效的编程经验总结 ,并可以在不同的项目中复用。设计模式并不是代码片段,而是对常见问题的抽象解决方案,它提供了代码结构和模块间交互的一种设计思路,帮助开发者解决特定的设计问题。
设计模式的特点:
- 通用性:设计模式针对的是软件开发中常见的设计问题,适用于各种软件工程项目。
- 可复用性:设计模式可以在不同项目和环境下被重复使用,提高代码的可维护性和扩展性。
- 可扩展性:设计模式有助于让代码结构更加灵活,易于扩展和修改。
- 模块化:通过设计模式,可以减少代码的耦合性,增强模块间的独立性。
- 提高沟通效率:设计模式为开发者提供了一种通用的设计语言,使得团队成员能够快速理解并讨论设计方案。
二、装饰者模式(Decorator Pattern)
装饰者模式(Decorator Pattern) 是一种结构型设计模式,允许动态地为对象添加新的功能,而不改变其原有的结构。它通过创建一个装饰类,包装原始对象,以扩展对象的功能。这种模式非常灵活,因为可以在不修改现有类的情况下动态地添加功能。
装饰者模式的核心思想是将功能封装到独立的类中,通过这些类来为对象提供额外的行为。它避免了继承带来的类爆炸问题,因为不需要创建大量的子类去实现不同的功能组合。
装饰者模式的组成部分
- Component(组件接口/抽象类):定义了对象的接口,装饰类和被装饰的类都需要实现该接口。
- ConcreteComponent(具体组件):被装饰的原始对象,通常是我们想要动态添加功能的对象。
- Decorator(装饰者):装饰者类,继承自组件接口,持有一个组件对象,并通过它为被装饰的对象增加额外的功能。
- ConcreteDecorator(具体装饰者):实际的装饰类,负责为对象增加具体的功能。
1、什么时候使用装饰者模式
需要动态地为对象添加功能:如果你需要在运行时为对象添加或删除功能,而不是通过编译时继承来决定功能,装饰者模式是非常适合的。
功能扩展的组合要求复杂:如果对象的功能需要以多种组合形式出现,使用继承可能会导致大量子类的产生,而使用装饰者模式可以通过灵活的组合方式来避免这种复杂性。
需要在不修改原有类的情况下扩展功能:当你不能或不想修改已有类的代码时,可以通过装饰者模式来实现功能扩展。
2、使用装饰者模式的好处
灵活扩展功能:装饰者模式允许我们在运行时动态地添加或删除对象的功能,而不需要修改对象的结构或创建子类。相比继承,装饰者模式更具灵活性。
符合开闭原则:装饰者模式遵循了"开闭原则"(对扩展开放,对修改封闭)。它允许通过扩展装饰者类来增加新功能,而不需要修改现有类。
组合功能:多个装饰者可以自由组合使用,为对象动态叠加多个功能,避免了因继承引发的类爆炸问题。
解耦职责:装饰者模式将对象的核心职责和额外功能解耦,使得功能扩展变得更加清晰和灵活。
3、使用装饰者模式时的注意事项
装饰者数量的过多嵌套:如果装饰者叠加得过多,可能会导致对象的行为难以理解和调试。应避免过度装饰,以免导致对象的行为变得复杂。
与继承的平衡:装饰者模式是继承的替代方案,但并不是所有情况都应该使用装饰者。对于简单、单一职责的扩展,继承可能会更合适。应根据实际场景选择使用继承还是装饰者。
维护原始接口:装饰者类必须遵守组件的接口,不能改变原始接口的行为,否则会导致装饰后的对象和未装饰对象的行为不一致,破坏系统的稳定性。
注意性能问题:装饰者模式通过叠加装饰对象来实现功能扩展,每个装饰者都会增加额外的调用开销。如果过多使用,可能会影响系统性能。
三、在 Unity 中使用 装饰者模式
在 Unity 中,我们可以使用装饰者模式来动态为 3D 游戏对象添加不同的功能,比如给角色添加不同的外观组件或效果(例如盔甲、武器、特殊能力等)。通过装饰者模式,我们可以在不修改原始对象的情况下,灵活地给游戏对象添加功能,保持代码的简洁性和扩展性。
装饰者模式示例:动态装饰 3D 游戏角色
这个示例展示如何使用装饰者模式为一个简单的 3D 游戏角色添加盔甲和武器。装饰者模式允许我们动态地为对象叠加功能,而无需修改对象本身。
参考类图如下:
1、定义组件接口 ICharacter
首先,定义一个 ICharacter
接口,包含一个 Display
方法用于展示角色。
这个接口规定了角色的基本行为,即 Display
方法,表示角色将如何在场景中展示。
cs
public interface ICharacter
{
void Display();
}
2、实现具体角色类 PlayerCharacter
接下来,我们实现一个具体的角色类 PlayerCharacter
,它是一个简单的角色,用 Unity 的 PrimitiveType.Capsule
作为视觉表示。
这个类实现了 ICharacter
接口,并渲染一个基本的 3D 角色(胶囊体)。
cs
using UnityEngine;
public class PlayerCharacter : ICharacter
{
public void Display()
{
Debug.Log("Displaying Player Character");
GameObject.CreatePrimitive(PrimitiveType.Capsule); // 使用胶囊体来表示角色
}
}
3、实现装饰者基类 CharacterDecorator
装饰者基类 CharacterDecorator
,它持有一个 ICharacter
对象,并通过它来扩展角色的功能。
这个装饰者基类继承了 ICharacter
,并持有一个 ICharacter
类型的对象,Display
方法会调用原始对象的 Display
方法。
cs
public abstract class CharacterDecorator : ICharacter
{
protected ICharacter decoratedCharacter;
public CharacterDecorator(ICharacter character)
{
decoratedCharacter = character;
}
public virtual void Display()
{
decoratedCharacter.Display();
}
}
4、实现具体的装饰者类
4.1 添加盔甲功能的装饰者 ArmorDecorator
cs
using UnityEngine;
public class ArmorDecorator : CharacterDecorator
{
public ArmorDecorator(ICharacter character) : base(character) { }
public override void Display()
{
base.Display(); // 调用原始角色的 Display 方法
AddArmor(); // 添加盔甲
}
private void AddArmor()
{
Debug.Log("Adding Armor to Character");
// 使用立方体来表示盔甲
GameObject armor = GameObject.CreatePrimitive(PrimitiveType.Cube);
armor.transform.position = new Vector3(0, 1, 0); // 将盔甲放在角色的顶部
}
}
4.2 添加武器功能的装饰者 WeaponDecorator
cs
using UnityEngine;
public class WeaponDecorator : CharacterDecorator
{
public WeaponDecorator(ICharacter character) : base(character) { }
public override void Display()
{
base.Display(); // 调用原始角色的 Display 方法
AddWeapon(); // 添加武器
}
private void AddWeapon()
{
Debug.Log("Adding Weapon to Character");
// 使用圆柱体来表示武器
GameObject weapon = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
weapon.transform.position = new Vector3(0, 2, 0); // 将武器放在角色的手上
}
}
这两个装饰者类分别为角色添加盔甲和武器的功能。通过调用 base.Display()
方法,装饰者可以先调用原始对象的行为,再添加新的功能。
5、在 Unity 中使用装饰者模式
在 Unity 中,我们可以将这些装饰者组合起来,为一个角色动态地添加盔甲和武器。以下代码展示了如何在 Unity 场景中使用装饰者模式。
cs
using UnityEngine;
public class DecoratorPatternExample : MonoBehaviour
{
void Start()
{
// 创建一个基本的玩家角色
ICharacter player = new PlayerCharacter();
// 为玩家角色添加盔甲装饰
ICharacter armoredPlayer = new ArmorDecorator(player);
// 为玩家角色添加武器装饰
ICharacter fullyEquippedPlayer = new WeaponDecorator(armoredPlayer);
// 渲染角色,并添加盔甲和武器
fullyEquippedPlayer.Display();
}
}
6、运行结果分析
-
在控制台中,你将看到以下输出:
csDisplaying Player Character Adding Armor to Character Adding Weapon to Character
-
在 Unity 场景中,将会渲染出一个胶囊体(表示玩家角色),并在其上方附加一个立方体(表示盔甲),以及一个圆柱体(表示武器)。
通过这个示例,你可以看到如何在 Unity 中使用装饰者模式动态地为 3D 游戏角色添加不同的功能。这种设计模式特别适合游戏开发中需要灵活扩展功能的场景。
四、外观模式(Facade Pattern)
外观模式(Facade Pattern) 是一种结构型设计模式,提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,使得这一子系统更加容易使用。它封装了子系统的复杂性,简化了客户端的调用。
在复杂的系统中,往往会有许多类和接口相互交织,导致使用起来比较复杂。外观模式通过引入一个外观类,提供一个简单的接口来隐藏子系统的复杂实现,客户端只需通过这个外观类与子系统交互,而不需要直接面对复杂的内部逻辑。
外观模式的组成
- Facade(外观类):提供一个简单的接口,简化与复杂子系统的交互。
- Subsystems(子系统):一组复杂的类、模块或功能,外观类负责封装这些复杂的交互和依赖关系。
1、什么时候使用外观模式
系统复杂且有多个子系统:当一个系统包含多个子系统,且每个子系统都有许多接口时,使用外观模式可以为这些复杂子系统提供一个简洁的接口。
希望隐藏系统的复杂性:当你想要隐藏系统的复杂实现,简化客户端的使用时,外观模式是一个很好的选择。通过外观类,客户端只需要关心简化的接口,而不需要了解复杂的内部逻辑。
降低耦合度,提供更清晰的层次结构:在分层架构中,外观模式可以用来组织层与层之间的交互,使得上层模块不需要直接依赖于底层的复杂子系统。
为了避免直接依赖复杂系统的多个类:当系统的子系统过于庞大,客户端可能需要依赖多个类时,外观模式提供了统一的接口,减少了客户端与多个类的耦合。
2、使用外观模式的好处
简化接口:外观模式为复杂的子系统提供了一个简化的接口,使得客户端可以更方便地使用复杂系统,而不需要了解子系统的内部结构。
降低耦合性:客户端与子系统的复杂交互通过外观类来处理,降低了客户端与子系统的直接依赖关系,增强了系统的灵活性和维护性。
更好的分层设计:在分层系统中,外观模式可以用来组织子系统中的不同层次,使得上层只与外观类交互,保持系统的模块化和可扩展性。
提高代码的可维护性:通过外观类进行对外接口的封装,客户端的代码更加简单,同时系统的内部结构可以随时调整而不影响客户端的使用。
3、使用外观模式时的注意事项
不要滥用外观模式:虽然外观模式可以简化接口,但不要将所有功能都封装在外观类中。如果外观类承担的责任过多,可能导致外观类本身变得过于复杂,失去了简化的意义。
外观模式不是强制的:如果系统的子系统已经足够简单,或者客户端已经可以方便地使用子系统的接口,则没有必要使用外观模式。
保持外观类的职责单一:外观类的主要职责是提供简化的接口,它不应该处理过多的业务逻辑,保持它的职责单一,避免成为一个"上帝对象"。
外观模式不一定要取代子系统接口:外观模式提供了一个简化的接口,但并不意味着客户端不能直接使用子系统的接口。在一些高级用法中,客户端依然可以直接与子系统交互。
通过引入一个外观类,将子系统的复杂性封装起来,提供一个简洁的接口,使得客户端可以方便地与子系统交互。
- 简化系统的使用,降低了客户端与子系统之间的耦合。
- 隐藏子系统的复杂实现,客户端无需关注细节。
- 提高系统的可维护性,外观类可以调整内部子系统的逻辑,而不会影响客户端。
使用场景:当系统内部结构复杂,且子系统之间相互依赖时;当希望简化客户端对复杂子系统的调用时;在分层架构中使用外观模式,提供清晰的接口分层;
注意事项,不要滥用外观模式,不要将所有功能都封装在外观类中,以免外观类变得过于复杂。
确保外观类的职责单一,专注于简化接口,而非实现过多业务逻辑。
总之,外观模式是一个非常有用的设计模式,尤其是在需要与复杂子系统交互时,通过引入外观类可以简化客户端的操作,同时提高系统的可维护性和扩展性。
五、在 Unity 中使用 外观模式
在 Unity 中,外观模式可以简化对多个复杂子系统的使用,例如场景中各种 3D 对象的创建、控制动画、音频管理等。我们可以创建一个外观类来封装多个功能,让客户端通过一个简单的接口管理这些复杂的子系统。
以下是一个使用外观模式的示例,展示如何创建、移动 3D 对象并播放音效。我们将外观类 GameManagerFacade
封装这些子系统,使得客户端只需通过外观类进行操作。
参考类图如下:
1、定义子系统类
1.1 子系统 1:3D 对象管理(ObjectManager
)
负责创建不同类型的 3D 对象(例如立方体、球体等):
cs
using UnityEngine;
public class ObjectManager
{
public GameObject CreateObject(PrimitiveType type, Vector3 position)
{
GameObject obj = GameObject.CreatePrimitive(type);
obj.transform.position = position;
Debug.Log(type.ToString() + " created at " + position);
return obj;
}
}
1.2 子系统 2:对象移动管理(MovementManager
)
负责处理对象的移动操作:
cs
using UnityEngine;
public class MovementManager
{
public void MoveObject(GameObject obj, Vector3 targetPosition, float speed)
{
obj.transform.position = Vector3.Lerp(obj.transform.position, targetPosition, speed * Time.deltaTime);
Debug.Log("Moving " + obj.name + " to " + targetPosition);
}
}
1.3 子系统 3:音频管理(AudioManager
)
负责播放音效:
cs
using UnityEngine;
public class AudioManager
{
public void PlaySound(AudioClip clip, Vector3 position)
{
AudioSource.PlayClipAtPoint(clip, position);
Debug.Log("Playing sound at " + position);
}
}
2、实现外观类 GameManagerFacade
GameManagerFacade
封装了 ObjectManager
、MovementManager
和 AudioManager
,为客户端提供一个统一的接口:
cs
using UnityEngine;
public class GameManagerFacade
{
private ObjectManager objectManager;
private MovementManager movementManager;
private AudioManager audioManager;
public GameManagerFacade()
{
objectManager = new ObjectManager();
movementManager = new MovementManager();
audioManager = new AudioManager();
}
// 创建对象
public GameObject CreateObject(PrimitiveType type, Vector3 position)
{
return objectManager.CreateObject(type, position);
}
// 移动对象
public void MoveObject(GameObject obj, Vector3 targetPosition, float speed)
{
movementManager.MoveObject(obj, targetPosition, speed);
}
// 播放音效
public void PlaySound(AudioClip clip, Vector3 position)
{
audioManager.PlaySound(clip, position);
}
}
3、客户端代码
通过 GameManagerFacade
操作子系统,简化了客户端的使用。以下示例展示如何使用外观模式创建 3D 对象、移动对象和播放音效。
cs
using UnityEngine;
public class FacadePatternExample : MonoBehaviour
{
public AudioClip soundClip; // 拖入音效文件
private GameManagerFacade gameManager;
private GameObject cube;
void Start()
{
// 初始化外观类
gameManager = new GameManagerFacade();
// 创建立方体
cube = gameManager.CreateObject(PrimitiveType.Cube, new Vector3(0, 0, 0));
// 播放音效
gameManager.PlaySound(soundClip, new Vector3(0, 0, 0));
}
void Update()
{
// 在每帧更新时,移动立方体
if (cube != null)
{
gameManager.MoveObject(cube, new Vector3(5, 0, 0), 1f);
}
}
}
4、运行示例
在
Start
方法中 :客户端使用GameManagerFacade
创建一个立方体,并在其位置播放音效。在
Update
方法中 :每帧更新时,立方体会向目标位置移动,移动的过程由MovementManager
负责。
场景设置
- 音效文件 :将一个音频文件(
AudioClip
)拖入FacadePatternExample
的soundClip
字段中。- Unity 场景:在 Unity 场景中运行该脚本,立方体会在场景中创建并开始移动,同时播放音效。
这个示例展示了如何使用外观模式简化 3D 对象的管理和控制,在 Unity 中使用这种模式可以帮助我们更好地组织和管理复杂的子系统。
总之,外观模式的好处
- 简化系统交互 :通过
GameManagerFacade
统一管理对象创建、移动、音效播放的操作,客户端不需要了解子系统的细节。- 降低耦合:客户端与各个子系统之间通过外观类交互,降低了耦合性,使得系统更具扩展性和维护性。
- 隐藏复杂性:通过外观类,客户端无需关注子系统的复杂性,专注于使用简化的接口。
使用外观模式的注意事项:
- 职责单一:外观类应该仅负责简化系统接口,而不应该承担过多的业务逻辑。
- 不妨碍直接访问子系统:如果需要,客户端仍然可以直接访问子系统的功能。外观模式提供的是一个简化的选择,而不是替代所有交互。
六、享元模式(Flyweight Pattern)
享元模式(Flyweight Pattern) 是一种结构型设计模式,旨在减少对象的创建数量,以降低内存占用和提高性能。享元模式通过将对象的状态分为内部状态 (共享)和外部状态(不共享)来实现共享对象的使用。
- 内部状态:可以被多个对象共享的状态,通常是不可变的(如颜色、形状等)。
- 外部状态:不能被共享的状态,通常与具体对象的上下文有关(如位置、大小等)。
享元模式通过共享相同的对象实例来减少内存占用,适合用于需要大量相似对象的场景。
1、什么时候使用享元模式
对象数量庞大:当需要创建大量相似的对象,而这些对象之间的差异很小时,使用享元模式是合适的。
内存消耗敏感的应用:在内存使用受限的情况下,享元模式可以通过共享对象实例来降低内存占用。
对象状态相似:当对象的内部状态相似,且可以共享时,享元模式可以提高对象的重用率。
需要提高性能:在对象的创建和销毁开销较大的情况下,享元模式可以通过重用现有对象来提升性能。
2、使用享元模式的好处
降低内存使用:通过共享对象,减少了实例的数量,从而降低了内存消耗。
提高性能:对象的创建和销毁可能会消耗大量资源,享元模式通过重用已有对象来提高性能。
支持大量对象:在需要处理大量相似对象的情况下,享元模式能够有效地管理这些对象。
明确对象的状态管理:将对象状态分为内部和外部,使得状态管理更加清晰,有助于提高代码的可维护性。
3、使用享元模式时的注意事项
内部和外部状态管理:确保内部状态是不可变的,避免共享对象在使用过程中被修改,导致状态不一致。
对象的识别:在实现享元模式时,需要有明确的方法来区分不同的外部状态,以便正确地管理和使用共享对象。
共享的粒度:根据实际情况,合理设计共享的粒度,过于细小的共享可能导致管理的复杂性增加,过于粗糙的共享可能无法充分利用内存。
实现复杂性:享元模式的实现可能增加系统的复杂性,尤其是在管理外部状态和共享对象的生命周期时,因此在设计时要权衡其带来的复杂性和内存性能的提升。
享元模式是处理大量相似对象时的一种有效策略,能够显著降低内存占用和提高性能。
七、在 Unity 中使用 享元模式
以下是一个基于 Unity 的享元模式示例,展示如何在场景中高效地创建和管理大量相似的 3D 对象,例如树木。我们将实现一个简单的树木工厂,该工厂使用享元模式来共享树木的类型和外观,以减少内存占用。
参考类图如下:
1. 定义享元类
首先,我们定义一个树木类型类 TreeType
,它包含树木的内部状态(如颜色和预制体)。
cs
using UnityEngine;
public class TreeType
{
public string Name { get; private set; }
public Color Color { get; private set; }
public GameObject Prefab { get; private set; }
public TreeType(string name, Color color, GameObject prefab)
{
Name = name;
Color = color;
Prefab = prefab;
}
public void Draw(Vector3 position)
{
GameObject tree = Object.Instantiate(Prefab, position, Quaternion.identity);
tree.GetComponent<Renderer>().material.color = Color;
}
}
2. 创建享元工厂
然后,我们实现一个工厂类 TreeFactory
,它负责创建和管理树木类型的实例。工厂会检查是否已经存在特定的树木类型,如果存在则重用。
cs
using System.Collections.Generic;
public class TreeFactory
{
private Dictionary<string, TreeType> treeTypes = new Dictionary<string, TreeType>();
public TreeType GetTreeType(string name, Color color, GameObject prefab)
{
if (!treeTypes.ContainsKey(name))
{
treeTypes[name] = new TreeType(name, color, prefab);
}
return treeTypes[name];
}
}
3. 客户端代码
接下来,我们编写一个脚本来使用 TreeFactory
创建树木。在 Unity 的场景中放置一个树木预制体并将其拖入 treePrefab
字段。
cs
using UnityEngine;
public class FlyweightExample : MonoBehaviour
{
public GameObject treePrefab; // 拖入树木预制体
private TreeFactory treeFactory;
void Start()
{
treeFactory = new TreeFactory();
// 创建不同颜色和类型的树木
CreateTrees();
}
private void CreateTrees()
{
for (int i = 0; i < 10; i++)
{
// 创建橡树
TreeType oakTree = treeFactory.GetTreeType("Oak", Color.green, treePrefab);
oakTree.Draw(new Vector3(i * 2, 0, 0));
// 创建松树
TreeType pineTree = treeFactory.GetTreeType("Pine", Color.red, treePrefab);
pineTree.Draw(new Vector3(i * 2, 0, 2));
}
}
}
4. 场景设置
- 树木预制体 :在 Unity 中创建一个树木的预制体(例如使用一个简单的立方体或球体),然后将其拖入
FlyweightExample
脚本的treePrefab
字段。
5、运行示例
- 将
FlyweightExample
脚本添加到一个空的 GameObject。 - 运行场景,你将看到两种类型的树木(橡树和松树)在场景中生成,使用享元模式有效地减少了内存使用。
这个示例展示了如何在 Unity 中利用享元模式来优化对象管理,使得开发者能够更高效地处理复杂场景中的对象,通过共享树木类型的实例,降低了内存占用,允许大量相似对象的高效创建和管理,该模式在需要处理大量相似对象的场景中表现出色,尤其是在游戏开发中。
八、代理模式(Proxy Pattern)
代理模式(Proxy Pattern) 是一种结构型设计模式,通过为另一个对象提供一个代理以控制对该对象的访问。代理对象可以在客户端和真实对象之间充当中介,负责访问控制、延迟加载、安全检查等功能。
代理模式通常分为以下几种类型:
- 虚拟代理(Virtual Proxy):用于延迟创建和初始化资源,只有在需要时才创建真实对象。
- 保护代理(Protection Proxy):控制对真实对象的访问,提供安全性和权限管理。
- 远程代理(Remote Proxy):用于表示位于不同地址空间的对象,常用于网络通信。
1、什么时候使用代理模式
需要控制访问:当需要控制对真实对象的访问权限时,使用保护代理可以有效实现。
资源消耗较大:在创建和初始化资源较为昂贵的情况下,虚拟代理可以延迟加载这些资源。
远程对象:当对象位于远程位置,需要进行网络通信时,使用远程代理可以简化客户端的使用。
附加功能需求:需要在不修改真实对象代码的情况下,为其添加一些额外功能时,可以使用代理模式。
2、使用代理模式的好处
控制访问:通过代理可以对真实对象的访问进行控制,提供更好的安全性和权限管理。
延迟加载:虚拟代理可以延迟对象的创建和初始化,从而减少资源的消耗和提高性能。
透明性:客户端可以像使用真实对象一样使用代理对象,代理的存在对客户端是透明的。
职责分离:将实际操作和代理操作分开,使得系统的职责更加清晰,有助于维护和扩展。
增强功能:可以在代理中添加额外的功能,如日志记录、性能监控等,而不修改真实对象的代码。
3、使用代理模式时的注意事项
性能开销:代理的使用可能会引入额外的性能开销,特别是在频繁访问代理的情况下,需要谨慎设计。
维护复杂性:引入代理可能会增加系统的复杂性,特别是在代理和真实对象之间存在多层关系时。
实现透明性:确保代理的行为与真实对象尽可能一致,以保持对客户端的透明性。
对象生命周期管理:在虚拟代理的实现中,需要妥善管理对象的生命周期,以避免内存泄漏或资源浪费。
九、在 Unity 中使用 代理模式
以下是一个基于 Unity 的代理模式示例,展示如何使用虚拟代理来延迟加载和显示 3D 模型。这个示例通过代理类控制对真实 3D 模型的访问,以减少内存占用和提高性能。
参考类图如下:
1、定义组件接口IModel
首先,定义一个 IModel 接口,包含一个 Display
方法用于展示角色。
这个接口规定了角色的基本行为,即 Display
方法,表示角色将如何在场景中展示。
cs
public interface IModel
{
void Display();
}
2、定义真实对象
首先,我们定义一个真实对象类 RealModel
,它负责加载和显示 3D 模型。我们假设加载模型是一个耗时的操作。
cs
using UnityEngine;
public class RealModel :IModel
{
private string modelPath;
private GameObject model;
public RealModel(string path)
{
modelPath = path;
LoadModelFromDisk(); // 假设这是一个耗时的操作
}
private void LoadModelFromDisk()
{
Debug.Log($"Loading model from: {modelPath}");
// 这里可以使用 Resources.Load 或者其他方式加载模型
model = GameObject.CreatePrimitive(PrimitiveType.Cube); // 使用立方体作为示例
model.name = modelPath; // 设置模型名称
}
public void Display()
{
if (model != null)
{
Debug.Log($"Displaying model: {model.name}");
model.SetActive(true); // 激活模型显示
}
}
}
3、定义代理类
接下来,我们实现一个代理类 ProxyModel
,用于延迟加载和控制对真实模型的访问。
cs
public class ProxyModel : IModel
{
private IModel realModel;
private string modelPath;
public ProxyModel(string path)
{
modelPath = path;
}
public void Display()
{
if (realModel == null)
{
realModel = new RealModel(modelPath); // 延迟加载真实模型
}
realModel.Display();
}
}
4、客户端代码
我们编写一个客户端代码,在 Unity 场景中使用代理模式来加载和显示模型。
cs
using UnityEngine;
public class ProxyPatternExample : MonoBehaviour
{
void Start()
{
IModel proxyModel = new ProxyModel("My3DModel");
// 第一次显示,触发真实对象的加载
proxyModel.Display();
// 再次显示,使用已加载的真实对象
proxyModel.Display();
}
}
5、 运行示例
-
在 Unity 中 ,创建一个空的 GameObject,并将
ProxyPatternExample
脚本附加到该对象上。 -
运行场景 ,你会看到第一次调用
Display
方法时,真实模型被加载,而第二次调用时则直接显示,不再加载。
代理模式的实现 ,通过 ProxyModel
控制对 RealModel
的访问,实现延迟加载和资源管理**;适用场景** ,当加载 3D 模型或其他资源消耗较大时,代理模式能够优化性能,减少内存占用**;示例说明**,这个示例展示了如何在 Unity 中有效地使用代理模式,以提高资源管理的效率和灵活性。