Интерфейс, определяющий подпись конструктора?



странно, что это первый раз, когда я столкнулся с этой проблемой, но:



как определить конструктор в интерфейсе C#?



Edit

Некоторые люди хотели пример (это проект свободного времени, так что да, это игра)



IDrawable

+ Обновление

+Розыгрыш



чтобы иметь возможность обновлять (проверить край экрана и т. д.) и рисовать себя, он всегда будет нужен GraphicsDeviceManager. Поэтому я хочу убедиться, что объект имеет ссылка на него. Это будет принадлежать конструктору.



теперь, когда я записал это, я думаю, что я реализую здесь IObservable и GraphicsDeviceManager стоит взять IDrawable...
Кажется, либо я не получаю структуру XNA, либо структура не очень хорошо продумана.



Edit

Кажется, есть некоторая путаница в моем определении конструктора в контексте интерфейса. Интерфейс действительно не может быть создан, поэтому не нужен конструктор. То, что я хотел определить, было подписью конструктора. Точно так же, как интерфейс может определить подпись определенного метода, интерфейс может определить подпись конструктора.

589   16  

16 ответов:

как уже отмечалось, вы не можете иметь конструкторы в интерфейсе. Но поскольку это такой высоко ранжированный результат в Google примерно 7 лет спустя, я подумал, что я бы скинул здесь - в частности, чтобы показать, как вы можете использовать абстрактный базовый класс в тандеме с вашим существующим интерфейсом и, возможно, сократить количество рефакторинга, необходимого в будущем для подобных ситуаций. На эту концепцию уже намекали в некоторых комментариях, но я подумал, что стоит показать, как это сделать на самом деле сделать это.

Итак, у вас есть основной интерфейс, который выглядит так:

public interface IDrawable
{
    void Update();
    void Draw();
}

теперь создать абстрактный класс с конструктором, который вы хотите применить. На самом деле, поскольку он теперь доступен с момента написания вашего исходного вопроса, мы можем немного пофантазировать здесь и использовать дженерики в этой ситуации, чтобы мы могли адаптировать его к другим интерфейсам, которые могут нуждаться в той же функциональности, но имеют другой конструктор требования:

public abstract class MustInitialize<T>
{
    public MustInitialize(T parameters)
    {

    }
}

теперь вам нужно будет создать новый класс, который наследует как интерфейс IDrawable, так и абстрактный класс MustInitialize:

public class Drawable : MustInitialize<GraphicsDeviceManager>, IDrawable
{
    GraphicsDeviceManager _graphicsDeviceManager;

    public Drawable(GraphicsDeviceManager graphicsDeviceManager)
        : base (graphicsDeviceManager)
    {
        _graphicsDeviceManager = graphicsDeviceManager;
    }

    public void Update()
    {
        //use _graphicsDeviceManager here to do whatever
    }

    public void Draw()
    {
        //use _graphicsDeviceManager here to do whatever
    }
}

затем просто создайте экземпляр Drawable, и вы хорошо пойдете:

IDrawable drawableService = new Drawable(myGraphicsDeviceManager);

самое классное здесь то, что новый Drawable класс, который мы создали, по-прежнему ведет себя так же, как мы ожидали бы от IDrawable.

Если вам нужно передать более одного параметра в MustInitialize конструктора, вы можете создать класс, который определяет свойства для всех полей вам необходимо пройти.

вы не можете. Иногда это может быть боль, но вы не сможете его вызвать в любом случае, используя обычные методы.

в блоге я предложил статические интерфейсы который будет использоваться только в ограничениях общего типа - но может быть очень удобно, ИМО.

один пункт о том, если вы может определите конструктор в интерфейсе, у вас будут проблемы с получением классов:

public class Foo : IParameterlessConstructor
{
    public Foo() // As per the interface
    {
    }
}

public class Bar : Foo
{
    // Yikes! We now don't have a parameterless constructor...
    public Bar(int x)
    {
    }
}

очень поздний вклад, демонстрирующий другую проблему с сопряженными конструкторами. (Я выбираю этот вопрос, потому что он имеет самую четкую формулировку проблемы). Предположим, мы могли бы:

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

class Person : IPerson, ICustomer
{
    Person(string name) { }
    Person(DateTime registrationDate) { }
}

где по соглашению реализация "конструктора интерфейса" заменяется именем типа.

теперь сделайте экземпляр:

ICustomer a = new Person("Ernie");

можно ли сказать, что контракт ICustomer соблюдается?

А насчет это:

interface ICustomer
{
    ICustomer(string address);
}

ты не можешь.

интерфейсы определяют контракты, которые реализуют другие объекты и поэтому не имеют состояния, которое необходимо инициализировать.

Если у вас есть некоторое состояние, которое должно быть инициализировано, вы должны рассмотреть возможность использования абстрактного базового класса вместо этого.

невозможно создать интерфейс, который определяет конструкторы, но это - это можно определить интерфейс, который заставляет тип иметь конструктор без параметров, хотя это очень уродливый синтаксис, который использует универсальные выражения... Я вообще не уверен, что это действительно хороший шаблон кодирования.

public interface IFoo<T> where T : new()
{
  void SomeMethod();
}

public class Foo : IFoo<Foo>
{
  // This will not compile
  public Foo(int x)
  {

  }

  #region ITest<Test> Members

  public void SomeMethod()
  {
    throw new NotImplementedException();
  }

  #endregion
}

С другой стороны, если вы хотите проверить, имеет ли тип конструктор без параметров, вы можете сделать это с помощью отражения:

public static class TypeHelper
{
  public static bool HasParameterlessConstructor(Object o)
  {
    return HasParameterlessConstructor(o.GetType());
  }

  public static bool HasParameterlessConstructor(Type t)
  {
    // Usage: HasParameterlessConstructor(typeof(SomeType))
    return t.GetConstructor(new Type[0]) != null;
  }
}

надеюсь, что это помогает.

я оглядывался на этот вопрос, и я подумал про себя, может быть, мы подходим к этой проблеме неправильно. Интерфейсы могут не подходить, когда речь идет об определении конструктора с определенными параметрами... но (абстрактный) базовый класс.

Если вы создаете базовый класс с конструктором, который принимает необходимые вам параметры, каждый класс, который извлекается из него, должен их предоставить.

public abstract class Foo
{
  protected Foo(SomeParameter x)
  {
    this.X = x;
  }

  public SomeParameter X { get; private set }
}

public class Bar : Foo // Bar inherits from Foo
{
  public Bar() 
    : base(new SomeParameter("etc...")) // Bar will need to supply the constructor param
  {
  }
}

общий заводской подход по-прежнему кажется идеальным. Вы бы знали, что фабрика требует параметра, и это просто так случилось бы, что эти параметры передаются вместе с конструктором создаваемого объекта.

Примечание, это просто проверенный синтаксис псевдокода, там может быть предостережение во время выполнения я здесь не хватает:

public interface IDrawableFactory
{
    TDrawable GetDrawingObject<TDrawable>(GraphicsDeviceManager graphicsDeviceManager) 
              where TDrawable: class, IDrawable, new();
}

public class DrawableFactory : IDrawableFactory
{
    public TDrawable GetDrawingObject<TDrawable>(GraphicsDeviceManager graphicsDeviceManager) 
                     where TDrawable : class, IDrawable, new()
    {
        return (TDrawable) Activator
                .CreateInstance(typeof(TDrawable), 
                                graphicsDeviceManager);
    }

}

public class Draw : IDrawable
{
 //stub
}

public class Update : IDrawable {
    private readonly GraphicsDeviceManager _graphicsDeviceManager;

    public Update() { throw new NotImplementedException(); }

    public Update(GraphicsDeviceManager graphicsDeviceManager)
    {
        _graphicsDeviceManager = graphicsDeviceManager;
    }
}

public interface IDrawable
{
    //stub
}
public class GraphicsDeviceManager
{
    //stub
}

пример использования:

    public void DoSomething()
    {
        var myUpdateObject = GetDrawingObject<Update>(new GraphicsDeviceManager());
        var myDrawObject = GetDrawingObject<Draw>(null);
    }

конечно, вы хотите только создать экземпляры через фабрику, чтобы гарантируйте, что у вас всегда есть соответствующий инициализированный объект. Возможно, используя структуру инъекции зависимостей, такую как AutoFac имеет смысл; Update () может "спросить" контейнер IoC для нового объекта GraphicsDeviceManager.

один из способов решить эту проблему, которую я нашел, состоит в том, чтобы отделить конструкцию в отдельную фабрику. Например, у меня есть абстрактный класс iqueueitem, и мне нужен способ перевести этот объект в другой объект (CloudQueueMessage). Так что на интерфейсе IQueueItem у меня есть -

public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}

теперь мне также нужен способ для моего фактического класса очереди перевести CloudQueueMessage обратно в IQueueItem-т. е. необходимость статической конструкции, такой как iqueueitem objMessage = ItemType.FromMessage. Вместо этого я определил другой интерфейс IQueueFactory -

public interface IQueueItemFactory<T> where T : IQueueItem
{
    T FromMessage(CloudQueueMessage objMessage);
}

теперь я могу, наконец, написать свой общий класс очереди без ограничения new (), которое в моем случае было главной проблемой.

public class AzureQueue<T> where T : IQueueItem
{
    private IQueueItemFactory<T> _objFactory;
    public AzureQueue(IQueueItemFactory<T> objItemFactory)
    {
        _objFactory = objItemFactory;
    }


    public T GetNextItem(TimeSpan tsLease)
    {
        CloudQueueMessage objQueueMessage = _objQueue.GetMessage(tsLease);
        T objItem = _objFactory.FromMessage(objQueueMessage);
        return objItem;
    }
}

теперь я могу создать экземпляр, который соответствует критериям для меня

 AzureQueue<Job> objJobQueue = new JobQueue(new JobItemFactory())

надеюсь, это поможет кому-то еще когда-нибудь, очевидно, много внутреннего кода удалено, чтобы попытаться показать проблему и решение

один из способов решить эту проблему-использовать дженерики и новое ограничение ().

вместо того, чтобы выражать свой конструктор как метод/функцию, вы можете выразить его как фабричный класс/интерфейс. Если вы зададите общее ограничение new () для каждого сайта вызова, который должен создать объект вашего класса, вы сможете передать аргументы конструктора соответствующим образом.

для вашего IDrawable примера:

public interface IDrawable
{
    void Update();
    void Draw();
}

public interface IDrawableConstructor<T> where T : IDrawable
{
    T Construct(GraphicsDeviceManager manager);
}


public class Triangle : IDrawable
{
    public GraphicsDeviceManager Manager { get; set; }
    public void Draw() { ... }
    public void Update() { ... }
    public Triangle(GraphicsDeviceManager manager)
    {
        Manager = manager;
    }
}


public TriangleConstructor : IDrawableConstructor<Triangle>
{
    public Triangle Construct(GraphicsDeviceManager manager)
    {
        return new Triangle(manager);
    } 
}

теперь, когда вы используете это:

public void SomeMethod<TBuilder>(GraphicsDeviceManager manager)
  where TBuilder: IDrawableConstructor<Triangle>, new()
{
    // If we need to create a triangle
    Triangle triangle = new TBuilder().Construct(manager);

    // Do whatever with triangle
}

вы даже можете сконцентрировать все методы создания в одном классе, используя явную реализацию интерфейса:

public DrawableConstructor : IDrawableConstructor<Triangle>,
                             IDrawableConstructor<Square>,
                             IDrawableConstructor<Circle>
{
    Triangle IDrawableConstructor<Triangle>.Construct(GraphicsDeviceManager manager)
    {
        return new Triangle(manager);
    } 

    Square IDrawableConstructor<Square>.Construct(GraphicsDeviceManager manager)
    {
        return new Square(manager);
    } 

    Circle IDrawableConstructor<Circle>.Construct(GraphicsDeviceManager manager)
    {
        return new Circle(manager);
    } 
}

использовать:

public void SomeMethod<TBuilder, TShape>(GraphicsDeviceManager manager)
  where TBuilder: IDrawableConstructor<TShape>, new()
{
    // If we need to create an arbitrary shape
    TShape shape = new TBuilder().Construct(manager);

    // Do whatever with the shape
}

другой способ заключается в использовании лямбда-выражений в качестве инициализаторов. В какой-то момент в начале иерархии вызовов вы узнаете, какие объекты вам нужно будет создать (т. е. когда вы создаете или получаете ссылку на свой объект GraphicsDeviceManager). Как только вы его получите, передайте лямбда

() => new Triangle(manager) 

к последующим методам, чтобы они знали, как создать треугольник с тех пор. Если вы не можете определить все возможные методы, которые вам понадобятся, вы всегда можете создать словарь типов, реализующих IDrawable с помощью отражения и зарегистрировать лямбда-выражение, показанное выше, в словаре, который можно либо сохранить в общем расположении, либо передать другим вызовам функций.

Вы можете сделать это с помощью трюка дженериков, но он все еще уязвим для того, что написал Джон Скит:

public interface IHasDefaultConstructor<T> where T : IHasDefaultConstructor<T>, new()
{
}

класс, реализующий этот интерфейс, должен иметь конструктор без параметров:

public class A : IHasDefaultConstructor<A> //Notice A as generic parameter
{
    public A(int a) { } //compile time error
}

нет.

конструктор является частью класса, который может реализовать интерфейс. Интерфейс - это просто контракт методов, которые должен реализовать класс.

было бы очень полезно, если бы можно было определить конструкторы в интерфейсах.

учитывая, что интерфейс-это контракт, который должен использоваться указанным способом. Следующий подход может быть жизнеспособной альтернативой для некоторых сценариев:

public interface IFoo {

    /// <summary>
    /// Initialize foo.
    /// </summary>
    /// <remarks>
    /// Classes that implement this interface must invoke this method from
    /// each of their constructors.
    /// </remarks>
    /// <exception cref="InvalidOperationException">
    /// Thrown when instance has already been initialized.
    /// </exception>
    void Initialize(int a);

}

public class ConcreteFoo : IFoo {

    private bool _init = false;

    public int b;

    // Obviously in this case a default value could be used for the
    // constructor argument; using overloads for purpose of example

    public ConcreteFoo() {
        Initialize(42);
    }

    public ConcreteFoo(int a) {
        Initialize(a);
    }

    public void Initialize(int a) {
        if (_init)
            throw new InvalidOperationException();
        _init = true;

        b = a;
    }

}

один из способов заставить какой-то конструктор объявить только Getters в интерфейсе, что может означать, что реализующий класс должен иметь метод, в идеале конструктор, чтобы иметь набор значений (privately) для него.

хотя вы не можете определить подпись конструктора в интерфейсе, я считаю, что стоит упомянуть, что это может быть место для рассмотрения абстрактного класса. Абстрактные классы могут определять нереализованные (абстрактные) сигнатуры методов так же, как и интерфейс, но также могут иметь реализованные (конкретные) методы и конструкторы.

недостатком является то, что, поскольку это тип класса, он не может быть использован для любого из сценариев множественного типа наследования, которые интерфейс мочь.

цель интерфейса заключается в применении определенной подписи объекта. Он явно не должен быть связан с тем, как объект работает внутри. Поэтому конструктор в интерфейсе на самом деле не имеет смысла с концептуальной точки зрения.

есть несколько альтернатив, хотя:

  • создать абстрактный класс, который действует как минимальная реализация по умолчанию. Этот класс должен иметь конструкторы, которые вы ожидаете реализации классов к иметь.

  • если вы не возражаете против перебора, используйте абстрактный шаблон и объявите метод в интерфейсе класса фабрики, который имеет необходимые подписывание.

  • передать GraphicsDeviceManager в качестве параметра Update и Draw методы.

  • используйте композиционную структуру объектно-ориентированного программирования для передачи GraphicsDeviceManager в ту часть объекта, которая этого требует. Это довольно экспериментальное решение в моем мнение.

ситуация, которую вы описываете, не так проста в обращении в целом. Аналогичным случаем могут быть сущности в бизнес-приложении, которым требуется доступ к базе данных.

Если я правильно понял OP, мы хотим применить контракт, где GraphicsDeviceManager всегда инициализируется путем реализации классов. У меня была аналогичная проблема, и я искал лучшее решение, но это лучшее, что я могу придумать:

добавьте SetGraphicsDeviceManager (GraphicsDeviceManager gdo) в интерфейс, и таким образом реализующие классы будут вынуждены написать логику, которая потребует вызова из конструктора.

Comments

    Ничего не найдено.