Un suggerimento comune è mettere tutta la logica in ogni oggetto Pdf
. Tuttavia, è necessario estendere l'interfaccia di Pdf
e modificare l'implementazione di ogni oggetto Pdf
ogni volta che si desidera supportare una nuova operazione. Richiede inoltre che ogni oggetto Pdf
debba sapere tutto su come operare su di esso, piuttosto che sapere come memorizzare la propria rappresentazione. Il risultato è che si finisce con giganteschi oggetti Pdf
che in pratica devono fare tutto.
Probabilmente vorrai utilizzare il Pattern visitatori per realizzare questo.
Modello visitatore standard
Aggiungi un nuovo metodo alla tua classe base Pdf
:
class Pdf {
...
public abstract void AcceptVisitor(IPdfVisitor visitor);
}
L'interfaccia IPdfVisitor è abbastanza semplice:
interface IPdfVisitor {
void Visit(FirstPdf pdf);
void Visit(SecondPdf pdf);
void Visit(ThirdPdf pdf);
}
Ora, ogni oggetto classe Pdf implementato definisce la funzione AcceptVisitor allo stesso modo (ma è necessario scrivere l'implementazione in ogni classe).
class FirstPdf : Pdf {
...
public override void AcceptVisitor(IPdfVisitor visitor) {
visitor.Visit(this);
}
}
Quindi, perché devi avere l'implementazione in ogni classe? Perché il compilatore conosce il tipo statico di ogni classe. Pertanto, quando chiami visitor.Visit(this)
da un oggetto di tipo FirstPdf
, chiamerà la funzione Visit che accetta un argomento di tipo FirstPdf
. Ora, attui i visitatori per ogni "verbo" che vuoi implementare. Forse hai un SavePdfVisitor
, un PrintPdfVisitor
e un DisplayPdfVisitor
, per esempio. Sembrerebbero così:
class SavePdfVisitor : IPdfVisitor {
public void Visit(FirstPdf pdf) {
// FirstPdf specific save logic
}
public void Visit(SecondPdf pdf) {
// SecondPdf specific save logic
}
public void Visit(ThirdPdf pdf) {
// ThirdPdf specific save logic
}
}
Ora, il tuo metodo di salvataggio generico per Pdf è simile al seguente:
public void Save(Pdf pdf) {
IPdfVisitor saveVisitor = new SavePdfVisitor();
pdf.AcceptVisitor(saveVisitor);
}
L'oggetto pdf
chiama Visit
e passa al visitatore di salvataggio, senza bisogno di sapere quale tipo di operazione viene eseguita. Viene chiamato il metodo corretto di SavePdfVisitor. Come minimo, questo ti consente di mettere tutta la logica di salvataggio in un'unica posizione e utilizzare metodi privati per la funzionalità condivisa.
Un vantaggio di questo è che se si definisce un nuovo tipo di Pdf
, quando si implementa il suo metodo AcceptVisitor
, si otterrà un errore del compilatore a meno che non si aggiunga quel tipo all'interfaccia IPdfVisitor
e quindi a ciascun degli oggetti visitatori. Questo garantisce che quando definisci un nuovo tipo di Pdf
, non dimentichi di implementare la logica per tutte le operazioni che esegui su Pdf
.
Pattern Visitatore migliore (nelle mie opinioni)
D'altra parte, se hai molta logica condivisa - per esempio, l'operazione di salvataggio è la stessa per molti tipi di Pdf
ma è personalizzata per pochi - puoi definire i tuoi oggetti visitatori usando un classe base astratta invece di un'interfaccia come questa.
abstract class PdfVisitor {
public abstract void Visit(Pdf pdf);
public virtual void Visit(FirstPdf pdf) {
Visit((Pdf)pdf);
}
public virtual void Visit(SecondPdf pdf) {
Visit((Pdf)pdf);
}
public virtual void Visit(ThirdPdf pdf) {
Visit((Pdf)pdf);
}
}
class Pdf {
...
public abstract void AcceptVisitor(PdfVisitor visitor);
}
class FirstPdf : Pdf {
...
public override void AcceptVisitor(PdfVisitor visitor) {
visitor.Visit(this);
}
}
// Do the same for other Pdf classes
Tieni presente che PdfVisitor
NON definisce l'implementazione di Visit(Pdf pdf)
. Tuttavia, fornisce un'implementazione predefinita di Visit
per tutti i tipi di implementazione concreta. Quella implementazione predefinita chiama solo Visit(Pdf pdf)
. Quando implementi Visit(Pdf pdf)
in ciascuno dei tuoi oggetti visitatore concreti, ciò definisce il comportamento predefinito per quel visitatore o azione. Ad esempio, supponi che DisplayPdfVisitor
utilizzi la stessa logica per ogni tipo di Pdf
. Lo implementa in questo modo:
class DisplayPdfVisitor : PdfVisitor {
public override void Visit(Pdf pdf) {
// Display logic here
}
}
D'altra parte, se SavePdfVisitor
fa la stessa cosa per tutti gli oggetti Pdf
, ad eccezione del fatto che SecondPdf
ha bisogno di un'implementazione speciale, fai questo:
class SavePdfVisitor : PdfVisitor {
public override void Visit(Pdf pdf) {
// default save implementation
}
public override void Visit(SecondPdf pdf) {
// custom save implementation for SecondPdf type
}
}
Ora se chiami pdf.AcceptVisitor(saveVisitor)
su un Pdf
oggetto di tipo SecondPdf
e chiama visitor.Visit(this)
, chiamerà il metodo che accetta un oggetto di tipo SecondPdf
. Tuttavia, lo stesso percorso di codice per qualsiasi altro tipo di Pdf
chiamerà solo l'implementazione predefinita.
Il vantaggio di questo approccio è che puoi definire una nuova operazione Pdf
creando un visitatore che implementa solo un singolo metodo e si applicherà a tutti i tipi di oggetto. Puoi anche definire un nuovo tipo di oggetto Pdf
e otterrà il comportamento predefinito su tutti i tuoi visitatori senza dover apportare alcuna modifica. Se crei una nuova classe Pdf
, ad esempio FourthPdf
, e ti accorgi che hai bisogno di una logica personalizzata per il tuo DisplayPdfVisitor
, devi solo modificare la classe base:
abstract class PdfVisitor {
// previous stuff
public virtual void Visit(FourthPdf pdf) {
Visit((Pdf)pdf);
}
}
E poi in DisplayPdfVisitor
:
class DisplayPdfVisitor : PdfVisitor {
// other implementation
public override void Visit(FourthPdf pdf) {
// custom FourthPdf display logic
}
}
Non hai dovuto modificare SavePdfVisitor
o PrintPdfVisitor
o qualsiasi altra cosa. Applicano volentieri il comportamento di salvataggio e stampa predefinito agli oggetti FourthPdf
, ma DisplayPdfVisitor
li gestisce in modo diverso.