꼬리에 꼬리를 무는 - 유사 디자인 패턴들

 (3/4편)

 

 

패턴을 공부하거나 할 때 UML 에 집중해서 공부하면 안된다고 생각한다. 구조만을 외우고 구조로 구분을 한 사람은  공부한것을 금방 까먹거나 헤깔려하기 쉽기 때문인데, 이유는 거의 비슷한 구조를 갖춘 패턴들은 정말 많기 때문이다. ( 더 헥깔린것은 동일 패턴이 구조가 다른 경우도 부지기수이다. 의도가 같기 때문. 즉 "의도", "목적" 이 중요하다.) 

아래 구조를 보자.

정말 많지 않나?? 이 구조만 보고 뭘 알 수 있을까? 자신이 Composite 패턴만 공부했다면 , 이러한 구조를 보고 무조건 "컴포지트 패턴" 이라고 어디가서 우기지나 않을까 염려된다.

 

이런 식 또한 많다. 무엇인가?? 당연히 알수가 없다.
또한 저런 동일한 구조에 여러 패턴이 같이 참여 할 수 도 있다. 예를들어 Strategy패턴과 Flyweight 패턴이 같은 클래스들을 공유하는 경우가 실제 세계에는 부지기수로 일어나기도 한다. 그럴 때는 A 클래스와 B 클래스는 OO 패턴 과 XX 패턴에 어떤 역할로 참여하고 있다 라고 말하며 회의시에 커뮤니케이션 할 수 있겠다. 

따라서 앞으로 나올 패턴 비교에서는 UML 에 대한 설명은 하지 않으며  그냥 말로 "의도" 를 전달 할 예정이다.
마지막으로 디자인패턴을 처음 공부하는 사람을 위한 글은 아니며, 대략 공부한 상태에서 정리하기 위한 목적의 글이라는 점을 양해 드린다. 

 

1,2편은 여기에 -> http://hamait.tistory.com/868

3. Adapter 패턴 vs Facade 패턴

자 이전 글에서 어댑터 패턴에 대해서 알아보았다. 남의 인터페이스를 내 설계에 맞춰서 사용하고 싶을 때 사용한다는것을 알았는데, Facade 도 비슷하지 않을까? Facade 는 건물의 출입구 쪽의 외벽을 말하는 것으로 소프트웨어 디자인 패턴에서는 감추어져 있는 건물(서비시스템등) 의 안을 들어가서 샅샅히 탐험 할 필요 없이, 출입구에서 심플하게 처리하겠다는 의미를 가지고 있다. 즉 내부에서 일어나는 복잡한 행동들을 이해할 필요 없이 중간 매개체에서 한방에 모든게 해결되게 하자는 뜻이다. 

결국 자신과 타인 사이의 중간 지점에서 타인의 기능을 사용한다는 느낌에서는 두 패턴이 비슷하지만, 

 어댑터 패턴은 중간에서 남의 인터페이스를 자신의 인터페이스의 모양으로 "인터페이스의 변경"  하기 위함이 목적이라면  Facade 는 서비시스템의 복잡한 기능을 중간에서 감추고 심플하게 통신하자는것 (단방향) 이 목적이다. 

이제 UML 을 보자.

 

먼가 느낌오지 않는가? 제발 느낌만 받아드려라~

패키지가 뭐지~ 저런 화살표가 뭐지? 이런것은 잊어버리시고~ 그냥 그 느낌 그대로 해당 언어에 맞게 구현하면그게 Facade 이다. 혹시 구조가 그게 아닐까봐 눈치보지마라~ 구조가 어쨋든 상관없으니~ (언어마다 해당 언어의 이디엄이나 패러다임에 따라서 좀 달라 질 수도 있을 것이다) 

4. Facade 패턴 vs Brigde 패턴

자  이제 위에서 어댑터 패턴과 퍼사드패턴에 대해서 알아보았다. 어댑터 패턴도 퍼사드도 중간에서 먼가 하는 거 같다. 근데 중간?? 강남과 강북을 잇는 한강다리... Bridge ?? 중간에서 뭘 하는 느낌을 주는 것은 Bridge 패턴도 빼놓을 수는 없지 않겠는가~

그럼 브리지 패턴은 그 패턴들과 무엇이 다른것일까? 중간에서 뭘 하긴 하는데 그 동안 다룬 패턴들이 안 다룬게 무엇이 있을가 먼저 생각해보라~ 패턴이란 것은 다 생활에서 항상 일어나는 일을 정리한 것이니 분명히 생각해 낼 수도 있을 것이다. 

* Adapter 는 중간에서 인터페이스 변환

* Facade 는 중간에서 외부 모듈의 사용을 단순화 

* Proxy 는 중간에서 대리하여 기존 구현을 다른 방식으로 컨트롤 

* Decorator 는 중간에서 기존 구현에 다른 구현을 추가 

Facade 가 복잡성을 감추는게 목적이라면 Bridge 는 상대방의 구현들을 직접 사용하면 강합 결합도가 발생하기 때문에 중간에서 매개체 삽입!! 좀 더 있어보이게 말하면 서브시스템(외부 모듈)의 구현으로부터 레이어를 두어 "디커플링/유연성증가"  를 하기 위한 의도가 강하다.

사실 의도는 확 와닿긴 하지만 , 구조 잡을 때 어떻게 해야 할지 가장 선택의 폭이 큰 패턴 중 하나인거 같다.
따라서 어려우며, 일반 디자인 패턴들 보다는 큰 범위의 설계라고 볼 수 있다. (Abactract Factory 와 같이 아키텍처 급 )

이제 UML 을 보는데, 여기서는 UML 에 대해서 좀 자세히 설명해야 할 것 같다.

* 위의 UML 은 디자인패턴의 풍운아~ 본좌~구루이신 홀럽님의 패턴책에서 발췌된 것이다. 

집중해보자!! 

Client Class 는 Abstaraction 역할로써 호출자(User)는 이 추상객체를 이용해서 실제 서비스를 호출하게 된다.  이 그림에서는 좀 헦깔리게 Client 가 Abstraction 이다. Client (User) -> Abstraction 이런 그림이면 더 이해하기 쉬웠을듯~

Java.sql.Connection 등은 Implementor 로써 실제 서비스에 대한 인터페이스이다. 위의 Abstraction 은 보통 이 인터페이스를 통해 실제 시스템 구현과 대화이며  이 인터페이스는 보통 Abstract Factory 패턴에서 Abstract Product 역할을 하기도 한다.

com.holub.database.JDBCConnection 등은 위의 Implementor 를 구현한 실제 시스템이다. 보통 Abstract Factory 패턴에서 Concrete Product 역할을 하기도 한다.

 
 그렇다 뭔가 복잡해 보이지만 사실 그냥 중간에 Abstraction 이라는 녀석 하나 만들고 이 녀석이 다양한 구현체들을 상황에 맞춰서 유연하게~ 호출 할 수 있게 해준다는것이다. 위에서 Client 는 Java.sql.Connection 인터페이스에 대해 호출 했지만, Samba.sql.Connection 을 호출 할 수도 있겠고~ 유연함이 목적이니깐~

 

이번엔 꽤나 단순한 Bridge 패턴의 UML 이다. 별거 없지 않나? 그냥 오른쪽의 서브시스템 구현과 Client 를 Abstraction 을 이용해서 분리 시키는 것일 뿐이다. Client 는 오른쪽 서브시스템의 구현이 어떻게 변하더랴도 영향을 받지 않게 된다. 그 반대도 마찬가지~  "단순하게 구현하건 복잡하게 구현하건 의도가 그렇다면 그건 같은 패턴이된다."

 

5. Facade vs Mediator 패턴 

->  두 패턴 모두 복잡한 시스템간의 관계를 단순화 시켜준다. Facade 가 단방향이라면 Mediator 는 양방향의 통신에 대해 중간에서 다리역할을 해준다. 이때 Mediator 가 너무 많은 역할을 하게 될 수 가 있는데 경계해야 할 것이다.

6. Mediator vs Bridge 패턴 

-> 두 패턴 모두 서브시스템 간의 결합도를 줄여준다. Bridge 이 외부 시스템과의 유연성 증가를 위한 것이라면,  Mediator 는 다양한 객체간의 관계를 중간에서 정리해주는 역할 즉 복잡성을 감추기 위한 의도가 강하다. Bridge 가 무엇인가 생략하지 않고, 표준인터페이스를 통한 연결다리를 놔 준다면, Mediator 는 시스템 전반에 나타나는 복잡한 코드를 단순한 인터페이스로 통일 시켜준다. 그런 복잡성 감소의 목적에서는 Facade 와 비슷하다. 

7. Builder vs Abstract Factory 패턴 

-> Abstract Factory 는 여러 군들에 관한 생성을 감춘다면 , Builder 는 하나의 군에 대한 생성하는 다양한 방식에 대한 정보를 감춘다.

8.  Abstract Factory vs Factory Method

->Gof 의 Factory Method 는 파생된 클래스가 오버라이딩한 메소드를 통해 객체를 생성하는 매우 간단한 의미를 가진다. 자바진영에서 사용되는 팩토리패턴과는 조금 다르다. 팩토리 패턴은 오버라이딩한 메소드를 통해 생성한다는 족쇄가 없다.  Abstarct Factory 는 종종 Factory Method 를 이용하여 (이용 안해도 됨) 여러군에 대한 생성을 유연하게 생성하는데 목적이 있다. 

9. Factory Method vs Templet Method 패턴 
->
 Factory Method 는 파생된 클래스가 오버라이딩한 메소드를 통해 객체를 생성하는 것을 의미하는 간단한 거라면, Template Method 는 부모클래스의 메소드를 통해 로직의 와꾸를 정하고, 그 로직에서 추상메소드를 호출하는 것이다. 파생된 클래스는 그 추상메소드의 상세 알고리즘을 구현하게 된다.

10. Iterator vs Visitor 패턴 

-> Visitor 는 노드에서 노드로 자신이 넘겨지는 반면, Iterator 는 하나의 노드 내부를 탐색하기 위함이다.

11. Visitor vs Strategy 패턴 

-> Visitor 는 노드에서 노드로 넘겨지면서 자신의 알고리즘이 수행되는 반면, Strategy 는 하나의 노드에서 특정 알고리즘을 선별하여 사용한다. 

12. Strategy  vs 

Chain of Responsibility 패턴 

-> , Strategy 는 하나의 노드에서 특정 알고리즘을 선별하여 사용하는 반면, Chain of Responsibility 는 하나의 메세지가 다양한 노드에 존재하는 알고리즘의 적용을 거치게 한다.

13. 

Chain of Responsibility

  vs 

Visitor 패턴 

-> Chain of Responsibility 는 하나의 메세지가 다양한 노드에 존재하는 알고리즘을 거치는 반면, Visitor 는 자신의 알고리즘이 노드에서 노드로 옮겨지면서 적용된다. 

13. Strategy vs State 패턴 

-> Strategy 는 다양한 알고리즘 중에 하나를 주입받아 사용하는것이고, State 는 상태별 행위를 외부에 정의해 놓고 , 자신의 상태에 따라서 외부에 정의한 행위를 수행한다. 

14. Flayweight vs Prototype 패턴

-> Flayweight 는 자기와 남이 (외부에서 주입을 받는 등등) 동일한 객체를 참조하는 것이고, Prototype은 이미 만들어진 객체의 복사본을 만드는것이다. 클래스가 틀이 되는게 아니라 이미 만들어진 객체가 틀이 된다. 따라서 이미 만들어진 객체와는 조금 다른 속성을 지는 clone 이 만들어 진다. DeepCopy 를 하든 ,ShallowCopy 를 하든 상관없다. 

15. Prototype vs Memento 패턴 

-> Prototype은 이미 만들어진 객체의 복사본을 만드는것이다. Memento 도 이미 존재하는 객체들의 사본을 만든다. 하지만 의도는 전혀 다르다. Prototype 이 클래스를 너무 많이 만들게 되는 우를 범할까봐, 하나의 클래스를 통한 다양한 객체를 만들어두고, 그 객체들의 Clone 을 만드는 것이라면, Memento 는 다시 돌려지기 위한 객체들의 상태를 기록 (보통 외부로) 해 두기 위함이다. 

16. 

Memento vs Command 패턴 

-> Memento 가 상태와 연산이 알려지지 않은 객체를 캡슐화하고 저장해 놓는게 목적인 반면에, Command 는 행위를 객체로 만드는것이 목적이며, 그 객체에는 상태와 연산이 존재하며, 사용자는 그것에 대해 잘 알고 있다. 왜냐하면 행위를 번복(Undo) 하려 할때, 상태를 되돌리고, 반대연산을 수행해 줘야 하기 때문이다.  둘다 Undo /Redo 에 많이 사용된다.

* 본 글은 생각나는데로 투박하게 써 내려갔으며, 저자의 주관이 많이 들어가 있음을 말해드립니다. 디자인 패턴은 패러다임,언어특성 마다 구현이 달라지나 의도는 일치해야합니다. 왜냐하면 패턴은 커뮤니케이션의 목적이 꽤나 강한데, "의도" 가 달라져 버리면 , 패턴을 정의하는 의미가 없어지기 때문입니다.따라서 본 글에서의 잘못된 "의도" 설명 or 추가 할 필요가 있는 부분이 있으면 언제라도 지적 부탁드립니다. 감사합니다.

* 자아비판좀 하자면 패턴에 대해서 꽉 잡고(?) 있더라도 바쁘다는 핑계하에 그냥 함수쪼개기/외부주입 정도만 신경 쓰고 더 신경 쓰기 귀찮아서 더 잘 할 수 있는것을 나중으로 미루고 있는 저보다는 패턴 같은거 잘 몰라도 코딩 할 때 마다 진지하게 장인정신을 발휘하는 사람이 훨씬 좋은 코드를 만드는게 사실일 겁니다.


꼬리에 꼬리를 무는 - 유사 디자인 패턴들 (1/2편)


패턴을 공부하거나 할 때 UML 에 집중해서 공부하면 안된다고 생각한다. 구조만을 외우고 구조로 구분을 한 사람은  공부한것을 금방 까먹거나 헤깔려하기 쉽기 때문인데, 이유는 거의 비슷한 구조를 갖춘 패턴들은 정말 많기 때문이다. ( 더 헥깔린것은 동일 패턴이 구조가 다른 경우도 부지기수이다. 의도가 같기 때문. 즉 "의도", "목적" 이 중요하다.) 

아래 구조를 보자.

정말 많지 않나?? 이 구조만 보고 뭘 알 수 있을까? 자신이 Composite 패턴만 공부했다면 , 이러한 구조를 보고 무조건 "컴포지트 패턴" 이라고 어디가서 우기지나 않을까 염려된다.


이런 식 또한 많다. 무엇인가?? 당연히 알수가 없다.
또한 저런 동일한 구조에 여러 패턴이 같이 참여 할 수 도 있다. 예를들어 Strategy패턴과 Flyweight 패턴이 같은 클래스들을 공유하는 경우가 실제 세계에는 부지기수로 일어나기도 한다. 그럴 때는 A 클래스와 B 클래스는 OO 패턴 과 XX 패턴에 어떤 역할로 참여하고 있다 라고 말하며 회의시에 커뮤니케이션 할 수 있겠다. 

따라서 앞으로 나올 패턴 비교에서는 UML 에 대한 설명은 하지 않으며  그냥 말로 "의도" 를 전달 할 예정이다.
마지막으로 디자인패턴을 처음 공부하는 사람을 위한 글은 아니며, 대략 공부한 상태에서 정리하기 위한 목적의 글이라는 점을 양해 드린다. 



1. Proxy 패턴 vs Decorator 패턴 

두 패턴 모두 기존에 존재하는 객체의 동일한 인터페이스 (쉽게 말하면 메소드) 를 이용해서 (변경하지 않고서) 다양한 행위를 추가하기 위한 의도가 있다. 즉 a 라는 객체에 doAction() 이라는 메소드가 있으면, 다른 객체에서도 동일한 시그니쳐의 doAction() 메소드를 호출해서 a 의 doAction()을 실행하는 동시에 다른 첨가물을 추가해 준다는거다. 

그럼 두 패턴이 같은거네? 뭐 비슷하다고 할 수 있으나 "의도" 에 미묘한 차이가 있다.
Decroator 는 어떤 추가적인 기능을 제공한다고 치면 Proxy 는 추가적인 컨트롤을 제공한다고 볼 수 있다. 

예를 들어보자

Decorator 는 

a 객체의 doAction 이  "hello" 를 출력하는 것이라면 , 데코레이터 역할의 b 객체의 doAction 에서는 a 객체의 doAction 을 호출하여 "hello" 를 출력하는 동시에 그 위 아래로 "********" 이런것을 추가적으로 처리해 주는 것이다. 즉 주요 기능에 무엇인가능력을 부가해서 추가해주는것이 의도이다. 어떤 메소드의 시작이나 끝에 로깅이나 트랙잭션을 추가해 주는 것 또한 데코레이터라 할 수 있다.

Proxy 는 

a 객체의 doAction이 "hello" 를 현재 로컬에 출력하는 것이라면 , Proxy 역할의 b 객체의 doAction 는 "hello" 를 Remote 컴퓨터로 보내서 출력해주는 역할을 한다. 즉 구현된 내용에 부가 기능을 추가해 주려는게 아니라, 그 주요기능을 다양한 방식으로 컨트롤 해주는 역할이다. 외부로 보낸다든지, lazy initailization 한다든지, 캐쉬된 것을 사용한다든지~


이제 다시 UML 을 보자.



구조는 같다. 다만 객체의 역할이 무엇이냐에 따라 나뉜다는 점을 명심하자.


질문) 


그럼 자바의 Dynamic Proxy 는 프록시 패턴인가? 데코레이터 패턴인가? 

답 : "Dynamic Proxy 기능을 어떤 의도로 사용하냐 에 따라서 달라진다." 


하둡이라는 자바기반의 분산처리 시스템에서 Dynamic Proxy 는 RPC 를 위한 Proxy 로 사용되었으며,
스프링에서는? 아래 읽을꺼리를 참고하라~

읽을꺼리: 자바의 Dynamic Proxy는 프록시 패턴인지 데코레이터 패턴인가? 

읽을꺼리: 하둡(Hadoop) 에서 RPC 구조 


2. Decorator 패턴 vs  Adapter 패턴

자  이제 위에서 데코레이터 패턴에 대해서 알아보았다. 어떤 기능에 추가 기능을 유연하게 추가 시켜주기 위한 의도가 있음을 알 수 있었다. 또한 Proxy 패턴을 알아 보았는데, 그것은 추가 컨트롤을 할 수 있게 도움을 주는 것이 었다. 그럼 Adapter 패턴은 무엇일까? 이름 그대로 생각하면 될 거 같다. 예를들어 우리 나라는 전기를 220v 를 사용하는데 110v 사용하는 나라로 여행을 갈 때면 어댑터가 필요하다.  어댑터 패턴도 마찬가지이다. 다른 누군가 만들어 놓은 어떤 기능을 사용하는데 , 자신의 설계는 그대로 가져가 되  다른 라이브러리를 내부에서 그대로 사용하고 싶을 때가 있다.이때 중간에서 변환을 시켜 준다. 즉 사용해야할 메소드가 methodB() 로 생겼는데, 나는 methodA() 라고 호출 해야 한다고 하자. 그럼 어댑터 객체는 methodA() 제공하고 내부에서 methodB()을 호출해 주면 될 것이다.

즉 데코레이터는 중간에서 기능을 추가해 준다면, 어댑터는 중간에서 인터페이스를 변경 해 줄 뿐이다.

이제 UML 을 보자.


Adaptee 는 변환되야할 타깃객체의 인터페이스를 포함한다. Adapter 객체는 내부에서 Adaptee 의 메소드를 호출하고 있다. 간단히 정리하면  Adapter 호출해야할 객체의 터페이스를 중간에서 변경해 주는 목적을 가지고 있다.

구체적인 예는 아래 읽을꺼리를 참고하라~

읽을꺼리: JDBC 와 디자인패턴 - 5.Adapter 패턴 



3,4 편 바로가기 -> http://hamait.tistory.com/869

5. Facade vs Mediator 패턴 

6. Mediator vs Observer 패턴 

7. Bridge vs Abstract Factory 패턴 

8. Abstract Factory vs Factory Method 

9. Factory Method vs Templet Method 패턴 

10. Templet Method vs Builder 패턴 

11. Iterator vs Visitor 패턴 

12. Visitor vs Strategy 패턴 

13. Strategy  vs Chain of Responsibility 패턴 

14. Strategy vs State 패턴 

15. Flayweight vs Prototype 패턴

16. Prototype vs Memento 패턴 

17. Command vs Composite 패턴 



멀티쓰레드 디자인패턴의 꽃이라고 한다면 단연코 "생산자-소비자" 패턴이라고 할 수 있습니다. 그럼 Gof 의 디자인패턴의 꽃은 무엇인가? 라고 묻는다면   DI (Dependency Injection) 와 밀접한 "전략패턴" 이라고 대답하고 싶습니다. 물론 둘다 제 개인적인 생각이죠 :-) 

멀티쓰레드/서버코드를 작성할때 거의 무조건 "생산자-소비자" 패턴이 사용되기 마련이며, 다른 고차원 패턴들 (예를들면 node.js 의 기반패턴인 react 패턴) 의 기반이 되면서 동시에 멀티쓰레드 코어패턴을 포함하고 있는 , 즉  "허리" 역할을 제대로 하고 있는 패턴이라고 볼수 있기 때문에 아주 중요하다고 볼 수 있습니다. 

자 그럼 생산자-소비자 패턴이 무엇인지 살펴보도록 하죠. 이해하기 쉽게 코드를 쉽게 쉽게 간략히 만들었습니다. (예외등 빠짐)

* 제 글이 그렇듯이 폴리글랏으로 보여주며, 추가 기술들도 섞여있습니다. 


1.  싱글쓰레드상에서 생산자 - 소비자 

먼저 멀티쓰레드 말고 그냥 간단하게 코드로 살펴보자. 개발자들은 말보다는 코드가 더 이해하기 쉬우니~

class Producer {

    int id = 0

int produce(){

return nextid()

}

int nextId(){

return  id = id + 1

}

}

class Consumer{

void consume(int id){

        print ("ID : " + id)

}

}

void  test(){

     Producer  p = new Producer();

     Consumer c = new Consumer(); 

result = p.produce()

c.consume(result)

}

 1 씩 증가시키는 생산을 하는  Producer 클래스가 있고, 그것을 전달 받아 사용하는 Consumer 가 있습니다.
 즉 간단하게 생산하는 녀석 따로, 소비하는 녀석 따로 있으면 생산자-소비자 패턴입니다. 

 싱글쓰레드에서의 특징은 호출하면 바로 반응한다 입니다. produce 를 호출하면 다른짓을 하지 않고 바로 새로운 id 를 리턴해주죠.실시간 반응 (실시간 리턴은 아님) 입니다.  다만 produce 에서 먼가 오래 작업을 한다면 Consumer 객체는 오래 기다려야겠지요.

이것을 멀티쓰레드로는 어떻게 만들까요? 


2.  멀티쓰레드상에서 생산자 - 소비자 (by JAVA core) 


이 이미지는 좀 복잡한데..



     사실 매우 단순하다. Thread 1이 생산자 Thread 2가 소비자가 된다.



멀티쓰레드에서도 생산자-소비자 따로입니다만은 싱글쓰레드와 다른 이 소스의 특징은 

1.  Producer 와 Consumer 가 각각 쓰레드를 가지고있다. (내부에 루프를 가지고 있다) 
2.  전달 매개체 (보통 큐로 구현) 가 생겼다. (아래에서 Table 클래스)

입니다.  소스를 보시죠. 

public class main {

    public static void main(String[] args) {

        

    Table table = new Table(100);     

        new producerThread(table).start();

        new consumerThread(table).start();

    }

}


public class producerThread extends Thread {

    private static int id = 0; 

    Table table;

   

    public producerThread(Table table) {

        this.table = table;

    }

    public void run() {

            while (true) {

            Thread.sleep(1000);

                String packet = "No : " + nextId();

                table.put(packet);  // 큐에 추가 

            }

    }

    private static synchronized int nextId() {

        return id++;

    }

}



public class consumerThread extends Thread {

    private final Table table;

    public consumerThread(Table table) {

        this.table = table;

    }

    public void run() {

            while (true) {

            String packet = table.take();   // 큐에서 가져옴                      

                System.out.println("consumer : " + packet);

            }

    }

}


public class Table {

    private final String[] buffer;

    private int tail;  

    private int head;  

    private int count; 

    public Table(int count) {

        this.buffer = new String[count];

        this.head = 0;

        this.tail = 0;

        this.count = 0;

    }

    public synchronized void put(String packet)  {

        while (count >= buffer.length) {   // 버퍼가 가측 차면 대기!

            wait();

        }

        buffer[tail] = packet;   // 후입하라!

        tail = (tail + 1) % buffer.length;  // Circular 큐라서 tail 의 위치가 바뀜!

        count++;

        notifyAll();  // 버퍼에 먼가가 들어 갔으니 take 해도 된다고 이벤트 날림!!

    }

    public synchronized String take()  {

        while (count <= 0) {   // 버퍼에 아무것도 없으면 대기!

            wait();

        }

        String packet = buffer[head];  // 선출하라!

        head = (head + 1) % buffer.length;  // Circular 큐라서 header 의 위치가 바뀜!

        count--;

        notifyAll();  // 버퍼에서 하나를 가져갔으니 put 해도 된다고 이벤트 날림!!!

        return packet;

    }

}



코드의 중요 부분을 설명해보면 (주석과 함께 살펴보세요)

Producer 클래스는  1초 에 한번씩 table 에 id 를 집어 넣습니다. 
Consumer 클래스는 table 에서 id 를 가져와서 출력합니다.

Table 클래스는 좀 눈여겨 봐야합니다.
String 배열을 가지고 큐를 구현한 코드이며. 더불어 생산자와 소비자 클래스에서 그 배열에 동시접근을 막기위한 장치들이 있습니다. synchronized 와 wait() 인데요. 이 두가지는 매우 코어적인  방식이며 바른 이해가 필요합니다. 이것을 직접 조작하면서 멀티쓰레드 프로그래밍을 하는것에 대한 위험성이 공감대를 얻으면서, 좀 더 하이레벨에서 조작하는 방식들이 생겨납니다. 
그것을 위해 등장한것이 STM , Actor 등이 있으며, 아래에서는 2번의 큐를 훌륭한 개발자들이 미리 구현한 java.util.concurrent 를 사용합니다.


3.  멀티쓰레드상에서 생산자 - 소비자 (by JAVA Concurrent 패키지) 

위의 Table 클래스를  java.util.concurrent 패키지를 이용하여 구현해보면 


public class Table {

private final BlockingQueue<String> buffer; 

public Table(int count) {

 this.buffer = new ArrayBlockingQueue<String>(10);

}

public  void put(String packet) {

Thread.sleep(1000);

buffer.put(packet);

}

public  String take()  {

String packet = buffer.take();

return packet;

}

}

이렇게 BlockingQueue 를 사용함으로써 매우 간단해졌으며 실수의 여지를 줄였습니다.

하지만!! 이것도 동기화 객체를 사용하며, 상태변경에 대한 책임소재가 명확하지 않습니다. 결국 한층 더 동기화 문제를 줄일수있는 Actor 패턴이라는것으로 진화됩니다.   (조금있다가 설명합니다) 


4.  멀티쓰레드상에서 생산자 - 소비자 (by Python) 

* java 코드와 기능이 동일합니다. 설명은 주석으로 대신합니다.

#-*- coding: utf-8 -*-

from threading import Thread, Condition

import time

import random

 

queue = []  # 리스트입니다만 동기화가 기본적으로 됩니다. 

condition = Condition()  #  java 의 wait() 및 synchronized  혼종 

 

class ProducerThread(Thread):

    i = 0

    def run(self):

        global queue

        while True:

            condition.acquire() 

            if len(queue) == 100: // 버퍼가 가측 차면 대기!

                condition.wait()

            num = self.nextId()  // 생산 

            queue.append(num)  // 큐에 후입하라!

            condition.notify()      // 버퍼에 먼가가 들어 갔으니 take 해도 된다고 이벤트 날림!!

            condition.release()

            time.sleep(1)

 

    def nextId(self):

        self.i = self.i + 1

        return self.i

 

class ConsumerThread(Thread):

    def run(self):

        global queue

        while True:

            condition.acquire()

            if not queue:   // 버퍼가 비어있으면  대기!

                condition.wait()

            num = queue.pop(0)  // 선출하라!

            print "Consumed", num  // 소비~~~~~~

            condition.notify()  // 버퍼가 비었으니 put 해도 된다고 이벤트 날림!!

            condition.release()

            time.sleep(1)

 

 

ConsumerThread().start()  

ProducerThread().start()

* 참고로 하나의 쓰레드가 wait() 되는 순간에 condition.acquire() 는 해제됩니다. 

자. 이렇게 생산자-소비자 패턴에 대해서 알아봤는데요.
위에 3번에서 말해다시피 java.util.concurrent 를 사용하는것도 쉽지 않기 때문에 (물론 복잡도에 따라서 코어를 사용하는것이 문제점을 더 잘 파악하는 길이기도 합니다)  결국 한층 더 동기화 문제를 줄일수있는 Actor 패턴이라는것으로 진화됩니다.  


5.  Actor  

Actor 란 말 그대로 '행동자' 입니다. 능동적으로 비동기 메세지를 처리하는 녀석인데요.
능동적이라는 말은 쉽게 말해 쓰레드 하나가 할당되어 있다는 얘기입니다. 살아 숨쉬고 있죠. 
또한 비동기 적으로 메세지를 처리할수있습니다. 이건 메세지를 담아둘수있는 자신만의 큐를 가지고 있다는 얘기입니다.

즉 Actor = 객체+루프를 가진 쓰레드+큐 로 이루어진 것 이라고 보면됩니다.

이왕 Actor 가 나온김에 이것으로부터 나온 패턴들을 간단히 살펴보면 Reactor 패턴이 있습니다.

6.  Reactor 

 Node.js 의 기반패턴이라고 알려져있으며 , 쉽게 말해 Actor + 이벤트 핸들러 라고 보시면됩니다.
 우리말로 해석해보면 "반응로" 정도 될거 같은데요.  즉 Actor 안에다가 어떤 swich 문을 두고서 어떤 이벤트가 날라오면 
 그것과 연결된 어떤 핸들러를 디스패칭하라입니다.  보통 단일쓰레드로 이루어져있기때문에 하나의 핸들러가 시간을 많이 잡  아먹게 되면 전체적으로 성능이 급격히 저하됩니다.

 성능면이라든지 몇몇가지 단점이 있습니다. 이 얘기는 Node.js 도 그런 단점이 있다는 얘기겠지요. 
 물론 페라리보다 더 빠른 것들이 많이 있다고해서 페라리가 후진차가 되는것은 아닙니다. 

 다시 돌아와서 , Reactor 패턴 보다 더 나은 방식은 무엇이냐 라고 한다면 Proactor 패턴이라고 있습니다.

 7.  Proactor 

  Proactor 패턴 을 우리말로 바꾸면 음 잘 떠오르질 않네요. Reactor 랑 비교해서 생각해보면 Reactor 는 어떤 이벤트가 날라오   면 그 이벤트에 해당하는 행동을 하는것이라고 말씀드렸습니다.
  Proactor 는 먼저 행동을 디스패치하고 , 그 행동에 따른 결과가 날라오게 됩니다. 
  예를들면, Reactor 는  "너 지금 버퍼에 메세지를 담을 수 있어"   라고 메세지가 날라오면 그때서야 메세지 담는 행동을 하고
  Proactor 는 먼저 메세지에 담는 행동을 요청하면  "버퍼에 메세지가 모두 담겼어"  라는 결과 메세지가 날라오게 됩니다.
  사실 이해하기가 쉽지 않긴할텐데요. 윈도우 네트워크 프로그래밍을 해보셨다면 Select 가 react , IOCP 가 proact 라고 생각하   시면 얼추 들어 맞습니다. 



8.  Actor 패턴으로 해보는 생산자 - 소비자 (by scala) 

* 참고로 스칼라 액터는 deprecated 되었습니다. 밑에 설명할 akka 를 대신 사용해야합니다. 

* 액터를 사용한다면 , 어떤한 객체 및 값의 상태관리는 단 하나의 액터에서 전담시키는 방향으로 해야합니다. 내가 무엇인가를 수정하고 싶으면 그 해당 액터에 메세지를 전달하라는 뜻이지요. 

* 액터는 위치투명성을 갖기때문에 , 네트워크 넘어에 있는 액터와도 동일한 방식으로 사용 할 수 있게됩니다.

* 여기서는 actor 안에 Thread.sleep 으로 강제로 멈춤을 시도했지만,  액터안에서 저런식으로 흐름을 막는것은 지양해야합니다.

굳이 흐름을 막고 싶으면, 다른 전용 액터를 만들어서 그쪽 메세지를 보내고 받는 타이밍을 이용해야합니다.

import scala.actors.Reactor


object Test {

  case class Stop()

  case class Get(from: Reactor[Any])

  case class Put(x: Int)


  class UnboundedBuffer extends Reactor[Any] {

    def act() {

      react {

        case Get(from) =>

          val consumer = from

          react {

            case Put(x) =>

              consumer ! x

              act()

          }

      }

    }

  }


  class Producer(buf: UnboundedBuffer) extends Reactor[Any] {

    

    def act() {

      var i = 0

      while (i < 10) {

        i += 1

        Thread.sleep(1000)

        buf ! Put(i)

      }

    }

  }


  class Consumer(buf: UnboundedBuffer) extends Reactor[Any] {

    

    def act() {

      Thread.sleep(1000)

      buf ! Get(this)

      react {

        case res =>

            println(res)

          act()

      }

    

    }

  }


  def main(args: Array[String]) {

    val parent = new Reactor[Any] {

      def act() {

        val buffer = new UnboundedBuffer

        buffer.start()

        val producer = new Producer(buffer)

        producer.start()

        val consumer = new Consumer(buffer)

        consumer.start()

       

      }

    }

    parent.start()

  }

}


9.  Akka 로 해보는 생산자 - 소비자 (by scala) 

object HelloWorld extends App {

    val system = ActorSystem("ProConSystem")

    val con = system.actorOf(Props[ConsumerActor])

    val pro =  system.actorOf(Props(new ProducerActor(con)))

    pro ! "start"


}


class ProducerActor (con: ActorRef) extends Actor {

  

  def receive = {

    case "start" => 

      var i = 0

      

      while (i < 10){

           con ! i  

           i+=1

           Thread.sleep(1000)

      }

  }

}


class ConsumerActor extends Actor {

  def receive = {

    case id : Integer =>

        println("ID : " + id)

  

  }

}


10.  Akka 로 해보는 생산자 - 소비자 (by java) 

  public static void main(String... args) throws Exception {


        final ActorSystem actorSystem = ActorSystem.create("procon-System");

        final ActorRef actorRef = actorSystem.actorOf(Props.create(ProducerActor.class), "ProducerActor");


        actorRef.tell(new Command("START"), null);

        Thread.sleep(10000);

        System.out.println("Actor System Shutdown Starting...");

        actorSystem.shutdown();

    }


public class ProducerActor extends UntypedActor {


    private final ActorRef conActor;

    public ProducerActor() {

        conActor = getContext().actorOf(Props.create(ConsumerActor.class), "ConsumerActor");

    }


    @Override

    public void onReceive(Object msg) throws Exception {


        if (msg instanceof Command) {

            final String data = ((Command) msg).getData();

            if (data.equals("START")){

            int i = 0;

           

            while (i < 10){

            conActor.tell(i, null);

            System.out.println("send: "  + i );

            i++;

            Thread.sleep(1000);

            }

           

            }


        } 

    }

}


public class ConsumerActor extends UntypedActor {


    @Override

    public void onReceive(Object msg) {

     System.out.println("Received Event: " + msg);

    }



}


12.  Vertx 로 해보는 생산자 - 소비자 (by java) 

public class VertxApp {


    public static void main(String[] args) {

        Vertx vertx = Vertx.vertx();

        vertx.deployVerticle(new ConsumerVerticle());

        vertx.deployVerticle(new ProducerVertcle());

    }


}


public class ProducerVertcle extends AbstractVerticle {

    

private static int id = 0; 

    

@Override

    public void start() {

        System.out.println("ProducerVertcle started!");

        while (true) {

            String packet = "No : " + nextId();

            vertx.eventBus().send("anAddress", packet);

        }

       

    }


    private static synchronized int nextId() {

        return id++;

    }


}


public class ConsumerVerticle extends AbstractVerticle {


    public void start() {

       

    vertx.eventBus().consumer("anAddress", message -> {

            System.out.println(" received message: " + message.body());

        });

    }

    

}


13. Akka 와 Vert.x 의 선택 이슈 

왜 우리는 클라우드 시스템에  Akka 를 사용하게 되었나?   참고 

제목이 "고찰" 이라니 먼가 있어보이긴한데.. 사실 별거 없습니다. ^^

어떤 개발자싸이트에 올라온 질문에 대한 저의 답변을 블로그에 정리해봅니다.


질문:


헤드퍼스트 책 보고 공부하다보니깐, 심플 팩토리보다 팩토리 메서드 패턴이 더 좋다는것같은데,

심플팩토리도 충분히 괜찮은것같은데 어떤점이 더 좋은건가요..? 책을 또 읽어봐도 이해가 잘 안되서...

답변 부탁드립니다~!


답변:


심플팩토리는 그냥 하나의 부모로부터 상속받은 객체중 하나를 클라이언트에게 던저주는것.

팩토리 메소드는 자신이 구현상속을 해야 하며,오버라이딩을 통해 객체 생성하는것!  입니다.


두개의 공통점은 어떤 객체를 생성해서 사용할지를 런타임시까지 미룬다는것이며

중요한 차이점은 팩토리가 상속받은거냐, 아니냐일 뿐입니다.

따라서  객체생성의 종류의 수 와는 무관합니다.

차라리 객체생성군으로 넘어가려면 abstract  factory  와 비교해야 맞는거 같습니다.


결국 둘중하나의 선택의 기로에서는 팩토리 자신의 부모가 있냐 없냐에 따른 장단점에 집중하면 폭을 좁힐수 있을거 같습니다

  

개인적으로 팩토리 메소드 패턴은 GOF 의 모든 패턴중 가장 이상하다고 보며 (  이름도 이상하고 부모레벨에 로직이 들어 간다는것도 별로고 , 유지보수등) , 대부분 심플 팩토리나 abstract  factory  로 대체 해야 한다고 볼 정도입니다. 이건 정말 고정적일꺼야 하는 최후의 코드정도 쓸까?



재질문:


일단 하마님이 하신 말씀중에 ,

심플팩토리는 그냥 하나의 부모로부터 상속받은 객체중 하나를 클라이언트에게 던저주는것.

이게 무슨말인지 이해가 잘 안가네요..ㅠ



재답변:


 "심플팩토리는 그냥 하나의 부모로부터 상속받은 객체중 하나를 클라이언트에게 던저주는것"

아래소스보시면  하나의 부모란 interfaceA 이고

상속받은 객체란 AProduct / BProduct 가 되는것이며, 

팩토리에서는 클라이언트한테 위의 두객체중 하나를 던저주고있습니다.  이해되실런지요?

 (http://corey.quickshiftconsulting.com/blog/first-post  <- UML 참조) 

class SimpleFactory{


     interfaceA create_A(String type){

          if(type == A){

               return new AProduct();   

           }
           else{

               return new BProduct();              
           }

     }

}


interfaceA{}

class AProduct implements interfaceA{}
class BProduct implements interfaceA{}


그 책은 먼가 팩토리메소드 패턴에 대해서 굉장히 긍정적으로 바라보고 있나 보군요 :-) 

부모레벨에 코드가 고착화 되있는데, 유연성이 좋아진다라..-.-a  편의성/합치성이 좋아진다거나 

abstract factory 라면 모를까..흠흠 퇴근길에 참고해 보도록 하겠습니다.


어쨋던 가장 중요한부분은 팩토리 자신이 부모로부터 상속받았느냐? 라는것입니다.

부모에 이미 구현된 코드들이 있으면, 하위 클래스들은 어떤부분에서  편하겠지요? 

그리고 어떤 순서등에 대한 약속이 부모에게 단단히 고정되어있다면 얻는 장점이 있겠지요? 

그런게 팩토리메소드의 장점 중 하나입니다.

결국 심플팩토리에 비한 장점이란 ~  "객체지향에서 상속의 장점은 무엇이냐"  와 마찬가지인겁니다.

마찬가지로 단점이란~~ " 객체지향에서 상속의 단점은 무엇이냐?"  와 같아지는겁니다.

순서


1. 소개 

2. Abstract Factory 패턴 

3. State 패턴

4. chain of Responsibility 패턴 

5. Adapter 패턴

6. Bridge 패턴 

 

디자인 패턴을 공부할때 가장 유념해야할 단어는 "의도" 이다.  절대 모양 (구조) 가 아니다.  그리고 구현함에 있어서 책 등에 나와 있는 모양 그대로 구현하려고 할 필요도 없다. 너무 잘 하려고 하다보면 아예 하지도 못할지도 모른다.  "의도" 만 확실히 이해한 다음에 구현을 이것저것 거침없이 하다보면.... 코딩에 대한 재미는 생겨날 것이다. 재미는 실력향상을 의미하기도 하고 ~  


JDBC 는 자바에서 정한 DB 와의 관계에 대한  행동 지침이다. 행동지침을 공통화하려면 어느 정도의 유사성이 있어야 한다. 세상에는 많은 자동차가 있지만 행동 지침은 비슷하다. 그러기에 운전자들이 다른 메이커의 자동차를 운전하더라도 어느정도는 쉽게 할수있지 않은가.. 마찬가지로  세상에는 많은  DB 가 있고.   각각의 DB 에서 데이터를 가져오는 방법(행동) 이  DB  마다 다르다면 따로 따로 공부를 해야하지만 , 행동지침이 정해져 있고, 모든 DB 가 그것에 맞춰서 개발을 한다면 , 개발자는 정해져있는 행동지침 ( JDBC ) 만 공부하면 된다.  


JDBC  를 디자인패턴과 함게 묶은 이유는 ,  일단 디자인 패턴을 이해하는 가장 효율적인 방법은 다른사람(특히 구루들) 이 잘 구현해놓은 코드(패턴)을 보고 감흥하는것인데 JDBC 를 구현한 코드들에는 몇몇개의 패턴이 뚜렷하게 녹아져 있다.  


5. Adapter  패턴




* Apdater 패턴은 위 전체 JDBC 패턴 모식도중 빨강 네모상자 안에서 이루어집니다.


어댑터하면 떠오르는것은 외국의 110v 콘센트에 우리나라에서 사용하고있는 가전제품 220v 짜리를 끼기위한  110v -> 220v 짜리 어댑터인데요.  여기서 변환하는것을 "어댑터" 라고 하고 , 변환당하는 110v 컨센트를 "어댑티" 라고 합니다. 따라서 위의 모식도에 Cursor 에 어댑티라고 써있고 , HAMAResultSet 에 어댑터라고 써있는것을 보아 , 클라이언트는  Cursor 를 직접적으로 사용하지 못하고 어댑터인  HAMAResultSet  라는 어댑터를 사용한다는것을 알수있습니다. 좀 더 깊이 들어가기 전에  정의를 내려봅니다.

클래스(어댑티)가 실제로는 지원하지 않는 인터페이스를 지원하는것 처럼 만든다.  (즉 클래스의 인터페이스를 클라이언트가 기대하는 인터페이스로 변환해 준다). 이를 통해 리팩토링 없이도 기존의 클래스를 이용해 새로운 클래스를 만들수있다.  - 실용주의 디자인 패턴 - 

(CURSOR 는 DB 에서 제공하는 객체이고java.sql.ResultSet 은 인터페이스 표준이다.) 



Adaptee : (클라이언트가) 원하는 인터페이스를 지원하지 않는 객체.

Target    : Adaptee 가 지원하길 바라는 인터페이스

Adpater : Adaptee 가 Target 인터페이스를 지원하는 것 처럼 보이게 해주는 클래스. 


결국 CURSOR 를 java.sql.ResultSet 처럼 보이게 하기위한 클래스를 만들자!!!  입니다.



어댑티를 어댑터의 부모클래스로 둘수도 있으며, 

어댑티를 어댑터의  연관(Aggregation)으로  관계를 맺을수도 있다.


// 어댑티가 부모클래스

class Adapter  extends Adpatee implements java.sql.ResultSet{

      void func(){   // ResultSet 인터페이스의 구현   

              orgFunc();  // 실제 작동하는 Adpatee 의 함수

     }

}


// 어댑티를 내부에 연관(Aggregation)

class Adapter  implements java.sql.ResultSet{

      Adpatee  adp;

      Adapter(Adaptee adp){

this.adp = adp;

}

     void func(){   // ResultSet 인터페이스의 구현   

             adp.orgFunc();  // 실제 작동하는 Adpatee 의 함수

     }

}


JDBC 나 보통 프레임워크에서 어댑터 패턴은 거의 쓰이질 않는다. 왜냐면 어댑터라는건 만들어져있는  라이브

러리를 사용할때 ( 유연성을 높히기 위해 )필요로 하기때문이다.

 

자!! 이제 실제 HAMAResultSet 클래스의 내부를 보자.

public lass HAMAResultSet implements java.sql.ResultSet {  // HAMAResultSet  는 어댑터이다. 

private final Cursor cursor;   // adpatee 역할을 하는 Cursor 를  가지고있다.

public HAMAResultSet(Cursor cursor){

     this.cursor = cursor;

}


public boolean next(){

     return cursor.advance();      // 클라이언트가 next 를 호출하면, advance() 로 변경시켜준다.

}                                                       110v 가  220v 로 바뀌듯이..


public int getInt(String columnName){     // HAMA DB 는 모든컬럼이 Sting 으로 저장되어있음.

String contents = getString(columnName);

return (contents ==  null) ? 0 : format.parse(contents).intValue();

}


public ResultSetMetaData getMetaData(){

return new HAMAResultSetMetaData(cursor);

}


HAMAResultSetMetaData (  이 버전은 모든 컬럼의 형이 VARCHAR 임) 

public class HAMAResultSetMetaData implements java.sql.ResultSetMetaData{

private final Cursor cursor;

public HAMAResultSetMetaData(Cursor cursor){

this.cursor = cursor;

      }

      public int getColumnType(int colum){

return java.sql.Types.VARCHAR;

      }

      public String getColumnTypeName(int column){

return "VARCHAR";

     }





레퍼런스 :  실용주의 디자인 패턴 (http://www.kangcom.com/sub/view.asp?sku=200607180006 )



순서


1. 소개 

2. Abstract Factory 패턴 

3. State 패턴

4. chain of Responsibility 패턴 

5. Adapter 패턴

6. Bridge 패턴 

 

디자인 패턴을 공부할때 가장 유념해야할 단어는 "의도" 이다.  절대 모양 (구조) 가 아니다.  그리고 구현함에 있어서 책 등에 나와 있는 모양 그대로 구현하려고 할 필요도 없다. 너무 잘 하려고 하다보면 아예 하지도 못할지도 모른다.  "의도" 만 확실히 이해한 다음에 구현을 이것저것 거침없이 하다보면.... 코딩에 대한 재미는 생겨날 것이다. 재미는 실력향상을 의미하기도 하고 ~  


JDBC 는 자바에서 정한 DB 와의 관계에 대한  행동 지침이다. 행동지침을 공통화하려면 어느 정도의 유사성이 있어야 한다. 세상에는 많은 자동차가 있지만 행동 지침은 비슷하다. 그러기에 운전자들이 다른 메이커의 자동차를 운전하더라도 어느정도는 쉽게 할수있지 않은가.. 마찬가지로  세상에는 많은  DB 가 있고.   각각의 DB 에서 데이터를 가져오는 방법(행동) 이  DB  마다 다르다면 따로 따로 공부를 해야하지만 , 행동지침이 정해져 있고, 모든 DB 가 그것에 맞춰서 개발을 한다면 , 개발자는 정해져있는 행동지침 ( JDBC ) 만 공부하면 된다.  


JDBC  를 디자인패턴과 함게 묶은 이유는 ,  일단 디자인 패턴을 이해하는 가장 효율적인 방법은 다른사람(특히 구루들) 이 잘 구현해놓은 코드(패턴)을 보고 감흥하는것인데 JDBC 를 구현한 코드들에는 몇몇개의 패턴이 뚜렷하게 녹아져 있다.  


4. Chain of Responsibility 패턴





* chain of responsibility 패턴은 위 전체 JDBC 패턴 모식도중 빨강 네모상자 안에서 이루어집니다.



1. DriverManager 와 Driver 와의 관계 


먼저 DriverManager 와 Driver 에대해서  알아보겠습니다.

보통 JDBC 프로그래밍할때 아래처럼 하는데요

static final String JDBC_DRIVER = "com.hama.jdbc.HAMADriver";  
static final String DB_URL = "jdbc:hama://localhost/EMP";

   
      // 1. JDBC driver 등록 
      Class.forName(JDBC_DRIVER);

      // 2. DB 로의 connection
      conn = DriverManager.getConnection(DB_URL,USER,PASS);

      // 3. Statement 를 통한 Query 명령 
      stmt = conn.createStatement();
      
      String sql = "SELECT id, age FROM Employees";
      
      // 4. result set 으로 부터 데이터 가져오

      ResultSet rs = stmt.executeQuery(sql);

      // 5. 커서를 통해 한 로우씩
      while(rs.next()){

         // 6. 한 컬럼씩 가져오기 
         int id  = rs.getInt("id");
         int age = rs.getInt("age");
       

      }
                             (코드 -1)


위의 소스 예제에서 

// 1. JDBC driver 등록 Class.forName(com.hama.jdbc.HAMADriver); // JDBC_DRIVER -> com.hama.jdbc.HAMADriver // 2. DB 로의 connection conn = DriverManager.getConnection(DB_URL,USER,PASS);


이 부분에 집중해 보도록 하겠습니다.

1 번에서 com.hama.jdbc.HAMADriver 를 자바 리플렉션을 이용하여 메모리로 올리고 있습니다.

JDBC 드라이버를 메모리로 올릴때 어떤 행동을 할가요? 소스를 살펴보겠습니다.


public class HAMADriver implements java.sql.Driver {

private HAMAConnection connection;

static{

java.sql.DriverManager.registerDriver(new HAMADriver() );

}

..

public boolean acceptsURL(Stirng url){

return url.startWith("jdbc:hama");

}

}


(코드 -2)


HAMADriver  라는 클래스 파일이 하드에서 메모리로 Class.forName 을 통해서 메모리로 끌어 올려

질때  위의 (코드-2) 의 빨강색 코드가 호출됩니다. 코드를 보시면 DriverManager 에 스스로의 인스턴

스를 생성해 서 등록시키고 있습니다. 


그렇습니다~!!!  그렇기 때문에 아래의 코드가 성립이 되는것이지요.

conn = DriverManager.getConnection(DB_URL,USER,PASS);


위의 코드에서 DriverManager 가 어떻게 내가 원하는 DB 에 접속할수 있는 Connection 을 리턴할수있었겠습니까?  Class.forName 을 통해서 HAMADriver 가 DriverManager 에 등록되 있기때문에 가능한것입니다.


자!! 여기서 질문 !~


Class.forName 에  위의 HAMA 데이터베이스에 접근할수있는 Driver 에 추가적으로

MySql 에 접근할수있는 드라이버를  Class.forName("com.mysql.jdbc.Driver");  요렇게 

아래에 추가했다고 칩시다.


이럴때 conn = DriverManager.getConnection(DB_URL,USER,PASS);는 어떤 DB 에 접속할수있는 드라이버의 Connection 인터페이스를 리턴해줄까요??


자!! 이럴때 필요한게 Chain of Responsibility 입니다~ 이름에 "의도" 가 그대로 뭍어나 있습니다.

"체인을 돌면서 해당 행위에 책임감있는 놈에게 일을 시키겠다." 뭐 이쯤 이겠지요.


그럼 책임감 있는놈을 어떻게 찾을것인가?? 

위의 그림 2의 파랑색 코드를 보시면 


public boolean acceptsURL(Stirng url){

return url.startWith("jdbc:hama");

}


요렇게 되있습니다.  그렇습니다. DB 에 접속하기위한 URL 이 어떻게 시작되냐에 따라서 선택된다는 야그~

무지 간단하죠??  DriverManager 는 자신한테 등록되있는 드라이버를 for 문 돌면서 각 드라이버의 

acceptsURL 를 호출해서 ok 떨어진놈을 찾는거죠. 


실제 오라클(썬?)에서 구현한 java.sql.DriverManager 코드를 보면



public static Driver getDriver(String url)

  258           throws SQLException {

                 .... 생략 ...

  268           for (DriverInfo aDriver : registeredDrivers) {   //  (1)  for 문 돌면서 

  269               // If the caller does not have permission to load the driver then

  270               // skip it.

  271               if(isDriverAllowed(aDriver.driver, callerCL)) {

  272                   try {

  273                       if(aDriver.driver.acceptsURL(url)) {  //  (2) 적합한 드라이버를 찾는다.

  274                           // Success!

  275                           println("getDriver returning " + aDriver.driver.getClass().getName());

  276                       return (aDriver.driver);

  277                       }

  278   

  279                   } catch(SQLException sqe) {

  280                       // Drop through and try the next driver.

  281                   }

  282               } else {

  283                   println("    skipping: " + aDriver.driver.getClass().getName());

  284               }

  285   

  286           }

              ......

  290       }



위 처럼 (1) 등록된 드라이버를 순회하면서 (2) 적합한 드라이버를 찾고 있습니다.


jdbc:hama:// 로 시작되는 DB_URL 일 경우는 HAMADriver 가 가지고있는 Connection 을 

리턴해주고  Postgresql 은 jdbc:postgresql 일테고  MySQL 은 jdbc:mysql 입니다.


chain of responsibility 패턴 은 이렇게 끝났습니다.  너무 간단해서 허무할 정도네요 ^^



너무 간단하게 끝나서 , 각자 판단할꺼리를 던저 보도록 하겠습니다. 댓글로 각자의 생각을 달아주시

면 고맙겠는데요. 웹 개발자분들은 대부분 아시는 서블릿 필터에 대한 이야기입니다. 몇몇 책이나 사들이 

필터를 얘기할때  "데코레이터 패턴" 이라 고 말하기도 하는데요  토비의 스프링인가 ? 그 책에서도 언급된거 같

은데 잘 기억은 안납니다만..~  (다이나믹 프록시 설명할때 나왔나 하기도 하네요) 

참고로 데코레이터 패턴의 전형적인 사용예는 자바의 IO 가 있지요.   


자~!!!   서블릿 필터는 진짜 데코레이터 패턴에 가까울까요?


그럼 이 시리즈에서 강조한 각 패턴의 "의도" 를 써 봅니다. 너무중요해서 빨강색 쫙~~

( 제가 정리한건 아니고, "GOF 책" 과 실용주의 디자인 패턴" 책 참고)

Chain of Responsibility 패턴 

일련의 객체 집합이 잘 정의된 통로(chain) 을 통해 메세지를 전달함으로써 하나 이상의 객체에 메시지를 처리할수 있는 기회를 준다.

주어진 메세지에 가장 적합한 객체가 메시지를 처리한다. 또한 하나 이상의 객체가 메시지를 처리하는것이 가능하다.


GOF :  요청을 처리할수 있는 기회를 하나 이상의 객체에게 부여함으로써 요청하는 객체와 처리하는 객체 사이의 결합도를 없애려는 것이다. 요청을 해결할 객체를 만날 때 까지 객체 고리를 따라서 요청을 전달한다.


Decorator 패턴 

런타임시 객체에새로운 능력을 추가하거나 객체의 행위를 변화시킨다. Decorator 는 상속 대신 합성을 사용하여 클래스 계층 구조를 단순화 시킬수 있다. 

GOF: 객체에 동적으로 책임을 추가할수 있게한다. 데코레이터 패턴은 기능의 유연한 확장을 위해 상속대신 사용할수 있는 방법이다.



자 어떠십니까?? 

필터는 브라우저에서 요청에 대해서 파이프라인을 가지면서 , 어떤 선행 처리를 합니다.

그 처리를 단계별로 거치면서 중첩적인 ( 꾸미는) 처리를 하는걸까요?

파이프라인중 일부분을 선택해서 처리를 하는걸까요? 

필터는 기능의 유연한 확장일까요? 


뭐에 더 가까울까요? 선택은 여러분의 몫입니다. 저는 결정했지만 말하지는 않겠습니다. 


p.s 

제 3의 패턴이다. 라고 말할수도있습니다.  이것 저것 생각해보는 시간을 갖으면  좋겠습니다.

Core J2EE Patterns: Intercepting Filter Pattern  :

http://www.informit.com/articles/article.aspx?p=1398619&seqNum=3





레퍼런스 :  실용주의 디자인 패턴 (http://www.kangcom.com/sub/view.asp?sku=200607180006 )




순서


1. 소개 

2. Abstract Factory 패턴 

3. State 패턴

4. chain of Responsibility 패턴 

5. Adapter 패턴

6. Bridge 패턴 

 

http://brad2014.tistory.com/344  <---  1,2편 (abstract factory 패턴) 은 여기링크.


3. State 패턴



* JDBC state 패턴은 위 전체 JDBC 패턴 모식도중 빨강 네모상자 안에서만 이루어집니다.

JDBC state 패턴을 공부하기 전에 잠시 JDBC 의 트랜잭션을 살펴보도록합시다.  


 JDBC 트랜잭션 기초 

JDBC API의 Connection 객체는 트랜잭션을 위하여 commit() 메소드와 rollback() 메소드를 제공한다.

 Connection 객체에는 setAutoCommit  이란 메소드가 있는데 기본값이 true 로 설정이 되어 있다. 

하나의 쿼리당 자동시작~자동커밋이 일어난다는 이야기이다그러나  여러 개의 쿼리 문장이 하나의 작업으로 수행되어야
할경우에  각각의 문장이 자동으로 작동되지 못하게 해야한다.
오토커밋이 자동으로 작동되지 못하게 하려면 
setAutoCommit(false); 
로 지정해야 한다.


자 그럼 begin() 은 어딨느냐?? 


AutoCommit = true 일경우



암시적으로 각각의 액션시 (각각 SQL 문에서)  자동으로 BEGIN()



AutoCommit = false  일경우



이건 좀 희안하다. setAutoCommit(false) 를 호출하여 오토커밋코드를 끄면  시스템은 이 호출과 동시에 


BEGIN 요청을 하게 된다. 이후의 commit() 과 rollback() 함수안에서  각각 작업을 한후에 마지막


에  begin() 을 요청하며 함수가 끝난다. 



그 밖에 setAutoCommit 을 통해 모드를 바꾸면  동시에 commit() 1회 발생한다.




코드1) 

 Connection conn = DriverManager.getConnection(.....); // connection 객체생성

conn.setAutoCommit(false); // 오토커밋 꺼버리고  

Statement stmt = conn.createStatement(); // statement 객체생성 String SQL = "INSERT INTO Employees VALUES (106, 20, 'Rita', 'Tez')";  

stmt.executeUpdate(SQL); conn.commit(); // 수동 커밋 ㄱㄱ 씽~




위의 JDBC 소개를 왜 했냐면 



 JDBC 에는 오토커밋이라는 상태를 가질것이냐, 안가질것이냐~  2가지 상태(STATE) 가 있다라는것을 말하고자 한것이다.







쉽게 설명한 State 패턴~  




state 패턴은 "상태에 기반을 둔 행위를 클래스로 만들어 놓은것이다.  즉 상태별 클래스를 만든것"



무슨이야기인지 실생활에 맞추어 설명해보면 


보통 사람들은   밥을 먹고, 길을 걷고 , TV 를 보는 행위를 한다. <-- 일반적인 행동..(함수) 

하지만 상태에 따라서 조금씩 달라질수있는데,


몸이 굉장히 아픈 상태에서는 = 죽을 천천히 먹을것이며

몸이 가뿐하며 돈이 많은 상태에서는 = 소고기를 폭풍흡입할것이다.


 ( 이런것은 자동차게임같은것에서 '달리다' 는 같지만 부스터를 사용했다처럼 상태를 갖는곳에 모두 사용될수있다)


"먹는다" 라는 행위를 상태 ( 아프냐, 안아프냐 ) 에따라서 다르게 행동하는 함수를 만들자는것인데 ..


사람이라는 클래스가 있다고 해보고 , 다음 코드를 보자.


class Person {

    public eat(){

    }

}


아래처럼  상태에 따라 다른 코드를  모두 클래스 내부에 넣는다면 


class Person {

    boolean state = 0;  // true 은 멀쩡함 , false 은 아픔 

   

    public eat(){

       if(state == true){

           // 폭풍흡입

       }

       else {

           // 겨우 목구멍에 넘김

       }

    }

 

}


이렇게 될것이다.  보통 이렇게도 많이 짠다. 이게 가독성 및 유지보수에 더 좋을수도있다.디자인패턴을 써서 퍼트리는것보다


(무조건적인 패턴 남발이 좋은건 아니다. 잘 판단해서 사용하자) 


State 패턴에서는 아래와 같이 짜는데 


위에 언급했듯이 state 패턴은 "상태에 기반을 둔 행위를 클래스로 만들어 놓은것이다. "


따라서 상태 ( 아프냐, 안아프냐를 ) 를 클래스로 만들고 상태에 기반을 둔 행위( eat , walk ) 를 함수로 만들어보자.


interface State{

  

    public void eat();

    public void walk();


}


class PainState implements Eat{


public void eat(){

     // 죽먹기

}


public void walk(){

...

}


}


class NormalState implements Eat{


public vodi eat(){

    // 고기 폭풍흡입 

}

public void walk(){

...

}


}


이렇게 상태에 따라서 먹는 클래스를 만들게 되는데, 전체 코드를 대략 만들어보면~(익명내부클래스로 인터페이스 상속기법)


class Person {

   

    private interface BodyState {   // 상태마다 공통으로 가져야할 행위를 나타내는 인터페이스

void eat();

void walk();

void setBodyState(boolean state);

    }


    private BodyState normal = new BodyState() {    // 멀쩡할때의 행위를 구현한 클래스


public void eat(){ .... 잘먹는다 ....}

public void walk(){ .... 잘 걷는다 ....}

public void setBodyState(boolean state){

if(state == false){

currentBodyState = pain;   // 상태 바꾸기

}

}

   }


    private BodyState pain = new BodyState() {    // 아플때  행위를 구현한 클래스


public void eat(){ .... 못 먹는다 ....}

public void walk(){ .... 못 걷는다 ....}

public void setBodyState(boolean state){

if(state == true){

currentBodyState = normal;   // 상태 바꾸기

}

}

   }



    public eat(){

        currentBodyState.eat();   // 이렇게 if 혹은 switch 문을 제거하였다. 현재 상태에 따라서 행동하게됨.

    }


public walk(){

currentBodyState.walk();   // 이렇게 if 혹은 switch 문을 제거하였다. 현재 상태에 따라서 행동하게됨.

}


    public setBodyState(boolean state){   // 외부에서 상태를 바꿔줌 ( 이 예에서는 하느님이 바꾸어주시겠지요 ^^) 

         currentBodyState.setBodyState(state); 

    }

 

    private State  currentBodyState = normal;

}



자 간단한 State 패턴 활용예를 살펴보았다. 이제 본격적으로 JDBC 에서 어떻게 활용되는지 살펴보자.


일단 위의 예를 이해했다면 JDBC 예는 누워서 떡먹기이다.





 JDBC 에서의 State 패턴 



 JDBC 에는 오토커밋이라는 상태를 가질것이냐, 안가질것이냐~  2가지 상태(STATE) 가 있다. 를 먼저 상기해보자.


 JDBC 의 connection 클래스 내부에서 저 오토커밋의 상태에 따라서 rollback, commit 가 다르게 일어난다.


다음 클래스 다어그램을 살펴보자.



 

               (그림 1) 


 이것은 (그림1) 위의 일반적인 State 패턴 예에서의 클래스 다이어그램이다.  사람 클래스가 상태클래스를 포함하고 있으며 


상태클래스는 Normal , Pain 2가지를 가지고있다는것을 확인하자. 





                                                                      (그림 2)

      

    이것은 JDBC 에서의 State 패턴인데, 위의 그림 1 과 똑같다. JDBCConnection 은 AutoCommitBehavior 을 구현한 


         concrete State 들에 따라서 행동을 하게된다.


코드를 살펴보면 이해가 더 빠를것이다.


다시 JDBC 의 수동커밋에 대한 예제 코드를 다시 보자


Connection conn = DriverManager.getConnection(.....); // connection 객체생성

conn.setAutoCommit(false); // 오토커밋 꺼버리고  

Statement stmt = conn.createStatement(); // statement 객체생성 String SQL = "INSERT INTO Employees VALUES (106, 20, 'Rita', 'Tez')";  

stmt.executeUpdate(SQL); conn.commit(); // 수동 커밋 ㄱㄱ 씽~


 conn.setAutoCommit(false)이 부분이 어떻게 구현되었는지 JDBCConnection 코드를 통해 살펴보자.


        

 public class JDBCConnection extends java.sql.Connection {


     public void commit() {

         autoCommitState.commit();    // 오토커밋 상태에 따라서  commit 을 처리한다.

     }


     public void rollback(){

autoCommitState.rollback();    // 오토커밋 상태에 따라서  rollback을 처리한다.

    }


    publci void setAutoCommit( boolean enable ){      // 이게 위의 예제에서  conn.setAutoCommit(false)이다.

autoCommitState.setAutoCommit(enable);

    }


    ....


    // 내부에 상태 인터페이스 및 내부익명클래스로 상태 클래스 2개를  만들어준다.

    private interface AutoCommitBehavior{

       void close();

       void commit();

       void rollback();

       void setAutoCommit( boolean enable );

    }

   

    private AutoCommitBehavior  enabled = new AutoCommitBehavior{   

   void close() { /* 아무것도 안한다 */ }

       void commit() { /* 아무것도 안한다 */ }

       void rollback() { /* 아무것도 안한다 */ }

       void setAutoCommit( boolean enable ){

if(enable == false){

   database.begin();

   autoCommitState = disabled;

}

       }


   }


   private AutoCommitBehavior  disabled = new AutoCommitBehavior{   

   void close() { 

       database.commit();

   }

       void commit() {

           database.commit();

           database.begin();

        }

       void rollback() 

           database.rollback();

           database.begin();

        }

       void setAutoCommit( boolean enable ){

if(enable == true){

   database.begin();

   autoCommitState = enabled;

}

       }


   }


    private AutoCommitBehavior  autoCommitState = enabled;

 }


코드를 유심히 살펴보면, 사실 State 패턴에 관한건 금방 이해할것이다.  autoCommitState 의 상태에 따라서 


rollback, commit 등이 이루어지고있다.패턴과 상관없이 기능적으로 코드 몇군데를 보면 보통 우리는 원격의 


DB에 접근을 하기때문에 dabatase 로 RPC 콜을 날려줄 것지만, 1부에 말했다시피, 임베디드DB 이기때문에 


database.commit() 처럼 직접 호출해주는 부분이 있다.


그 밖에 헤깔릴수있는 부분은 , 


왜 database.begin(); 이 commit / rollback 함수 안에 있냐 ? 


왜 setAutoCommit 을 호출하면 database.begin(); 을 해주냐? 


인데, 이것이 궁금하다면  이 게시물 가장 처음에 있는  JDBC 트랜잭션 기초를 다시 읽어보자~


다음엔 Adapter 패턴에 대해서 알아보자.



레퍼런스 :  실용주의 디자인 패턴 (http://www.kangcom.com/sub/view.asp?sku=200607180006)




p.s  해당글에 대한 질문이 올라온것에 대한 답변 



질문: 



State 패턴의 의도가 상태별 클래스를 만들어두는 것이라고 하셨는데,

위 예제처럼 내부에서 사용보다 외부로 빼서 확장성있게도 사용이 이루어지나요?


답변:


보통 외부로 빼서 구현합니다. 본문의 예가 "일반적" 이라고 보기 힘듭니다.

자바라는 언어가 저렇게 내부클래스를 지원하니 그 기능을 십분 발휘한것일뿐이에요 ^^


사실 디자인패턴을 쓰면  소스를 너무 퍼트려 놓는데, 때로는 그게 가독성에 지장을 줄수도

있지 않겠습니까. (if , switch 문이 전체맥락보기 편할때가 많은거 같습니다. 소스수정도 IDE 가 워낙 강력해

서 뭐... 개인적인 생각입니다.) 


따라서 자바 언어의 기능을 살려서,  유연함을 주되,  소스를 찾으러 멀리가지 않게 하였다. 정도로 보시면 될거 같습니다. (클래스 내부에 있는 인터페이스를 보고 전체 상태별 기능을 한눈에 보게하자)


아래 코드처럼 외부 클래스를 컴포지션해서 할수도 있습니다. (DI  로도 )

class people {

    state n_state = new normalState;

    state p_state = new painState;

    state s;

    eat(){

         s.eat();

    }

    setState(int i){

         if(i ==  0)

         	s = n_state;

         else (i == 1)

                s = p_state;

    }

}

interface state{

   eat();

}

class normalState implements state{

     eat(){ ....}

}



class painState implements state{

     eat(){ ....}

}


원하는 답변인지 잘 모르겠네요 ;;;    

혹 다른 생각하시는 구현이 있으면 보여주시면 의사소통이 원할할듯 합니다. 감사합니다.


디자인 패턴을 공부할때 가장 유념해야할 단어는 "의도" 이다.  절대 모양 (구조) 가 아니다.  그리고 구현함에 있어서 책 등에 나와 있는 모양 그대로 구현하려고 할 필요도 없다. 너무 잘 하려고 하다보면 아예 하지도 못할지도 모른다.  "의도" 만 확실히 이해한 다음에 구현을 이것저것 거침없이 하다보면.... 코딩에 대한 재미는 생겨날 것이다. 재미는 실력향상을 의미하기도 하고 ~  

JDBC 는 자바에서 정한 DB 와의 관계에 대한  행동 지침이다. 행동지침을 공통화하려면 어느 정도의 유사성이 있어야 한다. 세상에는 많은 자동차가 있지만 행동 지침은 비슷하다. 그러기에 운전자들이 다른 메이커의 자동차를 운전하더라도 어느정도는 쉽게 할수있지 않은가.. 마찬가지로  세상에는 많은  DB 가 있고.   각각의 DB 에서 데이터를 가져오는 방법(행동) 이  DB  마다 다르다면 따로 따로 공부를 해야하지만 , 행동지침이 정해져 있고, 모든 DB 가 그것에 맞춰서 개발을 한다면 , 개발자는 정해져있는 행동지침 ( JDBC ) 만 공부하면 된다.  

JDBC  를 디자인패턴과 함게 묶은 이유는 ,  일단 디자인 패턴을 이해하는 가장 효율적인 방법은 다른사람(특히 구루들) 이 잘 구현해놓은 코드(패턴)을 보고 감흥하는것인데 JDBC 를 구현한 코드들에는 몇몇개의 패턴이 뚜렷하게 녹아져 있다.  

순서

1. 소개 

2. Abstract Factory 패턴 

3. State 패턴

4. Adapter 패턴

5. Bridge 패턴 

6. chain of Responsibility 패턴

 



1. 소개 

이 글은 JDBC 에 녹아져있는 패턴들을 살펴볼것인데 , 그 전에  DB 에 대해서 잠시 살펴보자.

가. DB  

A 군은  학교/학원에서 자바 / 객체지향 프로그래밍을 배웠습니다.  무엇을 만들어 볼까 생각하다가 "오키" 에 물어보니 DB 를 만들어 보라는 댓글을 보고 DB 에 대해 곰곰히 생각해 보았습니다.  데이터를 저장한다라..   - .-a
그래 결심했어!! 이름은 HAMASQL 이라고 짓고 . 
일단 데이타베이스는 '폴더' 라고 하자.  그리고 테이블을 '파일' 이라고 하자.!! 그리고 파일에 한줄씩 콤마(,) 로 나누어서 컬럼을 구분하고 , 각각의 데이터를 문자열로 넣자.  이렇게 되면 데이타베이스 하나에 테이블이 여러개 들어갈것이다.

PSEUDO  코드 

class HAMADatabase {

   public  createDatabase(String db_path , String db_name) {

             makeDirectory(db_pathdb_name);    // db_path위치에 , db_name이라는 폴더를 만들어라.

   }

}

class Table {

  String [] column;

   public  createTable(String db_path, String name, String column){

makeFile(db_path, name);       // db_path 데이타베이스에 name 이라는 테이블( 파일 ) 을 만들고,               

    this.column = column.split(',');   // 컬럼은 column 문자열에서 , 로 토큰을 나누자.      

   }

   public insertRow(...) ...

   public deleteRow(...) ...

   public updateRow(...) ...

   public select(...) ...

}

class Cursor {

    ...

}

                                                                     (코드 -1)

자 일단 데이터를 저장하는 방법과 클래스를 작성했고 (-.-)  
이제 사용자와의 커뮤니케이션을 해야하는데 SQL  언어를 통해서 명령을 받아드려야지.
SQL 엔진은 인터프리터 패턴으로 개발하자. (http://brad2014.tistory.com/200자 이제 SQL 엔진도 개발했고 

(-.-;;  이 글은 JDBC  디자인패턴이 목적이니 양해를..이제 JDBC 인터페이스만 구현하면 될것 같다.  !!!

나. JDBC 

                             (그림 1: http://docstore.mik.ua/orelly/java-ent/jenut/ch18_01.htm 이미지참조)

위의 이미지는 java.sql 패캐지에 포함되어 있는 개별 JDBC 드라이버를 만들기 위한 뼈대(인터페이스) 입니다.우리는 저기에서 Connection / Driver / PreparedStatement/ ResultSet / 등을 구현하면서 패턴이 어떤식으로 적용되는지 자세하게 살펴볼 것입니다.

먼저 인터페이스 각각을 간략하게 설명하자면 java.sql 패키지는 전체 JDBC API 를 포함합니다.  SQL 구문을 데이타베이스에 보내며 그것에 따른 결과셋을 가져오는데  Driver 인터페이스는 특정 데이타베이스를 위한 JDBC 구현을 하게되며 , Statement 등등은 SQL 구문을 실행하는데 사용됩니다.  ResultSet 은 데이타베이스에 의해 SQL 명령이 실행되어진후에 결과 데이터 를 담게되며, 커서등을 통해서 최종 사용자가 데이터를 사용하게됩니다. ResultSetMetaData 는 ResultSet 의 메타데이터를 담당하게되며 DatabaseMetaData 는 데이타베이스 전체에 대한 메타데이터를 제공합니다.

다. JDBC 구현 전체 클래스 다이어그램 및 패턴 

                                       (그림 2) 

이것은 "실용주의 디자인 패턴 그림 4-18" 을 조금 각색한 그림이다.힘들게 그리고 이렇게 올려두니 매우 뿌듯하다. ^^
간략히 소개하자면

1. 연한주황색은 인터페이스이다. 이것은 java.sql 패키지에서 기본적으로 제공하는것으로 "공통 행위 지침" 이다.  
2. 검정색 네모칸은 우리가 구현한 클래스를 나타낸다. 인터페이스를 상속받았다. (  ◁-----  : 인터페이스 상속
3. 색깔이 있는 동그란원은 특정 패턴을 나타낸다. 그것과 연결된 점선은 해당 패턴에 포함된 클래스들이다. 
4. <----  점선 화살표는 의존관계를 나타낸다. DriverManager 는 HAMADriver / HAMADriver 는 HAMAConnection 의존한다.
5. ◆-----  다이아몬트 직선은복합연관(컴포지트) 관계이며, ◇------ 빈다이아몬드 직선은 집합연관 관계이다.

   중요한 차이점은 복합연관 관계는 좀더 강한 결합관계를 가진다. 즉  HAMAResultSet 이 소멸되면 Cursor 도 소멸한다. 

위의 JDBC 구현을  사용하는 예 살펴보자. 

static final String JDBC_DRIVER = "com.hama.jdbc.HAMADriver"; static final String DB_URL = "jdbc:hama://localhost/EMP"; // 1. JDBC driver 등록 Class.forName(JDBC_DRIVER); // 2. DB 로의 connection conn = DriverManager.getConnection(DB_URL,USER,PASS); // 3. Statement 를 통한 Query 명령 stmt = conn.createStatement(); String sql = "SELECT id, age FROM Employees";   // 4. result set 으로 부터 데이터 가져오

ResultSet rs = stmt.executeQuery(sql); // 5. 커서를 통해 한 로우씩 while(rs.next()){ // 6. 한 컬럼씩 가져오기 int id = rs.getInt("id"); int age = rs.getInt("age"); } (코드 -2)                    

MySQL 을 사용할때  jdbc:mysql  를 사용한것처럼 jdbc:hama 를 사용하였으며 , DriverManager 는 저것을 통해 DB를 구분한다.  나의 디비를 접속하기위해 새로만든 com.hama.jdbc.HAMADriver  를 사용한다. MySql 을 사용한다면 

com.mysql.jdbc.Driver 를 사용해야할것이다.

1. 내가 만든 JDBC 드라이버 클래스를 로딩한다. (로딩함과 동시에 내부에서 HAMADriver 인스턴스를 생성한후에 DriverManager 에게 넘겨준다.)
2.  DriverManager 는 1번에서 얻게된 HAMADriver  를 통해서 디비에 접속한후에 HAMAConnection 객체를 얻는다. 
3. HAMAConnection 클래스를 통해서 HAMAStatement 객체를 생성한다. 
4. HAMAStatement  클래스를 통해서 디비에 쿼리명령을 실행후 ResultSet 로 테이블의 일부분의 데이터를 가져온다. 
5. ResultSet 안의 커서를 통해서 ROW 별로 접근한다.
6. 하나의 컬럼에 접근한다. 

자 JDBC 에 대해 전반적인 사항을 알아보았다. 이제 진짜 패턴에 대해서 집중해서 살펴보자. 



2. Abstract Factory 패턴 

JDBC 에서 사용하는 패턴중 가장 눈에 뛰는 패턴은 "Abstract Factory" 이다.  위의 그림 2를 보면 Abstract Factory 으로 빼곡한 모습을 볼수있다. 한부분을 떼어내 보면 아래 그림과 같다.

                    (그림 3) 
공장역활을 하는것은 Driver 이며 , 공장이 만들어 내는것은 Connection 이다.
JDBC 에서 특이한것은 생산품이 다시 공장역할을 한다는것인데 Connection  이 생산품에서 
공장역할을 하게되면 , Connection   의 생산품은 ?  그렇다 Statement 클래스가 된다. 

Abstract Factory 의 모호함

패턴중에 가장 이름에 현혹되지 말아야하는 패턴이 Abstract Factory  라고 생각한다. 서두에도 말했다시피 패턴은 "의도" 가 가장중요한데 , 이 패턴은 이름으로 의도를 느끼기에는 좀 힘든면이 있다. Abstract Factory 는 의도를 전혀 나타내고 있지 못한다. Abstract Factory 는 단지 Abstract Factory 패턴에서 이루어지고있는 "의도" 에 하나의 부품일 뿐이다.  Abstract Factory 는 Bridge 패턴과 함께 , 패턴이라기보다는 아키텍처를 나타낸다고 볼만큼 규모가 있는 느낌의 패턴이다.  그래서 실제로 일반개발자가 이 패턴을 사용할일은 그다지 많지 않다. 

팩토리!!! 란 무엇인가?  

팩토리는 무엇인가를 만들어 주는 공장이다. 실생활에서 우리는 공장에 집중하지 않는다. 공장에서 만들어주는 "어떤것"에 더 친숙하고 집중하게 되는데 Abstract Factory 패턴에서도 마찬가지이다. Abstract Factory 는 무엇인가를 만들어주는것이며, 실제 만들어지는것이 우리가 집중해야할 "어떤것" 이다.  이름에서 느껴지듯이 Abstract Factory  는 실제 무엇을 만들수있는 공장이 아니다. 어떤 방식으로 만들라는 "행동지침" 만 정의하며 실제 공장은 concrete Factory 라고 한다.  위의 JDBC 에서는 그림 1을 보자. Abstract Factoryjdbc.sql.Driver 인터페이스이며  Concrete Factory 는 그것을 구현상속한 HAMADriver 가 된다.  팩토리는 공장이며, 위에 말한것처럼 "어떤것" 을 만들어주는 역할을 한다고 했다. 그럼 HAMADriver  라는 Concrete Factory 가 만들어주는 "어떤것" "상품" 은 무엇이 될까?

그것은 바로 jdbc.sql.Connection 을 상속받은 HAMAConnection 이 바로 Concrete Product 가 된다.   

코드로 보면 (PSEUDO

// abstract factory   :  필요한 생산품 ( Connection ) 을 생산하는 추상공장 


interface Driver {

public Connection connect(String url);

}


// concrete factory  :  실제 필요한 생산품 ( HAMAConnection or MySQLConnection ) 을 생산하는 실제공장 

class HAMADriver implements java.sql.Driver{


public Connection connect(String url) {

return new HAMAConnection(url);

}


}

// abstract product  : 생산품의 공통  행동 양식을 나타내는 인터페이스 ( Connection )

interface Connection {

public close();

public commit();

public createStatement();

}


// concrete Product  : 구체적인 생산품  ( HAMAConnection or MySQLConnection  )

class HAMAConnection implements java.sql.Connection{


   private HAMADatabase database;

   public HAMAConnection(Sting url) {

database = new Database(url);

   }

   public close() {

      ...

   }

   public commit() {

     . ..

   }

   public Statement createStatement() {

return new HAMAStatement(database);

   }


}

이걸 왜 쓰는건가?  

실생활에서 공장에는 여러가지의 공장(자동차, 핸드폰) 이 있을수 있다. 

=> JDBC 를 구현하는 DB 에는 여러가지가 있다.

생산되는 제품(핸드폰) 은 회사마다 내부작동법 (아이폰, 갤럭시 ) 이 다양할수있다. 

=> Connection 하는 방법, 데이터를 가져오는 방법은 DB 마다 다르다. 

이처럼 공장과 생산품을 클래스로 분류시켜놓으면 새로운 공장 / 새로운 생산품을 기존의 시스템에 끼워넣을때 

기존의 코드는 전혀 건드리지 않아도 된다는 장점이 있습니다. 대규모의 프로덕트'군'의 추가/삭제가 유연해 진다는것이지요.

여기에 중요한 장점이 추가되는데 

공장이나 생산품을 사용하는 방법은 회사마다 내부 구현은 다르지만 , 사용자한테는 동일합니다. 핸드폰의 경우를 예로들면 내부적으로는 다르겠지만 외부적으로는 ,"통화하기" "인터넷보기" 처럼 같으며 자동차를 예로들면 엔진은 다르겠지만 "핸들돌리" "브레이크 밟기" 는 동일합니다.

무슨 이야기냐하면. Abstract Factory 를 사용하면 클라이언트는 어떤 프로덕트 '군' 이 추가되더라도 기존에 사용하는 방식 그대로 사용할수 있게됩니다. 위의 예에서 MySQL 의 JDBCDriver 를  사용하던 방법대로 HAMA DB 의 JDBCDriver ( 위에서는 HAMADriver) 를 어떤 추가적인 러닝커브 없이도 바로 사용할수 있다는 점이지요. ^^ 

단점 

만약 생산품이 원하는 기능이 없거나 그게 아니라면 Abstract Product 의 인터페이스를 추가하거나 고쳐야하며 이 이야기는 그것을 구현한 모든것의 변경을 요한다는 점이다.jdbc.sql.Driver 에서 어떤 큰 변경이 일어난다면 그것을 구현한 수 많은 데이타베이스 밴더( 오라클, MySQL 등) 이 골치아파진다는 이야기이다.

다른 사용 예

사용Abstract FactoryConcrete 
Factory

Abstract
Product
Concrete Product
JDBCjava.sql.DriverHAMADriver /
MySQLDriver
java.sql.ConnectionHAMAConnection/
MySQLConnection
Collection 
Collection LinkedList /
Tree / 
IteratorLinkedListIterator/
TreeIterator
UI systemUIFactoryWindowUIFactory/
LinuxButton
ButtonWindowButton / 
LinuxButton 


다음에는 State 패턴에 대해서 알아보겠습니다~ 




레퍼런스 :  실용주의 디자인 패턴 (http://www.kangcom.com/sub/view.asp?sku=200607180006)


+ Recent posts