Интерфейс, определяющий сигнатуру конструктора? - PullRequest
498 голосов
/ 06 марта 2009

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

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

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

IDrawable
+ Update
+ Draw

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

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

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

Ответы [ 17 ]

308 голосов
/ 06 марта 2009

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

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

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

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)
    {
    }
}
133 голосов
/ 11 ноября 2011

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

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);
}
110 голосов
/ 17 января 2017

Как уже отмечалось, у вас не может быть конструкторов в интерфейсе. Но так как этот результат получил столь высокий рейтинг в 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, вы можете создать класс, который определяет свойства для всех полей, которые вам нужно передать.

61 голосов
/ 06 марта 2009

Вы не можете.

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

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

19 голосов
/ 02 марта 2012

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

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

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
  {
  }
}
19 голосов
/ 06 марта 2009

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

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;
  }
}

Надеюсь, это поможет.

6 голосов
/ 29 июня 2012

Один из способов решить эту проблему, которую я обнаружил, - разделить конструкцию на отдельный завод. Например, у меня есть абстрактный класс с именем 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())

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

5 голосов
/ 29 июня 2012

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

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

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);
    }

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

3 голосов
/ 19 апреля 2016

Одним из способов решения этой проблемы является использование обобщений и ограничения new ().

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

2 голосов
/ 18 февраля 2018

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

Есть несколько альтернатив:

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

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

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

  • Используйте платформу Compositional Object-Oriented Programming для передачи GraphicsDeviceManager в ту часть объекта, которая этого требует. На мой взгляд, это довольно экспериментальное решение.

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

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