"Imprinting" come funzione del linguaggio?

2

Idea

Ho avuto questa idea per una funzione linguistica che penso sarebbe utile, qualcuno sa di una lingua che implementa qualcosa di simile?

L'idea è che, oltre all'ereditarietà, una classe può anche usare qualcosa chiamato "imprinting" (per mancanza di termini migliori). Una classe può imprimere una o più classi (non astratte). Quando una classe imprime un'altra classe ottiene tutte le sue proprietà e tutti i suoi metodi. È come se la classe memorizzasse un'istanza della classe impressa e reindirizzasse i suoi metodi / proprietà a essa. Una classe che imprime un'altra classe quindi, per definizione, implementa anche tutte le sue interfacce e la sua classe astratta.

Quindi qual è il punto? Bene, ereditarietà e polimorfismo sono difficili da ottenere. Spesso la composizione dà molta più flessibilità. L'ereditarietà multipla offre un gran numero di problemi diversi senza molti vantaggi (IMO).

Spesso scrivo le classi dell'adattatore (in C #) implementando alcune interfacce e passando i metodi / le proprietà effettive a un oggetto incapsulato. Lo svantaggio di questo approccio è che se l'interfaccia cambia le interruzioni di classe. Devi anche inserire un sacco di codice che non fa altro che passare le cose lungo l'oggetto incapsulato.

Un esempio classico è che hai una classe che implementa IEnumerable o IList e contiene una classe interna che usa. Con questa tecnica le cose sarebbero molto più semplici

Esempio (c #)

[imprint List<Person> as peopleList]
public class People : PersonBase 
{
    public void SomeMethod()
    {
        DoSomething(this.Count); //Count is from List
    }
}

 //Now People can be treated as an List<Person>
 People people = new People();
 foreach(Person person in people)
 {
  ...
 }

PeopleList è un alias / variablename (di tua scelta) utilizzato internamente per creare alias l'istanza, ma può essere saltato se non necessario. Una cosa che è utile è sovrascrivere un metodo impresso, che potrebbe essere ottenuto con la sintassi di override ordinaria

public override void Add(Person person)
{ 
    DoSomething();
    personList.Add(person); 
}

nota che quanto sopra è equivalente funzionale (e potrebbe essere riscritto dal compilatore) a:

public class People : PersonBase , IList<Person>
{
    private List<Person> personList = new  List<Person>();


    public override void Add(object obj)
    {
       this.personList.Add(obj)
    }

   public override int IndexOf(object obj)
    {
       return personList.IndexOf(obj)
    }

   //etc etc for each signature in the interface
}

solo se IList cambierà la tua classe si romperà. IList non cambierà, ma un'interfaccia che tu, qualcuno del tuo team o una terza parte ha progettato potrebbe semplicemente cambiare. Inoltre, questo ti consente di risparmiare un sacco di codice per alcune interfacce / classi astratte.

Avvertimenti

Ci sono un paio di trucchi. Per prima cosa dobbiamo aggiungere la sintassi per chiamare i costruttori delle classi imprinted dal costruttore della classe imprinting.

Inoltre, cosa succede se una classe imprime due classi che hanno lo stesso metodo? In tal caso il compilatore lo rileva e impone alla classe di definire un override di quel metodo (in cui è possibile scegliere se si desidera chiamare una classe imprinted o entrambi)

Quindi cosa ne pensi, sarebbe utile, eventuali avvertimenti? Sembra che sarebbe abbastanza semplice implementare qualcosa del genere nel linguaggio C # ma potrei mancare qualcosa:)

Nota a margine - Perché è diverso dall'eredità multipla

Ok, quindi alcune persone hanno chiesto questo. Perché è diverso dall'ereditarietà multipla e perché non dall'ereditarietà multipla. In C # i metodi sono virtuali o meno.

Diciamo che abbiamo ClassB che eredita da ClassA. ClassA ha i metodi MethodA e MethodB. ClassB sovrascrive MethodA ma non MethodB.

Ora dite che MethodB ha una chiamata a MethodA. se MethodA è virtuale chiamerà l'implementazione che ha ClassB, altrimenti utilizzerà la classe base, il MetodoA di ClassA e finirai per chiedersi perché la tua classe non funziona come dovrebbe.

Secondo la terminologia finora potresti già confonderti. Quindi, cosa succede se ClassB eredita sia da ClassA che da un altro ClassC. Scommetto che sia i programmatori che i compilatori si staranno grattando la testa.

Il vantaggio di questo approccio IMO è che le classi di imprinting sono totalmente incapsulate e non devono essere progettate pensando in particolare all'eredità multipla. Puoi praticamente imprimere qualsiasi cosa.

    
posta Homde 01.03.2011 - 18:53
fonte

4 risposte

9

Il nome normale per questo concetto è mix-in . È supportato da molte lingue, ma non C # o Java. Ti suggerirei di imparare alcune nuove lingue, ma ciò potrebbe rovinarti per C #.

    
risposta data 01.03.2011 - 20:27
fonte
3

Questa è una caratteristica interessante.

Scala supporta anche questo.

Non esattamente come lo descrivi, ma lo fa, dai un'occhiata a questo:

trait  A { 
    def a() = "Hola"
}

trait B { 
    def b() = ", mundo!"
}

class C extends Object with A with  B  {}

object Main { 
    def main( args : Array[String] ) { 
        val c = new C()
        println( c.a() + c.b() );

    }
}

Output:

Hola, mundo!

Python ha qualcosa di simile, ma non so esattamente come sia implementato.

    
risposta data 02.03.2011 - 17:30
fonte
2

Il comportamento che stai cercando è esattamente equivalente all'ereditarietà multipla.

I problemi che stai descrivendo con ereditarietà multipla in C # non sono esattamente presenti in tutte le lingue, puoi guardare alcuni altri esempi (sono già stati citati mixin dal ruby) su come implementare l'ereditarietà multipla per trovare approcci che potrebbero rendere più senso per te.

Vale la pena notare che i problemi che hai descritto con C # derivano dai tentativi di gestire le complessità di base nella configurazione, complessità che il tuo approccio imprinting non risolve. In altre parole, con l'implementazione dell'imprinting, mentre la descrivi, gli implementors non possono ancora ignorare le stesse complessità che esistono nell'ereditarietà multipla. Di 'che imprimi sia PeopleList che ChemistList. Quale messaggio di "conteggio" verrà inviato nel tuo esempio? Penso che scoprirai che una volta preso in considerazione questo e tutti gli altri casi d'angolo della tua implementazione, ti ritroverai con una copia esatta della funzionalità di ereditarietà multipla, con un nome diverso.

    
risposta data 01.03.2011 - 19:59
fonte
2

Perl 5 ha il Moose del sistema OO, che fornisce ruoli (a volte chiamati anche" tratti ", che ritengo derivino dallo sfondo accademico originale). Questo include anche la composizione di metodi e attributi come il rilevamento dei conflitti e la risoluzione come hai specificato.

    
risposta data 02.03.2011 - 17:15
fonte