параметры циклического универсального типа - PullRequest
19 голосов
/ 15 ноября 2011

У меня есть 2 общих класса, класс BaseComponent и класс BaseManager.

Они оба абстрактные и предназначены для бетонирования.

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

BaseManager имеет список базовых компонентов, поэтому я хочу сделать его общим, поэтому PhysicsManager : BaseManager<PhysicsComponent> будет иметь список PhysicsComponents.

Я хочу (точнее, думаю, что мне нужно) BaseComponent быть универсальным, потому что я только хочу, чтобы классы, производные от BaseComponent, были «привязаны» к соответствующему менеджеру. В идеале мне не нужно писать конструктор для каждого производного компонента, просто чтобы я мог добавить его к переданному в конкретном классе менеджера. В идеале я хочу иметь конструктор, который принимает абстрактный класс BaseManager.

Как мне справиться с такой круговой зависимостью?

Ответы [ 2 ]

27 голосов
/ 15 ноября 2011

Звучит так, как будто вы хотите иметь два параметра общего типа:

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 голосов
/ 15 ноября 2011

Самая слабая связь была бы, если бы компоненты не знали о своих менеджерах. Вот пример того, как это будет работать. Обратите внимание, что этот подход требует своего рода фабричного механизма, если все компоненты должны быть добавлены в менеджер. (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 может завершиться ошибкой. Я бы все-таки предпочел этот способ и «сохранить запах в моей инфраструктуре», а не иметь круглые шаблоны, загрязняющие все мои конкретные компоненты и менеджеров.

...