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

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

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

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

IDrawable
+ Update
+ Draw

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

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

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

Ответы [ 17 ]

2 голосов
/ 14 августа 2013

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

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
}
0 голосов
/ 13 марта 2019

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

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

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

        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());
    }
0 голосов
/ 06 марта 2009

нет.

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

0 голосов
/ 20 декабря 2016

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

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

0 голосов
/ 06 ноября 2016

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

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

0 голосов
/ 12 февраля 2014

Один из способов заставить конструктор определенного типа - объявить в интерфейсе только Getters, что может означать, что класс реализации должен иметь метод, в идеале конструктор, для установки значения (private ly) для это.

0 голосов
/ 18 мая 2012

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

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

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

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