상속과 합성 이야기-2

  1. 합성으로 문제 해결하기
  2. 합성 이용해서 정책 조합 하기

지금까지 ‘문자 타입 정책’ 및 ‘문자 상세 내용 정책’에 따른 상속 통해 코드 재사용 하는 방법을 알아보았습니다.
하지만 상속에 따른 단점도 알아보았습니다. 앞서 이야기 한대로 이러한 단점을 회피 하기 위해서는 다른 구조 설계가 필요 할 것 입니다.

합성으로 문제 해결하기

상속과 다르게 합성 관계는 런타임 시점에 동적으로 변경이 가능하다는 것 입니다. 반면 상속 관계는 컴파일 시점에 관계가 결정되기 떄문에 코드가 실행하는 도중
변경이 가능하지 않습니다. 그래서 모든 조합에 따른 구현체를 모두 구현 해야 하는 단점이 있습니다. 즉 클래스 폭팔 문제

합성을 사용하면 런타임 시점에 관계를 유연하게 변경이 가능 합니다. 각각의 ‘문자 타입 정책’ 및 ‘문자 상세 내용 정책’ 구성하는 요소들을 개별 클래스로 구현 후
실행 시점에 인스턴스를 조립 하는 방법이라고 생각하면 됩니다.

Decorator 패턴으로 합성을 구현 해보고자 합니다. Decorator 패턴을 간단히 말씀드리자면 기존에 있는 코드를 변경하지 않으면서 부가적인 기능을 추가 할 수 있는 구조적인 패턴 입니다.
원하는 부가기능을 런타임 시점에 추가를 할 수 있다는 장점이 있습니다.

기존 상속을 이용한다면 ‘문자 타입 정책’, ‘문자 상세 내용 정책’ 에 따른 조합으로 모든 경우의 수를 모두 구현체 구현 해야 하는 단점이 있습니다.
이를 합성으로 극복 하겠습니다.

합성 이용해서 정책 조합 하기

합성 상속 구조.png

1
2
3
4
public interface SmsPolicy {

String smsMessageProcessing(String message);
}

우선 데코레이터 패턴을 사용할려고 하면 데코레이터 와 콘트리트 컴포넌트 상위 ‘SmsPolicy’ 인터페이스를 선언해야 합니다.
그리고 메시지를 가공해서 가져오는 오퍼레이션 ‘smsMessageProcessing()’ 정의 합니다.

1
2
3
4
5
6
7
public class PromosionSmsPolicy implements SmsPolicy {

@Override
public String smsMessageProcessing(String message) {
return "안녕하세요. 고객님 이번에 새로운 상품이 나와서 홍보 드립니다." + message;
}
}
1
2
3
4
5
6
public class PurchaseSmsPolicy implements SmsPolicy {

@Override
public String smsMessageProcessing(String message) {
return "안녕하세요. 고객님 구입 하고자 하는 상품 준비가 되었습니다." + message;
}

문자 타입 정책 중 ‘상품 홍보’, ‘상품 구입 안내’ 를 ‘SmsPolicy’ 인터페이스 상속 받아 ‘PromosionSmsPolicy’, ‘PurchaseSmsPolicy’ 클래스를 구현 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SmsPolicyDecorator implements SmsPolicy {

private final SmsPolicy smsPolicy;

public SmsPolicyDecorator(SmsPolicy smsPolicy) {
this.smsPolicy = smsPolicy;
}

@Override
public String smsMessageProcessing(String message) {
return smsPolicy.smsMessageProcessing(message);
}
}

그 다음은 데코레이터를 구현 해야 합니다. 여기서 특징은 먼가를 감싸고 있는 레퍼클래스 활용이라고 보시면 됩니다. 그래서 감싸서 호출 해주는 역활을 합니다.
그리고 ‘SmsPolicy’ 인터페이스를 상속 받아야 합니다.

여기서 중요하게 볼것은 ‘SmsPolicy’ 객체를 하나 참조 하는것을 주목 해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class GuidePromotionType extends SmsPolicyDecorator {

private final String defaultMessage;
private final Product product;

public GuidePromotionType(SmsPolicy smsPolicy, Product product) {
super(smsPolicy);
this.product = product;
this.defaultMessage = "이번에 새로 나온 " + product.getName() + "상품은 배터리가 오래 갑니다.";
}

@Override
public String smsMessageProcessing(String message) {

return (message != null) ? super.smsMessageProcessing(this.defaultMessage + message) : super.smsMessageProcessing(this.defaultMessage);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DiscountType extends SmsPolicyDecorator {

private final String defaultMessage;
private final Product product;

public DiscountType(SmsPolicy smsPolicy, Product product) {
super(smsPolicy);
this.product = product;
this.defaultMessage = "특별히 이번주 한에서 10% 할인을 하고 있습니다.";
}

@Override
public String smsMessageProcessing(String message) {

return (message != null) ? super.smsMessageProcessing(this.defaultMessage + message) : super.smsMessageProcessing(this.defaultMessage);
}
}

이제 ‘문자 상세 내용 정책’ 관련해서 구현체를 만들어야 합니다. 앞써 객체를 생성했던 ‘SmsPolicyDecorator’ 객체를 상속 받아 구현 합니다.
여기서 smsMessageProcessing() 메소드를 보면 ‘문자 상세 내용 정책’ 에 필요한 각각의 메시지를 가공을 해서 데코레이터 객체에 전달 하는 역활을 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {
public static void main(String[] args) {

// 상품 정의 하기
Product product = new Product("키위 전기 자동차", BigDecimal.valueOf(100000));

// 문자 타입 정책 - 상품 홍보 정의 하기
SmsPolicy smsPolicy = new PromosionSmsPolicy();

// 문자 상세 내용 정책 - 상품 설명 안내 정의 하기
smsPolicy = new GuidePromotionType(smsPolicy, product);

// 문자 상세 내용 정책 - 상품 할인 적용 안내 정의 하기
smsPolicy = new DiscountType(smsPolicy, product);

// 고객에게 전송 하고자 하는 메시지 추출 하기
String message = smsPolicy.smsMessageProcessing(null);
System.out.println("message = " + message);
}
}

클라이언트측에서는 ‘문자 타입 정책’ - 상품 홍보 정의 한다면 ‘PromosionSmsPolicy’ 클래스로 선언 한 뒤에
이후 원하는 ‘문자 상세 내용 정책’ 관련 구현된 객체를 최상위 부모 ‘SmsPolicy’ 클래스를 전달 한뒤에 생성자 통해 객체를 생성 하게 됩니다.
즉 각각에 선언했던 정책 타입 경우 모두 ‘SmsPolicy’ 최상위 부모로 구현되어 있으니 합성을 통해 관리 및 감싸게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
public static void main(String[] args) {

// 상품 정의 하기
Product product = new Product("키위 전기 자동차", BigDecimal.valueOf(100000));

// 문자 타입 정책 - 상품 구입 안내 정의 하기
SmsPolicy smsPolicy = new PurchaseSmsPolicy();

// 문자 상세 내용 정책 - 상품 할인 적용 안내 정의 하기
smsPolicy = new DiscountType(smsPolicy, product);

// 고객에게 전송 하고자 하는 메시지 추출 하기
String message = smsPolicy.smsMessageProcessing(null);
System.out.println("message = " + message);
}
}

만약 ‘문자 타입 정책’ 중 ‘상품 구입 안내’ 를 정의하고 ‘문자 상세 내용 정책’ 증 ‘상품 할인 적용 안내’ 정의 한다고 했을때
‘PurchaseSmsPolicy’ 클래스를 먼저 선언하고 ‘문자 상세 내용 정책’ 의 ‘상품 할인 적용 안내’ 구현 클래스인 ‘DiscountType’ 객체로 감싸면 됩니다.

상속 경우 각각의 모든 케이스 마다 구현체를 모두 만들었지만 합성을 이용하면 그럴 필요가 없습니다.

만약 정책이 추가 해도 그렇습니다. 다른 새로운 정책이 생성 해도 새로 추가된 정책 관련 클래스만 생성해서 조합만 하면 됩니다.


Copyright 201- syh8088. 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.

💰

×

Help us with donation