최고 엔지니어의 꿈을 향해서

'Computer Science/Software architecture'에 해당되는 글 7건

  1. 2007.06.07 여섯번째, 어댑터패턴
  2. 2007.06.07 일곱번째, 퍼싸드 패턴
  3. 2007.06.04 다섯번째, 싱글톤 (1)
  4. 2007.06.03 네번째, 데코레이터 패턴
  5. 2007.06.02 세번째, 옵저버 패턴 (1)
  6. 2007.03.13 두번째
  7. 2007.03.02 Let's get it started (1)

[Adapter pattern]

어댑터 패턴이란?

실생활에서 흔히 볼수 있는 어댑터들이란, 110v에서 220v로 바꿔주는 것, 콘센트 끝부분만 바꿔주는것(돼지코), 뭐 즐 다양하다.
소프트웨어에서의 어댑터란 같은 의미다.
양자간의 의사소통만을 위해 어댑터가 존재하며 양끝단은 서로를 몰라도 어댑터로 인한 커뮤니케이션이 이뤄진다.
한국사람과(불어를 모르는) 프랑스인이(한국어를 모르는) 의사소통하기위해선, 한국어와 불어를 동시에 아는사람, 통역사를 데리고 있으면 커뮤니케이션이 이뤄질수 있다. 이때의 통역사가 바로 어댑터에 해당한다고 볼수있다.

어댑터 패턴의 기본원리는 간단하므로
자동차의 예를들어 설명하겠다.

혼다회사의 어코드 자동차는 07년 실적이 저조하여, 08년 새로운 어코드 모델을 선보이기로 했다.
새로운 모델로 전환되면서, 신형엔진으로 교체하는것은 필수인데, 혼다에서 어코드08년신형 엔진개발의 자금력과 시간부족으로 인하여, 신차출시의 난항을 겪게 됐다. 그러던차, 한창 뜨고 있는 현대차 소나타V6 07년의 엔진을 그냥 탑재 하자는 의견이 나왔다. 그렇게 하면, 엔진개발비용과 시간단축등 혼다사에서는 엄청난 이득이 될것이라는 혼다 어느 직원이야기 이다.(바로 해고됐다-_-)
결국 그 직원의 말대로, 현대에 요청하여, 소나타v6엔진을 08년 어코드 모델에 탑재할테니, 좀 엔진좀 싸게 달라고 주문했다. 그런데 처음에는 가능할것 같았던, 자동차 엔진 이식문제가 기술적으로 문제가 발생했다. 어코드차는 지금껏 엔진5단변속으로만 되어있어서, 엔진이외의 모든부품이 그에 맞게 설계 되어있었다. 그러나 현대소나타v6엔진은 8단변속이여서, 서로 궁합이 안맞는 것이였다. 그 엔진을 쓰려면 어코드 자동차 엔진이외의 모든 부품을 8단에 맞게 다시 만들어야 하는데, 불가능한것이였다.
그래서 현대사에게 요청하기를, 8단엔진이 성능이 5단보다 훨씬 좋기때문에, 우리는 8단v6소나타엔진의 장점을 이용하면서 단지 어코드차에 맞게만(5단변속베이스) 설계해 달라고 주문하였다.
현대사에서는 "어댑터"를 만들어서 그렇게 해주겠다고 했다. 정말 재미있다.

그럼 이야기를 간단한 코드로 풀어보면.....

public interface HondaAccord08Engine {
 public void changeEngineMode_5_level();
}
요놈은 혼다 어코드 08년형 엔진 인터페이스 이다. 새롭게 출시되기때문에 새로운 인터페이스를 만들어
앞으로도 이 인터페이스를 사용할 것이라고 한다.

public class AccordCar implements HondaAccord08Engine {
 private String name = "Accord";
 private int year = 2008;
 private int valve = 2;
 private int currspeed = 0;
 private int amount_ventilation = 0;
 
 public void speedUp() {
  currspeed += 2;
 }
 public void Ventilation() {
  amount_ventilation += 20;
 }  
 
 public void changeEngineMode_5_level() {
  System.out.println("드르릉 드르릉 드르릉"); // 변속할때 발생하는 소리
  Ventilation();  // 일련의 필요한 트랜잭션 (변속시)
  speedUp();  
  Ventilation();  
 }
}
요놈이 어코드자동차 엔진..
 
////////////////////////////////////////////////////////////

public interface HyundaiV6newEngine {
 public void changeEngineMode_8_level();
}
현대차에서도 역시나 v6엔진에 대한 인터페이스가 존재한다. 역시나 8단변속이다.

public class HyundaiSonata implements HyundaiV6newEngine {
 private String name = "Sonata";
 private int year = 2007;
 private int valve = 5;
 private int currspeed = 0;
 private int amount_ventilation = 0;
 
 public void increaseSpeed() {
  currspeed += 5;
 }
 public void increaseVentilation() {
  amount_ventilation += 2;
 }  
 
 public void changeEngineMode_8_level() {
  System.out.println("펑펑펑 펑펑펑 펑펑펑"); // 소나타 엔진 변속시 발생하는 소리
  increaseSpeed();  // 소나타 엔진 변속시 발생하는 트랜잭션들....
  increaseVentilation(); // ...
 
 }
}
 요놈은 소나타 자동차엔진 샘플인데, v6 8단 엔진을 장착했다. (*실제로는 엔진클래스 만들어서 인스턴스변수로 넣어주던지 해야할텐데 귀찮아서 생략하고)

그럼 이제 어코드 08년산에 장착할 엔진 5단변속 기준하고, 현대 소나타 8단변속엔진과의 호환을 위해서 새로운 변환 인터페이스를 현대에서 만들었다.


public class EngineAdapter_8_to_5_level implements HondaAccord08Engine {
 HyundaiV6newEngine hyundaiEngine; // 8-level new engine
 
 public EngineAdapter_8_to_5_level(HyundaiV6newEngine hyun) {
  hyundaiEngine = hyun;
 }
 public void changeEngineMode_5_level(){
// 요놈이 바로 실제로 인터페이스호환에 핵심적
  hyundaiEngine.changeEngineMode_8_level();  // 역할을 하는 .....놈
 }
}


그럼 한번 실행시켜보자.

public class AdapterTester {

 public static void main(String[] args) {
  HondaAccord08Engine hondacar = new AccordCar();  // 순수 어코드차
  hondacar.changeEngineMode_5_level();  // 기어 변속시
 
  HyundaiV6newEngine hyundaicar = new HyundaiSonata();  // 순수 소나타 차
  hyundaicar.changeEngineMode_8_level(); // 기어 변속시
 
  hondacar = new EngineAdapter_8_to_5_level(hyundaicar);  // 소나타 엔진을 탑재한 어코드차
  hondacar.changeEngineMode_5_level(); // 기어변속시..(역시 엔진 바꾸는 소리가 소나타차 소리)
 }
}

뭐 이런식이다. 뭐 내가 재밌는 예를 생각하다가 자동차예를 선택했는데, 이해하기 쉬웠는지 모르겠다.
너무 간략하게 설명을 하다보니 빼먹은것도 많고, 귀찮기도 하고,,
이 글의 목적은 아주 처음 감잡을때 읽는용이고 좀더 기술적인 테크닉 세세한 자료들은 역시나 Gof 앤드 웹서치..ㅋㅋ

Posted by 정해권 Trackback 0 Comment 0
[Facade pattern]

퍼싸드 패턴이란?
패턴세계에서 몇몇 안되는 간단하믄서 쉬운 패턴. 그러나 언제나 강력한.

일단 한마디로 설명하면, 서브시스템의 복잡성을 심플하게 접근할 수 있는 좀더 추상화된 인터페이스 제공


예를들면, 내가 전기 밥솥을 사용하는데, 전기 밥솥 내부의 원리나 작동원리를 알아야만 밥을 할수 있는것인가? 그렇다면 밥 못먹는다.
일반 사용자들이 알아야 할 것은 오직 전원버튼을 누르는 것뿐.(밥해주세요)

 그럼 밥솥은 알아서 압력게이지를 올리고, 온도를 적정수준으로 도달하게 하고, 시간설정하고, 알아서 다 해준다. 너무 편리하다. 사용자가 일일이 온도 몇도까지 맞추고, 압력게이지 올리고 할필요없이 알아서 다 사용자 대신 해주다니.
그럼 클라이언트에서는 단지 TurnOn(); 만 호출하면, 저 함수 내부에서 복잡한 일들이 벌어진다고 생각하면 된다. 한마디로, 각각의 수많은 트랜잭션을 하나의 클래스로 묶었다고 생각하면 된다.

결국 사용자에겐 편리한 인터페이스를 제공한다. 그리하여, 재사용 할만한 단위를 좀더 확장시켜 준다.
예를들면 압력게이지, 온도센서 등등의 클래스의 재사용 단위 를 압력 밥솥이라는 재사용단위를 확장!!
그렇다고 디테일한 부품을 캡슐화 시켜서 접근할수 없게 만드느냐? 그건 아니다. 양쪽다 가능하기 때문에 바로 이점이 이 패턴의 장점중 하나이다.
결국 큰 시스템간의 종속성만을 생각하고, 작은 아주 디테일한 부분의 종속 결합도는 없앨 수 있다.
여러 단위의 계층화를 시켜서 적용할 경우에도 Facade단위로써만 종속되어있기때문에, 좀더 유연한 시스템을 만들 수 있다.

배고파서 잠시 즐쉬러..






Posted by 정해권 Trackback 0 Comment 0

[Singleton pattern]


싱글톤 패턴에 대해서...

대부분의 사람이 가장 먼저 익히게 되는 패턴중에 하나로, 다른패턴에 비해 목적성과 구현이
상대적으로 쉽다.
싱글톤이란 말그대로, 단하나밖에 생성할 수 없다는 것인데, 가령, 시스템 내에서 단 하나만 만들어져야 하는 객체가 있다면 싱글톤 패턴을 적용하므로써 전체적인 시스템의 안정성을 확보할 수 있다.
일반적으로, 프린터 스풀 (프린트가 여러대라도 스풀은 하나여야 하므로)등이 하나의 예가 될수 있다. 이외에도 설계자가 생각하기에 이 객체는 하나만 만들어져야 한다면 무조건 싱글톤!!! ㅋㅋ(무조건이라는건 위험한 발상 ㅋ)

구현 방법은 가장먼저 생성자를 private로 설정하는것이다. 이렇게 하므로써, 외부에서 객체를 생성할 수 없게 한다.(생성자가 private이면, 객체 생성이 안되며, 상속도 불가). *물론 생성자를 protected로 하므로써 서브클래스를 만들수 있다.*
그럼 어떻게 단 하나만 만든다는 것일까? 클래스 안에 static getInstance()함수를 하나 만들고, 그 안에서 객체생성을 해주는것이다(단 하나만). 외부에서는 생성이 불가 하였지만, 내부에서는 생성이 가능하다(클래스 내부에서 private함수, private변수 접근 가능하니까). 그 안에서 if문으로 생성이 되어있는지 안되어있는지 확인해서 단 하나만 만든다는것이다.

class Singleton {
    private static Singleton single;  // 오직 하나의 객체
   
    private Singleton() { }  // 생성자를 private로 하므로써 외부에서 이 객체 생성 불가
    public static Singleton getInstance() {
        if(single == null)
            single = new Singleton();
        return single;
 }
 이런식으로 말이다. 위의 코드는 lazy instantiation 이며, 이말은 사용자가 getInstance()를 호출할때 비로서 객체가 생긴다는 것이다. 좋은점은 이 객체의 비용이 높을때(자원등) 미뤄왔다가 필요시 만듬으로써, 사용 이전의 자원소모를 방지한다. (컴파일시 처음부터 객체생성과는 달리)
위의 코드 다 좋다. 그러나 위의 코드에 문제점이 있는데, 멀티쓰레드 사용시, 동기화문제가 발생한다.
if(single==null) 부분에서 컨텍스트스위칭이 발생되고, 다른쓰레드에서 또 if(single==null)부분을 돌려버리면 객체 두개 생성되는 식으로 말이다. 이를 방지하기위해 아싸리 처음부터 객체를 만들어 버리는것이다.
public class Singleton {
   private static Singleton single = new Singleton();
 
   private Singleton() {}
   public static Singleton getInstance() {
      return single;
   }
  }
 이런식으로 말이다. 이방법은 컴파일시부터 이객체의 사용시점과 상관없이 미리 만들어 두는것인데,
 리소스 면에서 lazy instantiation 보다 안좋지만은 반면, 속도문제에 있어서는 장점이 될수 있는
 것이다. 뭐 객체 하나 만드는데 속도 문제에 있어 얼마나 메리트가 있느냐 할수 있지만(-_-)
허나, 이 방법은 객체를 처음부터 정의 해야한다는 단점이 ...객체를 정적초기화 시점이 아닌 실행시 주어지는 결과값으로 결정되어야 할때는 문제가 있다. 이런경우 레지스트리를 사용해야 하는데, Gof책을 참조하기 바란다.(-_-)
  이 밖에도 언어에서 제공하는 쓰레드 동기화 키워드 들이 존재하는데 자바의 경우 syncronized라던가 volatile 이라던지 등등 제공해준다.
 * 참고로 전역변수는 생각지도 말자.(전역변수로도 해결할 수 있지만 단점을 극복한 것이 이 싱글톤패턴)
이 싱글톤 패턴 간단하다고 했지만, 의외로 신경써줘야 할 부분이....읔
참고로 패턴의 디테일한 정보는 다 책이나 웹에 있다. 나는 그저 핵심만 설명하는 것이다. 부수적인 테크닉은 역시 책을 봐야 한다 ㅋㅋㅋ
Posted by 정해권 Trackback 0 Comment 1

[Decorator pattern]

데코레이터 패턴이란?

일단, 가장 먼저 집고 넘어 가야 할 것이, oop의 중요한 장점이자 특성중 하나가 inheritance 상속인데
상속의 장점이야 뭐 코드 재사용과 뭐 폴리몰피즘의 기반을 제공해준다고 볼수 있다.
좋은 놈임에는 틀림이 없으나, 그것도 역시 상황에 맞게 써야 좋지, 무턱대고 상속을 써버리면,
시스템이 융통성 없이 무조건 커져버리게 된다는 단점이 있다. 이 상속 기법에 반하여 융통성에 초점을 두고 있는것이 delegation (composition) 인데 요놈의 장점은 런타임시에도 시스템을 유연하게 대처할수 있게 해준다, 허나 코드재사용은 기대하기 어렵게 된다.
이 두가지는 정말 어떻게 보면, 각각 기회비용이 있어서, 신중하게 뭐를 택할 것인지 결정해야 하며, 시스템의 확장성과도 연관지어서 생각해봐야 하고, 그 시스템의 부분이 fixed 쪽에 가까운지, flexible한쪽에 가까운지 설계자의 판단에 의해 결정된다.

그럼 상속이란 것이 컴파일타임시에 코드 자체로서 굳어져 버리는 석고상 이라는것을 알았다. 런타임시에는 코드수정이 불가하므로..
좀더 런타임시에도 새로운 서비스(기능)의 추가를 쉽게 하기위해 Decorator패턴이 등장했는데, 서비스의 추가등을 상속을 이용하는 것보다 유연하게 할수 있는 방법을 제공한다.

설명이 추상적이어서 이해가 잘 안갈수 있으니 구체적인 예를 들어 설명해보면,
자바유저들은 Java I/O에 대해서 알것이고, C#도 뭐 마찬가지 일 것이다.(C#은 안써봐서 모르지만-_-)
엄청나게 많은 I/O관련 클래스들이 존재한다. 그런 즐~많은 클래스들을 보면서 수많은 클래스를 어떻게 다 익힐지, 더군다나 사용법은 약간 특이하기까지 (특이하기보단, 함수 인자로 이전에 만들었던 객체를 넘겨주는 방식을 여러번 취한다)
나도 이 패턴을 알기 전까지는 그 I/O의 복잡성에 손발을 들었었고, 클래스 관계도를 보아도
이놈이 이놈인지, 이름도 다들 비슷한게, 암튼 어질했었다. 결론은 이 패턴을 알면 Java I/O의 관계들도 손쉽게 머리에 들어오게 된다. 왜냐면 Java I/O가 이패턴을 이용해서 설계된것이기땜시.
뭐 다른예들은 실제로 편집기에서 윈도우창에 새롭게 데코레이터를 할때나.., 뭐 활용성에 대해서는 Gof책에도 많이 나와있다.

새로운 예를 들어 코드와 함께 설명하자면, (HeadFirst책에있는 예가 이해하기 아주 쉽기에 같은 예를 들어)
스타벅스에 가보면 다양한 커피가 존재하는데, 기본이 되는 커피 몇개 위에 다양한 것들을 추가할수있는데, 크림이라던지, 모카, 밀크 등등 추가비용만 더 내면 더 추가 할수 있다고 생각해보자.
여기서 생각해봐야 할것은, 여러 조합이 가능하다는것이다. 가령, 기본이 되는 커피 A에 밀크만 추가해서 팔수도 있고, 크림만 추가할수도 있고, 두개다 추가해서 팔수도 있고, 커피B에다가도 똑같이 적용된다. 뭐 이래 저래 조합을 생각해보믄, 엄청나게 많아 지는데, 상속을 이용해보면
Class Coffee{ } // super class
class CoffeeAWithMilk extends Coffee{ }, class CoffeeBWithSugar extends Coffee, class CoffeeAWithMilkandSugar extends Coffee 등등
즐 많아진다. 정적으로 구성하다보니 많아 질수밖에 없는것이다.
뭐 꼭 저렇게 하지 않고 다른방법이 있긴한데, 추가적인 우유, 크림등등에 대한 속성을 Coffee추상클래스에 집어넣은다음에, 기본이 되는 커피만을 서브클래스로 구현하여, 각각의 추가성분은 super클래스Coffee안의 변수등을 통하여, 알아내는 방법인데, 이방법역시, 모든 subclass들이 모두 같은 속성을 (필요없을지라도) 물려받게 된다는 단점이 있다. Coffee클래스가 점점 비대해 질수 있다.
그리하여, 상속으로서 모든조합에 해당하는 클래스를 만든다는건 미친짓이고, 그럼 어떻게 해야 한단말인가? 아까 처음에 언급했던 delegation의 장점을 빌려와야 할것같다.
가장 먼저 가장 superclass격인 class Coffee를 만들고 그 subclass로 기반이 되는 가벼운 클래스 를 선언해준다. (여기서는 커피의 종류?) 그리고 크림이나 설탕이나 등등 추가적인 클래스들의 추상클래스 하나를 상속해준다.(사실 클래스 도식표를 봐야 이해가 빠른데..)
그 추상클래스의 서브클래스로 각각 크림, 우유 등등을 상속해주면 전체적인 디자인은 끝인데, 이게 말로만 하려니 참 어렵...
쓰다보니 개 귀찮아져, 잠시 중단!!!!
자 다시 걍 빠르게 속행;;
public abstract class Coffee {
 String description = "Nothing";
 
 public String getDescription() {
  return description;
 }
 
 public abstract double cost();
}
먼저 가장 윗단계의 추상슈퍼클래스를 만들고 나서 모든클래스가 이 클래스로부터 상속받게 할것이다.
실제 에스프레소커피, 하우스블렌드커피등등 커피 그 자체도 물론 여기서 상속받는것이고, 잡다하게 들어갈 첨가물, 밀크, 모카 등등도 여기에서 상속받게 될것이다. 그로 인하여, 같은 추상클래스로부터 상속받았기때문에, 첨가물이던, 커피 그자체이건 상관없이 서로 호환이 된다는것이다.
public abstract class Condiment extends Coffee {
 public abstract String getDescription();
}
요녀석은 첨가물의 추상클래스이다.

public class Espresso extends Coffee {
 public Espresso() {
  description = "Espresso";
 }
 public double cost() {
  return 1.99;
 }
}
요녀석은 에스프레소이다.
public class HouseBlend extends Coffee {

 public HouseBlend() {
  description = "House Blend";
 }
 
 public double cost() {
  return 0.89;
 }

}
요녀석은 하우스블렌드 커피 ..;;;
////////////////////////////////////////////////////
자 그럼 첨가물 구현
public class Milk extends Condiment {
 Coffee coffee;
 
 public Milk(Coffee coffee) {
  this.coffee = coffee;
 }
 
 public String getDescription() {
  return coffee.getDescription() + ", Milk";
 }
 
 public double cost() {
  return 0.12 + coffee.cost();
 }
}
요녀석은 우유인데 잘 살펴보면, 우유같은 첨가물들은 coffee 인스턴스 변수를 가지고 있기때문에
(바로 composition방식) 행위를 추가시키거나 뭐 덧붙이고 싶은것을 할수있다, 여기에서는 0.12더해준것...이 바로...또한 getDescription함수에서 + Milk 더해준것이 바로 덧붙여진 행위에 해당한다.
.............
public class Mocha extends Condiment {
 Coffee coffee;
 
 public Mocha(Coffee coffee) {
  this.coffee = coffee;
 }

 public String getDescription() {
  return coffee.getDescription() + ", Mocha";
 }
 public double cost() {
  return 0.20 + coffee.cost();
 }
}
모카는 이런식으로 똑같다..
그럼
실제로 어떻게 구동되는지 살펴보면.......................
public class DecoratorPatternTester {
 public static void main(String[] args) {
  Coffee coff1 = new Espresso();
  System.out.println(coff1.getDescription() + coff1.cost());
  coff1 = new Milk(coff1);
  System.out.println(coff1.getDescription() + coff1.cost());
 }
}
요런식으로 .............하는것이다. 자바의 I/O 라이브러리 쓸때랑 같은 형식인걸 알수있다.
자 지금은 단지 데코레이터 패턴에 대해서만 언급한 것이고, 생성과정에서의 오류나 실수를
방지하기위해서는 팩토리관련 패턴이 참여하게 되는데, 그것은 다음장에서 설명하겠다.
중요한것은 데코레이터패턴에서 눈여겨봐야 할것은 래퍼객체 이전의 객체에 대해서는 알수도, 알아서도 안되는것이다. 이 디자인패턴이 생겨나게 된 이유중에 하나가 이전의 데코레이터가 뭔지 상관안하는 상태에서 덧붙이는 것이기때문에, 그런 일련의 생성과정은 사용자가 알아서 하는것이다.
흠........지금까지의 설명은 아주 베이직한 핵심 원리 였고, 좀더 디테일한 정보는 역시나 Gof책에서..
** 디자인패턴 기본작동원리를 모르는상태에서 GOF책 보면 이해절대 불가이다.장담한다.
    그러나 알고보면 새로운 진리를 발견하게 된다...**



Posted by 정해권 Trackback 0 Comment 0
[Observer pattern]

간만에 방학맞이 기념으로 옵져버 패턴에 대해 간략하게 무엇인지 적어보겠다.
내가 생각하기에 패턴중에서도 중요순위 넘버 파이브 안에 들어간다.
실제 이패턴을 이용하는 경우가 많이 있어서, 나중에 다른 테크닉을 익힐때, 이 패턴을 알아두면 도움이 될듯 싶다.( 대표적으로 MVC모델에서 쓰이며, 자바의 경우에서 보면 RMI, JavaBeans, Swing 등등 많이 쓰인다)

MVC패턴은 정말로 꼭 알아두어야 하는 것이기 땜시 다음번에 설명을 꼭 하겠다. 실제 3-tier 모델로
설계할경우 많이 이용된다.

옵져버 패턴은 Subject와 Observer 이렇게 나뉘어 있는데, Subject에서 데이타를 변경하면 그에 종속되어 있는 수많은 Observer들이 영향을 받게 된다.

비유해보면, 신문사의 신문을 구독자에게 모두 전달하는 것과 같다. 단 전달순서에 대해 의존해서는 안된다.

대표적인 예가, 엑셀에서 데이타를 변경하면 그에 해당하는 각가지 visual diagram이 변경된다.
도표 모양이 타원이던 막대그래프이건간에 상관없이 데이타를 정확하게 표현해 주기만 하면된다.
이런경우 데이타와 표현부분에 있어서 sync를 맞춰줘야 하는데, 옵져버패턴을 사용안하고 구현하게 되면, 데이타와 표현부분의 대한 의존도가 높아지고, 데이타 싱크 부분에서 개런티를 보장하기 위해 지저분한 조건문 같은게 포함될 것이다.

그럼 옵저버 패턴을 이용하면, 얻게 되는 장점중에 하나가 loose coupling 이고, 그 말 즉슨, Subject와 Observer 각각 재사용이 좀더 용이해 진다 이것이다. 동적으로 observer에 해당하는 객체를 쉽게 추가/삭제 할수 있다.

그럼 이해를 돕기 위해, 예를 들어 설명을 하겠다.
미국 캘리포니아에 EarthQuake라는 회사가 있다. 그 회사가 하는일은 전세계 지진관련 정보를 수집하며, 필요시 정보를 제공하기도 한다. 정보중에는 어느지역이 지진가능성이 있는지, 발생되었는지에 대한 좌표도 포함한다.
어느날 지진관련 프로젝트를 맡은 팀이 회사에 컨택을 하여 정보제공여부를 확인한뒤, 승락받았다.
이제 해야할 일은 그 데이타를 가지고, 웹상에서 구글맵위에 매핑시켜주는 일과, 그 데이타를 가지고 각가지 도표로도 표현해 주어야 하며, (실시간으로) 뭐 이런식의 일을 해야한다.
핵심은 한가지 데이타를 가지고 다양한 프레젠테이션을 만들어 낼수 있다는 것이다. (뭐 단지 프레젠테이션에만 국한되는것은 아니지만) ### 여기서 왜 꼭 패턴을 사용해야 하는가 라는 물음이 나올수 있는데 결론은 안써도 된다, 쓰건 안쓰건 개발자 마음이며, 이같은 상황에 옵져버패턴을 적용하는 경우라면 개발자의 생각은 다음과 같을 수 있다. 앞으로 점점 지진자료를 가지고 실시간으로 처리해야 하는 프레젠테이션 부분의 수가 시간이 가면 갈수록 점점 늘어날 테이고, 데이타부분과 프레젠테이션 부분의 결합을 될수 있는한 약하게 하여, 나중에 꼭 지진좌표가 아니더라도, 다양한 데이타를 가지고 처리를 하기 위해서 적용했다 라고)

그럼 다시한번 코드를 훑어 보면서 이해를 돕자.

1. 지진관련 데이타 클래스 (요놈이 실제 subject에 해당하는 놈, 인터페이스로 만들자, 추상적인 결합을 만들기 위해)
public interface Subject {
   public void registerObserver(Observer o);  //옵저버로 등록하기 위해
   public void removeObserver(Observer o);  // 옵저버를 리스트에서 제거하기 위해
   public void notifyObservers();  // 리스트에 들어있는 모든 옵저버에 통보를 한다.(update(..)호출)
}

2. 위에 있는 인터페이스를 상속받아, 실제 지진관련 클래스를 작성.
public class Earthquake implements Subject {
   private ArrayList observers; // 옵저버 보관리스트, 배열말고 다른 자료구조 이용해도 상관없음.
   private float earthquakePosition;
   private float earthquakePressure;
   // 등등등의 데이타들.

   public Earthquake() {  // constructor
       observers = new ArrayList();
   }
   public registerObserver(Observer o) {  // 인터페이스 구현
      observers.add(o);  // 배열에 추가
   }
   public removeObserver(Observer o) {  인터페이스 구현
       if(o의 인덱스가 있으면...)
      observers.remove(i); // something like that
   }
   public void notifyObservers() {
       for(int i=0; i< observers.size(); i++) {
          Observer ob = (Observer)observers.get(i); // 옵저버를 불러내어
          observer.update(earthquakePosition, earthquakePressure); //인자는 전달하고싶은것들
        }
   }
   public void mesurementsChanged() {
        if(가져온 루틴이 갱신됐거나 옵저벼들에게 알리고 싶으면)
            notifyObservers();
   }
   public void retrieveMesurements() {
      // 회사로부터 관련 자료를 가져오는 루틴
      mesurementsChanged();
  }
 /////////////////////////////////////////////////////////////////////////
3. 옵져버 (일명 신문구독자)의 코드
   * 먼저 옵져버의 기본 인터페이스를 만들자 (loose coupling)
  public interface Observer {
      public void update(float earthquakePosition, float earthquakePressure);
  }
  * 실제 옵저버들 구현
   public class MappingtoGooglemap implements Observer {
        private float earthquakePosition;
        private earthquakePressure;
        private Subject earthquakeData;
        public MappingtoGooglemap (Subject earthquakeData) {  // constructor
                this.earthquakeData = earthquakeData; // 나중에 리스트에서 빠지기위해
                earthquakeData.registerObserver(this); // 옵저버리스트에 가입함
        }
        public void update(float earthquakePosition, float earthquakePressure) {
           this.earthquakePosition = earthquakePosition;
           this.earthquakePressure = earthquakePressure;
           draw();
        }
        public void draw() {  // 이함수에 대한 공통 인터페이스, 개런티를 원한다면, 인터페이스를 만
                                      // 든뒤 인터페이스 구현
           // earthquake자료들을 가지고 실제 구글웹에 매핑해주는 소스
        }
     public class StoretoTexfile implements Observer {
           // 위에 있는 코드대로 ..
 
           public void draw() {
                 // earthquake자료들을 실제 파일로 저장..
           }
      }
///////////////////////////////////////////////////////////////////////////////
이런식으로 전반적인 모습이 이렇다. Subject와 Observer들 사이에서의 데이타 전송은,
서브젝트쪽에서 데이타 변경시 옵저버들에게 알려주는 pushing방법과 옵저버들 자신이 데이타를 원한경우 서브젝트에게 데이타를 요구하는(getter메서드를 통하여) pulling 방식 두가지가 있는데,
물론 장단점이 다 있다. 수많은 옵저버들이 존재 할텐데 각각 옵저버들이 원하는 데이타가 다를것이다.
이럴경우 subject입장에서는 모든 옵저버가 원하는 데이타를 일일이 보내주기 거시기 하기때문에
이럴경우는 pulling방식을 써서 옵저버가 직접 서브젝트에게 getXXX() 등의 함수를 호출하여 값을 뽑아내는 식으로...이럴경우는 좀더 확장에 대해 열려있다고 보믄되고, 하지만 이런경우 옵져버들이 일일이 요청해야 하는 번거로움이 증가되고, 자료 교환의 트랜잭션 수가 증가된다고 볼수있다.Getter함수를 여러번 호출해야 할테니까.

한마디로 옵저버 패턴은 네트웍 개념에서 보면 broadcast로써, 옵저버수와 상관없이 리스트 등록되어있는 모든 객체에게 메시지를 전달하는 것이다. 메시지의 처리여부는 observer들이지 subject는 상관할바 아니다.
뭐 이외에도 옵저버패턴의 많은 장단점 활용가치 등이 있으나, 자세한것은 Gof책을 참조하기 바란다.


Posted by 정해권 Trackback 0 Comment 1
State Pattern [2번째]

간만에 다음 디자인패턴에 대해서..글을 쓰게 되었다.
첫번째 Strategy pattern으로 시작하였기 때문에, 이번엔 무슨패턴으로 글을 쓸까 하다가 그래도 설명을 쉽게 하기 위해서, Strategy pattern과 가장 유사하다고 볼 수 있는 State pattern에 대해서 쓰기로 맘먹었다.

* 일반적으로 패턴이란것이, 꼭 써야 하는것은 아니고, 남용 해서도 안되고, 잘못 써서는 더더욱 안되는것이다. 일반적으로 내가 알고 있는 철학은 혹은 나만의 철학은, 프로그램 설계 시, 먼저 프로그램이 나중에 어떻게 변할 것인가를 먼저 염두해 둔다. 내가 만들고자 하는것이 프로그램이던 비지니스상의 프로세스이건, 나중에 어떤식으로 확장이 되고, 특히 어느 부분(코드)이 나중에 사용자의 수에 따라서 혹은, 사용자 요구상황에 따라서, 변경이 될 것인가, 라는 것을 항상 염두 해 두고, 그 부분을 고려해서 설계 해야 한다. 결국 그 말은, 변경의 여지가 다소 없는 부분들은 구태여 패턴의 사용으로 코드 복잡성을 높일 이유가 없다는 것이다. 왜냐면 확장성 있는 로버스트 한 코드들은 대부분 퍼포먼스랑은 상반되기 때문에, 아무리 퍼포먼스의 영향이 적다 할지라도, 그리고 코드 이해하는 부분에서도 그리 쉽지 않기 때문에..등등..처음 예상했던 것과 달리, 어떤 부분의 코드가 매우 비효율 적이라던지, 예측못한 부분에서 코드를 변경해야 할 경우라던지, 등등 나중에 리펙토링단계에서 갈아 어플수 있기때문에..구태여 설계단계에서부터, 너무 모든 코드 곳곳에 확장성 고려 목적으로 패턴적용을 할 필요는 없고 해서도 안된다는것이 나의 마인드이다. 결국 패턴은 아주 큰 틀에서, 생각해야 한다..

[State pattern]

이 패턴은 매우 간단하다. 그리고 전략패턴과 매우 유사 하기때문에 이해하기 쉽다.
다만 전략패턴과 차이점을 이해하는 것이 중요하다고 볼 수 있겠다.(* 패턴의 차이점이란, 코드의 구현방법차이에서 도 물론 포함하지만, 사용목적, 용도의 의미 모두를 포함하므로, 코드구현은 거의 비슷한데 왜 별도의 패턴이름을 명명 했을까? 라는 의문은 패턴의 근본 탄생배경, 목적이 다르기 때문이라고 보면 된다.

먼저 상태 패턴이란 말그대로 , 상태에 따라서 동작을 바꾼다는 것인데, 상태와 행위가 서로 밀접한 관계에 있을때 사용한다. 그리고 상태관리의 변수가 많고, 그에 의존하는 행위가 복잡할 경우, 사용하면
깔금한 디자인을 만들수 있다 하겠다. 그렇지 않을 경우엔 조건문 등을 이용해야 하는데, 시스템이 커질수록 코드가 지저분해진다.

한가지 예를 들어서 설명하면( GOF책에서)
TCPConnection 이란 객체가 있다고 생각하자.
저 객체는 사용자의 접속 요청을 관리해 주는 객체인데, 현재 네트워크 Tcp의 상태에 따라 사용자의 요구를 어떻게 처리해줘야 할지 바뀌게 된다. 예를들면, Open()이란 함수를 콜 하였을때도, Tcp네트웍 상태가 TCPEstablished인지 TcpListen상태인지 TcpClosed상태 인지에 따라서 같은 함수처리에 대한 내용이 바뀐다는것이다. 패턴적용 안하면, 상태변수를 이용하여 조건문으로 해줘야 한다. (즐~)
물론 뭐가 좋다 안좋다는 아니고, 상황에 맞게 맞는 디자인을 적용해야 한다 이것이다.
그럼 TCP상태를 나타내는 클래스를 만들때 일단, 추상 클래스 한개 만들자.
TCPState 를 한개 추상으로 만들고 각각 서브클래스로 TCPEstablished , TCPListen, TCPClosed 를 만들자.
다음 TCPConnection객체를 만들어 사용자의 리퀘스트에 응답한다, 함수는 Open(), Close() 정도 만들고, 각각의 함수에서 각 행위를 상태객체에 위임한다. (델리게이션, 전략패턴이랑 같다.)
넘길때, 인자로 TCPConnection의 객체도 같이 넘긴다.(상태객체가 TCPConnection객체의 데이타 이용을 할수있기 위함이며, TCPConnection같은 객체가 여러개 존재할경우, 각각 상태객체를 공유할때, 상태객체에서 누가 호출했는지 구분하기 위해서).

TCPConnection객체는 TCPState추상 클래스의 인스턴스 변수를 가지고 있고, 그 변수를 통하여 실제 상태 객체들에게 접근한다. 할당은 TCPConnection 생성자나 뭐 자기가 하고싶을때 적절히 해준다.
TCPConnection클래스에는 Setter클래스를 하나 만들어, 상태를 바꿀수 있는 함수 한개 만들고, 각각 상태 클래스에서 상태의 전이를 위해, 필요시 TCPConnection클래스의 상태전이변경함수를 호출해준다. 여기서 주의할점은, 상태전이를 상태객체 클래스내부에서 처리하느냐, 아니면 TCPConnection에서 해주느냐 인데, 전자의 경우, 클래스 상호 의존도가 높아지는 단점이 있는 반면(구현종속), 좀더 동적으로 구성할수 있고, (변수의 값에 의한 상태전이라는지..), 후자의 경우는 의존도가 낮아지는 대신, 좀더 유연한 대처가 불가능하다.)

수도 코드를 작성해보겠다

class TCPConnection {
   TCPState state; // 추상클래스 인스턴스변수
   
   TCPEstablished estState;
   TCPListen listState;     // 각각 상태클래스 인스턴스 변수;
   TCPClosed closeState;

   public void TCPConnection() {
         state = new TCPEstablised(); // 초기화
         estState = new TCPEstablished(this);
         listState = new TCPListen(this);          // 요런식으로 초기화
         closeState = new TCPClosed(this);
   }

   void open() {
        state.open();        // 단순히 행위 위임만 하였지만, 행위가 시퀀셜하게 뒷따라나와야 하는 경우
   }                             //  함수 호출이후 다른 예를들면, close()를 여기서 호출하거나 할수있다.
   void close() {
        state.close();
   }
   void setState(TCPState s) {
       state = s;
   }
////////////////////////////////////////////////////////////

class abstract TCPState {
   public abstract void open();  
   public abstract void close();
}
위에서 함수가 추상함수뿐인데, 인터페이스로 왜 안만들었느냐에 대해선, 다른 실질 함수가 올수 있다는것을 의미. 물론 여기에서는 귀찮아서 뺀것뿐. 그리고 변수들도 당근 올수있음.

class TCPEstablished extends TCPState {
   TCPConnection con;  // 요거 주목. 이유는 상태객체에서도 TCPConnection객체에 접근하기위함.
   public void TCPEstablished(TCPConnection con) {
      this.con = con;
   }
public void open() {
   // 연결이 확립되었을경우의 open코드 처리.
}
public void close() {
  // 연결상태에서의 close()동작
}
}
///////////////////////////////////////////////////////////////     
class TCPListen extends TCPState {
   TCPConnection con;  
   public void TCPListen(TCPConnection con) {
      this.con = con;
   }
public void open() {
   // 리스닝상태의 open코드 처리.
}
public void close() {
  // 리스닝상태에서의 close()동작
}
}
/////////////////////////////////////////////////////////////////
class TCPClosed extends TCPState {
   TCPConnection con;  
   public void TCPClosed(TCPConnection con) {
      this.con = con;
   }
public void open() {
   // 클로즈상태의 open코드 처리.
}
public void close() {
  // 클로즈상태에서의 close()동작
  // 이미 닫힌 상태입니다 라는식의 관련 코드.. 혹은 엠티
}
}

머 일단 이런식인데, 위의 코드에서는 상태 이전에 관련된 코드가 없다.
상태 이전이란  클로즈상태에서 open을 호출하였을경우, 더이상 클로즈상태가 아니라 리스닝 상태라든지 뭐 될것이다. 그럼 그 함수 내부에서 다음 상태를 세팅해준다. TCPConnection클래스 내부의 setter를 이용해서 해준다. 위의 상태 클래스에서 TCPConnection의 인스턴스 변수를 가지고 있었던 이유가 바로 여기.

암튼 설명을 엄청 허접하게 하였다. 암튼 전체적인 그림이 이런식으로 흘러간다는 것만 알면 된거같다.
Posted by 정해권 Trackback 0 Comment 0

처음부터 끝까지 나열식으로 줄줄이 분류해가며 설명하기 보다는 (그럼 재미없소) 각개격파식으로다가
한개씩 야금야금 (음..) 설명할께요, 전혀 사전지식이 없더라도 이해할 수 있게 쉽게 풀어쓸께영..

다만 그림을 그리지 못하니 양해바람
스캔해서 까지 올릴만한 시간이 없어성...
--------------------------------------------------------------------------------------------
0. 시작 하기에 앞서서. [Prelude]
   어느 패턴이건 단독으로 쓰이는 경우 보다는 복합적으로 사용되는 경우가 훨씬 많고, 지금 설명하는 패턴들은 배우는 단계의 전형적인 코드 형식이다. 그러니 실제 프로젝트(바깥세상) 에서는 변종 형태의 디자인을 많이 볼수 있다. 그러나 기본 패턴의 토대를 바탕으로 이루어져 있으므로, 기본을 확실이 다지자.
  패턴 종류에 상관없이 제네랄한 컨셉이 있다. 바로 특정형식, 특정클래스, 특정 콘크리트(concrete)클래스 지정을 지양하고, 상위형식(Super class), 인터페이스(Interface), 추상클래스(Abstract class)쪽의 상위레벨 중심으로 프로그래밍 하는것이다. 이유는 대부분 알고 있을 것이다, 바로 다형성(Polymorphism)을 사용하기 위해서 이다. OOP의 핵심 아닌가!. 또한 디자인패턴하고는 별개된 내용일 수 는 있으나, 요새 트렌트가 Generic class or function 사용이다. Java 5에도 추가되었다. 사실은 C++유저들은 알수 있겠지만, 제네릭 컨셉이 C++의 Template이다. 피상적으로 보면, 기본 핵심 컨셉, 역할은 같으나 디테일하게 보면, 내부 작동원리는 다소 차이가 난다.(Java는 계속 업글되면서 디자인도 최신을 따라가므로) C++보다는 한단계 진보했다고 볼 수 있다. whatever! 여기서 말하고자 하는 바는
특정형식, 특정상황의 구현보다는 점점 general, flexible, robustic 한 코드쪽으로 흘러 가고 있다.
물론 퍼포먼스랑은 상반되는 이야기이지만 말이다. 퍼포먼스만 고집하면, ASM으로 코딩하시든지..

그런 취지에서 시작한 첫번째 맛보기 패턴
1. Strategy pattern. [전략패턴]
  "나의 설명방식, 먼저 적용할 수 있는 사례부터 알아보자."
    a. 게임에 관심있는, 혹은 제작에 관심있는 사람들이 꼭 알아야 할것들
      ㄱ. 게임 캐릭터가 가지고 있을 수 있는 행위들(점프하기, 몸비틀기, 칼던지기, 땅바닥에 자빠지기, 죽은척 하기, 하품하기, 적들 주먹으로 때리는 시늉하기.........곱하기 백만개?) ==> 핵심사항 (행위가 언제든지 나중에 쉽게 추가 할수 있다는 것에 별다섯개): 개발단계에서 이것쯤은 예상하고 플밍하겠지요.
      ㄴ. 게임을 하다가 (피파축구?) 컴퓨터가 나의 플레이 스타일을 간파하고, 나의 스타일(잘하고 못하는지 파악)에 따라서, 컴퓨터 인공지능을 자유자재로 (키포인트) 런타임시 바꿀수 있는 것을 생각해보자. 내가 게임플레이를  잘하는지 간파하고 피파축구게임에서 "님은 플레이를 잘하시는것 같습니다 난이도 올려볼까요?" 라는 식의..
    b. 실제 회사에서 유아용의 교육용프로그램을 개발하였다. 프로그램은 동물들의 울음소리, 동물들의 행위들을 실제로 디스플레이하거나, 들려주기도 하고, 등등등의 그런것이다. 내가 현재 사자의 울음소리를 어르릉으로 지정해 놓았다면, 10년후의 사자가 유전자가 변형되서 야옹야옹 거릴 수도 있는것이다(과연? ㅋㅋ) 혹은 원래 먹이사슬구조에서 본다면, 뱀이 개구리를 먹는다, 허나 개구리중의 대마왕 황소개구리가 나타나 뱀들을 먹어 삼키기 시작했다. 이전에 개발할 때, 처음부터 fixed한 코딩방식으로 구현해 놨다면, 나중에 다 갈아 어퍼야 한다.

위 예뿐만 아니라, 엄청 많을 것이다. 위에서 설명한 것의 공통분모는 바로 가변적인 행위를 디자인 단계에서 독립적인 모듈로, (모듈이라는 용어보다는, 클래스가 낫겠다.(oop니깐)) 만드는 것이다. 이제 왜 전형적인 23가지 패턴중의 한가지인 전략패턴을 공부 해야 하는지 알것 같다.

이제 좀더 디테일 하게 기술적인 이야기 하겠다. (예는 위의 피파축구를 들어서 설명)
 
1. 새로 게임개발에 착수 했다. 박지성의 개발슛, 베컴의 오른발 휘둘러차기슛, 설기현의 역주행, 등등 캐릭터 마다 독특한 행위를 잘 표현해 보려한다. 그러나 게임 출시후에도 계속 버젼업해서 팔아먹어야 한다. 몇년 후에는 박지성이 이영표의 헛다리 집기 기술을 습득할지 누가 알겠느냐. 혹은 어떤선수는 슛 자체를 못한다.(이영표) 고로 캐릭터의 행위를 별도의 클래스로 만들어야 한다. 그 행위를 좀더 세분화 해서 "슛" 의 행위인지, "코너킥" 의 행위인지, "파울" 의 행위인지 디테일하게 세분화 해서 만들면 좀더 oop에 가깝다 할수 있겠다.

일단 예로 허접한 코딩스타일 보여주겠다.

class player {
   int age,
   int height;
   ...
   void shoot() { real coding..}
   void foul() { real coding..}
   void throwing() {...}
   ...
}

class park extends player {
    void shoot() { // 오버라이딩 }
    void foul() { // 오버라이딩 }
}

class kihyun extends player {
    void shoot() { // 오버라이딩 }
    void foul() { // 오버라이딩 }
}

class youngpyo extends player {
    void shoot() { // 에잉 난 슛 못하는데. 누가 나 호출하면 어떻겡 ㅜ.ㅠ 걍 익셉션 던져버려? }
    void foul() { // 오버라이딩 }
}

첫번째 super 클래스 격인 player가 concrete클래스이건 abstract 이건 사실 별반 도움 안된다.
물론 abstract가 물론 저 위의 경우 당연히 낫다고 볼수 있다. 그럼 그렇게 바꾸도록 해봐라.
허나, 플레이어 선수들이 한두명도 아니고, 코드 재사용을 볼수도 없거니와, 나중의 변경할때도 실제 클래스를 변경해야 한다는 위험성을 가지고 있다. 장점보다는 단점이 훠벌나게 많다고 볼수 있다. 그러나 저 위의 코드는 oop입문단계에서 허벌나게 많이 볼수 있는 코드 아닌가? ㅋㅋㅋ 오버로딩의 전형적인 코드이기 때문에, 왓에버

자 이제 저위의 스타일의 문제점을 알고 한단계 업그레이드 버젼을 사용해 보자. (상속방법은 될수 있으면 하지 말자)

위의 player클래스에서 shoot(), foul() 함수가 flexible한 것이기땜시, 별도로 빼야 할것같다는 생각이
머리속에서 용솟움 치고 있다.

그럼 어떻게 해야 할까?

자 이렇게..

class player {
   int age,
   int height;
   ...
   void throwing() {...}
   ...
}

class park extends player {
    void shoot()  // 더이상 오러라이딩 아니에엽!!!
    void foul()  // 오러바이딩 아님 밑에꺼도 마찬가지
}

class kihyun extends player {
    void shoot()
    void foul()
}

class youngpyo extends player {
    void shoot()
    // void foul()  => 헛 실수로 주석처리 해버렸네, 그래도 에러 안나서 몰랐엉(compile time)
                             흑.. (runtime에러때 깨달았지) 조금만 더 빨리 알았더라도...
}

자 여기서 문제점, 머야 이거? 이건 더문제잖아? 선수마다 꼭 shoot()하고 foul()을 가져야 한다는 개런티도 없고, 더 악화됐네?

아 ~ 그래 이럴때 인터페이스 이용해야지...(인터페이스 왈: 내 자식들은 내가 가지고 있는거 만들어 주지 않으면 컴파일 안시킬꺼야!!)

그럼 이렇게...

interface shootable {
  public void shoot();
}

interface foulable {
  public void foul();
}

자 박지성, 이영표, 설기현 아그들아 내 인터페이스 가져다가 써라, 그래야만 내가 너네들이 꼭 저함수를 구현했다는 사실을 믿을 수 있걸랑. ~

so....

class kihyun extends player implement shootable, foulable {
    void shoot()
    void foul()
}

class youngpyo extends player implement foulable {
     //void shoot()  -> 난 슛을 못쏘니, 차라리 날 빼주삼..응 그래서 너 뺐어 ㅋㅋ
     void foul()  //인터페이스를 상속받았으니, 난 무조건 파울만 하라는거야?? 어쩌라구? ㅋㅋ(이영표
                        안티 제작)
}


오~~그래 바로 이거야... yikes~
근데 문제는.....더 심각하다. 코드 재사용은 말할 것도 없거니와 player라는 상위 클래스를 가지고 내가 자식넘들이 가지고 있는 shoot()슛소기 와 foul()파울저지르기 에 접근할 수 없다니...내가 자식넘들을 잘못 키웠구나 (ㅋㅋㅋㅋ -_-;;) (((잠깐 여기서;; 이문제는 정말 커다란 문제다. 다형성의 이점을 살리지 못하는 코드로 바뀔 우려가 있다. shoot()을 호출할 수 있는건 자식넘들 뿐이니 자식을 직접 생산해야 하지 않겠는가...설기현이면 설기현 , 박지성이면 박지성을 내가 손수 코딩으로 , new 박지성() 이렇게 쳐야 하다니...말도 안대.....아니 그럼 너가 new 박지성() 안하고 박지성을 탄생시킬 수 있다는 말이야??? 아 -_- 그건 아니고, 언젠가 당연히 저런 코드야 있어야 겠지, 근데 저런 스페시픽한 코드를 최소한으로 줄이면서 코드 곳곳에 흩어져 있기보다는, 한곳으로 몰고, 최소한으로 줄이고..그래야 나중에 유지보수,버그최소화,확장성, 등등 쉽게쉽게 할수 있으니 ....아 먼말인지 감이온다..소프트웨어설계 란게 이런거였구나? ㅋㅋ)

자 그럼 두번째 버전에는 더 많은 문제점이 있었으므로 세번째 단계로 가보자

아 아무리 생각해봐도, 가변적인 저런 행동하고, 원래 축구선수로서 가지고 있어야 할 다른 행동하고 어떻게 구분한단 말이야...상속을 통해서는 전혀 해결할 수 없었고 말이야.. 두번째 버젼은 자식들만 가변적인 행위를 가지고 있었기 때문에, 부모가 자식의 함수에 접근할 수가 없었고....흠...도무지 모르겠어.
아무래도 gg쳐야 할꺼같애..그때 갑자기 산신령이 나타나서 한마디 왈, 함수를 모두 밖으로 빼버려라. 툭 한마디 내뱉고 사라졌으니.... 아 ...그렇게 하면 될꺼 같은데? 놀구있네..ㅋㅋ 미리 알고있었으면서..ㅡㅡ;;

그래 그럼 어떻게?

별도의 가변적인 행위에 해당하는 클래스를 제작하기에 이르렀다.

물론 인터페이스의 장점을 살려서.. 이렇게

class Shoot_JisungPark implements Shootable {
       public void shoot() { // real coding such as 공이 떼굴떼굴 굴러간다 슛이였으나...
}

class Foul_JisungPark implements Foulable {
       public void foul() { // real coding such as 반칙을 하더라도 얌전하게 반칙을 한다...
}

자 감이 오는가? 그럼 맨 상위 클래스를 다시 가보면..

class player {
   int age,
   int height;
   ...
   void throwing() {...}
   ...
}

자 여기에서 무엇을 추가시켜 줘야 겠는가? (자식들의 가변행위함수에 접근 하기 위해서)...
바...바로 인스턴스 변수만 추가해주면...으윽 이렇게 쉬운것을..
이렇게..

class player {
   int age,
   int height;
   ////////////////// the part added
   Shootable shootBehavior;
   Foulable foulBehavior;
   //////////////////

   ///////////////// the part added
   void performShoot() { shootBehavior.shoot(); }  // 참조변수 객체에 실제 행위 위임
   void performFoul() { foulBehavior.foul(); } //
   /////////////////
   ...
   void throwing() {...}
   ...
}

자 위에 새롭게 추가된 두개의 변수를 가지고 이제 새로운 클래스인스턴스를 가리킬 수 있게 되었다.
저 코드를  살펴 보면, 내가 가지고 있는 넘이 박지성 슛클래스인지, 설기현껀지 신경 안써도 된다.
단지 정해진 인터페이스를 통해서 호출만 하면 끝.

박지성 클래스 코드를 살펴 보면,

class park extends player {
    public park() { // constructor
        shootBehavior = new Shoot_JisungPark();
        foulBehavior = new Foul_JisungPark();
    }
}
요런식으로 ...

아 근데 저기에서도 특정 박지성슛을 할당하고 있네? 특정스페시픽한 클래스 할당 안좋다메? 아~~
물론 맞는말인데, 다음 패턴을 배우고 나서 설명을 해야 하기 땜시 일단은 저걸로 만족하자.

자 그럼 나중에라도, 박지성이 박지성슛만 쏘지 않고, 가끔 설기현 슛도 싸줘야 하고, 아주 100번에 한번꼴로, 베컴슛도 쏴줘야 하기 때문에, 동적으로 재할당 해줘야 하는 메서드만 추가해주면 거의 끝난다.

class player {
   int age,
   int height;
   Shootable shootBehavior;
   Foulable foulBehavior;  
   void performShoot() { shootBehavior.shoot(); }  // 참조변수 객체에 실제 행위 위임
   void performFoul() { foulBehavior.foul(); } //
   ///////////////// this part added now
   void setShootBehavior(Shootable sh) { this.shootBehavior = sh ;}
   void setFoulBehavior(Foulable foul) { this.foulBehavior = foul;}
   /////////////////
   ...
   void throwing() {...}
   ...
}

아 위의 클래스를 abstract로 만드는것이 좋겠군요..좀더 이치에 닿네요.

자 거의 완성이다 그럼 실제 클라이언트에서는 어떻게 호출하는지 알아보자.

public class StrategyPattern {
  public static void main(String[] args) {
     Player parkJiSung = new park();
     parkJiSung.performShoot();  // 박지성슛을 쏘고있습니다...
     .....
     parkJiSung.performFoul(); // 박지성이 상대편을 얌전히 반칙을 저지르는 군요..
     .....
     .....
     // 박지성도 여러번 박지성 슛 쐈으니 가끔 베컴슛도 싸줘야 하지 않겠어?
     parkJiSung.setShootBehavior(new 베컴스타일클래스());
     parkJiSung.performShoot(); // 오 박지성이 베컴스타일 슛을? 오~
     ...
}}
감이 오는가요?
내가 박지성슛클래스, 베컴슛클래스 이렇게 재미를 위해서 클래스 이름을 지었지만,
실제 만들때는, StyleofShoot0, StyleofShoot1...이렇게 해도 될것이고, 저런것들을 미리미리 만들어놔서 컨테이너에 보관해 놓은다음, 아무거나 빼서 실행시켜도 되고, .....
자 이 패턴의 장점은 알았을테고....단점은 없느냐?  패턴세계에서는 단점없는 패턴 없다. 모든지 트레이드오프가 있기마련이라.. 앗 근데 박지성슛 클래스와 박지성 클래스와 의존관계가 없다는 것은 알겠는데(좋은것임), 박지성의 기분상태에 따라 슛의 정확도라든지, 슛의 날카로움이라든지 그런건 어떻게 해야하지? 아 이런것까지 고려해서 당현히 게임만들어야 하는거 아닌가? 당근이죵.. 그건 박지성의 컨디션이란 변수를 가지고(있다고 가정) 그 변수에 따라 행위가 변하는 것을 만들면 되겠네요...(State pattern) 나중에 설명하겠노라.. 일단 이패턴은 여기까지 하고 나중에 다른패턴도 하고 나서 실질적인 세세한 테크닉과 함께..다음기회에..

다른 패턴을 하나하나 살펴 보면서 이런 코드드를 좀더 나이스하게 바꿀수도 있다.
단점에 대해선 다음장에서 설명하겠다.

일단 오늘은 여기까지하고....
그리고 읽어보신 분들은 피드백 부탁해영......

 




 

Posted by 정해권 Trackback 0 Comment 1


티스토리 툴바