Effective Java Item 20 note
Prefer class hierarchies to tagged classes
Published in
6 min readJun 18, 2018
Tagged class是什麼呢?
看完這篇的理解,是不夠OO,把各種可能會變化的行為放在同一個class中,例如把長方形跟圓形都放在同一個class,然後有各自的constructor,class存在著不同屬性的field。會破壞可讀性,也會增加memory的size,也會使得class不容易變成immutable。
參考底下例子,一定會覺得為什麼要把半徑跟長寬放一起呢
class Figure {
enum Shape {
RECTANGLE, CIRCLE
};// Tag field - the shape of this figure
final Shape shape;// These fields are used only if shape is RECTANGLE
double length;
double width;// This field is used only if shape is CIRCLE
double radius;// Constructor for circle
Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}// Constructor for rectangle
Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}double area() {
switch (shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError();
}
}
}
這樣的例子很簡單,所以很好拆開,所以可能也看不太出來為什麼會想要這樣寫,但試想一個情境,如果你需要寫一個連server的config class,然後有多個不同性質的server DNS,每個server都提供不同的功能,然後連線的時候需要帶每個server的config,這些config裡面有9成的設定項是可以共用的。大概像底下這段程式碼的情況,好像就會有點猶豫到底該不該這樣使用。
public class Config {
public static final String SERVERA = "https://test1.com";
public static final String SERVERB = "https://test2.com";
public static final String SERVERC = "https://test3.com"; public String serverUrl; public int configGeneral1;
public int configGeneral2;
public int configGeneral3;
public int configGeneral4;
public int configGeneral5;// for server A
public String configServerA;// for server B
public String configServerB;// for server C
public String configServerC;}
不過照這個item來檢討,就不該這樣使用,因為:
- 不一定要撰寫這個config的人都會需要知道每個server的用處。造成閱讀的不便性。
- config如果不設定成immutable的話會有被竄改的風險,但這樣的class要設定成immutable就有一定的麻煩在,因為你要把final加上去就代表要設定不需要的config設定。
- 使用到多餘的memory在越多個server存在的時候也就會越來越明顯。
如果是這樣的使用情況的話,我覺得可以把相同的config又抽出來,然後用合成的方式來取代這個tagged的config class。
public class ConfigA {
public ConfigA(GeneralConfig config, String configServerA) {
this.config = config;
this.configServerA = configServerA;
} private final GeneralConfig config
// for server A
private final String configServerA; public GeneralConfig getGeneralConfig() {
return config;
}
public String getGeneralConfig() {
return configServerA;
}
}public class GeneralConfig { // 這些都可以嘗試使用Builder讓程式碼保護性更高一點。 private final String serverUrl; private final int configGeneral1;
private final int configGeneral2;
private final int configGeneral3;
private final int configGeneral4;
private final int configGeneral5; public GeneralConfig(String url, int config1, int config2, int config3, int config4, int config5) {
serverUrl = url;
configGeneral1 = config1;
configGeneral2 = config2;
configGeneral3 = config3;
configGeneral4 = config4;
configGeneral5 = config5;
} public String getUrl() {
return serverUrl;
} public int getConfig1() {
return configGeneral1;
}
...
}