Linee guida sintetiche per il flattening dell'ereditarietà
Seguire diverse linee guida concise per appiattire l'eredità.
- Per ciascuna classe nella gerarchia di tipi, ridenominare la classe da
[Name]
a[Name]Impl
. - Estrarre un'interfaccia da ciascuna di queste classi, dando a ciascuna i nomi originali utilizzati per quelle classi. Includere tutti i metodi pubblici e i campi statici costanti in ogni interfaccia.
- Creare una gerarchia dei tipi attraverso le interfacce estratte che sono identiche alla serie di classi originale. Ad esempio, se
BImpl
estendeAImpl
,B
estendeA
. - Copiare metodi e campi da ogni classe principale agli elementi secondari diretti. Ripetere ricorsivamente in tutta la gerarchia dei tipi.
- Risolvere i conflitti di denominazione per i metodi sostituiti o i campi shadow ridenominando i metodi e i campi copiati dalla classe principale.
- Sostituire le chiamate a
super.method()
richiamando direttamente i metodi che sono stati raggruppati nella classe appiattita. - Risolvere gli errori dai super-costruttori copiati analogamente ai passi 5 e 6.
- Rimuovere le estensioni dei tipi dalle classi di implementazione (ovvero, se
BImpl
estendeAImpl
, rimuoveextends AImpl
) per eliminare l'eredità. - (Facoltativo) Eliminare il codice inattivo, le classi astratte o qualsiasi classe o metodo a cui non si fa più riferimento.
Come semplice esempio di appiattimento dell'eredità, considerare la seguente gerarchia di classi.
public class Bird {
private String description;
public void setDecription(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
public class Bluejay extends Bird {
public String getName() {
return "Bluejay";
}
}
public class Cardinal extends Bird {
public String getName() {
return "Cardinal";
}
}
Applicando queste linee guida per l'appiattimento dell'eredità, si produce questa serie di interfacce:
public interface Bird {
public void setDecription(String description);
public String getDescription();
}
public interface Bluejay extends Bird {
public String getName();
}
public interface Cardinal extends Bird {
public String getName();
}
Inoltre, si fornisce una classe di implementazione per ognuna di queste interfacce:
public class BirdImpl implements Bird {
private String description;
public void setDecription(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
public class BluejayImpl implements Bluejay {
private String description;
public void setDecription(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public String getName() {
return "Bluejay";
}
}
public class CardinalImpl implements Cardinal {
private String description;
public void setDecription(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public String getName() {
return "Cardinal";
}
}
Di conseguenza, la gerarchia dei tipi originale viene mantenuta e l'ereditarietà viene eliminata. Questa architettura consente la creazione del codice per le classi refactoring da distribuire tra le partizioni.
In alternativa alla copia di metodi e campi dalla classe principale, il flattening può ancora essere raggiunto tramite delega modificando la relazione delle classi da estensione a contenimento. Ad esempio, le classi BluejayImpl
e CardinalImpl
vengono modificate in modo da avere un'istanza di BirdImpl
e delegano le chiamate ai relativi metodi.
public class BluejayImpl implements Bluejay {
private Bird bird = new BirdImpl();
public void setDecription(String description) {
bird.setDecription(description);
}
public String getDescription() {
return bird.getDescription();
}
public String getName() {
return "Bluejay";
}
}
public class CardinalImpl implements Cardinal {
private Bird bird = new BirdImpl();
public void setDecription(String description) {
bird.setDecription(description);
}
public String getDescription() {
return bird.getDescription();
}
public String getName() {
return "Cardinal";
}
}