循环泛型类型参数

21

我有两个通用类,一个是 BaseComponent 类,另一个是 BaseManager 类。

它们都是抽象类,旨在变得具体化。

public abstract class BaseManager<T> where T : BaseComponent<?>
public abstract class BaseComponent<T> where T : BaseManager<?>

BaseManager 有一个 BaseComponents 列表,这就是为什么我想要让它成为通用的原因。因此,PhysicsManager : BaseManager<PhysicsComponent> 将拥有一个 PhysicsComponents 列表。

我希望(或者更确切地说,认为我需要)BaseComponent 是通用的,因为我只希望从 BaseComponent 派生的类被“附加”到它们所属的适当的管理器上。理想情况下,我不想为每个派生组件编写构造函数,以便将其添加到传入的具体管理器类中。理想情况下,我希望有一个构造函数,该函数接受抽象的 BaseManager 类。

如何管理这种循环依赖?


我强烈建议重新设计以避免循环依赖。例如,使BaseComponent非泛型。让它依赖于一个IManager。将从BaseComponentTComponent的转换放在BaseManager<TComponent>中。 - default.kramer
我同意Jon所说的有点臭,但我不太明白。如果BaseComponent依赖于IManager,那么我该如何确保所有派生自BaseComponent的类都有一个接受正确的具体IManager实现的构造函数,以便将其添加到管理器列表中?如果您有时间,我希望能得到详细的答案解释。 - George Duckett
2个回答

30

听起来你可能想要有两个通用类型参数:

public abstract class BaseManager<TComponent, TManager>
    where TComponent : BaseComponent<TComponent, TManager>
    where TManager : BaseManager<TComponent, TManager>
public abstract class BaseComponent<TComponent, TManager>
    where TComponent : BaseComponent<TComponent, TManager>
    where TManager : BaseManager<TComponent, TManager>

是的,这很臭 - 但这是我在Protocol Buffers中做的事情。

那么你就会有:

public class PhysicsManager : BaseManager<PhysicsComponent, PhysicsManager>

public class PhysicsComponent : BaseComponent<PhysicsComponent, PhysicsManager>

3
最松散的耦合是组件不知道它们的管理器。以下是演示如何实现这一点的示例。请注意,如果所有组件都必须添加到管理器中,则此方法需要某种工厂机制。(Nat Pryce - 如果两个对象之间存在关系,则其他对象应该建立关系。)
abstract class BaseComponent
{
    public event EventHandler SomethingHappened;
}

abstract class BaseManager<TComponent> where TComponent : BaseComponent
{
    List<TComponent> components = new List<TComponent>();

    public virtual void AddComponent(TComponent component)
    {
        components.Add(component);
        component.SomethingHappened += (s, e) => OnSomethingHappened(component);
    }

    public abstract void OnSomethingHappened(TComponent component);
}

如果组件不能独立于其管理器,则最好依赖于由组件需求定义的接口。这就是接口隔离原则
interface IManager
{
    void ManageMe(BaseComponent component);
}

abstract class BaseComponent
{
    public BaseComponent(IManager manager)
    {
        manager.ManageMe(this);
    }
}

abstract class BaseManager<TComponent> : IManager where TComponent : BaseComponent
{
    void IManager.ManageMe(BaseComponent component)
    {
        ManageMe((TComponent)component);
    }

    protected abstract void ManageMe(TComponent component);
}

interface IPhysicsManager : IManager
{
    void AnotherCallback(PhysicsComponent comp);
}

abstract class PhysicsComponent : BaseComponent
{
    public PhysicsComponent(IPhysicsManager manager)
        : base(manager)
    {
        manager.AnotherCallback(this);
    }
}

abstract class PhysicsManager : BaseManager<PhysicsComponent>, IPhysicsManager
{
    protected override void ManageMe(PhysicsComponent component)
    {
        throw new NotImplementedException();
    }

    public void AnotherCallback(PhysicsComponent comp)
    {
        throw new NotImplementedException();
    }
}

缺点是类型系统不强制要求传递正确的管理器,然后在BaseManager中的转换将失败。我仍然更喜欢这种方式,并且“保留我的基础设施中的臭味”,而不是让循环模板污染所有具体组件和管理器。

有趣。请注意,循环依赖关系可以使用纯接口来完成,因此它们不会污染具体的组件和管理器。通用类型名称可以使用“using”指令缩短(例如:https://dev59.com/WnVC5IYBdhLWcg3w21Iq#161484)。 - Stéphane Gourichon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接