web-dev-qa-db-fra.com

Interface définissant une signature du constructeur?

C'est bizarre que ce soit la première fois que je me heurte à ce problème, mais:

Comment définissez-vous un constructeur dans une interface C #?

Modifier
Certaines personnes voulaient un exemple (c'est un projet de temps libre, alors oui, c'est un jeu)

IDrawable
+ Mise à jour
+ Dessiner

Pour pouvoir mettre à jour (vérifier le bord de l’écran, etc.) et se dessiner, il faudra toujours une GraphicsDeviceManager. Je veux donc m'assurer que l'objet est référencé. Cela appartiendrait au constructeur.

Maintenant que j'ai écrit ceci, je pense que ce que je suis en train de mettre en œuvre ici est IObservable et le GraphicsDeviceManager devrait prendre le IDrawable... .

Modifier
Il semble y avoir une certaine confusion à propos de ma définition du constructeur dans le contexte d’une interface. Une interface ne peut en effet pas être instanciée, elle n’a donc pas besoin d’un constructeur. Ce que je voulais définir était une signature à un constructeur. Exactement comme une interface peut définir la signature d’une certaine méthode, l’interface peut définir la signature d’un constructeur.

483
Boris Callens

Comme déjà noté, vous ne pouvez pas avoir de constructeurs sur une interface. Mais comme il s’agit d’un résultat très bien classé dans Google quelques 7 ans plus tard, j’ai pensé que j’entrerais dans cette catégorie, en particulier pour montrer comment utiliser une classe de base abstraite conjointement avec votre interface existante et peut-être réduire le nombre de refactorisations. nécessaires dans le futur pour des situations similaires. Certains ont déjà fait allusion à ce concept, mais j’ai pensé que cela valait la peine de montrer comment le faire.

Donc, vous avez votre interface principale qui ressemble à ceci jusqu'à présent:

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

Créez maintenant une classe abstraite avec le constructeur que vous souhaitez appliquer. En fait, comme il est disponible depuis le moment où vous avez rédigé votre question initiale, nous pouvons avoir un peu de fantaisie ici et utiliser des génériques dans cette situation afin de pouvoir l'adapter à d'autres interfaces pouvant nécessiter les mêmes fonctionnalités mais ayant des exigences de constructeur différentes:

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

    }
}

Vous devez maintenant créer une nouvelle classe qui hérite à la fois de l'interface IDrawable et de la classe abstraite 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
    }
}

Ensuite, créez simplement une instance de Drawable et vous êtes prêt à partir:

IDrawable drawableService = new Drawable(myGraphicsDeviceManager);

Le point positif est que la nouvelle classe Drawable que nous avons créée se comporte toujours comme ce à quoi nous nous attendions d’un IDrawable.

Si vous devez transmettre plusieurs paramètres au constructeur MustInitialize, vous pouvez créer une classe définissant les propriétés de tous les champs à transmettre.

99
Dan

Tu ne peux pas. C'est parfois une douleur, mais vous ne pourriez pas l'appeler en utilisant des techniques normales de toute façon.

Dans un article de blog, j'ai suggéré des interfaces statiques qui ne seraient utilisables que dans des contraintes de type générique - mais pourraient s'avérer très utiles, IMO.

Un point sur si vous pourriez définir un constructeur dans une interface, vous auriez du mal à dériver des classes:

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)
    {
    }
}
305
Jon Skeet

Une contribution très tardive démontrant un autre problème avec les constructeurs interfacés. (J'ai choisi cette question parce qu'elle a la formulation la plus claire du problème). Supposons que nous pourrions avoir:

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

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

Où, par convention, l'implémentation du "constructeur d'interface" est remplacée par le nom du type.

Maintenant, créez un exemple:

ICustomer a = new Person("Ernie");

Dirions-nous que le contrat ICustomer est obéi?

Et que dire de ça:

interface ICustomer
{
    ICustomer(string address);
}
133
Gert Arnold

Tu ne peux pas.

Les interfaces définissent les contrats que d'autres objets implémentent et n'ont donc aucun état à initialiser.

Si vous avez un état à initialiser, vous devriez plutôt utiliser une classe de base abstraite.

60
Michael

Il n’est pas possible de créer une interface définissant les constructeurs, mais il est possible de définir une interface qui oblige un type à avoir un constructeur sans paramètre, bien c'est une syntaxe très laide qui utilise des génériques ... Je ne suis en fait pas si sûr que ce soit vraiment un bon modèle de codage.

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
}

D'autre part, si vous voulez tester si un type a un constructeur sans paramètre, vous pouvez le faire en utilisant la réflexion:

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

J'espère que cela t'aides.

20
Jeroen Landheer

Je regardais cette question en arrière et je me suis dit, peut-être que nous abordons ce problème dans le mauvais sens. Les interfaces ne sont peut-être pas la solution lorsqu'il s'agit de définir un constructeur avec certains paramètres ... mais une classe de base (abstraite) l'est. 

Si vous créez une classe de base avec un constructeur qui accepte les paramètres dont vous avez besoin, chaque classe qui en dérive doit les fournir. 

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
Jeroen Landheer

L'approche d'usine générique semble toujours idéale. Vous saurez que la fabrique a besoin d'un paramètre et qu'il arriverait que ces paramètres soient transmis au constructeur de l'objet en cours d'instanciation. 

Notez que ceci est juste du pseudo-code vérifié par la syntaxe. Il se peut que je manque ici un avertissement d'exécution:

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
}

Un exemple d'utilisation possible:

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

Cela dit, vous ne voudriez que les instances de création via la fabrique garantissent que vous avez toujours un objet initialisé de manière appropriée. Peut-être qu'utiliser un framework d'injection de dépendance comme AutoFac aurait du sens; Update () pourrait "demander" au conteneur IoC un nouvel objet GraphicsDeviceManager.

5
Matthew

Un moyen de résoudre ce problème que j'ai trouvé est de séparer la construction en une usine séparée. Par exemple, j'ai une classe abstraite appelée IQueueItem et j'ai besoin d'un moyen de traduire cet objet en et à partir d'un autre objet (CloudQueueMessage). Donc, sur l'interface IQueueItem j'ai - 

public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}

À présent, j'ai également besoin d'un moyen pour ma classe de file d'attente réelle de convertir un objet CloudQueueMessage en un objet IQueueItem - c'est-à-dire la nécessité d'une construction statique comme IQueueItem objMessage = ItemType.FromMessage. Au lieu de cela, j'ai défini une autre interface IQueueFactory -

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

Maintenant, je peux enfin écrire ma classe de files d'attente générique sans la contrainte new () qui, dans mon cas, était le problème principal.

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

maintenant je peux créer une instance qui satisfait les critères pour moi

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

j'espère que cela aidera quelqu'un d'autre un jour, évidemment beaucoup de code interne a été supprimé pour essayer de montrer le problème et la solution

5
JTtheGeek

Un moyen de résoudre ce problème consiste à utiliser les génériques et la contrainte new ().

Au lieu d'exprimer votre constructeur en tant que méthode/fonction, vous pouvez l'exprimer en tant que classe/interface de fabrique. Si vous spécifiez la contrainte générique new () sur chaque site d'appel devant créer un objet de votre classe, vous pourrez passer les arguments du constructeur en conséquence.

Pour votre exemple 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);
    } 
}

Maintenant, quand vous l'utilisez:

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
}

Vous pouvez même concentrer toutes les méthodes de création dans une même classe en utilisant une implémentation d'interface explicite:

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

Pour l'utiliser:

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
}

Une autre méthode consiste à utiliser des expressions lambda comme initialiseurs. À un moment donné, au début de la hiérarchie des appels, vous saurez quels objets vous devrez instancier (c'est-à-dire lorsque vous créez ou obtenez une référence à votre objet GraphicsDeviceManager). Dès que vous l'avez, passez le lambda

() => new Triangle(manager) 

aux méthodes suivantes afin qu’ils sachent comment créer un triangle à partir de ce moment. Si vous ne pouvez pas déterminer toutes les méthodes possibles dont vous aurez besoin, vous pouvez toujours créer un dictionnaire de types mettant en œuvre IDrawable en utilisant la réflexion et enregistrer l'expression lambda présentée ci-dessus dans un dictionnaire que vous pouvez stocker dans un emplacement partagé ou transmettre autres appels de fonction.

3
Cesar

Vous pouvez faire cela avec le tour des génériques, mais cela reste vulnérable à ce que Jon Skeet a écrit:

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

La classe qui implémente cette interface doit avoir un constructeur sans paramètre:

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

Il serait très utile de pouvoir définir des constructeurs dans les interfaces.

Étant donné qu'une interface est un contrat qui doit être utilisé de la manière spécifiée. L'approche suivante pourrait constituer une alternative viable pour certains scénarios:

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

}
0
Lea Hayes

J'utilise le modèle suivant pour le rendre pare-balles.

  • Un développeur qui tire sa classe de la base ne peut pas créer accidentellement un constructeur accessible au public
  • Le développeur de la classe finale est obligé de passer par la méthode create habituelle
  • Tout est conforme à la typographie, aucun moulage n'est requis
  • Il est 100% flexible et peut être réutilisé partout, où vous pouvez définir votre propre classe de base.
  • Essayez-le, vous ne pouvez pas le casser sans apporter de modifications aux classes de base (sauf si vous définissez un indicateur obsolète sans indicateur d'erreur défini sur true, mais vous vous retrouvez malgré tout avec un avertissement)

        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: Et voici un exemple basé sur votre exemple de dessin qui applique même l'abstraction d'interface

        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
Tom

Une façon de forcer une sorte de constructeur est de déclarer uniquement Getters dans interface, ce qui pourrait alors signifier que la classe d'implémentation doit avoir une méthode, idéalement un constructeur, pour avoir la valeur définie (privately).

0
Kunal

vous ne le faites pas.

le constructeur fait partie de la classe pouvant implémenter une interface. L'interface n'est qu'un contrat de méthodes que la classe doit implémenter. 

0
royatl

Le but d'une interface est d'imposer une certaine signature d'objet. Cela ne devrait pas explicitement concerner la manière dont un objet fonctionne en interne. Par conséquent, un constructeur dans une interface n'a pas vraiment de sens d'un point de vue conceptuel.

Il y a quelques alternatives cependant:

  • Créez une classe abstraite qui fonctionne comme une implémentation par défaut minimale . Cette classe doit avoir les constructeurs que vous attendez d'implémentation des classes

  • Si cela ne vous dérange pas, utilisez le modèle AbstractFactory et déclarez une méthode dans l'interface de classe fabrique qui possède les signatures .__ requises.

  • Transmettez la GraphicsDeviceManager en tant que paramètre aux méthodes Update et Draw.

  • Utilisez un cadre de programmation orienté objet de composition pour transmettre la variable GraphicsDeviceManager à la partie de l'objet qui en a besoin. C'est une jolie solution expérimentale à mon avis.

La situation que vous décrivez n'est pas facile à gérer en général. Un cas similaire serait des entités dans une application métier qui nécessitent un accès à la base de données.

0
MauganRa

Bien que vous ne puissiez pas définir une signature de constructeur dans une interface, j'estime qu'il convient de mentionner qu'il peut s'agir d'un endroit idéal pour envisager une classe abstraite. Les classes abstraites peuvent définir des signatures de méthode non implémentées (abstraites) de la même manière qu'une interface, mais peuvent également avoir implémenté des méthodes et des constructeurs (concrets).

L'inconvénient est que, comme il s'agit d'un type de classe, il ne peut être utilisé pour aucun des scénarios de type d'héritage multiples pouvant être gérés par une interface.

0
IrishLagger

Les interfaces sont contractuelles L'interface n'autorise pas les champs, pas besoin d'initialiser quoi que ce soit. 

0
Sunil Dhappadhule