Question

C'est étrange 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, un GraphicsDeviceManager sera toujours nécessaire. 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 j'implémente ici est IObservable et le GraphicsDeviceManager doit prendre le IDrawable ... Il semble que je ne reçois pas le framework XNA ou que le framework ne soit pas très bien pensé.

Modifier
Il semble y avoir une certaine confusion à propos de ma définition de 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, elle pourrait définir la signature d’un constructeur.

Était-ce utile?

La solution

Comme déjà noté, vous ne pouvez pas avoir de constructeurs sur une interface. Mais comme ce résultat est si bien classé dans Google environ 7 ans plus tard, je me suis dit que je pouvais entrer ici, en particulier pour montrer comment utiliser une classe de base abstraite en tandem avec votre interface existante et peut-être réduire le nombre de refactorisations. nécessaires dans le futur pour des situations similaires. Certains commentaires ont déjà fait allusion à ce concept, mais j’ai pensé que cela valait la peine de montrer comment le faire.

Vous avez donc 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
    }
}

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

IDrawable drawableService = new Drawable(myGraphicsDeviceManager);

La chose intéressante ici 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.

Autres conseils

Vous ne pouvez 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é interfaces statiques qui ne seraient utilisables que dans des contraintes de type génériques, mais qui pourraient s'avérer très pratiques, IMO.

Si vous pouviez définir un constructeur au sein d'une interface, vous auriez du mal à obtenir 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)
    {
    }
}

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 puissions avoir:

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

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

Où, par convention, la mise en oeuvre du "constructeur d'interface" " est remplacé par le nom du type.

Créez maintenant une instance:

ICustomer a = new Person("Ernie");

Dirions-nous que le contrat ICustomer est respecté?

Et que dire de cela:

interface ICustomer
{
    ICustomer(string address);
}

Vous ne pouvez 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.

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, même s'il s'agit d'une syntaxe très laide qui utilise des génériques ... Je ne suis pas vraiment 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 vérifier 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 vous aidera.

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

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

Maintenant, j'ai également besoin d'un moyen pour ma classe de file d'attente réelle de traduire un CloudQueueMessage en un IQueueItem - c'est-à-dire le besoin 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

L’approche générique en usine semble toujours idéale. Vous saurez que l’usine a besoin d’un paramètre, et il arriverait que ces paramètres soient transmis au constructeur de l’objet instancié.

Remarque: il s'agit simplement d'un pseudo-code vérifié par la syntaxe. Il se peut qu'il manque une mise en garde à l'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);
    }

Bien entendu, 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’il serait judicieux d’utiliser un cadre d’injection de dépendance comme AutoFac ; Update () pourrait " demander " le conteneur IoC pour un nouvel objet GraphicsDeviceManager.

Une solution à 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
}

Vous pouvez également utiliser les expressions lambda comme initialiseurs. À un moment donné, au début de la hiérarchie des appels, vous saurez quels objets vous devez 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 pour 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 implémentant IDrawable à l'aide de 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.

Vous pouvez le faire 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
}

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 du point de vue conceptuel.

Il existe cependant quelques alternatives:

  • Créez une classe abstraite agissant comme une implémentation minimale par défaut. Cette classe devrait avoir les constructeurs que vous attendez d'implémentation de classes avoir.

  • Si le surmenage ne vous dérange pas, utilisez le motif AbstractFactory et déclarer une méthode dans l'interface de classe factory qui a la valeur requise signatures.

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

  • Utilisez un cadre de programmation orienté objet de composition pour transmettre le GraphicsDeviceManager à la partie de l’objet qui le requiert. 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 celui des entités d’une application métier nécessitant un accès à la base de données.

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.

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

}

Une façon de forcer une sorte de constructeur consiste à déclarer uniquement Getters dans l'interface, ce qui pourrait alors signifier que la classe d'implémentation doit avoir une méthode, idéalement un constructeur, pour que la valeur soit définie (< code> privé ly) pour cela.

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 propice pour considérer 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 elles 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, elle ne peut être utilisée dans aucun des scénarios de types d’héritage multiples qu’une interface peut utiliser.

J'utilise le modèle suivant pour le rendre à l'épreuve des balles.

  • Un développeur qui tire sa classe de la base ne peut pas créer accidentellement un constructeur accessible au public
  • Les développeurs de classes finaux sont obligés de passer par la méthode de création courante
  • Tout est conforme au type, aucun casting n'est requis
  • Il est 100% flexible et peut être réutilisé partout, vous permettant de définir votre propre base. classe.
  • 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, vous obtenez malgré tout 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);
    }
    

MODIFIER: 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());
    }

Si j’ai bien compris OP, nous voulons appliquer un contrat dans lequel GraphicsDeviceManager est toujours initialisé par l’implémentation de classes. J'ai eu un problème similaire et je cherchais une meilleure solution, mais c'est le mieux que je puisse penser:

Ajoutez un SetGraphicsDeviceManager (GraphicsDeviceManager gdo) à l'interface, afin que les classes d'implémentation soient obligées d'écrire une logique qui nécessitera un appel du constructeur.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top