了解一下啊面向对象的七大设计原则

面向对象编程(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开发实践

  1. 组件化设计:充分利用Unity的组件系统

  2. 接口优先:定义清晰的接口契约

  3. 依赖注入:使用构造函数或方法注入依赖

  4. 事件驱动:使用事件系统减少直接依赖

  5. 脚本分离:将逻辑、数据、UI分离

  6. 资源管理:使用对象池和资源管理器

代码质量检查清单

  • 每个类是否只有一个变化的原因?
  • 子类是否可以完全替换父类?
  • 是否依赖抽象而不是具体实现?
  • 接口是否足够小和专一?
  • 是否避免了深层嵌套调用?
  • 是否可以扩展而不修改现有代码?
  • 是否优先使用组合而不是继承?

遵循这些设计原则,可以创建出高质量、可维护、可扩展的Unity游戏代码。记住,原则是指导性的,在实际开发中需要根据具体情况灵活应用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值