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

StackOverflow https://stackoverflow.com/questions/619856

  •  03-07-2019
  •  | 
  •  

Вопрос

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

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

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

Пригодный для использования
+Обновление
+Ничья

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

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

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

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

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

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

public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}

Теперь мне также нужен способ для моего фактического класса queue преобразовать 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())

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

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

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

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 с помощью factory создавались только для того, чтобы гарантировать, что у вас всегда есть соответствующим образом инициализированный объект.Возможно, используя фреймворк внедрения зависимостей, такой как АвтоФас это имело бы смысл;Update() может "запросить" контейнер IoC для нового объекта GraphicsDeviceManager.

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

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

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
}

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

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

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

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

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

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

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

ты этого не делаешь.

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

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

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

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) за это.

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

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

Я использую следующий шаблон, чтобы сделать его пуленепробиваемым.

  • Разработчик, производящий свой класс от базового, не может случайно создать общедоступный конструктор
  • Разработчик конечного класса вынужден использовать общий метод create
  • Все типобезопасно, отливки не требуются
  • Он на 100% гибкий и может использоваться повторно везде, где вы можете определить свой собственный базовый класс .
  • Попробуйте, вы не сможете взломать его, не внеся изменений в базовые классы (за исключением если вы определяете устаревший флаг без флага ошибки, установленного в true, но даже тогда вы получаете предупреждение)

        public abstract class Base<TSelf, TParameter>
        where TSelf : Base<TSelf, TParameter>, new()
    {
        protected const string FactoryMessage = "Use YourClass.Create(...) instead";
        public static TSelf Create(TParameter parameter)
        {
            var me = new TSelf();
            me.Initialize(parameter);
    
            return me;
        }
    
        [Obsolete(FactoryMessage, true)]
        protected Base()
        {
        }
    
    
    
        protected virtual void Initialize(TParameter parameter)
        {
    
        }
    }
    
    public abstract class BaseWithConfig<TSelf, TConfig>: Base<TSelf, TConfig>
        where TSelf : BaseWithConfig<TSelf, TConfig>, new()
    {
        public TConfig Config { get; private set; }
    
        [Obsolete(FactoryMessage, true)]
        protected BaseWithConfig()
        {
        }
        protected override void Initialize(TConfig parameter)
        {
            this.Config = parameter;
        }
    }
    
    public class MyService : BaseWithConfig<MyService, (string UserName, string Password)>
    {
        [Obsolete(FactoryMessage, true)]
        public MyService()
        {
        }
    }
    
    public class Person : Base<Person, (string FirstName, string LastName)>
    {
        [Obsolete(FactoryMessage,true)]
        public Person()
        {
        }
    
        protected override void Initialize((string FirstName, string LastName) parameter)
        {
            this.FirstName = parameter.FirstName;
            this.LastName = parameter.LastName;
        }
    
        public string LastName { get; private set; }
    
        public string FirstName { get; private set; }
    }
    
    
    
    [Test]
    public void FactoryTest()
    {
        var notInitilaizedPerson = new Person(); // doesn't compile because of the obsolete attribute.
        Person max = Person.Create(("Max", "Mustermann"));
        Assert.AreEqual("Max",max.FirstName);
    
        var service = MyService.Create(("MyUser", "MyPassword"));
        Assert.AreEqual("MyUser", service.Config.UserName);
    }
    

Редактировать: И вот пример, основанный на вашем примере рисования, который даже применяет абстракцию интерфейса

        public abstract class BaseWithAbstraction<TSelf, TInterface, TParameter>
        where TSelf : BaseWithAbstraction<TSelf, TInterface, TParameter>, TInterface, new()
    {
        [Obsolete(FactoryMessage, true)]
        protected BaseWithAbstraction()
        {
        }

        protected const string FactoryMessage = "Use YourClass.Create(...) instead";
        public static TInterface Create(TParameter parameter)
        {
            var me = new TSelf();
            me.Initialize(parameter);

            return me;
        }

        protected virtual void Initialize(TParameter parameter)
        {

        }
    }



    public abstract class BaseWithParameter<TSelf, TInterface, TParameter> : BaseWithAbstraction<TSelf, TInterface, TParameter>
        where TSelf : BaseWithParameter<TSelf, TInterface, TParameter>, TInterface, new()
    {
        protected TParameter Parameter { get; private set; }

        [Obsolete(FactoryMessage, true)]
        protected BaseWithParameter()
        {
        }
        protected sealed override void Initialize(TParameter parameter)
        {
            this.Parameter = parameter;
            this.OnAfterInitialize(parameter);
        }

        protected virtual void OnAfterInitialize(TParameter parameter)
        {
        }
    }


    public class GraphicsDeviceManager
    {

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

    internal abstract class Drawable<TSelf> : BaseWithParameter<TSelf, IDrawable, GraphicsDeviceManager>, IDrawable 
        where TSelf : Drawable<TSelf>, IDrawable, new()
    {
        [Obsolete(FactoryMessage, true)]
        protected Drawable()
        {
        }

        public abstract void Update();
        public abstract void Draw();
    }

    internal class Rectangle : Drawable<Rectangle>
    {
        [Obsolete(FactoryMessage, true)]
        public Rectangle()
        {
        }

        public override void Update()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }

        public override void Draw()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }
    }
    internal class Circle : Drawable<Circle>
    {
        [Obsolete(FactoryMessage, true)]
        public Circle()
        {
        }

        public override void Update()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }

        public override void Draw()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }
    }


    [Test]
    public void FactoryTest()
    {
        // doesn't compile because interface abstraction is enforced.
        Rectangle rectangle = Rectangle.Create(new GraphicsDeviceManager());

        // you get only the IDrawable returned.
        IDrawable service = Circle.Create(new GraphicsDeviceManager());
    }

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top