I delegati sfidano l'OOP

5

Sto cercando di capire OOP in modo da poter scrivere codice OOP migliore e una cosa che continua a venire è questo concetto di delegato (usando .NET). Potrei avere un oggetto, che è totalmente autonomo (incapsulato); non sa nulla del mondo esterno ... ma poi vi allego un delegato.

Nella mia testa, questo è ancora abbastanza ben distinto in quanto il delegato sa solo cosa fare riferimento, ma questo di per sé significa che deve sapere qualcos'altro al di fuori del suo mondo! Che esiste un metodo all'interno di un'altra classe!

Qui mi sono trovato totalmente confuso, o si tratta di un'area grigia, o in realtà è solo un'interpretazione (e se è così, mi spiace che sarà fuori tema, ne sono sicuro). La mia domanda è: i delegati sfidano / infangano il modello OOP?

    
posta Dave 02.08.2013 - 14:07
fonte

6 risposte

7

In my head, this is still quite well separated as the delegate only knows what to reference, but this by itself means it has to know about something else outside it's world!

Ma non è così. Il tuo delegato è solo una variabile che quando invoca chiama qualcosa . Se hai una variabile string , potrebbe contenere qualsiasi cosa. La tua classe non interrompe in qualche modo l'incapsulamento se un'altra classe imposta la tua variabile stringa su "foobar". Allo stesso modo, l'incapsulamento della classe non è interrotto se il delegato è impostato su "nascondi questo widget UI quando viene attivato".

    
risposta data 02.08.2013 - 14:55
fonte
3

Se OOP significa che ogni oggetto è responsabile della propria interfaccia, i delegati logicamente potrebbero interrompere OOP se non sono stati utilizzati correttamente. Ad esempio, se ho creato una classe FileStream che, quando è stato chiesto di scrivere, ha chiamato un delegato per eseguire la scrittura per conto della classe FileStream , quindi violerebbe l'OOP. Questo perché la responsabilità dell'operazione di scrittura di FileStream è puramente su FileStream se stiamo seguendo OOP alla lettera.

Tuttavia, ciò non significa che l'utilizzo dei delegati vada sempre contro l'OOP. Utilizzando lo stesso esempio, se ho utilizzato un delegato per gestire gli eventi prima e dopo la scrittura, questo continua a seguire OOP perché non è responsabilità di FileStream sapere cosa dovrebbe accadere prima e dopo la scrittura. Dovrebbe semplicemente notificare le classi che sono responsabili.

Se passare delegati a un oggetto stava violando OOP perché sapeva qualcosa al di fuori del suo mondo, allora anche il passare delle variabili avrebbe violato l'OOP poiché si poteva pensare ai delegati come a codice di passaggio piuttosto che a dati in classi. Riconoscere che esiste codice al di fuori di una singola classe OOP non sta violando nulla.

    
risposta data 02.08.2013 - 14:37
fonte
3

No. L'incapsulamento non insiste sul fatto che un oggetto "non sappia nulla del mondo esterno". Di solito dice due cose:

  • Gli altri oggetti non hanno bisogno di sapere come un oggetto fa quello che dice di fare.
  • Tutti i metodi e gli attributi relativi solo a servire una particolare classe sono contenuti in quella classe.

Il secondo punto viene spesso affermato in vari modi che può dare un'impressione che gli oggetti non dovrebbero fare affidamento su cose esterne; ma questa è una comprensione altamente impraticabile, se non impossibile. Considera una classe veramente di base :

class Document {
  public String Title;
  public String Body;
}

Anche senza un'ulteriore definizione, la nostra classe Document sa già cose "esterne": conosce String s! ( String è una classe definita esternamente.) E, ignorando i potenziali problemi XSS per un momento, potremmo creare un metodo simile al seguente:

public String GetHtmlBlurb() {
  return "<div><b>" + Title.Substring(0, 60) + "</b> "
    + body.Substring(0, 120) + "</div>";
}

Il metodo Substring non esiste nemmeno nella nostra definizione Document ! È un metodo definito esternamente nella classe String ! Ma , questo è perfettamente previsto, accettabile, generalmente necessario codice OO. Ad eccezione delle classi costruite esclusivamente intorno alle primitive (esistono addirittura primitive in .NET?), Una classe è definita in termini di altre classi (cose esterne, mondo esterno, ecc.).

Per quanto riguarda i delegati, avremmo potuto definire in modo simile il nostro Document per lavorare con una varietà di tipi Body di cui non abbiamo altre informazioni. Tutto ciò che potremmo richiedere è un modo un po ' di produrre una sottostringa in cui ci sono più modi per farlo (forse una versione aggiunge punti in alcuni casi). Tutta la nostra Document riguarda la possibilità di chiamare un metodo che dichiara di essere un metodo Subtring -like.

class Document {
  public String Title;
  public Func<Int32, Int32, String> BodySubstring;

  public String GetHtmlBlurb() {
    return "<div><b>" + Title.Substring(0, 60) + "</b> "
      + BodySubstring(0, 120) + "</div>";
  }
}

In breve: un delegato è solo un riferimento a un metodo, piuttosto che l'intero oggetto che potremmo altrimenti includere come proprietà. La differenza è che il nostro delegato non richiede che l'oggetto sia di un certo tipo: solo che un particolare metodo ha una certa firma. La nostra classe non sa cosa fa l'oggetto e non insiste nel sapere nulla al riguardo. Quindi, a tale riguardo, in realtà conosce less su "il mondo esterno".

Forse non è una violazione dell'incapsulamento. Forse è uberencapsulation .

    
risposta data 07.08.2013 - 17:33
fonte
1

Un delegato è solo un'altra porzione di dati, un invocabile bit di dati, vero, ma ancora solo dati. Può essere passato a un altro oggetto, esposto o modificato da un altro oggetto. Il tuo oggetto non conosce necessariamente un altro oggetto: il tuo oggetto potrebbe avere il delegato impostato su un metodo al suo interno.

Tu come programmatore sai di più, ma la classe non ha bisogno di farlo. I delegati rendono i metodi in dati e questa è una buona cosa ...

    
risposta data 02.08.2013 - 17:58
fonte
1

Ma cosa sa veramente? Passando a un delegato, come un callback, un gestore di eventi, un metodo factory, ecc. Tutto ciò che è noto al codice che ottiene questo riferimento delegato è che ha un riferimento a qualcosa che può o dovrebbe chiamare in un momento specifico, o per uno scopo specifico. Non deve sapere esattamente cosa farà quella chiamata (al di fuori di qualsiasi valore di ritorno previsto), dove il codice effettivo che eseguirà le vite, come è stato scelto per essere utilizzato come delegato, ecc. Non ha bisogno di conoscenza a riguardo delegare diverso da quello che ha e sa come chiamarlo, che sono i requisiti minimi (almeno in C #).

La definizione del delegato è quindi poco più di una mini-interfaccia; si definisce un tipo di delegato, possibilmente denominato per il suo scopo apparente nel codice che consuma, che si aspetta un certo numero di parametri con nome e si aspetta a sua volta di restituire un valore particolare. Ciò è ulteriormente semplificato con i tipi di delegati generici incorporati di .NET 3.5 come Action, Func, Predicate, Comparison etc; non è nemmeno necessario definire il proprio delegato strongmente tipizzato, basta chiedere un Func<string, string> (il lato negativo è che i generici generici sono, beh, generici e generici, non è possibile utilizzare il nome del tipo delegato o nomi di parametri per auto-documentare ciò che il delegato dovrebbe fare per il codice chiamante, o che tipo di cose dovresti dargli).

È simile, quindi, a una definizione dell'interfaccia completa, che definisce un elenco di tali metodi, per nome, con firme completamente definite, che devono essere tutte disponibili in una classe di implementazione. In Java, questo è più o meno come si ottiene la stessa funzionalità che si ottiene con i delegati .NET; un'interfaccia viene definita con un singolo metodo che rappresenta il metodo delegate, le classi che espongono questo metodo per l'utilizzo come delegato sono contrassegnate come implementazione dell'interfaccia e viene fornito un riferimento alla classe, su cui è possibile chiamare il metodo. I delegati semplicemente rimuovono l'intermediario; non ti interessa più che la classe che contiene il metodo implementa qualsiasi interfaccia definita, ti preoccupi che il metodo effettivo in questione corrisponda alla firma richiesta (qui, la firma è solo elenco dei parametri e tipo di ritorno, non incluso il nome del metodo). La classe che contiene quel metodo potrebbe quindi avere una dozzina di metodi con gli stessi parametri e il tipo di ritorno, ma nomi diversi, e una lavorazione che il tuo codice non deve preoccuparsi può scegliere uno specifico da darti come delegato. p>

Sappi che mentre C # è considerato un linguaggio orientato agli oggetti, è anche un linguaggio multi-paradigma, che incorpora aspetti procedurali e funzionali di altri linguaggi in se stesso su richiesta o necessità dimostrata. I delegati sono stati una lezione imparata osservando i programmatori Java e il loro travaglio di configurare semplici applicazioni multi-livello basate su eventi in un paradigma orientato agli oggetti rigorosamente applicato, che richiede centinaia di interfacce a metodo singolo altamente specifiche. Java alla fine ha aggirato questo problema consentendo implementazioni di un'interfaccia anonima, quindi il codice che doveva iniettare un'interfaccia poteva definire un'implementazione una tantum in linea, ma può comunque essere un po 'prolisso. Il rumor è anche in continuo ribaltamento che Oracle ha ceduto alla domanda popolare, e dichiarazioni lambda e altri delegati faranno parte delle prossime specifiche Java 8 (insieme a un sacco di altre qualità di zucchero di sintassi che gli sviluppatori Java stanno salutando dopo aver guardato attraverso recinzione sul lato C #).

C ++ lo ha evitato mantenendo la flessibilità altamente basata sui puntatori del lignaggio C, ma il suo limite era che, a differenza delle interfacce, i puntatori di funzione non potevano auto-documentarsi; tutto quello che potevi sapere, senza documentazione esterna o codice sorgente, è che devi passare l'indirizzo di una funzione, ma non puoi sapere nulla su quali parametri il consumatore proverebbe a passare, cosa potrebbe aspettarsi come valore di ritorno , quale dovrebbe essere la tua funzione da fare e quando potrebbe essere chiamata, ecc. Poiché la capacità di scrivere codice auto-documentante era un principio chiave delle specifiche del linguaggio di C #, i progettisti combinavano i due, consentendo in un certo senso i puntatori di funzione, ma richiede una strong definizione della firma I / O del metodo come tipo, consentendo di puntatori di funzioni auto-descrittivi controllati dal compilatore senza la necessità di interfacce complete.

    
risposta data 02.08.2013 - 18:27
fonte
1

No. Delegato completa OOP.

Perché OOP non ha la nozione di avere funzione / metodo come dati da passare come valore del gestore eventi e per i callback. Inoltre, il fatto che il metodo come dati possa essere incluso come parametro nel metodo, ci dà l'espressività e la flessibilità.

La necessità diventa più se si viene introdotti nel codice in modo asincrono, dal momento che è necessario sapere quale funzione / metodo come callback eseguire qualcosa dopo che il codice restituisce continuazioni.

Pertanto, ti preghiamo di non limitarti alla definizione di OOP così com'è. Quelli linguaggi OOP consolidati come C #, VB, F #, anche C ++ hanno supporti per i delegati, altrimenti queste lingue perderanno competitività se non ci sono affatto supporti delegati.

Ovviamente se stai implementando i delegati in modo sbagliato, come avere un cattivo odore nel tuo codice, il risultato netto è lo stesso che implementa male l'OOP.

    
risposta data 18.09.2013 - 04:11
fonte

Leggi altre domande sui tag