面向对象编程(OOP)中的七大设计原则是指导开发者构建可维护、可扩展软件的核心指南。这些原则帮助减少代码耦合,提高复用性,并在实际项目中避免常见问题。本文基于经典原则进行说明,并在每个原则后添加少量C#示例,以帮助读者更好地理解应用方式。
1. 单一职责原则(Single Responsibility Principle, SRP)
每一个类应该专注于做一件事情。一个类只有一个引起它变化的原因。
- 职责分离:每个类只负责一个功能领域
- 变化轴线:每个职责都是变化的一个轴线
- 高内聚:类内部元素紧密相关
- 低耦合:减少类之间的依赖关系
示例:
违反单一职责原则的示例
/// <summary>
/// 违反单一职责原则的玩家类
/// 同时负责玩家数据和UI显示
/// </summary>
public class BadPlayer
{
public string playerName;
public int health;
public int level;
public float experience;
// 数据管理职责
public void SavePlayerData()
{
// 保存玩家数据到文件
string data = $"{playerName},{health},{level},{experience}";
File.WriteAllText("player.txt", data);
}
public void LoadPlayerData()
{
// 从文件加载玩家数据
string data = File.ReadAllText("player.txt");
string[] parts = data.Split(',');
playerName = parts[0];
health = int.Parse(parts[1]);
level = int.Parse(parts[2]);
experience = float.Parse(parts[3]);
}
// UI显示职责 - 违反单一职责原则
public void DisplayPlayerInfo()
{
Debug.Log($"玩家: {playerName}, 血量: {health}, 等级: {level}");
}
public void UpdateHealthBar()
{
// 更新血量条UI
Debug.Log($"更新血量条: {health}/100");
}
}
遵循单一职责原则的示例
/// <summary>
/// 玩家数据类 - 只负责数据管理
/// </summary>
public class PlayerData
{
public string PlayerName { get; set; }
public int Health { get; set; }
public int Level { get; set; }
public float Experience { get; set; }
public void Save()
{
var data = new PlayerDataModel
{
playerName = PlayerName,
health = Health,
level = Level,
experience = Experience
};
string json = JsonUtility.ToJson(data);
File.WriteAllText("player.txt", json);
}
public void Load()
{
if (File.Exists("player.txt"))
{
string json = File.ReadAllText("player.txt");
var data = JsonUtility.FromJson<PlayerDataModel>(json);
PlayerName = data.playerName;
Health = data.health;
Level = data.level;
Experience = data.experience;
}
}
}
[System.Serializable]
public class PlayerDataModel
{
public string playerName;
public int health;
public int level;
public float experience;
}
/// <summary>
/// 玩家UI类 - 只负责UI显示
/// </summary>
public class PlayerUI : MonoBehaviour
{
[SerializeField] private Text playerNameText;
[SerializeField] private Slider healthSlider;
[SerializeField] private Text levelText;
public void UpdatePlayerInfo(PlayerData playerData)
{
playerNameText.text = playerData.PlayerName;
healthSlider.value = playerData.Health / 100f;
levelText.text = $"等级: {playerData.Level}";
}
}
游戏开发应用
-
UI系统:将UI逻辑和数据逻辑分离
-
音频系统:音频播放和音频资源管理分离
-
场景管理:场景加载和场景数据管理分离
2. 里氏替换原则(Liskov Substitution Principle, LSP)
子类型必须能够替换掉它们的父类型。超类存在的地方,子类是可以替换的。
-
可替换性:子类对象可以替换父类对象
-
行为一致性:子类不应改变父类的预期行为
-
前置条件:子类方法的前置条件不应比父类更严格
-
后置条件:子类方法的后置条件不应比父类更宽松
示例:
违反里氏替换原则的示例
/// <summary>
/// 基础武器类
/// </summary>
public abstract class Weapon
{
protected int damage;
protected float range;
public virtual void Attack()
{
Debug.Log($"造成 {damage} 点伤害");
}
public virtual void Reload()
{
Debug.Log("重新装弹");
}
}
/// <summary>
/// 违反LSP的魔法武器类
/// </summary>
public class MagicWeapon : Weapon
{
private int manaCost;
public override void Attack()
{
if (manaCost > 0)
{
Debug.Log($"消耗 {manaCost} 魔法值,造成 {damage} 点伤害");
}
else
{
throw new InvalidOperationException("魔法值不足,无法攻击");
}
}
// 魔法武器不需要装弹,但抛出了异常
public override void Reload()
{
throw new NotSupportedException("魔法武器不需要装弹");
}
}
遵循里氏替换原则的示例
/// <summary>
/// 武器接口 - 定义基本契约
/// </summary>
public interface IWeapon
{
void Attack();
bool CanAttack();
}
/// <summary>
/// 可装弹武器接口
/// </summary>
public interface IReloadableWeapon : IWeapon
{
void Reload();
bool NeedsReload();
}
/// <summary>
/// 魔法武器接口
/// </summary>
public interface IMagicWeapon : IWeapon
{
void ConsumeMana(int amount);
bool HasEnoughMana(int amount);
}
/// <summary>
/// 步枪类 - 遵循LSP
/// </summary>
public class Rifle : IReloadableWeapon
{
private int ammo;
private int maxAmmo = 30;
public void Attack()
{
if (CanAttack())
{
ammo--;
Debug.Log($"步枪射击,剩余弹药: {ammo}");
}
}
public bool CanAttack()
{
return ammo > 0;
}
public void Reload()
{
ammo = maxAmmo;
Debug.Log("步枪重新装弹");
}
public bool NeedsReload()
{
return ammo == 0;
}
}
/// <summary>
/// 魔法杖类 - 遵循LSP
/// </summary>
public class MagicStaff : IMagicWeapon
{
private int mana = 100;
private int manaCost = 10;
public void Attack()
{
if (CanAttack())
{
ConsumeMana(manaCost);
Debug.Log($"魔法攻击,消耗 {manaCost} 魔法值");
}
}
public bool CanAttack()
{
return HasEnoughMana(manaCost);
}
public void ConsumeMana(int amount)
{
mana -= amount;
Debug.Log($"消耗 {amount} 魔法值,剩余: {mana}");
}
public bool HasEnoughMana(int amount)
{
return mana >= amount;
}
}
游戏开发应用
-
敌人AI系统:不同敌人类型可以替换基础敌人
-
UI组件系统:各种UI组件可以替换基础UI组件
-
音频系统:不同音频源可以替换基础音频源
3. 依赖倒置原则(Dependence Inversion Principle, DIP)
高层模块不应该依赖低层模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。
-
面向接口编程:依赖抽象而不是具体实现
-
控制反转:让框架控制程序流程
-
依赖注入:通过构造函数或方法注入依赖
-
解耦设计:减少模块间的直接依赖
示例
违反依赖倒置原则的示例
/// <summary>
/// 违反DIP的游戏管理器
/// 直接依赖具体的实现类
/// </summary>
public class BadGameManager : MonoBehaviour
{
// 直接依赖具体的实现
private FileDataManager fileDataManager;
private NetworkManager networkManager;
void Start()
{
// 紧耦合,难以测试和扩展
fileDataManager = new FileDataManager();
networkManager = new NetworkManager();
}
public void SaveGame()
{
// 直接调用具体实现
fileDataManager.SaveToFile("game.dat");
}
public void SendScore()
{
// 直接调用具体实现
networkManager.SendToServer("score");
}
}
/// <summary>
/// 文件数据管理器
/// </summary>
public class FileDataManager
{
public void SaveToFile(string filename)
{
Debug.Log($"保存到文件: {filename}");
}
}
/// <summary>
/// 网络管理器
/// </summary>
public class NetworkManager
{
public void SendToServer(string data)
{
Debug.Log($"发送到服务器: {data}");
}
}
遵循依赖倒置原则的示例
/// <summary>
/// 数据存储接口 - 抽象
/// </summary>
public interface IDataStorage
{
void Save(string key, string data);
string Load(string key);
}
/// <summary>
/// 网络通信接口 - 抽象
/// </summary>
public interface INetworkService
{
void Send(string data);
void OnDataReceived(string data);
}
/// <summary>
/// 遵循DIP的游戏管理器
/// </summary>
public class GameManager : MonoBehaviour
{
// 依赖抽象接口
private IDataStorage dataStorage;
private INetworkService networkService;
// 构造函数注入依赖
public void Initialize(IDataStorage storage, INetworkService network)
{
dataStorage = storage;
networkService = network;
}
public void SaveGame()
{
// 使用抽象接口,不关心具体实现
dataStorage.Save("game_data", "玩家进度数据");
}
public void SendScore(int score)
{
// 使用抽象接口,不关心具体实现
networkService.Send($"score:{score}");
}
}
/// <summary>
/// 文件存储实现
/// </summary>
public class FileStorage : IDataStorage
{
public void Save(string key, string data)
{
string filename = $"{key}.dat";
File.WriteAllText(filename, data);
Debug.Log($"保存到文件: {filename}");
}
public string Load(string key)
{
string filename = $"{key}.dat";
if (File.Exists(filename))
{
return File.ReadAllText(filename);
}
return null;
}
}
/// <summary>
/// 内存存储实现
/// </summary>
public class MemoryStorage : IDataStorage
{
private Dictionary<string, string> storage = new Dictionary<string, string>();
public void Save(string key, string data)
{
storage[key] = data;
Debug.Log($"保存到内存: {key}");
}
public string Load(string key)
{
return storage.ContainsKey(key) ? storage[key] : null;
}
}
/// <summary>
/// HTTP网络服务实现
/// </summary>
public class HttpNetworkService : INetworkService
{
public void Send(string data)
{
Debug.Log($"HTTP发送: {data}");
// 实际的HTTP请求实现
}
public void OnDataReceived(string data)
{
Debug.Log($"HTTP接收: {data}");
}
}
游戏开发应用
-
音频系统:音频播放器依赖音频接口,而不是具体音频实现
-
输入系统:游戏逻辑依赖输入接口,支持多种输入设备
-
渲染系统:渲染管理器依赖渲染接口,支持不同渲染管线
4. 接口隔离原则(Interface Segregation Principle, ISP)
客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。
-
接口细化:将大接口拆分为多个小接口
-
按需依赖:只依赖需要的接口
-
避免臃肿:防止接口过于庞大
-
适度设计:平衡接口数量和复杂度
示例
违反接口隔离原则的示例
/// <summary>
/// 违反ISP的庞大接口
/// 包含了太多不相关的方法
/// </summary>
public interface IBadGameObject
{
// 移动相关
void Move(Vector3 direction);
void Rotate(float angle);
// 渲染相关
void SetMaterial(Material material);
void SetColor(Color color);
// 物理相关
void AddForce(Vector3 force);
void SetGravity(bool enabled);
// 音频相关
void PlaySound(string soundName);
void SetVolume(float volume);
// 网络相关
void SendNetworkMessage(string message);
void OnNetworkMessage(string message);
// 动画相关
void PlayAnimation(string animationName);
void StopAnimation();
}
遵循接口隔离原则的示例
/// <summary>
/// 移动接口 - 只负责移动功能
/// </summary>
public interface IMovable
{
void Move(Vector3 direction);
void Rotate(float angle);
Vector3 Position { get; set; }
Quaternion Rotation { get; set; }
}
/// <summary>
/// 渲染接口 - 只负责渲染功能
/// </summary>
public interface IRenderable
{
void SetMaterial(Material material);
void SetColor(Color color);
void SetActive(bool active);
}
/// <summary>
/// 物理接口 - 只负责物理功能
/// </summary>
public interface IPhysicsObject
{
void AddForce(Vector3 force);
void SetGravity(bool enabled);
Rigidbody Rigidbody { get; }
}
/// <summary>
/// 音频接口 - 只负责音频功能
/// </summary>
public interface IAudioSource
{
void PlaySound(string soundName);
void SetVolume(float volume);
void StopSound();
}
/// <summary>
/// 网络接口 - 只负责网络功能
/// </summary>
public interface INetworkObject
{
void SendNetworkMessage(string message);
void OnNetworkMessage(string message);
int NetworkId { get; }
}
/// <summary>
/// 动画接口 - 只负责动画功能
/// </summary>
public interface IAnimatable
{
void PlayAnimation(string animationName);
void StopAnimation();
bool IsAnimationPlaying { get; }
}
/// <summary>
/// 玩家类 - 只实现需要的接口
/// </summary>
public class Player : MonoBehaviour, IMovable, IRenderable, IAudioSource
{
[SerializeField] private AudioSource audioSource;
public Vector3 Position
{
get => transform.position;
set => transform.position = value;
}
public Quaternion Rotation
{
get => transform.rotation;
set => transform.rotation = value;
}
public void Move(Vector3 direction)
{
transform.Translate(direction);
}
public void Rotate(float angle)
{
transform.Rotate(0, angle, 0);
}
public void SetMaterial(Material material)
{
GetComponent<Renderer>().material = material;
}
public void SetColor(Color color)
{
GetComponent<Renderer>().material.color = color;
}
public void SetActive(bool active)
{
gameObject.SetActive(active);
}
public void PlaySound(string soundName)
{
AudioClip clip = Resources.Load<AudioClip>(soundName);
audioSource.PlayOneShot(clip);
}
public void SetVolume(float volume)
{
audioSource.volume = volume;
}
public void StopSound()
{
audioSource.Stop();
}
}
/// <summary>
/// 静态装饰物类 - 只需要渲染功能
/// </summary>
public class Decoration : MonoBehaviour, IRenderable
{
public void SetMaterial(Material material)
{
GetComponent<Renderer>().material = material;
}
public void SetColor(Color color)
{
GetComponent<Renderer>().material.color = color;
}
public void SetActive(bool active)
{
gameObject.SetActive(active);
}
}
游戏开发应用
-
UI系统:按钮只需要点击接口,不需要拖拽接口
-
敌人系统:不同敌人类型实现不同的接口组合
-
特效系统:粒子特效只需要播放接口,不需要移动接口
5. 迪米特法则(Law of Demeter, LoD)
一个对象应该对其他对象保持最少的了解。只与直接的朋友通信。
-
最少知识:对象不应该知道其他对象的内部结构
-
直接朋友:只与成员变量、方法参数、方法返回值中的类通信
-
避免链式调用:减少a.b.c.d这样的调用
-
中介模式:通过中介对象来减少直接依赖
示例
违反迪米特法则的示例
/// <summary>
/// 违反LoD的游戏管理器
/// 直接访问深层嵌套对象
/// </summary>
public class BadGameManager : MonoBehaviour
{
public void UpdatePlayerHealth()
{
// 违反迪米特法则:直接访问深层嵌套对象
var player = FindObjectOfType<Player>();
if (player != null && player.healthComponent != null &&
player.healthComponent.currentHealth < player.healthComponent.maxHealth)
{
player.healthComponent.currentHealth = player.healthComponent.maxHealth;
player.healthComponent.healthBar.UpdateHealthBar(player.healthComponent.currentHealth);
player.healthComponent.healthBar.healthText.text = player.healthComponent.currentHealth.ToString();
}
}
}
/// <summary>
/// 玩家类
/// </summary>
public class Player : MonoBehaviour
{
public HealthComponent healthComponent;
}
/// <summary>
/// 血量组件
/// </summary>
public class HealthComponent : MonoBehaviour
{
public int currentHealth;
public int maxHealth;
public HealthBar healthBar;
}
/// <summary>
/// 血量条UI
/// </summary>
public class HealthBar : MonoBehaviour
{
public Text healthText;
public void UpdateHealthBar(int health)
{
// 更新血量条逻辑
}
}
遵循迪米特法则的示例
/// <summary>
/// 遵循LoD的游戏管理器
/// 只与直接朋友通信
/// </summary>
public class GameManager : MonoBehaviour
{
private Player player;
void Start()
{
player = FindObjectOfType<Player>();
}
public void HealPlayer()
{
// 只与直接朋友Player通信
if (player != null)
{
player.FullHeal();
}
}
public void UpdatePlayerUI()
{
// 只与直接朋友Player通信
if (player != null)
{
player.UpdateUI();
}
}
}
/// <summary>
/// 改进的玩家类 - 封装内部逻辑
/// </summary>
public class Player : MonoBehaviour
{
private HealthComponent healthComponent;
void Start()
{
healthComponent = GetComponent<HealthComponent>();
}
/// <summary>
/// 提供给外部的简单接口
/// </summary>
public void FullHeal()
{
healthComponent?.RestoreFullHealth();
}
/// <summary>
/// 更新UI的简单接口
/// </summary>
public void UpdateUI()
{
healthComponent?.UpdateHealthDisplay();
}
/// <summary>
/// 获取血量信息的简单接口
/// </summary>
public bool IsHealthLow()
{
return healthComponent?.IsLowHealth() ?? false;
}
}
/// <summary>
/// 改进的血量组件 - 封装内部实现
/// </summary>
public class HealthComponent : MonoBehaviour
{
[SerializeField] private int currentHealth = 100;
[SerializeField] private int maxHealth = 100;
[SerializeField] private HealthBar healthBar;
/// <summary>
/// 恢复满血
/// </summary>
public void RestoreFullHealth()
{
currentHealth = maxHealth;
UpdateHealthDisplay();
}
/// <summary>
/// 更新血量显示
/// </summary>
public void UpdateHealthDisplay()
{
healthBar?.UpdateHealthBar(currentHealth, maxHealth);
}
/// <summary>
/// 检查是否血量过低
/// </summary>
public bool IsLowHealth()
{
return currentHealth < maxHealth * 0.3f;
}
}
/// <summary>
/// 改进的血量条UI - 封装UI逻辑
/// </summary>
public class HealthBar : MonoBehaviour
{
[SerializeField] private Slider healthSlider;
[SerializeField] private Text healthText;
/// <summary>
/// 更新血量条显示
/// </summary>
public void UpdateHealthBar(int current, int max)
{
if (healthSlider != null)
{
healthSlider.value = (float)current / max;
}
if (healthText != null)
{
healthText.text = $"{current}/{max}";
}
}
}
游戏开发应用
-
UI系统:UI管理器不直接操作具体的UI组件
-
音频系统:音频管理器通过简单的接口控制音频
-
场景管理:场景管理器不直接操作场景中的具体对象
6. 开闭原则(Open Close Principle, OCP)
软件实体应该对扩展开放,对修改关闭。
-
扩展开放:可以通过添加新功能来扩展系统
-
修改关闭:不需要修改现有代码
-
抽象设计:使用抽象类或接口
-
多态机制:利用多态实现扩展
示例
违反开闭原则的示例
/// <summary>
/// 违反OCP的伤害计算系统
/// 每次添加新武器类型都需要修改现有代码
/// </summary>
public class BadDamageCalculator
{
public float CalculateDamage(string weaponType, int baseDamage)
{
// 违反开闭原则:需要修改现有代码来添加新类型
switch (weaponType)
{
case "Sword":
return baseDamage * 1.2f; // 剑类武器加成
case "Bow":
return baseDamage * 1.1f; // 弓箭类武器加成
case "Staff":
return baseDamage * 1.3f; // 法杖类武器加成
// 添加新武器类型需要修改这里的代码
default:
return baseDamage;
}
}
}
遵循开闭原则的示例
/// <summary>
/// 武器抽象基类 - 对扩展开放
/// </summary>
public abstract class Weapon
{
public abstract string WeaponType { get; }
public abstract float CalculateDamage(int baseDamage);
public abstract void Attack();
}
/// <summary>
/// 剑类武器 - 扩展实现
/// </summary>
public class Sword : Weapon
{
public override string WeaponType => "Sword";
public override float CalculateDamage(int baseDamage)
{
return baseDamage * 1.2f; // 剑类武器加成
}
public override void Attack()
{
Debug.Log("挥舞剑进行攻击");
}
}
/// <summary>
/// 弓箭类武器 - 扩展实现
/// </summary>
public class Bow : Weapon
{
public override string WeaponType => "Bow";
public override float CalculateDamage(int baseDamage)
{
return baseDamage * 1.1f; // 弓箭类武器加成
}
public override void Attack()
{
Debug.Log("拉弓射箭");
}
}
/// <summary>
/// 法杖类武器 - 扩展实现
/// </summary>
public class Staff : Weapon
{
public override string WeaponType => "Staff";
public override float CalculateDamage(int baseDamage)
{
return baseDamage * 1.3f; // 法杖类武器加成
}
public override void Attack()
{
Debug.Log("释放魔法");
}
}
/// <summary>
/// 遵循OCP的伤害计算系统
/// 不需要修改现有代码就能添加新武器类型
/// </summary>
public class DamageCalculator
{
public float CalculateDamage(Weapon weapon, int baseDamage)
{
// 对修改关闭:不需要修改这个方法
return weapon.CalculateDamage(baseDamage);
}
}
/// <summary>
/// 战斗系统 - 使用开闭原则
/// </summary>
public class CombatSystem : MonoBehaviour
{
private DamageCalculator damageCalculator = new DamageCalculator();
public void PerformAttack(Weapon weapon, int baseDamage)
{
// 对修改关闭:不需要修改这个方法来支持新武器
float finalDamage = damageCalculator.CalculateDamage(weapon, baseDamage);
weapon.Attack();
Debug.Log($"造成 {finalDamage} 点伤害");
}
}
/// <summary>
/// 新武器类型 - 扩展示例
/// 添加新武器不需要修改现有代码
/// </summary>
public class Dagger : Weapon
{
public override string WeaponType => "Dagger";
public override float CalculateDamage(int baseDamage)
{
return baseDamage * 0.9f; // 匕首类武器加成
}
public override void Attack()
{
Debug.Log("快速刺击");
}
}
游戏开发应用
-
敌人AI系统:通过继承添加新的敌人类型
-
特效系统:通过接口扩展新的特效类型
-
音频系统:通过抽象类添加新的音频源类型
7. 组合/聚合复用原则(Composite/Aggregate Reuse Principle, CARP)
尽量使用合成/聚合达到复用,尽量少用继承。
-
组合优于继承:使用"has-a"关系而不是"is-a"关系
-
灵活性:运行时可以改变组合关系
-
解耦设计:减少类之间的紧密耦合
-
单一职责:每个类专注于自己的功能
示例
过度使用继承的示例
/// <summary>
/// 违反CARP的继承设计
/// 使用继承来复用功能,导致类层次复杂
/// </summary>
public abstract class BadGameObject
{
public Vector3 position;
public virtual void Update() { }
}
public class BadPlayer : BadGameObject
{
public override void Update()
{
// 玩家更新逻辑
}
}
public class BadEnemy : BadGameObject
{
public override void Update()
{
// 敌人更新逻辑
}
}
// 问题:如果玩家需要AI,需要创建新的继承层次
public class BadPlayerWithAI : BadPlayer
{
// 重复AI代码
}
public class BadEnemyWithAI : BadEnemy
{
// 重复AI代码
}
// 问题:如果玩家需要渲染,又需要新的继承层次
public class BadPlayerWithRenderer : BadPlayer
{
// 重复渲染代码
}
// 继承爆炸:组合爆炸式的继承层次
遵循组合/聚合复用原则的示例
/// <summary>
/// 移动组件接口
/// </summary>
public interface IMovement
{
void Move(Vector3 direction);
void SetSpeed(float speed);
}
/// <summary>
/// AI组件接口
/// </summary>
public interface IAI
{
void UpdateAI();
void SetBehavior(string behaviorType);
}
/// <summary>
/// 渲染组件接口
/// </summary>
public interface IRenderer
{
void SetMaterial(Material material);
void SetVisible(bool visible);
}
/// <summary>
/// 音频组件接口
/// </summary>
public interface IAudio
{
void PlaySound(string soundName);
void SetVolume(float volume);
}
/// <summary>
/// 移动组件实现
/// </summary>
public class MovementComponent : IMovement
{
private Transform transform;
private float speed = 5f;
public MovementComponent(Transform transform)
{
this.transform = transform;
}
public void Move(Vector3 direction)
{
transform.Translate(direction * speed * Time.deltaTime);
}
public void SetSpeed(float speed)
{
this.speed = speed;
}
}
/// <summary>
/// AI组件实现
/// </summary>
public class AIComponent : IAI
{
private string currentBehavior = "Idle";
public void UpdateAI()
{
switch (currentBehavior)
{
case "Idle":
Debug.Log("AI: 待机状态");
break;
case "Patrol":
Debug.Log("AI: 巡逻状态");
break;
case "Chase":
Debug.Log("AI: 追击状态");
break;
}
}
public void SetBehavior(string behaviorType)
{
currentBehavior = behaviorType;
}
}
/// <summary>
/// 渲染组件实现
/// </summary>
public class RenderComponent : IRenderer
{
private Renderer renderer;
public RenderComponent(Renderer renderer)
{
this.renderer = renderer;
}
public void SetMaterial(Material material)
{
renderer.material = material;
}
public void SetVisible(bool visible)
{
renderer.enabled = visible;
}
}
/// <summary>
/// 音频组件实现
/// </summary>
public class AudioComponent : IAudio
{
private AudioSource audioSource;
public AudioComponent(AudioSource audioSource)
{
this.audioSource = audioSource;
}
public void PlaySound(string soundName)
{
AudioClip clip = Resources.Load<AudioClip>(soundName);
audioSource.PlayOneShot(clip);
}
public void SetVolume(float volume)
{
audioSource.volume = volume;
}
}
/// <summary>
/// 遵循CARP的游戏对象基类
/// 通过组合来复用功能
/// </summary>
public class GameObject : MonoBehaviour
{
// 组合各种组件
protected IMovement movement;
protected IAI ai;
protected IRenderer renderer;
protected IAudio audio;
protected virtual void Start()
{
InitializeComponents();
}
protected virtual void InitializeComponents()
{
// 初始化组件
if (GetComponent<Transform>() != null)
movement = new MovementComponent(transform);
if (GetComponent<Renderer>() != null)
renderer = new RenderComponent(GetComponent<Renderer>());
if (GetComponent<AudioSource>() != null)
audio = new AudioComponent(GetComponent<AudioSource>());
}
protected virtual void Update()
{
ai?.UpdateAI();
}
// 提供组件访问方法
public IMovement GetMovement() => movement;
public IAI GetAI() => ai;
public IRenderer GetRenderer() => renderer;
public IAudio GetAudio() => audio;
}
/// <summary>
/// 玩家类 - 通过组合实现功能
/// </summary>
public class Player : GameObject
{
protected override void InitializeComponents()
{
base.InitializeComponents();
// 玩家不需要AI,所以不设置AI组件
// 但需要移动、渲染、音频组件
}
void Update()
{
// 玩家输入处理
if (Input.GetKey(KeyCode.W))
{
movement?.Move(Vector3.forward);
}
}
}
/// <summary>
/// 敌人类 - 通过组合实现功能
/// </summary>
public class Enemy : GameObject
{
protected override void InitializeComponents()
{
base.InitializeComponents();
// 敌人需要AI组件
ai = new AIComponent();
ai.SetBehavior("Patrol");
}
}
/// <summary>
/// 装饰物类 - 只需要渲染功能
/// </summary>
public class Decoration : GameObject
{
protected override void InitializeComponents()
{
base.InitializeComponents();
// 装饰物只需要渲染,不需要移动、AI、音频
movement = null;
ai = null;
audio = null;
}
}
游戏开发应用
-
UI系统:UI组件通过组合实现复杂功能
-
特效系统:特效通过组合不同的效果组件
-
游戏对象系统:通过组合不同组件创建复杂游戏对象
总结
设计原则总结
| 原则 | 核心思想 | 关键点 |
|---|---|---|
| 单一职责原则 | 一个类只做一件事 | 高内聚、低耦合 |
| 里氏替换原则 | 子类可以替换父类 | 行为一致性 |
| 依赖倒置原则 | 依赖抽象不依赖具体 | 面向接口编程 |
| 接口隔离原则 | 接口要小而专一 | 按需依赖 |
| 迪米特法则 | 最少知识原则 | 只与直接朋友通信 |
| 开闭原则 | 对扩展开放对修改关闭 | 抽象设计、多态 |
| 组合复用原则 | 组合优于继承 | 运行时灵活性 |
Unity开发实践
-
组件化设计:充分利用Unity的组件系统
-
接口优先:定义清晰的接口契约
-
依赖注入:使用构造函数或方法注入依赖
-
事件驱动:使用事件系统减少直接依赖
-
脚本分离:将逻辑、数据、UI分离
-
资源管理:使用对象池和资源管理器
代码质量检查清单
- 每个类是否只有一个变化的原因?
- 子类是否可以完全替换父类?
- 是否依赖抽象而不是具体实现?
- 接口是否足够小和专一?
- 是否避免了深层嵌套调用?
- 是否可以扩展而不修改现有代码?
- 是否优先使用组合而不是继承?
遵循这些设计原则,可以创建出高质量、可维护、可扩展的Unity游戏代码。记住,原则是指导性的,在实际开发中需要根据具体情况灵活应用。
333

被折叠的 条评论
为什么被折叠?



