5.组合模式

5.结构型模式-组合模式


5.1 基础知识

学习难度:3

使用频率:4

总分:7

定义

组合模式(Composite Pattern)将对象组合成树形接口以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

说人话

把对象组织成树状结构进行管理。

结构图

实现步骤

  • 抽象节点类:定义共有属性和方法,例如名称属性、添加子节点方法、移除子节点方法、展示当前节点方法。
  • 叶子节点类:继承抽象节点类,没有子节点及其相关方法的具体实现,实现展示当前节点方法。
  • 容器节点类:继承抽象节点类,定义存储和管理子节点的集合,实现子节点相关方法,实现展示当前节点方法时递归调用子节点集合中的展示当前节点方法。
  • 客户端:实例化一个容器节点作为根节点,按需实例化其他子节点进行添加移除,最后让根节点进行展示。

说明

组合模式适用于需要处理树形结构的场景,它使得对单个对象和组合对象的使用具有一致性。实现组合模式时需要注意设计良好的抽象节点类和合适的递归结构。


5.2 模版代码

抽象节点类、叶子节点类和容器节点类

//抽象节点
public abstract class Component
{
    protected string name;

    public Component(string name)
    {
        this.name = name;
    }

    public abstract void Add(Component component);
    public abstract void Remove(Component component);
    public abstract void Display(int depth);
}

//叶子节点
public class Leaf : Component
{
    public Leaf(string name) : base(name)
    {
    }

    public override void Add(Component component)
    {
        Console.WriteLine("叶子节点无法添加子节点。");
    }

    public override void Remove(Component component)
    {
        Console.WriteLine("叶子节点无法移除子节点。");
    }

    public override void Display(int depth)
    {
        Console.WriteLine(new string(' ', depth) + name);//缩进表示深度
    }
}

//容器节点
public class Composite : Component
{
    private List<Component> children = new List<Component>();

    public Composite(string name) : base(name)
    {
    }

    public override void Add(Component component)
    {
        children.Add(component);
    }

    public override void Remove(Component component)
    {
        children.Remove(component);
    }

    public override void Display(int depth)
    {
        Console.WriteLine(new string(' ', depth) + name);

        foreach (Component component in children)
        {
            component.Display(depth + 2);//递归展示
        }
    }
}

客户端

using System;
using Lesson14_结构型模式_组合模式.Component;

class Program
{
    static void Main(string[] args)
    {
        // 创建一个组合结构
        Composite root = new Composite("根节点");
        Composite comp1 = new Composite("容器节点X");
        Composite comp2 = new Composite("容器节点Y");

        root.Add(comp1);
        root.Add(comp2);

        comp1.Add(new Leaf("叶子节点XA"));
        comp1.Add(new Leaf("叶子节点XB"));
        comp2.Add(new Leaf("叶子节点YA"));

        // 显示整个树结构
        Console.WriteLine("树状结构:");
        root.Display(1);
        // 树状结构:
        // 根节点
        //     容器节点X
        //          叶子节点XA
        //          叶子节点XB
        //     容器节点Y
        //          叶子节点YA

        
        // 删除一个节点
        root.Remove(comp2);
        Console.WriteLine("\n移除容器节点Y后:");
        root.Display(1);
        // 移除容器节点Y后:
        // 根节点
        //      容器节点X
        //          叶子节点XA
        //          叶子节点XB
    }
}

5.3 CSharp实践

实践需求

使用组合模式,实现总公司和子公司的管理结构。每个子公司要包括财务部和人力资源部。

抽象公司类、具体公司类、财务部和人力资源部

// 抽象公司类
abstract class Company
{
    protected string name; // 公司名称

    public Company(string name)
    {
        this.name = name;
    }

    // 添加子公司的方法
    public abstract void Add(Company c);

    // 移除子公司的方法
    public abstract void Remove(Company c);

    // 显示公司信息,带缩进表示深度
    public abstract void Display(int depth);

    // 描述公司职责的方法
    public abstract void ExecuteResponsibilities();
}

// 具体公司类
class ConcreteCompany : Company
{
    // 存储子公司的列表
    private List<Company> companyList = new List<Company>();

    // 构造函数,初始化公司名称
    public ConcreteCompany(string name) : base(name)
    {
        
    }

    // 添加子公司
    public override void Add(Company c)
    {
        companyList.Add(c);
    }

    // 移除子公司
    public override void Remove(Company c)
    {
        companyList.Remove(c);
    }

    // 显示公司结构
    public override void Display(int depth)
    {
        Console.WriteLine(new String('-', depth) + name);

        // 递归显示子公司结构
        foreach (Company component in companyList)
        {
            component.Display(depth + 2);
        }
    }

    // 执行公司职责
    public override void ExecuteResponsibilities()
    {
        foreach (Company component in companyList)
        {
            component.ExecuteResponsibilities();
        }
    }
}

// 财务部门
class FinanceDepartment : Company
{
    // 构造函数,设置财务部门的名称
    public FinanceDepartment(string name) : base(name)
    {
        
    }

    // 不支持添加子公司,这里留空
    public override void Add(Company c)
    {
        // 无操作
    }

    // 不支持移除子公司,这里留空
    public override void Remove(Company c)
    {
        // 无操作
    }

    // 显示财务部门名称
    public override void Display(int depth)
    {
        Console.WriteLine(new String('-', depth) + name);
    }

    // 显示财务部门的职责
    public override void ExecuteResponsibilities()
    {
        Console.WriteLine("{0} 公司财务收支管理", name);
    }
}

// 人力资源部门,继承自 Company 类
class HRDepartment : Company
{
    // 构造函数,设置部门名称
    public HRDepartment(string name) : base(name)
    {
        
    }

    // 不支持添加子公司,这里留空
    public override void Add(Company c)
    {
        // 无操作
    }

    // 不支持移除子公司,这里留空
    public override void Remove(Company c)
    {
        // 无操作
    }

    // 显示部门名称
    public override void Display(int depth)
    {
        Console.WriteLine(new String('-', depth) + name);
    }

    // 显示人力资源的职责
    public override void ExecuteResponsibilities()
    {
        Console.WriteLine("{0} 员工招聘培训管理", name);
    }
}

客户端

class Program
{
    static void Main(string[] args)
    {
        // 创建中国公司和子公司的层次结构
        ConcreteCompany chinaCompany = new ConcreteCompany("中国总公司");
        chinaCompany.Add(new HRDepartment("总公司人力资源部"));
        chinaCompany.Add(new FinanceDepartment("总公司财务部"));

        ConcreteCompany shanghaiCompany = new ConcreteCompany("上海分公司");
        shanghaiCompany.Add(new HRDepartment("上海分公司人力资源部"));
        shanghaiCompany.Add(new FinanceDepartment("上海分公司财务部"));
        chinaCompany.Add(shanghaiCompany);

        ConcreteCompany shenzhenCompany = new ConcreteCompany("深圳分公司");
        shenzhenCompany.Add(new HRDepartment("深圳分公司人力资源部"));
        shenzhenCompany.Add(new FinanceDepartment("深圳分公司财务部"));
        chinaCompany.Add(shenzhenCompany);

        ConcreteCompany guangzhouCompany = new ConcreteCompany("广州分公司");
        guangzhouCompany.Add(new HRDepartment("广州分公司人力资源部"));
        guangzhouCompany.Add(new FinanceDepartment("广州分公司财务部"));
        chinaCompany.Add(guangzhouCompany);

        Console.WriteLine("\n公司结构图:");
        // 显示公司结构
        chinaCompany.Display(1);
        
        // 公司结构图:
        // -中国总公司
        //     ---总公司人力资源部
        //     ---总公司财务部
        //     ---上海分公司
        //     -----上海分公司人力资源部
        //     -----上海分公司财务部
        //     ---深圳分公司
        //     -----深圳分公司人力资源部
        //     -----深圳分公司财务部
        //     ---广州分公司
        //     -----广州分公司人力资源部
        //     -----广州分公司财务部
        
        
        Console.WriteLine("\n公司职责:");
        // 执行各公司的职责
        chinaCompany.ExecuteResponsibilities();
        
        // 公司职责:
        // 总公司人力资源部 员工招聘培训管理
        // 总公司财务部 公司财务收支管理
        // 上海分公司人力资源部 员工招聘培训管理
        // 上海分公司财务部 公司财务收支管理
        // 深圳分公司人力资源部 员工招聘培训管理
        // 深圳分公司财务部 公司财务收支管理
        // 广州分公司人力资源部 员工招聘培训管理
        // 广州分公司财务部 公司财务收支管理
    }
}

5.4 Unity实践

实践需求

使用组合模式,创建一个Unity游戏中的角色系统,该系统包括一个名为Vayne的角色。这个角色系统需要满足以下要求:

  1. 实现一个角色数据存储类,其中包括生命值、攻击力和攻击速度等基本属性。这个数据类需要使用ScriptableObject进行实现,并提供在Unity编辑器中创建实例的选项。

  2. 创建一个角色组件体系,该体系支持组合模式。这个体系应包括以下基本要素:

    • 角色基本属性组件,包括加载和显示角色的基本属性,还能够包含其他子组件。
    • 技能组件,包括技能的名称、按键触发、和技能的触发逻辑。技能组件需要支持添加到基本属性组件中,但不能添加其他组件。
    • 技能系统组件,充当技能的管理器,能够包含多个技能组件,支持添加和移除技能,以及显示技能信息。
  3. 实现一个测试脚本,该脚本可以在Unity中使用,用于演示上述组件体系的功能。它需要完成以下任务:

    • 创建一个角色实例。
    • 添加基本属性组件到角色实例中。
    • 添加技能系统组件到角色实例中。
    • 创建多个技能组件,并将它们添加到技能系统组件中。
    • 设置技能的名称和按键触发。
    • 最后,显示角色的基本属性和已添加的技能信息。

薇恩数据ScriptableObject

// 薇恩数据ScriptableObject
[CreateAssetMenu(fileName = "VayneData", menuName = "ScriptableObject/VayneData", order = 0)]
public class VayneData : ScriptableObject
{
    // 定义VayneData的属性
    public int health;           // 生命值
    public int attack;           // 攻击力
    public float attackSpeed;    // 攻击速度

    // 自定义菜单项,用于在Unity编辑器中创建VayneData实例
    [MenuItem("ScriptableObject/CreateVayneData")]
    public static void CreateMyData()
    {
        // 创建一个新的VayneData实例
        VayneData vayneData = ScriptableObject.CreateInstance<VayneData>();

        // 将VayneData实例保存为Asset文件
        AssetDatabase.CreateAsset(vayneData, "Assets/Resources/Lesson14_结构型模式_组合模式/VayneData.asset");

        // 保存Asset文件
        AssetDatabase.SaveAssets();

        // 刷新资源数据库
        AssetDatabase.Refresh();
    }
}

薇恩组件

// 抽象薇恩组件
public abstract class VayneComponent : MonoBehaviour
{
    // 抽象方法,用于显示组件的信息
    public abstract void Display();

    // 抽象方法,用于添加子组件
    public abstract void Add(VayneComponent vayneComponent);

    // 抽象方法,用于移除子组件
    public abstract void Remove(VayneComponent vayneComponent);
}

// 薇恩基础组件
public class VayneBasic : VayneComponent
{
    private List<VayneComponent> componentList = new List<VayneComponent>();//薇恩子组件

    public VayneData vayneData;  // 存储薇恩的数据

    public Animator animator;  // 存储薇恩的动画控制器

    private void Awake()
    {
        animator = GetComponent<Animator>();  // 获取角色的动画控制器
        vayneData = Resources.Load<VayneData>("Lesson14_结构型模式_组合模式/VayneData"); // 加载薇恩的数据
    }

    public override void Display()
    {
        Debug.Log("薇恩预制体的基本属性:");
        Debug.Log("生命值:" + vayneData.health);
        Debug.Log("攻击力:" + vayneData.attack);
        Debug.Log("攻速:" + vayneData.attackSpeed);

        // 显示附加的其他组件的信息
        foreach (var component in componentList)
        {
            component.Display();
        }
    }

    public override void Add(VayneComponent vayneComponent)
    {
        componentList.Add(vayneComponent);  // 添加其他组件
    }

    public override void Remove(VayneComponent vayneComponent)
    {
        if (componentList.Contains(vayneComponent))
        {
            componentList.Remove(vayneComponent);  // 移除其他组件
        }
    }
}

// 薇恩技能组件
public class VayneSkill : VayneComponent
{
    public string skillName; // 技能名称
    public string skillKey; // 技能按键
    public bool isSetSkillNameAndSkillKey; // 是否已设置技能名称和按键

    // 显示技能信息
    public override void Display()
    {
        Debug.Log($"按下{skillKey}可以释放{skillName}");
    }

    // 不能为技能组件添加其他组件
    public override void Add(VayneComponent vayneComponent)
    {
        Debug.Log("不能为技能组件添加组件");
    }

    // 不能为技能组件移除其他组件
    public override void Remove(VayneComponent vayneComponent)
    {
        Debug.Log("不能为技能组件移除组件");
    }

    // 设置技能名称和按键
    public void SetSkillNameAndSkillKey(string skillName, string skillButton)
    {
        if (isSetSkillNameAndSkillKey) return;
        this.skillName = skillName;
        this.skillKey = skillButton;
        isSetSkillNameAndSkillKey = true;
    }

    // 更新方法,检测按键触发技能
    void Update()
    {
        if (!isSetSkillNameAndSkillKey)
        {
            Debug.Log("没有设置技能名和按键");
            return;
        }

        if (Input.GetKeyDown(SkillKeyConvertKeyCode(skillKey)))
        {
            GetComponent<Animator>().SetTrigger(skillKey);
        }
    }

    // 将技能按键字符串转换为 KeyCode 枚举
    public KeyCode SkillKeyConvertKeyCode(string inputKey)
    {
        KeyCode keyCode;
        if (Enum.TryParse(inputKey, out keyCode))
        {
            return keyCode;
        }
        else
        {
            Debug.LogWarning("无法将字符串 " + inputKey + " 转换为 KeyCode");
            return KeyCode.None;
        }
    }
}


// 薇恩技能系统组件
public class VayneSkillSystem : VayneComponent
{
    private Dictionary<string, VayneSkill> skillDictionary = new Dictionary<string, VayneSkill>();//薇恩技能字典

    // 显示技能系统的信息
    public override void Display()
    {
        foreach (var skill in skillDictionary)
        {
            skill.Value.Display();
        }
    }

    // 添加技能到技能系统
    public override void Add(VayneComponent vayneComponent)
    {
        if (vayneComponent is VayneSkill skill)
        {
            skillDictionary.Add(skill.skillName, skill);
        }
    }

    // 从技能系统中移除技能
    public override void Remove(VayneComponent vayneComponent)
    {
        if (vayneComponent is VayneSkill skill)
        {
            if (skillDictionary.ContainsKey(skill.skillName))
            {
                skillDictionary.Remove(skill.skillName);
            }
        }
    }
}

其他准备工作

准备薇恩数据的ScriptableObject和薇恩动画相关

客户端

public class TestCompositePattern : MonoBehaviour
{
    public GameObject vaynePrefab;

    private void Start()
    {
        // 实例化薇恩角色
        GameObject vaynePlayer = Instantiate(vaynePrefab, Vector3.zero, Quaternion.identity);

        // 创建薇恩的基本属性组件
        VayneBasic vayneBasic = vaynePlayer.AddComponent<VayneBasic>();
        
        // 创建薇恩的技能系统组件
        VayneSkillSystem vayneSkillSystem = vaynePlayer.AddComponent<VayneSkillSystem>();
        // 将技能系统添加到薇恩的基本属性中
        vayneBasic.Add(vayneSkillSystem);

        // 创建Q技能:闪避突袭(Tumble)并设置技能信息
        VayneSkill skillTumble = vaynePlayer.AddComponent<VayneSkill>();
        skillTumble.SetSkillNameAndSkillKey("闪避突袭", "Q");
        // 将闪避突袭添加到技能系统
        vayneSkillSystem.Add(skillTumble);

        // 创建E技能:恶魔审判(Condemn)并设置技能信息
        VayneSkill skillCondemn = vaynePlayer.AddComponent<VayneSkill>();
        skillCondemn.SetSkillNameAndSkillKey("恶魔审判", "E");
        // 将恶魔审判添加到技能系统
        vayneSkillSystem.Add(skillCondemn);
        
        // 显示薇恩的基本属性和技能信息
        vayneBasic.Display();
    }
}

运行结果





转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏