Effective Java Item 20 note

Prefer class hierarchies to tagged classes

Arwii
mycodingjourney
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來檢討,就不該這樣使用,因為:

  1. 不一定要撰寫這個config的人都會需要知道每個server的用處。造成閱讀的不便性。
  2. config如果不設定成immutable的話會有被竄改的風險,但這樣的class要設定成immutable就有一定的麻煩在,因為你要把final加上去就代表要設定不需要的config設定。
  3. 使用到多餘的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;
}
...
}

--

--

Arwii
mycodingjourney

Try not to become a man of success, but rather try to become a man of value — A. Einstein