일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- yolo
- DP
- 캘리그라피
- BFS
- MST
- 영어회화 100일의 기적
- 이분탐색
- Segment Tree
- 창훈쓰다
- 언어의 온도
- 삼성 코딩테스트
- multiset
- lower_bound
- 생활코딩
- upper_bound
- 다음 API
- 다이나믹 프로그래밍
- 안드로이드 스튜디오
- 평창동계올림픽
- 외판원 순회
- 그리디 알고리즘
- 백트레킹
- 성화봉송주자
- boj
- 인간이 그리는 무늬
- BOJ 2098
- 성화봉송
- 위상정렬
- 비트마스크
- 다음 지도 api
- Today
- Total
Hoon222y
[1227-1] 어댑터 패턴 / 팩토리 메서드 패턴 본문
어댑터 패턴 : 클래스와 인터페이스를 사용자가 기대하는 다른 인터페이스로 전환하는 기법
객체지향 프로그래밍에서 어댑터 패턴을 구현하는 방법
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 |
'코딩 > 교육' 카테고리의 다른 글
[1227-3] 디자인 패턴 / 리팩토링 (0) | 2018.12.27 |
---|---|
[1227-2] 옵저버 패턴 (0) | 2018.12.27 |
[1226-1] 추상화 / 인터페이스 /강결합/약결합/ (1) | 2018.12.26 |
[1220-5] 상속 / 오버라이딩 / 바인딩 / 업케스팅 (0) | 2018.12.20 |
[1220-4] 대입 연산자 오버로딩 / 비교 연산자 오버로딩 (0) | 2018.12.20 |