Strategie per invocare metodi di sottoclassi su oggetti generici [duplicato]

2

Mi sono imbattuto in questo problema in diversi punti e l'ho risolto in molti modi diversi, ma alla ricerca di altre soluzioni o opinioni su come affrontare. Lo scenario è quando hai una collezione di oggetti tutti basati sulla stessa superclasse ma vuoi eseguire determinate azioni basate solo su istanze di alcune sottoclassi.

Un esempio forzato di questo potrebbe essere un documento HTML composto da elementi. Potresti avere una superclasse chiamata HTMLELement e sottoclassi di intestazioni, paragrafi, immagini, commenti, ecc. Per invocare un'azione comune su tutti gli oggetti dichiari un metodo virtuale nella superclasse e implementazioni specifiche in tutte le sottoclassi. Pertanto, per eseguire il rendering del documento, è possibile eseguire il ciclo di tutti gli oggetti diversi nel documento e chiamare un metodo comune Render() su ogni istanza.

È il caso in cui, usando di nuovo gli stessi oggetti generici nella collezione, voglio eseguire azioni diverse per istanze di sottoclassi specifiche (o un insieme di sottoclassi). Ad esempio (si ricordi che questo è solo un esempio) durante l'iterazione sulla raccolta, è necessario scaricare elementi con collegamenti esterni (ad esempio JS, CSS, immagini) e alcuni potrebbero richiedere un'analisi aggiuntiva (JS, CSS). Qual è il modo migliore per gestire questi casi speciali.

Alcune delle strategie che ho utilizzato o visto utilizzato includono:

  • Metodi virtuali nella classe base.

Quindi nella classe base hai un metodo virtuale LoadExternalContent() che non fa nulla e poi lo sovrascrive nelle sottoclassi specifiche che devono implementarlo. Il vantaggio è che nel codice chiamante non esiste alcun oggetto test per inviare lo stesso messaggio a ciascun oggetto e lasciare che molti lo ignorino. Due aspetti negativi che mi viene in mente. Innanzitutto può rendere la classe base molto disordinata con metodi che non hanno nulla a che fare con la maggior parte della gerarchia. In secondo luogo, presuppone che tutto il lavoro possa essere svolto nel metodo chiamato e non gestisce il caso in cui potrebbero esserci azioni aggiuntive specifiche del contesto nel codice chiamante (ovvero si desidera eseguire qualcosa nell'interfaccia utente e non nel modello).

  • Avere metodi sulla classe per identificare in modo univoco gli oggetti.

Questo potrebbe includere metodi come ClassName() che restituiscono una stringa con il nome della classe o altri valori di ritorno come enumerazioni o valori booleani ( IsImage() ). Il vantaggio è che il codice chiamante può utilizzare le istruzioni if o switch per filtrare oggetti per eseguire azioni specifiche della classe. Il rovescio della medaglia è che per ogni nuova classe è necessario implementare questi metodi e può sembrare disordinato. Anche le prestazioni potrebbero essere inferiori ad alcune delle altre opzioni.

  • Utilizza le funzionalità del linguaggio per identificare gli oggetti.

Questo include gli operatori di riflessione e lingua per identificare gli oggetti. Ad esempio in C # esiste l'operatore is che restituisce true se l'istanza corrisponde alla classe specificata. Il vantaggio non è un codice aggiuntivo da implementare nella gerarchia degli oggetti. L'unico lato negativo sembra essere la mancanza di utilizzare qualcosa come una dichiarazione switch e il fatto che il tuo codice chiamante è un po 'più disordinato.

Ci sono altre strategie che mi mancano? Pensieri sugli approcci migliori?

    
posta Brad Patton 07.11.2012 - 21:26
fonte

2 risposte

1

Secondo il principio di sostituzione di Liskov, LSP, una classe base deve essere sostituibile da una qualsiasi delle sue sottoclassi.

Questo principio rende possibile che un codice di chiamata scritto per la superclasse funzioni con una sottoclasse progettata in un secondo momento.

Se il metodo di chiamata deve chiamare metodi che non esistono nella superclasse, allora tale metodo di chiamata non è stato progettato attorno alla superclasse.

L'aproach migliore è quello di migliorare il design della classe per non violare LISP.

Nel tuo esempio:

For example (an remember this is just an example) when iterating over the collection, elements with external links need to be downloaded (e.g. JS, CSS, images) and some might require additional parsing (JS, CSS).

L'analisi adizionale non deve essere richiamata dall'esterno, interrompendo LISP. È un comportamento sotto la cappa della sottoclasse che non ha bisogno di influenzare il contratto della superclasse.

LoadExternalContent() dovrebbe essere un metodo privato della sottoclasse chiamata nel costruttore o nel metodo Render o altro.

Inoltre, troverai alcuni comportamenti che dovrebbero essere promossi alla superclasse. Va bene.

E infine: se hai scritto un codice che itera un elenco di oggetti disparati e ha bisogno di chiamare metodi di sottoclasse specifici, allora quel codice non è scritto per la superclasse / interfaccia e il casting dei tipi specifici è intrinseco a tale metodo.

    
risposta data 07.11.2012 - 21:57
fonte
0

Un altro possibile approccio è utilizzare il schema di progettazione dei visitatori .
Ti dà la possibilità di implementare comportamenti diversi in classi diverse, ognuna delle quali dovrebbe implementare un metodo che funzionerà su ciascun tipo.
Il vantaggio rispetto alla dichiarazione dei metodi virtuali nella classe base e la sovrascrittura è che è possibile estendere le funzionalità aggiungendo nuove classi piuttosto che cambiando quelle esistenti.

    
risposta data 07.11.2012 - 21:54
fonte

Leggi altre domande sui tag