c# interface - Interfaccia che definisce una firma del costruttore?




tutorial (15)

Se ho capito bene OP, vogliamo imporre un contratto in cui GraphicsDeviceManager è sempre inizializzato implementando le classi. Ho avuto un problema simile e stavo cercando una soluzione migliore, ma questo è il meglio che posso pensare:

Aggiungi un SetGraphicsDeviceManager (GraphicsDeviceManager gdo) all'interfaccia e in questo modo le classi di implementazione saranno costrette a scrivere una logica che richiederà una chiamata da parte del costruttore.

È strano che questa sia la prima volta che mi imbatto in questo problema, ma:

Come si definisce un costruttore in un'interfaccia C #?

modificare
Alcune persone volevano un esempio (è un progetto a tempo libero, quindi sì, è un gioco)

IDrawable
+ Aggiornamento
+ Draw

Per essere in grado di effettuare l'aggiornamento (controllare il bordo dello schermo ecc.) E disegnare da solo, sarà sempre necessario un GraphicsDeviceManager . Quindi voglio assicurarmi che l'oggetto abbia un riferimento ad esso. Questo dovrebbe appartenere al costruttore.

Ora che ho scritto questo, penso che quello che sto implementando qui è IObservable e il GraphicsDeviceManager dovrebbe prendere l' IDrawable ... Sembra che non ottengo il framework XNA, o il framework non è pensato molto bene.

modificare
Sembra esserci una certa confusione circa la mia definizione di costruttore nel contesto di un'interfaccia. Un'interfaccia non può essere istanziata quindi non ha bisogno di un costruttore. Quello che volevo definire era una firma per un costruttore. Esattamente come un'interfaccia può definire una firma di un determinato metodo, l'interfaccia potrebbe definire la firma di un costruttore.


tu no.

il costruttore è parte della classe che può implementare un'interfaccia. L'interfaccia è solo un contratto di metodi che la classe deve implementare.


Non è possibile creare un'interfaccia che definisca i costruttori, ma è possibile definire un'interfaccia che costringa un tipo ad avere un costruttore paramerterless, anche se si tratta di una sintassi molto brutta che usa i generici ... In realtà non sono così sicuro che è davvero un buon modello di programmazione.

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'altra parte, se vuoi testare se un tipo ha un costruttore paramerterless, puoi farlo usando il reflection:

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

Spero che questo ti aiuti.


Puoi farlo con un trucco generico, ma è ancora vulnerabile a ciò che Jon Skeet ha scritto:

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

La classe che implementa questa interfaccia deve avere un costruttore senza parametri:

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

Non puoi

Le interfacce definiscono i contratti implementati da altri oggetti e quindi non hanno uno stato che deve essere inizializzato.

Se hai uno stato che deve essere inizializzato, dovresti prendere in considerazione l'utilizzo di una classe base astratta.


Lo scopo di un'interfaccia è di imporre una determinata firma dell'oggetto. Dovrebbe esplicitamente non essere interessato a come un oggetto funziona internamente. Pertanto, un costruttore in un'interfaccia non ha realmente senso da un punto di vista concettuale.

Ci sono alcune alternative però:

  • Crea una classe astratta che funge da implementazione predefinita minima. Quella classe dovrebbe avere i costruttori che ci si aspetta che implementino le classi.

  • Se non ti dispiace l'overkill, usa il pattern AbstractFactory e dichiara un metodo nell'interfaccia della classe factory che ha le firme richieste.

  • Passa GraphicsDeviceManager come parametro ai metodi Update e Draw .

  • Utilizzare un framework di programmazione orientata agli oggetti per la progettazione per trasferire GraphicsDeviceManager nella parte dell'oggetto che lo richiede. Questa è una soluzione piuttosto sperimentale secondo me.

La situazione che descrivi non è facile da gestire in generale. Un caso simile potrebbe essere entità in un'applicazione aziendale che richiede l'accesso al database.


Un modo per risolvere questo problema che ho trovato è quello di separare la costruzione in una fabbrica separata. Ad esempio, ho una classe astratta chiamata IQueueItem e ho bisogno di un modo per tradurre quell'oggetto da e verso un altro oggetto (CloudQueueMessage). Quindi sull'interfaccia IQueueItem che ho -

public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}

Ora, ho anche bisogno di un modo per la mia classe di coda effettiva per tradurre un oggetto CloudQueueMessage su un oggetto IQueue - cioè la necessità di una costruzione statica come IQueueItem objMessage = ItemType.FromMessage. Invece ho definito un'altra interfaccia IQueueFactory -

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

Ora posso finalmente scrivere la mia generica coda senza il nuovo vincolo () che nel mio caso era il problema principale.

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

ora posso creare un'istanza che soddisfi i criteri per me

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

speriamo che questo possa aiutare qualcun altro un giorno, ovviamente un sacco di codice interno rimosso per cercare di mostrare il problema e la soluzione


Le interfacce sono Contract Interface non consentono campi, ovvero non è necessario inizializzare nulla. Interfaccia conserva solo il riferimento. L'interfaccia deve richiedere una classe derivata per contenere la memoria.


Un contributo molto recente che dimostra un altro problema con i costruttori interfacciati. (Scelgo questa domanda perché ha l'articolazione più chiara del problema). Supponiamo di poter avere:

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

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

Dove per convenzione l'implementazione del "costruttore di interfacce" è sostituita dal nome del tipo.

Ora crea un'istanza:

ICustomer a = new Person("Ernie");

Diremmo che il contratto con l'utente è obbedito?

E che dire di questo:

interface ICustomer
{
    ICustomer(string address);
}

Sebbene non sia possibile definire una firma del costruttore in un'interfaccia, ritengo che valga la pena ricordare che questo potrebbe essere un punto da considerare in una classe astratta. Le classi astratte possono definire firme di metodo non astratte (astratte) allo stesso modo di un'interfaccia, ma possono anche aver implementato metodi e costruttori (concreti).

Lo svantaggio è che, poiché è un tipo di classe, non può essere utilizzato per nessuno degli scenari di tipo di ereditarietà multipli che un'interfaccia può fare.


Non puoi Occasionalmente è un dolore, ma non si potrebbe comunque chiamarlo usando le normali tecniche.

In un post del blog ho suggerito interfacce statiche che sarebbero utilizzabili solo in vincoli di tipo generico, ma potrebbero essere davvero a portata di mano, IMO.

Un punto su se potessi definire un costruttore all'interno di un'interfaccia, avresti problemi nel derivare le classi:

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

Un modo per risolvere questo problema è sfruttare i generici e il nuovo () vincolo.

Invece di esprimere il tuo costruttore come metodo / funzione, puoi esprimerlo come una classe / interfaccia di fabbrica. Se si specifica il nuovo vincolo generico () in ogni sito di chiamata che deve creare un oggetto della classe, sarà possibile passare di conseguenza gli argomenti del costruttore.

Per il tuo esempio identificabile:

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

Ora quando lo usi:

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
}

È anche possibile concentrare tutti i metodi di creazione in una singola classe utilizzando l'implementazione dell'interfaccia esplicita:

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

Per usarlo:

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
}

Un altro modo è usare le espressioni lambda come inizializzatori. Ad un certo punto all'inizio della gerarchia di chiamate, saprai quali oggetti dovrai istanziare (cioè quando stai creando o ricevendo un riferimento al tuo oggetto GraphicsDeviceManager). Appena lo hai, passa il lambda

() => new Triangle(manager) 

a metodi successivi in ​​modo che sappiano come creare un triangolo da quel momento in poi. Se non è possibile determinare tutti i possibili metodi necessari, è sempre possibile creare un dizionario di tipi che implementano IDrawable utilizzando la reflection e registrare l'espressione lambda mostrata sopra in un dizionario che è possibile memorizzare in un percorso condiviso o passare a ulteriori chiamate di funzione.


Come già ben osservato, non è possibile avere costruttori su un'interfaccia. Ma dato che questo è un risultato così elevato in Google 7 anni dopo, ho pensato che avrei fatto il chip qui - in particolare per mostrare come utilizzare una classe base astratta in tandem con la tua interfaccia esistente e magari ridurre la quantità di refactoring necessario in futuro per situazioni simili. Questo concetto è già stato accennato in alcuni dei commenti, ma ho pensato che sarebbe valsa la pena mostrare come effettivamente farlo.

Quindi hai la tua interfaccia principale che sembra finora:

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

Ora crea una classe astratta con il costruttore che desideri applicare. In realtà, dal momento che è ora disponibile dal momento in cui hai scritto la tua domanda originale, possiamo ottenere un po 'di fantasia qui e utilizzare i generici in questa situazione in modo che possiamo adattarlo ad altre interfacce che potrebbero richiedere la stessa funzionalità ma avere requisiti diversi per i costruttori:

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

    }
}

Ora è necessario creare una nuova classe che eredita dall'interfaccia IDrawable e dalla classe astratta 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
    }
}

Quindi crea un'istanza di Drawable e sei a posto:

IDrawable drawableService = new Drawable(myGraphicsDeviceManager);

La cosa interessante è che la nuova classe Drawable che abbiamo creato si comporta ancora esattamente come ci aspetteremmo da un IDrawable.

Se è necessario passare più di un parametro al costruttore MustInitialize, è possibile creare una classe che definisca le proprietà per tutti i campi che è necessario passare.


L'approccio generico alla fabbrica sembra ancora ideale. Sapresti che la fabbrica richiede un parametro e succederebbe che tali parametri vengano passati al costruttore dell'oggetto istanziato.

Nota, questo è solo uno pseudo codice verificato dalla sintassi, potrebbe esserci un avvertimento di runtime che mi manca qui:

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 esempio di possibile utilizzo:

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

Certo, vorresti solo creare le istanze tramite la factory per garantirti sempre un oggetto inizializzato in modo appropriato. Forse usare un framework di iniezione delle dipendenze come AutoFac avrebbe senso; Update () potrebbe "chiedere" al contenitore IoC per un nuovo oggetto GraphicsDeviceManager.


Implementando le interfacce si ottiene la composizione (relazioni "ha-a") anziché l'ereditarietà (relazioni "is-a"). Questo è un principio importante da ricordare quando si tratta di elementi come i modelli di progettazione in cui è necessario utilizzare le interfacce per ottenere una composizione di comportamenti anziché un'eredità.





c# interface constructor