Domanda

Poiché l'ereditarietà multipla è dannosa (rende la fonte più complicata) C # non fornisce direttamente un tale modello. Ma a volte sarebbe utile avere questa capacità.

Ad esempio sono in grado di implementare il modello di ereditarietà multipla mancante usando interfacce e tre classi simili:

public interface IFirst { void FirstMethod(); }
public interface ISecond { void SecondMethod(); }

public class First:IFirst 
{ 
    public void FirstMethod() { Console.WriteLine("First"); } 
}

public class Second:ISecond 
{ 
    public void SecondMethod() { Console.WriteLine("Second"); } 
}

public class FirstAndSecond: IFirst, ISecond
{
    First first = new First();
    Second second = new Second();
    public void FirstMethod() { first.FirstMethod(); }
    public void SecondMethod() { second.SecondMethod(); }
}

Ogni volta che aggiungo un metodo a una delle interfacce, devo cambiare anche la classe FirstAndSecond .

C'è un modo per iniettare più classi esistenti in una nuova classe come è possibile in C ++?

Forse esiste una soluzione che utilizza un qualche tipo di generazione del codice?

O potrebbe apparire così (sintassi c # immaginaria):

public class FirstAndSecond: IFirst from First, ISecond from Second
{ }

In modo che non sia necessario aggiornare la classe FirstAndSecond quando modifico una delle interfacce.


Modifica

Forse sarebbe meglio considerare un esempio pratico:

Hai una classe esistente (ad esempio un client TCP basato su testo basato su ITextTcpClient) che usi già in diverse posizioni all'interno del tuo progetto. Ora senti la necessità di creare un componente della tua classe per renderlo facilmente accessibile agli sviluppatori di moduli Windows.

Per quanto ne so attualmente hai due modi per farlo:

  1. Scrivi una nuova classe ereditata dai componenti e implementa l'interfaccia della classe TextTcpClient usando un'istanza della classe stessa come mostrato con FirstAndSecond.

  2. Scrivi una nuova classe che eredita da TextTcpClient e in qualche modo implementa IComponent (non l'ho ancora provato).

In entrambi i casi devi lavorare per metodo e non per classe. Dato che sai che avremo bisogno di tutti i metodi di TextTcpClient e Component, sarebbe la soluzione più semplice combinare questi due in una sola classe.

Per evitare conflitti ciò può essere fatto dalla generazione di codice in cui il risultato potrebbe essere modificato in seguito, ma digitarlo a mano è un vero dolore nel culo.

È stato utile?

Soluzione

  

Poiché l'ereditarietà multipla è dannosa (rende la fonte più complicata) C # non fornisce direttamente un tale modello. Ma a volte sarebbe utile avere questa capacità.

C # e .net CLR non hanno implementato MI perché non hanno ancora concluso come interagirebbe tra C #, VB.net e le altre lingue, non perché "renderebbe la fonte più complessa"

MI è un concetto utile, le domande senza risposta sono quelle come: - " Cosa fai quando hai più classi base comuni nelle diverse superclassi?

Perl è l'unica lingua con cui abbia mai lavorato in cui l'MI funziona e funziona bene. .Net potrebbe introdurlo un giorno, ma non ancora, il CLR supporta già l'MI ma, come ho già detto, non ci sono ancora costrutti linguistici oltre a quello.

Fino ad allora sei bloccato con oggetti proxy e più interfacce :(

Altri suggerimenti

Prendi in considerazione l'utilizzo di composizione invece di provare a simulare l'ereditarietà multipla . Puoi usare le interfacce per definire quali classi compongono la composizione, ad esempio: ISteerable implica una proprietà di tipo SteeringWheel , IBrakable implica una proprietà di tipo BrakePedal , ecc.

Al termine, è possibile utilizzare i Metodi di estensione funzionalità aggiunta a C # 3.0 per semplificare ulteriormente i metodi di chiamata su tali proprietà implicite, ad esempio:

public interface ISteerable { SteeringWheel wheel { get; set; } }

public interface IBrakable { BrakePedal brake { get; set; } }

public class Vehicle : ISteerable, IBrakable
{
    public SteeringWheel wheel { get; set; }

    public BrakePedal brake { get; set; }

    public Vehicle() { wheel = new SteeringWheel(); brake = new BrakePedal(); }
}

public static class SteeringExtensions
{
    public static void SteerLeft(this ISteerable vehicle)
    {
        vehicle.wheel.SteerLeft();
    }
}

public static class BrakeExtensions
{
    public static void Stop(this IBrakable vehicle)
    {
        vehicle.brake.ApplyUntilStop();
    }
}


public class Main
{
    Vehicle myCar = new Vehicle();

    public void main()
    {
        myCar.SteerLeft();
        myCar.Stop();
    }
}

Anche a me piacerebbe - è quello che personalmente definisco un mix-in, anche se mi rendo conto che è un termine sovraccarico. Vorrei poter specificare la variabile utilizzata per implementare l'interfaccia, con l'opzione di fornire la mia implementazione per metodi specifici.

Ho ha scritto questo blog in modo più dettagliato , sebbene nel contesto di una deliberata sopravvalutazione di ciò che potrebbe significare in termini di eredità.

Non vedo alcun motivo per cui questo non possa essere implementato nel compilatore C #, ma è un altro po 'di complessità del linguaggio ...

Ho creato un post-compilatore C # che consente questo tipo di cose:

using NRoles;

public interface IFirst { void FirstMethod(); }
public interface ISecond { void SecondMethod(); }

public class RFirst : IFirst, Role {
  public void FirstMethod() { Console.WriteLine("First"); }
}

public class RSecond : ISecond, Role {
  public void SecondMethod() { Console.WriteLine("Second"); }
}

public class FirstAndSecond : Does<RFirst>, Does<RSecond> { }

È possibile eseguire il post-compilatore come evento post-build di Visual Studio:

  

C: \ some_path \ nroles-v0.1.0-bin \ nutate.exe " $ (TargetPath) "

Nello stesso assieme lo usi in questo modo:

var fas = new FirstAndSecond();
fas.As<RFirst>().FirstMethod();
fas.As<RSecond>().SecondMethod();

In un altro assembly lo usi in questo modo:

var fas = new FirstAndSecond();
fas.FirstMethod();
fas.SecondMethod();

Potresti avere una classe base astratta che implementa sia First che Second, e quindi eredita da quella base.

L'MI non è male, tutti quelli che l'hanno (seriamente) lo adorano e NON complicano il codice! Almeno non più di altri costrutti potrebbe complicare il codice. Il codice errato è un codice errato indipendentemente dal fatto che MI sia presente nella foto.

Comunque, ho una bella soluzione per l'ereditarietà multipla che volevo condividere, è su; http: // ra- ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blog oppure puoi seguire il link nel mio sig ... :)

Se riesci a convivere con la restrizione che i metodi di IFirst e ISecond devono interagire solo con il contratto di IFirst e ISecond (come nel tuo esempio) ... puoi fare ciò che chiedi con i metodi di estensione. In pratica, questo è raramente il caso.

public interface IFirst {}
public interface ISecond {}

public class FirstAndSecond : IFirst, ISecond
{
}

public static MultipleInheritenceExtensions
{
  public static void First(this IFirst theFirst)
  {
    Console.WriteLine("First");
  }

  public static void Second(this ISecond theSecond)
  {
    Console.WriteLine("Second");
  }
}

///

public void Test()
{
  FirstAndSecond fas = new FirstAndSecond();
  fas.First();
  fas.Second();
}

Quindi l'idea di base è che tu definisca l'implementazione richiesta nelle interfacce ... questa roba richiesta dovrebbe supportare l'implementazione flessibile nei metodi di estensione. Ogni volta che devi " aggiungere metodi all'interfaccia " invece aggiungi un metodo di estensione.

Sì, usare Interface è una seccatura perché ogni volta che aggiungiamo un metodo nella classe dobbiamo aggiungere la firma nell'interfaccia. Inoltre, cosa succede se abbiamo già una classe con un sacco di metodi ma nessuna interfaccia per esso? dobbiamo creare manualmente l'interfaccia per tutte le classi da cui vogliamo ereditare. E la cosa peggiore è che dobbiamo implementare tutti i metodi nelle interfacce nella classe child se la classe child deve ereditare dall'interfaccia multipla.

Seguendo il modello di progettazione della facciata possiamo simulare l'ereditarietà da più classi usando accessori . Dichiarare le classi come proprietà con {get; set;} all'interno della classe che deve ereditare e tutte le proprietà e i metodi pubblici provengono da quella classe e nel costruttore della classe figlio crea un'istanza delle classi parent.

Ad esempio:

 namespace OOP
 {
     class Program
     {
         static void Main(string[] args)
         {
             Child somechild = new Child();
             somechild.DoHomeWork();
             somechild.CheckingAround();
             Console.ReadLine();
         }
     }

     public class Father 
     {
         public Father() { }
         public void Work()
         {
             Console.WriteLine("working...");
         }
         public void Moonlight()
         {
             Console.WriteLine("moonlighting...");
         }
     }


     public class Mother 
     {
         public Mother() { }
         public void Cook()
         {
             Console.WriteLine("cooking...");
         }
         public void Clean()
         {
             Console.WriteLine("cleaning...");
         }
     }


     public class Child 
     {
         public Father MyFather { get; set; }
         public Mother MyMother { get; set; }

         public Child()
         {
             MyFather = new Father();
             MyMother = new Mother();
         }

         public void GoToSchool()
         {
             Console.WriteLine("go to school...");
         }
         public void DoHomeWork()
         {
             Console.WriteLine("doing homework...");
         }
         public void CheckingAround()
         {
             MyFather.Work();
             MyMother.Cook();
         }
     }


 }

con questa struttura la classe Child avrà accesso a tutti i metodi e proprietà della classe Father and Mother, simulando l'ereditarietà multipla, ereditando un'istanza delle classi parent. Non è lo stesso, ma è pratico.

L'ereditarietà multipla è una di quelle cose che generalmente causa più problemi di quanti ne risolva. In C ++ si adatta allo schema di darti abbastanza corda per impiccarti, ma Java e C # hanno scelto di seguire la strada più sicura di non darti l'opzione. Il problema più grande è cosa fare se si ereditano più classi che hanno un metodo con la stessa firma che l'ereditario non implementa. Quale metodo di classe dovrebbe scegliere? O non dovrebbe essere compilato? Esiste generalmente un altro modo per implementare la maggior parte delle cose che non si basa sull'ereditarietà multipla.

Se X eredita da Y, ciò ha due effetti in qualche modo ortogonali:

  1. Y fornirà funzionalità predefinite per X, quindi il codice per X deve includere solo elementi diversi da Y.
  2. Quasi ovunque ci si aspetterebbe una Y, al suo posto è possibile utilizzare una X.

Sebbene l'ereditarietà preveda entrambe le funzionalità, non è difficile immaginare circostanze in cui entrambe potrebbero essere utili senza l'altra. Nessun linguaggio .net che conosco ha un modo diretto di implementare il primo senza il secondo, sebbene si possa ottenere tale funzionalità definendo una classe base che non viene mai utilizzata direttamente e avendo una o più classi che ereditano direttamente da essa senza aggiungere nulla nuovo (tali classi potrebbero condividere tutto il loro codice, ma non sarebbero sostituibili l'una con l'altra). Qualsiasi linguaggio conforme a CLR, tuttavia, consentirà l'uso di interfacce che forniscono la seconda funzionalità di interfacce (sostituibilità) senza il primo (riutilizzo dei membri).

lo so lo so anche se non è permesso e così via, a volte ne hai davvero bisogno così per quelli:

class a {}
class b : a {}
class c : b {}

come nel mio caso, volevo farlo     classe b: Form (sì, windows.forms)     classe c: b {}

perché metà della funzione era identica e con l'interfaccia devi riscriverle tutte

Poiché la questione dell'ereditarietà multipla (MI) si presenta di volta in volta, vorrei aggiungere un approccio che risolva alcuni problemi con il modello di composizione.

Mi baso su IFirst , ISecond , First , Second , FirstAndSecond approccio, come è stato presentato nella domanda. Riduco il codice di esempio a IFirst , poiché il modello rimane lo stesso indipendentemente dal numero di interfacce / classi di base MI.

Supponiamo che con MI First e Second deriverebbero entrambi dalla stessa classe base BaseClass , usando solo gli elementi dell'interfaccia pubblica di < code> BaseClass

Questo può essere espresso aggiungendo un riferimento contenitore a BaseClass nell'implementazione First e Second :

class First : IFirst {
  private BaseClass ContainerInstance;
  First(BaseClass container) { ContainerInstance = container; }
  public void FirstMethod() { Console.WriteLine("First"); ContainerInstance.DoStuff(); } 
}
...

Le cose diventano più complicate, quando si fa riferimento a elementi dell'interfaccia protetta da BaseClass o quando First e Second sarebbero classi astratte in MI, che richiedono le loro sottoclassi per implementare alcune parti astratte.

class BaseClass {
  protected void DoStuff();
}

abstract class First : IFirst {
  public void FirstMethod() { DoStuff(); DoSubClassStuff(); }
  protected abstract void DoStuff(); // base class reference in MI
  protected abstract void DoSubClassStuff(); // sub class responsibility
}

C # consente alle classi nidificate di accedere ad elementi protetti / privati ??delle loro classi di contenimento, quindi può essere utilizzata per collegare i bit astratti dall'implementazione First .

class FirstAndSecond : BaseClass, IFirst, ISecond {
  // link interface
  private class PartFirst : First {
    private FirstAndSecond ContainerInstance;
    public PartFirst(FirstAndSecond container) {
      ContainerInstance = container;
    }
    // forwarded references to emulate access as it would be with MI
    protected override void DoStuff() { ContainerInstance.DoStuff(); }
    protected override void DoSubClassStuff() { ContainerInstance.DoSubClassStuff(); }
  }
  private IFirst partFirstInstance; // composition object
  public FirstMethod() { partFirstInstance.FirstMethod(); } // forwarded implementation
  public FirstAndSecond() {
    partFirstInstance = new PartFirst(this); // composition in constructor
  }
  // same stuff for Second
  //...
  // implementation of DoSubClassStuff
  private void DoSubClassStuff() { Console.WriteLine("Private method accessed"); }
}

È coinvolto un po 'di plateplate, ma se l'implementazione effettiva di FirstMethod e SecondMethod è sufficientemente complessa e la quantità di metodi privati ??/ protetti accessibili è moderata, questo modello può aiutare a superare la mancanza di eredità multipla.

Questo è in linea con la risposta di Lawrence Wenham, ma a seconda del tuo caso d'uso, potrebbe essere o meno un miglioramento - non hai bisogno dei setter.

public interface IPerson {
  int GetAge();
  string GetName();
}

public interface IGetPerson {
  IPerson GetPerson();
}

public static class IGetPersonAdditions {
  public static int GetAgeViaPerson(this IGetPerson getPerson) { // I prefer to have the "ViaPerson" in the name in case the object has another Age property.
    IPerson person = getPerson.GetPersion();
    return person.GetAge();
  }
  public static string GetNameViaPerson(this IGetPerson getPerson) {
    return getPerson.GetPerson().GetName();
  }
}

public class Person: IPerson, IGetPerson {
  private int Age {get;set;}
  private string Name {get;set;}
  public IPerson GetPerson() {
    return this;
  }
  public int GetAge() {  return Age; }
  public string GetName() { return Name; }
}

Ora qualsiasi oggetto che sa come ottenere una persona può implementare IGetPerson e avrà automaticamente i metodi GetAgeViaPerson () e GetNameViaPerson (). Da questo punto in poi tutto il codice Persona va in IGetPerson, non in IPerson, a parte i nuovi ivar, che devono andare in entrambi. E usando tale codice, non devi preoccuparti se il tuo oggetto IGetPerson è in realtà un IPerson.

Sembra che tutti stiamo percorrendo il percorso dell'interfaccia con questo, ma l'ovvia altra possibilità, qui, è fare ciò che OOP dovrebbe fare e costruire il tuo albero ereditario ... (non lo è questo è il design di classe?)

class Program
{
    static void Main(string[] args)
    {
        human me = new human();
        me.legs = 2;
        me.lfType = "Human";
        me.name = "Paul";
        Console.WriteLine(me.name);
    }
}

public abstract class lifeform
{
    public string lfType { get; set; }
}

public abstract class mammal : lifeform 
{
    public int legs { get; set; }
}

public class human : mammal
{
    public string name { get; set; }
}

Questa struttura fornisce blocchi riutilizzabili di codice e, sicuramente, come deve essere scritto il codice OOP?

Se questo particolare approccio non si adatta perfettamente al conto, creiamo semplicemente nuove classi basate sugli oggetti richiesti ...

class Program
{
    static void Main(string[] args)
    {
        fish shark = new fish();
        shark.size = "large";
        shark.lfType = "Fish";
        shark.name = "Jaws";
        Console.WriteLine(shark.name);
        human me = new human();
        me.legs = 2;
        me.lfType = "Human";
        me.name = "Paul";
        Console.WriteLine(me.name);
    }
}

public abstract class lifeform
{
    public string lfType { get; set; }
}

public abstract class mammal : lifeform 
{
    public int legs { get; set; }
}

public class human : mammal
{
    public string name { get; set; }
}

public class aquatic : lifeform
{
    public string size { get; set; }
}

public class fish : aquatic
{
    public string name { get; set; }
}

Nella mia implementazione ho scoperto che l'uso di classi / interfacce per MI, sebbene "buona forma", tendesse ad essere una complicanza eccessiva poiché è necessario impostare tutta quell'eredità multipla solo per alcune chiamate di funzione necessarie, e nel mio caso, doveva essere fatto letteralmente dozzine di volte in modo ridondante.

Invece è stato più semplice creare semplicemente funzioni "statiche" che richiamano funzioni che chiamano funzioni " in diverse varietà modulari come una sorta di sostituzione OOP. La soluzione a cui stavo lavorando era il "sistema di incantesimi" per un gioco di ruolo in cui gli effetti devono fortemente combinare e abbinare la funzione chiamando per fornire un'estrema varietà di incantesimi senza riscrivere il codice, proprio come sembra indicare l'esempio.

La maggior parte delle funzioni ora può essere statica perché non ho necessariamente bisogno di un'istanza per la logica degli incantesimi, mentre l'eredità di classe non può nemmeno usare parole chiave virtuali o astratte mentre è statica. Le interfacce non possono usarle affatto.

La codifica sembra molto più veloce e più pulita in questo modo IMO. Se stai semplicemente eseguendo funzioni e non hai bisogno di proprietà ereditate , utilizza le funzioni.

Con C # 8 ora hai praticamente l'eredità multipla tramite l'implementazione predefinita dei membri dell'interfaccia:

interface ILogger
{
    void Log(LogLevel level, string message);
    void Log(Exception ex) => Log(LogLevel.Error, ex.ToString()); // New overload
}

class ConsoleLogger : ILogger
{
    public void Log(LogLevel level, string message) { ... }
    // Log(Exception) gets default implementation
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top