Nella superclasse, c'è (molto probabilmente) un valore predefinito per il parametro fileSize
. Possiamo approfittare di questo fatto qui.
Per prima cosa, dobbiamo avere un'idea di quale potrebbe essere il valore predefinito. Potremmo vedere qualcosa del tipo:
public class Handler {
public static final long DEFAULT_FILE_SIZE = 4096;
private final long fileSize;
public Handler(Transaction tr) {
this(tr, DEFAULT_FILE_SIZE)
}
public Handler(Transaction tr, long fsize) {
this.fileSize = fsize;
// ...
}
}
Questo rende particolarmente semplice, specialmente se tale costante è visibile nella sottoclasse:
public MetaDataPersistenceHandler(Transaction tr) {
this(tr, Handler.DEFAULT_FILE_SIZE);
}
public MetaDataPersistenceHandler(Transaction tr, long fileSize) {
super(tr, fileSize);
// etc...
}
In alternativa, se non esiste una costante, ma esiste un valore semplice, puoi estrapolarla a una costante nella superclasse e quindi farne riferimento.
Se la logica attorno al valore predefinito è un po 'più complicata, puoi invece estrarre un metodo statico visibile nella superclasse:
protected static long defaultFileSize(Transaction tr) {
if (tr.isHuge()) {
return 4096;
} else {
return 128;
}
}
E chiamalo dal costruttore della sottoclasse in modo simile:
public MetaDataPersistenceHandler(Transaction tr) {
this(tr, Handler.defaultFileSize(tr));
}
public MetaDataPersistenceHandler(Transaction tr, long fileSize) {
super(tr, fileSize);
// etc...
}
Queste modifiche alle due classi hanno il vantaggio di preservare l'interfaccia pubblica esposta. Nessuna delle firme di nessuno dei costruttori deve cambiare. E possiamo mantenere i membri della nostra classe definitivi come vogliamo.
Se non puoi modificare la superclasse, ma è improbabile che il valore predefinito cambi, puoi sempre inserire il valore predefinito nella sottoclasse come metodo costante o statico, con commenti rumorosi che indicano la duplicazione con la superclasse.
Questo è bello e tutto, ma la superclasse in realtà fa due cose completamente diverse tra i supercostruttori. Ahia, non è un buon progetto, ma OK, possiamo ancora fare qualcosa . Questo si applica anche se non vuoi la duplicazione perché non puoi modificare la superclasse.
Invece dell'ereditarietà, potremmo fare alcuni composizione .
Funziona meglio se c'è una super-interfaccia sopra la tua superclasse per quello che vuoi veramente, ma ci sono modi per farlo solo con una superclasse se non è troppo ostile. Supponiamo di avere un'interfaccia Handler
e una superclasse SimpleHandler
. La nostra classe può implementare Handler
invece di estendere SimpleHandler
, e possiamo cambiare il nostro costruttore per prendere un'istanza Handler
:
public class MetaDataPersistenceHandler implements Handler {
private final Handler handler;
// other memebers...
public MetaDataPersistenceHandler(Handler handler) {
this.handler = handler;
// etc...
}
}
Al posto delle nostre chiamate al costruttore originali, dovremmo fare:
new MetaDataPersistenceHandler(new SimpleHandler(transaction));
new MetaDataPersistenceHandler(new SimpleHandler(transaction, fileSize));
Il problema ora è che dobbiamo implementare tutti i metodi di Handler
noi stessi. Per fare ciò, creiamo tutti i metodi per l'interfaccia Handler
e li deleghiamo all'istanza Handler
del membro:
@Override
public void handle() {
this.handler.handle();
}
Questo può sembrare un po 'più semplice, purtroppo, ma è il modo più semplice per fare la delegazione. Per questo, però, non dobbiamo preoccuparci di modificare la superclasse, o se in qualche modo gestisce le modifiche alle dimensioni del file di default. La nostra interfaccia esterna della sottoclasse è cambiata da quando abbiamo dovuto modificare la firma del costruttore, quindi attenzione.
Se vuoi seguire questa strada ma davvero non puoi cambiare le firme del costruttore, c'è un modo per aggirare questo:
public class MetaDataPersistenceHandler implements Handler {
private final Handler handler;
// other memebers...
private MetaDataPersistenceHandler(Handler handler) {
this.handler = handler;
// etc...
}
public MetaDataPersistenceHandler(Transaction tr) {
this(new SimpleHandler(tr));
}
public MetaDataPersistenceHandler(Transaction tr, long fileSize) {
this(new SimpleHandler(tr, fileSize));
}
// etc...
}
Forse non sto riuscendo a farlo bene, però. Handler
è una classe di tipo "strategia" astratta. Ci sono alcuni metodi astratti per scavalcare, e un mucchio di metodi finali che non possiamo. Penso che possiamo ancora lavorare con questo, abbiamo solo bisogno di capovolgere la relazione di composizione.
Facciamo prima di tutto un'interfaccia per tutti i metodi astratti in Handler
:
public interface BasicHandler {
void handle();
}
Ora creiamo un semplice gestore che implementerà i metodi astratti e li delegherà a un BasicHandler
che prende tramite il suo costruttore:
public class DelegatingHandler extends Handler {
private final BasicHandler handler;
public DelegatingHandler(Transaction tr, BasicHandler handler) {
super(tr);
this.handler = handler;
}
public DelegatingHandler(Transaction tr, long fileSize, BasicHandler handler) {
super(tr, fileSize);
this.handler = handler;
}
@Override
public void handle() {
this.handler.handle();
}
}
E possiamo scrivere la nostra implementazione di BasicHandler
:
public class MetaDataPersistenceBasicHandler implements BasicHandler {
// members...
public MetaDataPersistenceBasicHandler(Transaction tr) {
// Everything that goes here...
}
@Override
public void handle() {
// our special way to handle...
}
}
Ora possiamo creare Handler
s come:
new DelegatingHandler(tr, new MetaDataPersistenceBasicHandler(tr));
new DelegatingHandler(tr, fileSize, new MetaDataPersistenceBasicHandler(tr));
Ancora una volta, abbiamo modificato la firma del costruttore qui.