You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity/C#中ScriptableObject作数据容器的类继承最佳实践咨询

解决方案

你当前的写法里Manager没有继承Employee,而是直接继承了MonoBehaviour,才会出现重复定义数据容器字段的问题。以下是符合继承逻辑、避免重复引用的3种常用方案:


方案1:泛型基类(最推荐,兼顾类型安全和灵活性)

把基类定义为带约束的泛型类,直接限定数据容器的类型,子类只要传入对应的SO类型即可,不需要重复定义字段,全程无多余引用。

// 泛型基类,约束T必须是EmployeeSO类型
public class Employee<T> : MonoBehaviour where T : EmployeeSO
{
    [SerializeField] protected T _dataSO;
    // 基类可以直接访问T中属于EmployeeSO的所有字段,比如employeeName
}

// 普通员工类,传入EmployeeSO作为泛型参数
public class NormalEmployee : Employee<EmployeeSO>
{
    // 不需要额外定义数据字段,直接用基类的_dataSO即可
}

// 经理类,传入继承自EmployeeSO的ManagerSO作为泛型参数
public class Manager : Employee<ManagerSO>
{
    public void Test()
    {
        // 直接访问基类的_dataSO,既可以拿到父类的employeeName,也可以拿到ManagerSO独有的role
        Debug.Log(_dataSO.employeeName);
        Debug.Log(_dataSO.role);
    }
}

// SO层的继承保持不变
public class EmployeeSO : ScriptableObject
{
    public string employeeName;
}

public class ManagerSO : EmployeeSO
{
    public string role;
}

优势:类型安全,不需要强制转换,Inspector面板只会显示一个对应类型的_dataSO字段,不会出现误赋值的问题


方案2:非泛型方案(适配不适合用泛型的场景)

如果基类需要被当作普通非泛型类使用(比如要存入统一的员工列表),可以把基类的SO字段用属性封装,子类只做类型转换即可,同时隐藏基类的序列化字段避免误操作。

public class Employee : MonoBehaviour
{
    // 把基类的SO字段隐藏,不在Inspector显示,避免手动赋值
    [SerializeField, HideInInspector] protected EmployeeSO _employeeSO;
    // 对外暴露的只读属性,基类逻辑都用这个属性访问数据
    public virtual EmployeeSO DataSO => _employeeSO;
}

public class Manager : Employee
{
    // 子类只定义自己的ManagerSO序列化字段
    [SerializeField] protected ManagerSO _managerSO;
    // 重写属性,返回子类的SO同时同步给基类引用,保证基类逻辑正常运行
    public override EmployeeSO DataSO
    {
        get
        {
            _employeeSO = _managerSO;
            return _managerSO;
        }
    }

    public void Test()
    {
        // 子类直接用自己的_managerSO访问独有属性
        Debug.Log(_managerSO.role);
        // 基类逻辑访问DataSO就能拿到统一的员工通用数据
        Debug.Log(DataSO.employeeName);
    }
}

优势:兼容非泛型的使用场景,Inspector只会显示Manager类的_managerSO字段,不会出现重复字段误导操作


方案3:抽象基类隔离(适合需要严格分层的场景)

如果要彻底隔离基类和子类的数据实现,可以把基类改成抽象类,只定义数据的访问接口,不持有具体的SO引用,所有数据持有都交给子类实现,完全不会有多余引用残留。

public abstract class Employee : MonoBehaviour
{
    // 抽象属性,基类只约定要能拿到EmployeeSO,不做具体实现
    public abstract EmployeeSO DataSO { get; }

    // 基类所有逻辑都通过DataSO访问员工通用数据
    public void PrintName()
    {
        Debug.Log(DataSO.employeeName);
    }
}

public class NormalEmployee : Employee
{
    [SerializeField] private EmployeeSO _employeeSO;
    public override EmployeeSO DataSO => _employeeSO;
}

public class Manager : Employee
{
    [SerializeField] private ManagerSO _managerSO;
    public override EmployeeSO DataSO => _managerSO;

    public void PrintRole()
    {
        Debug.Log(_managerSO.role);
    }
}

优势:基类完全不关心子类的具体数据实现,彻底避免引用不一致的问题


内容的提问来源于stack exchange,提问作者Eyap

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.11 23:12:03