Hoon222y

[1227-1] 어댑터 패턴 / 팩토리 메서드 패턴 본문

코딩/교육

[1227-1] 어댑터 패턴 / 팩토리 메서드 패턴

hoon222y 2018. 12. 27. 20:02

어댑터 패턴 : 클래스와 인터페이스를 사용자가 기대하는 다른 인터페이스로 전환하는 기법


객체지향 프로그래밍에서 어댑터 패턴을 구현하는 방법

1. 상속(비추) - 기존 객체의 멤버들도 외부에 노출되므로 객체의 상태가 불안해진다.

2. 포함 - 기존 객체의 멤버들이 외부로 노출되지 않으므로 상속보다 상대적으로 안전하다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import java.util.ArrayList;        //extends ArrayList를 쓰기위함
import java.util.Stack;
 
public class main {
    public static void main(String[] args) {
        Stack s = new Stack(5);        
        
        s.push(10);
        s.push(20);
        
        System.out.println(s.pop());    //20
        System.out.println(s.pop());    //10
        
        // s.add(0.20);    상속으로 하면 가능하지만 포함으로 하면 안된다.
                        // 포함으로 해야 객체의 안정성이 높아진다.
    }
}
 
//이 코드의 문제는 만약에 어뎁터 패턴을 사용하게 된다면 상속을 사용하지 않는것이 좋다.
// why? arraylist를 상속받아기 때문에 예를들어 s.add(0.20) 처럼 0번째 인덱스에 
//넣는다고 하면 스텍이라는 자료구조가 아니게 된다.  -> 따라서 포함을 사용해서 구현한다.
 
//아래의 버전은 포함을 사용한 어뎁터 패턴
class Stack{
    //배열 객체를 상속이 아닌 포함을 시킨다.
    private ArrayList arr;
    private int size;
    public Stack(int size) {
        this.arr = new ArrayList(size);
        this.size = size;
    }
    
    public void push(int data) {arr.add(data);    }
    public int pop() {return (int)arr.remove(arr.size()-1);}
    public boolean isEmpty() {return arr.size() == 0;}
    public boolean isFull() {return arr.size() == size;}
}
 
 
//// 아래의 버전은 상속을 사용한 어뎁터 패턴
//class Stack extends ArrayList{
//    private int size;
//    public Stack(int size) {this.size= size;}
//    public void push(int data) {
//        add(data);            //== add(new Integer(data)
//    }
//    public int pop() {
//        return (int)remove(size()-1);
//    }
//    public boolean isEmpty() {return size() == 0;}
//    public boolean isFull() {return size() == size;}
//}
 
////바퀴를 새로 깍아서 사용하지 말고, 이전 바퀴를 사용하자 -> 재사용성
//class Stack{
//    private int arr[];
//    private int top =0;
//
//    public Stack(int size) {arr=new int[size];}
//
//    public void push(int data) {arr[top++] = data;}
//    public int pop() {return arr[--top];}
//
//    public boolean isFull() {return top ==arr.length;}
//    public boolean isEmpty() {return top == 0;}
//
//}
cs

 40번째 줄 코드는 ArrayList를 extends 해서 해결하는 것이다. 이처럼 상속을 이용해서 stack를 구현하게 되면 14번째줄 처럼 이미 지정되어진 기능을 사용하게될 수 있는데 이는 객체의 안전성을 낮추게 된다. 따라서 이러한 리스트를 class 안에 포함하여 사용하는 방식으로 객체의 안전성을 높일 수 있다. 즉, 배열 객체를 상속이 아닌 포함을 시키는 방식을 사용하는 것이다.


팩토리 메서드 패턴 : 객체 생성을 부모가 아닌 자식에게 위임하는 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package FactoryMethod;
//팩토리 메소드 패턴
public class test {
    public static void main(String[] args) {
        Win7Dialog dlg=new Win7Dialog("hello world");
        dlg.show();
 
    }
}
//win7 style ------------------------------------------------------
class Win7Button{
    private String label;
    public Win7Button(String label) {this.label = label;}
    
    //GUI 컴포넌트들은 자신이 어떻게 그려져야 할지에 대한 정보를 가지고 있다. 
    public void draw(){
        System.out.printf("[ %s ]\n",label);
    }
}
//이제 버튼을 붙이기 위한 다이얼로그 창을 설계한다. 
class Win7Dialog{
    private String message;
    private Win7Button button;
    
    public Win7Dialog(String message) {
        this.message = message;
        this.button = new Win7Button("OK");
    }
    //대부분의 GUI 라이브러리들은 윈도우를 화면에 보여주기 위한 
    //별도의 인터페이스를 가지고 있다. 
    public void show() {
        System.out.println(message);
        button.draw();    
    }
}
 
cs

 예를들어 window7의 알림창을 만든다고 가정해보자. 그렇다면 위처럼 코드를 구현할 수 있다. 

하지만 여기서 업데이트를 할 때 window10을 넣는다고 가정해보자. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package FactoryMethod;
 
public class test {
    public static void main(String[] args) {
        //아래의 라이브러리는 코드의 중복 등 여러가지 문제점들이 있습니다. 
        //이를 개선해보아라
        Win10Dialog dlg=new Win10Dialog("hello world");
        dlg.show();
 
    }
}
 
//win10 style --------------------------------------------------
class Win10Button{
    private String label;
    public Win10Button(String label) {this.label = label;}
 
    public void draw(){
    System.out.printf("[ %s ]\n",label);
    }
}
class Win10Dialog{
    private String message;
    private Win10Button button;
    
    public Win10Dialog(String message) {
        this.message = message;
        this.button = new Win10Button("OK");
    }
    public void show() {
        System.out.println(message);
        button.draw();    
    }
}
 
//win7 style ---------------------------------------------------
class Win7Button{
    private String label;
    public Win7Button(String label) {this.label = label;}
 
    public void draw(){
        System.out.printf("[ %s ]\n",label);
    }
}
class Win7Dialog{
    private String message;
    private Win7Button button;
    
    public Win7Dialog(String message) {
        this.message = message;
        this.button = new Win7Button("OK");
    }
    public void show() {
        System.out.println(message);
        button.draw();    
    }
}
cs

 이런식으로 window7과 10에 따른 각각의 거의 똑같은 내용의 코드가 반복되게 된다. 이러한 문제를 해결하는 방법에 대해서 생각해보자. 

각 버전별 GUI 스타일이 다르다고 하더라도 Dialog class에는 공통적인 속성이 존재한다. 코드의 중복이 발생하므로 공통의 속성을 뽑아내어 부모 클래스로 설계한다. 

 이 때 코드를 살펴보면 Button 부분은 따로 건들지 않았고, Dialog 부분만 변화했다는 점이 있다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 각 버전별 GUI 스타일이 다르다 하더라고 다이얼로그 클래스에는 공통된 속성이 존재한다. 
// 코드의 중복이 발생하므로 공통의 속성을 뽑아내어 부모 클래스로 설계한다. 
class Dialog{
    // 모든 다이얼로그에는 버튼이 존재하지만 각 스타일에 대한 버튼은 아무런 관계가 없으므로 
    // 이를 부모 클래스로 이동시킬 수 없습니다. 따라서 각 스타일에 따른 타입코드를 도입한다. 
    public static enum Style{WIN7,WIN10,WIN11}        //타입코드 
                      //만약에 여기서 WIN11이 추가되면 추가하다보면 OCP를 만족 안함 
    private Style style;
    private String message;
    
    public Dialog(String message, Style style) {
        this.message = message;
        this.style = style;
    }
    
    public void show() {
        System.out.println(message);
        if(style == style.WIN7)
            new Win7Button("OK").draw();
        else if(style == style.WIN10)
            new Win10Button("OK").draw();
//        else if(style == style.WIN11)      //이처럼 WIN11이 추가되도 enum과 이곳에 이런식으로
//            new Win10Button("OK").draw();  //추가만 해주면 된다. 
    }
}
 
public class test {
    public static void main(String[] args) {
        Win10Dialog dlg=new Win10Dialog("hello world");
        dlg.show();
    }
}
 
class Win10Button{
    private String label;
    public Win10Button(String label) {this.label = label;}
 
    public void draw(){
        System.out.printf("[ %s ]\n",label);
    }
}
class Win7Button{
    private String label;
    public Win7Button(String label) {this.label = label;}
    
    public void draw(){
        System.out.printf("[ %s ]\n",label);
    }
}
 
//이제 각 버전별 다이얼로그를 구현하는 설계자는 Dialog 를 상속하기로 한다. 
class Win7Dialog extends Dialog{
    public Win7Dialog(String message) {
        super("hello world", Dialog.Style.WIN7);
    }
}
class Win10Dialog extends Dialog{
    public Win10Dialog(String message) {
        super("hello world", Dialog.Style.WIN10);
    }
}
 
cs

 

이런식으로 타입 코드를 이용해서 Dialog 부분의 공통적인 부분을 부모 클래스로 묶을 수 있다. 이처럼 버전에 따른 타입코드를 사용하여 문제를 해결할 수 있다고 생각한다. 하지만 코드에 타입코드가 도입되면 반드시 분기문이 추가되는데 이러한 코드가 존재할 경우, 새로운 기능이 추가되면 (ex- WIN11) 코드 수정이 불가피하고 결론적으로 OCP를 만족하지 않게 된다. 따라서 새로운 기능이 추가 되도 기존 코드의 수정 없이 혹은 최소로 수정될 수 있도록 구현해야 한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 버튼 객체 생성은 부모 클래스가 어떤 타입의 버튼을 생성 해야하는지 알수 없기 떄문에
// 자식에게 위임한다. 
abstract class Dialog{
    private Button button;
    private String message;
    
    public Dialog(String message) {
        this.message = message;
        this.button = createButton();
    }
    // 이제 버튼 생성을 부모가 아닌자식에게 위임한다. 
    // 이처럼 객체 생성을 부모가 아닌 자식 클래스에게 위임하는 패턴을 팩토리 매서드 패턴
    protected abstract Button createButton();
    
    public void show() {
        System.out.println(message);
        button.draw();
    }
}
 
//서로 다른 타입을 동종의 타입으로 처리하기 위해 부모클래스를 도입(상속) 합니다. 
abstract class Button{
    protected String label;
    public Button(String label) {this.label = label;}
    
    //자식의 공통의 기능은 반드시 부모가 제공해야 합니다.(LSP)
    public abstract void draw();
}
 
public class test {
    public static void main(String[] args) {
        Win10Dialog dlg=new Win10Dialog("hello world");
        dlg.show();
    }
}
 
class Win10Button extends Button{
    public Win10Button(String label) {super(label);}
    public void draw(){    System.out.printf("[ %s ]\n",label);}
}
class Win7Button extends Button{
    public Win7Button(String label) { super(label);}
    public void draw(){System.out.printf("[ %s ]\n",label);}
}
 
class Win7Dialog extends Dialog{
    public Win7Dialog(String message) {
        super("hello world");
    }
    protected Button createButton() {
        return new Win7Button("OK");
    }
}
class Win10Dialog extends Dialog{
    public Win10Dialog(String message) {
        super("hello world");
    }
    protected Button createButton() {
        return new Win10Button("OK");
    }
}
cs


Comments