Item23: Prefer class hierarchies to tagged classes¶
tagged class¶
개인적으로 가장 안 좋아하는 패턴 중 하나.
tagged class 는 아니고 tagged struct (?) 이다.
struct SomeFile {
// 파일의 타입 - "pdf" or "eml" ...
1: required string fileType
2: required string fileHash
3: required string fileSize
// PDF 타입의 파일에만 관련된 필드
100 : optional string pdfField1
101 : optional string pdfField2
// EML 타입의 파일에만 관련된 필드
200 : optional string emlField1
}
책에서 이야기하는 단점
- Cluttered with boilerplate
- Memory footprint is increased
- Readability is further harmed
책에서 이야기 하는 대안
- class hierarchy. 공통 필드를 abstract class 로 추출하고 각 태그에 해당하는 하위 타입을 정의한다.
장점
- code is simple and clear, containing none of the boilerplate
- eliminates the possibility of a runtime failure due to a missing switch case
- reflect natural hierarchical relationships among types
tagged class 가 조금더 정당화 되는것 같은 케이스¶
-
IDL - Interface Definition Language
- 도메인 모델을 이야기하는 논리와 외부 인터페이스를 위한 DTO 모델을 이야기 할때 논리가 조금은 다르게 적용될 수 있지 않을까?
-
모든 요청을 받아서 처리할 수 있는 Gateway 같은 것을 만들고 싶을때?
- 그 요청 타입을 tagged 하게 정의 해야한다.
- client 가 분기를 치게 할 수 없으므로
-
팀이 사용중인 thrift 라는 IDL 의 특성
- 구조체 (타입) 의 optional 필드 추가에 따른 deserialize 를 지원한다.
service FileGatewayService {
void call(1: SomeFile someFile)
}
struct SomeFile {
// 파일의 타입 - "pdf" or "eml" ...
1: required string fileType
2: required string fileHash
3: required string fileSize
// PDF 타입의 파일에만 관련된 필드
100 : optional string pdfField1
101 : optional string pdfField2
// EML 타입의 파일에만 관련된 필드
200 : optional string emlField1
}
class ServiceA {
val GatewayService gateway
def someMethod {
val paramB = ...
gateway.call(paramB)
}
}
- 이때 GatewayService 의 개발자는 ServiceA 의 코드와 관계없이 optional 필드를 추가해도 된다.
- 파일 타입에 pdf/eml 이 아닌 apk 라는 파일 타입이 추가가 되어도 client 의 코드에는 영향이 가지 않는다.
- 그리고 필드의 추가도 쉽다.
만약 tagged struct 가 아닌 잘게 구조화된 형태의 아래와 같은 구조였다면 Gateway 라는 이름을 붙일 수 없다. client 에서 각 파일 타입에 대한 구분이 이루어 져야한다.
필드가 아닌 구조체 (클래스)를 추가해야하고 FileService 에 새로운 rpc (메서드) 가 정의되어야 하고 무엇보다 이를 사용하는 측에서 코드의 변경이 없어도 된다.
service FileService {
void callPdf(1: SomePdf somePdf)
void callEml(1: SomeEml someEml)
// void callApk(1: SomeApk someApk)
}
struct BaseFile {
2: required string hash
3: required string size
}
struct SomePdf {
1: required BaseFile file
// PDF 타입의 파일에만 관련된 필드
100 : optional string pdfField1
101 : optional string pdfField2
}
struct SomeEml {
1: required BaseFile file
// EML 타입의 파일에만 관련된 필드
200 : optional string emlField1
}
/*
struct SomeApk {
1: required BaseFile file
// APK 타입의 파일에만 관련된 필드
300 : optional string apkField1
}
*/
사실 저런 Gateway 성 외부 Interface 자체가 좋은 방식인지는 의문이다.
abstract class BaseIdEntity {
abstract int id();
}
class Circle extends BaseIdEntity {
final double radius;
Circle(double radius) { this.radius = radius; }
}
class Rectangle extends BaseIdEntity {
final double length;
final double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
}