Tutti i metodi pubblici in una classe astratta devono essere contrassegnati come virtuali?


12

Di recente ho dovuto aggiornare una classe base astratta su alcuni OSS che stavo usando in modo che fosse più testabile rendendoli virtuali (non potevo usare un'interfaccia poiché ne combinava due). Questo mi ha fatto pensare se dovevo contrassegnare tutti i metodi di cui avevo bisogno virtuale o se dovessi contrassegnare ogni metodo / proprietà pubblica virtuale. Sono generalmente d'accordo con Roy Osherove sul fatto che ogni metodo dovrebbe essere reso virtuale, ma mi sono imbattuto in questo articolo che mi ha fatto pensare se fosse necessario o meno . Lo limiterò a classi astratte per semplicità, tuttavia (se tutti i metodi pubblici concreti dovrebbero essere virtuali è particolarmente discutibile, ne sono sicuro).

Potrei vedere dove potresti voler consentire a una sottoclasse di utilizzare un metodo, ma non volerlo ignorare l'implementazione. Tuttavia, fintanto che ti fidi che il Principio di sostituzione di Liskov sarà seguito, allora perché non dovresti permettere che venga annullato? Contrassegnandolo come astratto, stai forzando comunque un certo override, quindi mi sembra che tutti i metodi pubblici all'interno di una classe astratta debbano essere contrassegnati come virtuali.

Tuttavia, volevo chiedere nel caso ci fosse qualcosa a cui potrei non pensare. Tutti i metodi pubblici all'interno di una classe astratta dovrebbero essere resi virtuali?



1
Forse dovresti capire perché in C # il valore predefinito non è virtuale ma in Java lo è. Il motivo per cui probabilmente ti darebbe un'idea della risposta.
Daniel Little,

Risposte:


9

Tuttavia, fintanto che ti fidi che il Principio di sostituzione di Liskov sarà seguito, allora perché non dovresti permettere che venga annullato?

Ad esempio, perché voglio che l'implementazione dello scheletro di un algoritmo sia corretta e consenta che (solo) parti specifiche vengano (ri) definite da sottoclassi. Questo è ampiamente noto come modello di metodo del modello (enfasi da me sotto):

Il metodo modello gestisce quindi il quadro più ampio della semantica dell'attività e dettagli di implementazione più raffinati della selezione e della sequenza dei metodi. Questa immagine più ampia chiama metodi astratti e non astratti per l'attività da svolgere. I metodi non astratti sono completamente controllati dal metodo del modello, ma i metodi astratti, implementati in sottoclassi, forniscono il potere espressivo del modello e il grado di libertà. Alcuni o tutti i metodi astratti possono essere specializzati in una sottoclasse, consentendo allo scrittore della sottoclasse di fornire un comportamento particolare con modifiche minime alla semantica più ampia. Il metodo modello (che non è astratto) rimane invariato in questo modello, assicurando che i metodi subordinati non astratti e i metodi astratti siano chiamati nella sequenza originariamente prevista.

Aggiornare

Alcuni esempi concreti di progetti a cui ho lavorato:

  1. comunicare con un sistema mainframe legacy tramite vari "schermi". Ogni schermata ha un gruppo di campi, con nome, posizione e lunghezza fissi, contenenti bit di dati specifici. Una richiesta riempie alcuni campi con dati specifici. Una risposta restituisce dati in uno o più altri campi. Ogni transazione segue la stessa logica di base, ma i dettagli sono diversi su ogni schermata. Abbiamo utilizzato il Metodo modello in diversi progetti diversi per implementare lo scheletro fisso della logica di gestione dello schermo, consentendo allo stesso tempo alle sottoclassi di definire i dettagli specifici dello schermo.
  2. esportazione / importazione dei dati di configurazione nelle tabelle DB in / da file Excel. Ancora una volta, lo schema di base dell'elaborazione di un file Excel e l'inserimento / aggiornamento dei record DB o il dumping dei record in Excel è lo stesso per ogni tabella, ma i dettagli di ciascuna tabella sono diversi. Quindi Metodo Metodo è una scelta molto ovvia per eliminare duplicazioni di codice e rendere il codice più semplice da comprendere e mantenere.
  3. Generazione di documenti PDF. Ogni documento ha la stessa struttura, ma il loro contenuto è ogni volta diverso, a seconda di molti fattori. Ancora una volta, Metodo metodo semplifica la separazione dello scheletro fisso dell'algoritmo di generazione dai dettagli specifici del caso e modificabili. Infatti. si applica anche a più livelli qui, poiché il documento è composto da più sezioni , ognuna delle quali è composta da zero o più campi . Il metodo modello viene applicato su 3 livelli distinti qui.

Nei primi due casi, l'implementazione legacy originale utilizzava la strategia , risultando in un sacco di codice duplicato, che ovviamente negli anni ha fatto crescere sottili differenze qua e là, conteneva molti bug duplicati o leggermente diversi ed era molto difficile da mantenere. Il refactoring al metodo Template (e alcuni altri miglioramenti, come l'uso delle annotazioni Java) hanno ridotto le dimensioni del codice di circa il 40-70%.

Questi sono solo gli esempi più recenti che mi vengono in mente. Potrei citare più casi da quasi tutti i progetti a cui ho lavorato finora.


La semplice citazione del GoF non è una risposta. Dovresti dare una vera ragione per fare una cosa del genere.
DeadMG

@DeadMG, ho usato regolarmente il metodo Template durante la mia carriera, quindi ho pensato che fosse ovvio che si tratta di un modello molto pratico e utile (dato che la maggior parte dei modelli GoF sono ... questi non sono esercizi accademici teorici, ma raccolti dall'esperienza del mondo reale). Ma a quanto pare non tutti sono d'accordo con questo ... quindi aggiungo un paio di esempi concreti alla mia risposta.
Péter Török,

2

È perfettamente ragionevole, e talvolta desiderabile avere metodi non virtuali in una classe base astratta; solo perché è una classe astratta non significa necessariamente che ogni sua parte dovrebbe essere utilizzabile polimorficamente.

Ad esempio, potresti voler usare il linguaggio "Polimorfismo non virtuale", in base al quale una funzione viene chiamata polimorficamente da una funzione membro non virtuale, al fine di garantire che determinate condizioni preliminari o postcondizioni siano soddisfatte prima che venga chiamata la funzione virtuale

class MyAbstractBaseClass
{
protected:
    virtual void OverrideMe() = 0;
public:
    void CallMeFirst();

    void CallMe()
    {
        CallMeFirst();
        OverrideMe();
    }
};

Direi che, fintanto che il comportamento generale rimarrà lo stesso, questo potrebbe essere reso virtuale. È possibile riempire le condizioni pre / post utilizzando un'implementazione diversa (database vs in memoria). Altrimenti, dovrebbe essere una funzione privata?
Justin Pihony,

2

È sufficiente che una classe contenga UN metodo virtuale, affinché la classe diventi astratta. Potresti voler prestare attenzione a quali metodi desideri virtuali e quali no, in base al tipo di polimorfismo che intendi utilizzare.


1

Chiediti a cosa serve un metodo non virtuale in una classe astratta. Tale metodo dovrebbe avere un'implementazione per renderlo utile. Ma se la classe ha un'implementazione, può ancora essere definita una classe astratta? Anche se il linguaggio / compilatore lo consente, ha senso? Personalmente, non la penso così. Avresti una classe normale con metodi astratti che i discendenti dovrebbero implementare.

La mia lingua principale è Delphi, non C #. In Delphi, se contrassegni un estratto del metodo, devi anche contrassegnarlo come virtuale o il compilatore si lamenta. Non ho seguito troppo da vicino le ultime modifiche al linguaggio, ma se le classi astratte sono presenti o arrivassero a Delphi, mi aspetterei che il compilatore si lamentasse di metodi non virtuali, di metodi privati ​​e di implementazioni di metodi per un abstract contrassegnato da una classe a livello di classe.


2
Penso che abbia perfettamente senso avere una classe astratta che ha alcuni metodi astratti, alcuni metodi virtuali e alcuni metodi non virtuali. Penso che dipenda sempre da cosa esattamente vuoi fare.
svick,

3
Per prima cosa esaminerò i tuoi C # q. Un metodo astratto in C # è implicitamente virtuale e non può avere un'implementazione. Per quanto riguarda il tuo primo punto, una classe astratta in C # può e dovrebbe avere un'implementazione di qualche tipo (altrimenti dovresti semplicemente usare un'interfaccia). Il punto in cui una classe è astratta è che DEVE essere sottoclassata, tuttavia contiene una logica che (teoricamente) tutte le sottoclassi useranno. Questo riduce la duplicazione del codice. Quello che sto chiedendo è se una qualsiasi di queste implementazioni dovrebbe essere chiusa dall'essere invasa (essenzialmente dicendo che la via di base è l'unica via).
Justin Pihony,

@JustinPihony: grazie. In Delphi, quando si contrassegna un abstract di metodo, il compilatore si lamenterà se si fornisce un'implementazione per esso. Interessante come le diverse lingue implementano i concetti in modo diverso e quindi creano aspettative diverse nei loro utenti.
Marjan Venema,

@svick: sì, ha perfettamente senso, semplicemente non la definirei una classe astratta, ma una classe con metodi astratti ... Ma suppongo che potrebbe essere solo la mia interpretazione.
Marjan Venema,

@MarjanVenema, ma non è la terminologia utilizzata da C #. Se si contrassegnano metodi in una classe abstract, è necessario contrassegnare anche l'intera classe abstract.
svick,

0

Tuttavia, fintanto che ti fidi che il Principio di sostituzione di Liskov sarà seguito, perché> perché non permetteresti che venga annullato?

Non rendi virtuali alcuni metodi perché non ritieni che ciò avvenga. Inoltre, rendendo alcuni metodi non virtuali, stai segnalando agli eredi quali metodi dovrebbero essere implementati.

Personalmente, farò spesso sovraccarichi di metodo che esistono per comodità non virtuali, in modo che gli utenti della classe possano avere valori predefiniti coerenti e gli implementatori non siano nemmeno in grado di commettere l'errore di interrompere quel comportamento implicito.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.