8.2 工厂模式 (Factory) 8.2 工厂模式 (Factory) 工厂模式是一种创建型设计模式,它提供了一种创建对象的接口,但允许子类决定实例化哪个类。工厂模式将对象的创建逻辑从客户端代码中分离出来,从而提高代码的灵活性、可维护性和可扩展性。在Unity3D游戏开发中,工厂模式可以有效地管理游戏对象的创建,例如不同类型的敌人、武器、特效等,从而降低代码耦合度,提升游戏架构的清晰度和可扩展性。 8.2.1 工厂模式概述 在软件开发中,对象的创建是一个常见的操作。当对象的创建逻辑较为复杂,或者需要根据不同的条件创建不同的对象时,直接在客户端代码中进行对象创建会导致代码耦合度高、难以维护和扩展。
工厂模式是一种创建型设计模式,它提供了一种创建对象的接口,但允许子类决定实例化哪个类。工厂模式将对象的创建逻辑从客户端代码中分离出来,从而提高代码的灵活性、可维护性和可扩展性。在Unity3D游戏开发中,工厂模式可以有效地管理游戏对象的创建,例如不同类型的敌人、武器、特效等,从而降低代码耦合度,提升游戏架构的清晰度和可扩展性。
在软件开发中,对象的创建是一个常见的操作。当对象的创建逻辑较为复杂,或者需要根据不同的条件创建不同的对象时,直接在客户端代码中进行对象创建会导致代码耦合度高、难以维护和扩展。工厂模式应运而生,其核心思想是将对象的创建委托给一个专门的“工厂”类,客户端代码只需要与工厂类交互,而无需关心具体的对象创建细节。
工厂模式的主要优点包括:
解耦客户端代码和对象创建逻辑: 客户端代码不再直接依赖于具体的产品类,而是依赖于工厂接口或抽象工厂,降低了耦合度,提高了代码的灵活性。
提高代码的可维护性: 对象创建逻辑集中在工厂类中,当需要修改对象创建逻辑时,只需要修改工厂类,而无需修改客户端代码,降低了维护成本。
增强代码的可扩展性: 当需要添加新的产品类时,只需要添加新的工厂类或修改现有工厂类,而无需修改客户端代码,提高了系统的可扩展性。
隐藏对象创建的复杂性: 工厂类可以封装复杂的对象创建逻辑,客户端代码只需要简单地调用工厂方法即可获得所需的对象,降低了客户端代码的复杂度。
工厂模式的适用场景:
当一个类不知道它所必须创建的对象的类的时候。
当一个类希望由它的子类来指定它所创建的对象的类的时候。
当你想要封装对象创建过程,将客户端代码与具体类解耦的时候。
当你需要创建一系列相关或者相互依赖的对象集合的时候。
工厂模式主要有三种变体:简单工厂模式(Simple Factory)、工厂方法模式(Factory Method)和抽象工厂模式(Abstract Factory)。它们各有特点,适用于不同的场景。
简单工厂模式是最简单的一种工厂模式,它由一个工厂类负责创建所有产品类的实例。工厂类通常包含一个静态方法或实例方法,根据客户端提供的参数来决定创建哪个具体的产品类的实例。
结构图 (Mermaid):
代码示例 (Unity3D C#):
假设我们需要在Unity3D游戏中创建不同类型的敌人,例如近战敌人 (MeleeEnemy)、远程敌人 (RangedEnemy) 和飞行敌人 (FlyingEnemy)。
首先,定义一个敌人接口 IEnemy 和具体的敌人类:
// 敌人接口 public interface IEnemy { void Attack(); void Move(); GameObject EnemyGameObject { get; } // 用于获取实例化的GameObject } // 近战敌人 public class MeleeEnemy : IEnemy { public GameObject EnemyGameObject { get; private set; } public MeleeEnemy(GameObject prefab) { EnemyGameObject = GameObject.Instantiate(prefab); EnemyGameObject.name = "MeleeEnemy"; Debug.Log("MeleeEnemy Created!"); } public void Attack() { Debug.Log("MeleeEnemy Attack!"); } public void Move() { Debug.Log("MeleeEnemy Move!"); } } // 远程敌人 public class RangedEnemy : IEnemy { public GameObject EnemyGameObject { get; private set; } public RangedEnemy(GameObject prefab) { EnemyGameObject = GameObject.Instantiate(prefab); EnemyGameObject.name = "RangedEnemy"; Debug.Log("RangedEnemy Created!"); } public void Attack() { Debug.Log("RangedEnemy Ranged Attack!"); } public void Move() { Debug.Log("RangedEnemy Move!"); } } // 飞行敌人 public class FlyingEnemy : IEnemy { public GameObject EnemyGameObject { get; private set; } public FlyingEnemy(GameObject prefab) { EnemyGameObject = GameObject.Instantiate(prefab); EnemyGameObject.name = "FlyingEnemy"; Debug.Log("FlyingEnemy Created!"); } public void Attack() { Debug.Log("FlyingEnemy Air Attack!"); } public void Move() { Debug.Log("FlyingEnemy Fly!"); } }
接下来,创建简单工厂类 EnemyFactory:
using UnityEngine; public class EnemyFactory { public enum EnemyType { Melee, Ranged, Flying } // 敌人预制体,可以在Unity编辑器中设置 public GameObject meleeEnemyPrefab; public GameObject rangedEnemyPrefab; public GameObject flyingEnemyPrefab; public IEnemy CreateEnemy(EnemyType type) { switch (type) { case EnemyType.Melee: return new MeleeEnemy(meleeEnemyPrefab); case EnemyType.Ranged: return new RangedEnemy(rangedEnemyPrefab); case EnemyType.Flying: return new FlyingEnemy(flyingEnemyPrefab); default: return null; // 或者抛出异常 } } }
客户端代码 (Unity3D C#):
using UnityEngine; public class EnemySpawner : MonoBehaviour { public EnemyFactory enemyFactory; // 在Unity编辑器中拖拽EnemyFactory脚本所在GameObject void Start() { // 创建近战敌人 IEnemy meleeEnemy = enemyFactory.CreateEnemy(EnemyFactory.EnemyType.Melee); if (meleeEnemy != null) { meleeEnemy.EnemyGameObject.transform.position = new Vector3(0, 0, 0); meleeEnemy.Attack(); meleeEnemy.Move(); } // 创建远程敌人 IEnemy rangedEnemy = enemyFactory.CreateEnemy(EnemyFactory.EnemyType.Ranged); if (rangedEnemy != null) { rangedEnemy.EnemyGameObject.transform.position = new Vector3(5, 0, 0); rangedEnemy.Attack(); rangedEnemy.Move(); } // 创建飞行敌人 IEnemy flyingEnemy = enemyFactory.CreateEnemy(EnemyFactory.EnemyType.Flying); if (flyingEnemy != null) { flyingEnemy.EnemyGameObject.transform.position = new Vector3(-5, 0, 0); flyingEnemy.Attack(); flyingEnemy.Move(); } } }
简单工厂模式的优缺点:
优点:
易于理解和实现,代码简洁。
将对象创建逻辑集中管理,降低了客户端代码的复杂度。
缺点:
工厂类职责过重,违反了单一职责原则。
当需要添加新的产品类时,需要修改工厂类,违反了开闭原则。
不利于扩展复杂的对象创建逻辑。
简单工厂模式适用于产品类较少,创建逻辑相对简单的场景。在Unity3D中,如果只是简单地根据类型创建不同的游戏对象,可以使用简单工厂模式。
工厂方法模式定义了一个创建对象的接口,但将实际的对象创建延迟到子类中进行。每个具体工厂类负责创建一种或多种相关产品类的实例。工厂方法模式克服了简单工厂模式违反开闭原则的缺点,提高了系统的可扩展性。
结构图 (Mermaid):
代码示例 (Unity3D C#):
继续使用敌人类型的例子,我们使用工厂方法模式来创建敌人。
首先,定义抽象工厂类 EnemyFactory 和具体工厂类 MeleeEnemyFactory, RangedEnemyFactory, FlyingEnemyFactory:
using UnityEngine; // 抽象工厂类 public abstract class EnemyFactory { public abstract IEnemy CreateEnemy(); } // 近战敌人工厂 public class MeleeEnemyFactory : EnemyFactory { public GameObject meleeEnemyPrefab; // 在Unity编辑器中设置 public MeleeEnemyFactory(GameObject prefab) { meleeEnemyPrefab = prefab; } public override IEnemy CreateEnemy() { return new MeleeEnemy(meleeEnemyPrefab); } } // 远程敌人工厂 public class RangedEnemyFactory : EnemyFactory { public GameObject rangedEnemyPrefab; // 在Unity编辑器中设置 public RangedEnemyFactory(GameObject prefab) { rangedEnemyPrefab = prefab; } public override IEnemy CreateEnemy() { return new RangedEnemy(rangedEnemyPrefab); } } // 飞行敌人工厂 public class FlyingEnemyFactory : EnemyFactory { public GameObject flyingEnemyPrefab; // 在Unity编辑器中设置 public FlyingEnemyFactory(GameObject prefab) { flyingEnemyPrefab = prefab; } public override IEnemy CreateEnemy() { return new FlyingEnemy(flyingEnemyPrefab); } }
客户端代码 (Unity3D C#):
using UnityEngine; public class EnemySpawnerFactoryMethod : MonoBehaviour { public MeleeEnemyFactory meleeEnemyFactory; // 在Unity编辑器中拖拽各个工厂脚本所在GameObject public RangedEnemyFactory rangedEnemyFactory; public FlyingEnemyFactory flyingEnemyFactory; void Start() { // 创建近战敌人 IEnemy meleeEnemy = meleeEnemyFactory.CreateEnemy(); if (meleeEnemy != null) { meleeEnemy.EnemyGameObject.transform.position = new Vector3(0, 0, 0); meleeEnemy.Attack(); meleeEnemy.Move(); } // 创建远程敌人 IEnemy rangedEnemy = rangedEnemyFactory.CreateEnemy(); if (rangedEnemy != null) { rangedEnemy.EnemyGameObject.transform.position = new Vector3(5, 0, 0); rangedEnemy.Attack(); rangedEnemy.Move(); } // 创建飞行敌人 IEnemy flyingEnemy = flyingEnemyFactory.CreateEnemy(); if (flyingEnemy != null) { flyingEnemy.EnemyGameObject.transform.position = new Vector3(-5, 0, 0); flyingEnemy.Attack(); flyingEnemy.Move(); } } }
工厂方法模式的优缺点:
优点:
遵循开闭原则,易于扩展新的产品类,只需要添加新的具体工厂类即可。
每个具体工厂类只负责创建一种或多种相关产品,符合单一职责原则。
可以将对象创建的逻辑延迟到运行时,更加灵活。
缺点:
系统中类的数量增加,增加了系统的复杂度。
客户端代码需要知道具体工厂类的名称,并选择合适的工厂类。
工厂方法模式适用于产品类较多,需要扩展新产品类,且对象创建逻辑相对复杂的场景。在Unity3D中,如果需要创建多种类型的游戏对象,并且未来可能需要添加新的类型,可以使用工厂方法模式。
抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂模式进一步将工厂抽象化,可以创建一组相关的产品族,例如不同风格的UI元素 (按钮、文本框、窗口) 或不同类型的游戏世界元素 (角色、场景道具、特效)。
结构图 (Mermaid):
代码示例 (Unity3D C#):
假设我们需要创建不同风格的游戏UI元素,例如 "现代风格" (Modern) 和 "复古风格" (Retro)。每种风格都包含按钮 (Button) 和文本框 (TextField) 两种UI元素。
首先,定义抽象产品接口 IButton, ITextField 和抽象工厂接口 IUIFactory:
using UnityEngine; using UnityEngine.UI; // 抽象按钮接口 public interface IButton { GameObject ButtonGameObject { get; } void OnClick(); } // 抽象文本框接口 public interface ITextField { GameObject TextFieldGameObject { get; } void SetText(string text); } // 抽象UI工厂接口 public interface IUIFactory { IButton CreateButton(); ITextField CreateTextField(); }
接下来,创建具体产品类 ModernButton, ModernTextField, RetroButton, RetroTextField 和具体工厂类 ModernUIFactory, RetroUIFactory:
// 现代风格按钮 public class ModernButton : IButton { public GameObject ButtonGameObject { get; private set; } public ModernButton(GameObject prefab) { ButtonGameObject = GameObject.Instantiate(prefab); ButtonGameObject.name = "ModernButton"; ButtonGameObject.GetComponentInChildren<Text>().text = "Modern Button"; Debug.Log("ModernButton Created!"); } public void OnClick() { Debug.Log("ModernButton Clicked!"); } } // 现代风格文本框 public class ModernTextField : ITextField { public GameObject TextFieldGameObject { get; private set; } public ModernTextField(GameObject prefab) { TextFieldGameObject = GameObject.Instantiate(prefab); TextFieldGameObject.name = "ModernTextField"; Debug.Log("ModernTextField Created!"); } public void SetText(string text) { TextFieldGameObject.GetComponentInChildren<Text>().text = text; } } // 复古风格按钮 public class RetroButton : IButton { public GameObject ButtonGameObject { get; private set; } public RetroButton(GameObject prefab) { ButtonGameObject = GameObject.Instantiate(prefab); ButtonGameObject.name = "RetroButton"; ButtonGameObject.GetComponentInChildren<Text>().text = "Retro Button"; Debug.Log("RetroButton Created!"); } public void OnClick() { Debug.Log("RetroButton Clicked!"); } } // 复古风格文本框 public class RetroTextField : ITextField { public GameObject TextFieldGameObject { get; private set; } public RetroTextField(GameObject prefab) { TextFieldGameObject = GameObject.Instantiate(prefab); TextFieldGameObject.name = "RetroTextField"; Debug.Log("RetroTextField Created!"); } public void SetText(string text) { TextFieldGameObject.GetComponentInChildren<Text>().text = text; } } // 现代风格UI工厂 public class ModernUIFactory : IUIFactory { public GameObject modernButtonPrefab; // 在Unity编辑器中设置 public GameObject modernTextFieldPrefab; // 在Unity编辑器中设置 public ModernUIFactory(GameObject buttonPrefab, GameObject textFieldPrefab) { modernButtonPrefab = buttonPrefab; modernTextFieldPrefab = textFieldPrefab; } public IButton CreateButton() { return new ModernButton(modernButtonPrefab); } public ITextField CreateTextField() { return new ModernTextField(modernTextFieldPrefab); } } // 复古风格UI工厂 public class RetroUIFactory : IUIFactory { public GameObject retroButtonPrefab; // 在Unity编辑器中设置 public GameObject retroTextFieldPrefab; // 在Unity编辑器中设置 public RetroUIFactory(GameObject buttonPrefab, GameObject textFieldPrefab) { retroButtonPrefab = buttonPrefab; retroTextFieldPrefab = textFieldPrefab; } public IButton CreateButton() { return new RetroButton(retroButtonPrefab); } public ITextField CreateTextField() { return new RetroTextField(retroTextFieldPrefab); } }
客户端代码 (Unity3D C#):
using UnityEngine; public class UIFactoryClient : MonoBehaviour { public ModernUIFactory modernUIFactory; // 在Unity编辑器中拖拽工厂脚本所在GameObject public RetroUIFactory retroUIFactory; void Start() { // 使用现代风格工厂创建UI元素 IButton modernButton = modernUIFactory.CreateButton(); if (modernButton != null) { modernButton.ButtonGameObject.transform.position = new Vector3(0, 1, 0); modernButton.OnClick(); } ITextField modernTextField = modernUIFactory.CreateTextField(); if (modernTextField != null) { modernTextField.TextFieldGameObject.transform.position = new Vector3(0, 0, 0); modernTextField.SetText("Modern Text Field"); } // 使用复古风格工厂创建UI元素 IButton retroButton = retroUIFactory.CreateButton(); if (retroButton != null) { retroButton.ButtonGameObject.transform.position = new Vector3(0, -1, 0); retroButton.OnClick(); } ITextField retroTextField = retroUIFactory.CreateTextField(); if (retroTextField != null) { retroTextField.TextFieldGameObject.transform.position = new Vector3(0, -2, 0); retroTextField.SetText("Retro Text Field"); } } }
抽象工厂模式的优缺点:
优点:
隔离了客户端代码与产品族的实现细节。
易于更换产品族,只需要更换具体工厂类即可。
保证了产品族的一致性,创建的产品属于同一个产品族。
缺点:
难以扩展新的产品族,如果需要添加新的产品族,需要修改抽象工厂接口和所有的具体工厂类,违反了开闭原则。
系统中类的数量增加,系统结构更加复杂。
抽象工厂模式适用于需要创建一组相关或相互依赖的产品族,并且需要保证产品族一致性的场景。在Unity3D中,如果需要创建不同风格的游戏元素,例如不同主题的场景、角色、UI等,可以使用抽象工厂模式。
工厂模式在Unity3D游戏开发中有着广泛的应用,以下列举一些常见的应用场景:
对象池管理: 可以使用工厂模式结合对象池技术,创建和管理游戏对象,例如子弹、特效、敌人等。工厂负责创建新的对象,对象池负责回收和复用对象,提高游戏性能。
资源加载管理: 可以使用工厂模式加载不同类型的资源,例如模型、纹理、音频等。工厂根据资源类型和路径,加载对应的资源,并返回资源对象。
AI角色创建: 可以使用工厂模式创建不同类型的AI角色,例如巡逻兵、攻击型敌人、辅助型角色等。工厂根据角色类型,创建对应的AI角色,并赋予相应的行为逻辑。
UI元素创建: 可以使用工厂模式创建不同类型的UI元素,例如按钮、文本框、滑动条等。工厂根据UI元素类型,创建对应的UI元素,并设置相应的属性和事件。
关卡元素生成: 可以使用工厂模式根据关卡设计生成各种关卡元素,例如障碍物、道具、敌人等。工厂根据关卡数据,创建相应的关卡元素,并放置到场景中。
实践建议:
根据实际需求选择合适的工厂模式: 简单工厂适用于简单场景,工厂方法适用于扩展性要求较高的场景,抽象工厂适用于产品族管理场景。
合理设计工厂接口和产品接口: 接口应该足够抽象,能够适应未来的变化。
结合Unity3D的特性进行优化: 例如使用预制体 (Prefab) 加速对象实例化,使用对象池 (Object Pooling) 提高性能。
避免过度设计: 工厂模式虽然强大,但也增加了代码的复杂度。只有在真正需要解耦和扩展性的场景下才使用工厂模式。