레이블이 design pattern인 게시물을 표시합니다. 모든 게시물 표시
레이블이 design pattern인 게시물을 표시합니다. 모든 게시물 표시

2019년 11월 20일 수요일

[java] 상속 대신 구성을 사용하자.

왜 이 글을 쓰는 걸까

나는 자바 개발자로 일을 하면서 주위 사람들과 계속 이야기 하였다. 하지만 다들 수긍을 하는 듯 하면서 절대 바뀌지 않는 것이 있었다. 바로 **상속 대신 구성**이라는 말이다. 꽤나 오랜시간 구성을 사용하자고 설득하였지만 아무도 변화시키지 못했다. 나의 능력이 부족해서 속상하면서도 한 편으로는 내 말을 듣지 않는 것 같은 동료들이 마냥 아쉬웠다.

그런 의미로 나의 주장을 정리하고 가다듬어야 겠다는 생각을 했다.

내가 처음 자바를 공부할 때부터 들어온 말이 있다. 재사용을 위해 상속을 사용하라. 코드의 재사용을 위해서는 상속을 사용해야 한다. 두 개의 코드가 중복되면 상속을 해라. 그런데 뭔가 이상한 것을 느꼈다. 한 명이 말 한 것이 아니며 나에게 자바를 처음 알려준 강사부터 일터에 나가서 까지 그들은 마치 개발의 시작과 끝은 extends라는 키워드만 알면 다 할 수 있는 것으로 설명했다.

마치 상속이라는 단어가 마법의 단어이며, 이것만 있으면 다른 것을 알 필요도 없고 객체지향이건 유지보수 가능한 구조이며 모든 것이 가능하다고 설파했다.
그런데 막상 시간이 지나면 "이건 뭔가 잘못된 구조야", "아! 이건 상속을 좀 잘못했군. 제대로 했어야지", "앗! 이건 못 고친다. 이거 고치면 다 바꿔야해!" 상속은 점점 사람들을 옥죄었고 무엇이 잘못되었는지 몰랐다.

다들 너무 착하다. 사람들은 마치 상속으로 재사용 도모하라는 책들의 말을 믿고 구조가 이상하게 되면 마치 자기 탓인 것 마냥 아파한다. 자기 머리를 쥐어뜯는다. 하지만 끝까지 상속의 잘못은 아니라고 한다.

하지만 이건 그들의 잘못이 아니다 상속이라는 상아탑을 맹신하게하는 분위기가 그들의 도전을 막는다. 상속은 왠지 클래스 사이에 절대 바뀌지 않을 거라는 약속을 하며 새끼손가락을 거는 것 같다. 장기처럼 한 번 두면 무를 수 없는 약속이었다. 나는 이런 약속을 하고 싶지는 않았다. 무조건 깨질 것인데
후에 이 상속 구조를 자유롭게 수정할 수 있을까? 정말 이 구조가 맞는 걸까? 나중에 이 구조가 틀려서 바꿔야 한다면 바꿀 수는 있는 것일까?

없을 것 같았다. 시간이 지나면 지날수록 상속으로 끈끈하게 만들어진 유대관계는 깨기가 힘들어 보였다. 상속계층의 위로 올라갈 수록 그 약속이 많아지기 때문에 수정되면 안된다. 가령 자바에서 가장 위에 있는 객체는 `Object`이다. 이 녀석은 왠만해서는 절대 바뀌지 않는다. 수많은 자식들이 자신을 쳐다보고 있다. 그 자식들(서브클래스)은 당신(Object)이 변하지 않는다는 것만 믿고 재사용을 하기로 한 것이다.

상속은 마치 서브 클래스가 부모 클래스를 위에다가 그대로 복사해서 쓰는 것과 비슷하다. 만약 부모가 바뀐다고 해보자. 서브클래스도 당연히 바뀐 것이다.


2003년도부터 혹은 그 이전부터 상속은 문제였다.

https://www.javaworld.com/article/2073649/why-extends-is-evil.html
someone : If you could do Java over again, what would you change?
Gosling : I'd leave out classes
고슬링은 클래스를 없애고 싶다고 했다. 덧붙이자면 클래스 자체에는 문제가 없으나 extends관계가 문제이며 `implements`관계를 선호하라고 한다.


어떤 개발자는 자신의 99%의 코드에 상속을 사용하지 않았다고 한다

https://codeburst.io/inheritance-is-evil-stop-using-it-6c4f1caf5117
위 블로그에 있는 내용을 인용하겠다.
Using inheritance is not the only way to extend a class behavior.
But definitely is the most dangerous and harmful one.

I do not use inheritance in the 99% of my code.
And I really mean 99%.

- use interfaces to define the contracts between your classes.
- use final classes to implement behavior for those interfaces.
- use composition (through constructor dependency injection) to put things
together and prevent complexity


[이펙티브 자바]에서는 상속 대신 구성을 사용하라고 권고한다.

자세한 건 책을 참고


[오브젝트]에서는 상속을 절대 재사용을 위해 사용하지 말라고 한다.

계층구조를 표현하기 위해 사용해야 한다고 말이다. 즉 타입을 상속하기 위해서 쓰라는 말이다.


C++창시자분도 유튜브로 상속이 없이 어떻게 개발해야 하는지 발표하는 시대가 왔다.

세상은 정말 많이 변했다. 비야넷 아저씨도 변한 세상에 따라 자신의 주장을 바꿨다.

내가 정리한 문제점

내가 생각하는 상속의 문제점은 아래와 같다.
1. equals 구현에 문제가 생긴다(추이성)
2. 상속을 할 때 부모클래스 중에 구현된 기능이 있다면, 그것을 제대로 알고 구현을 해야 한다. 이 말은 하나의 소스코드가 둘로 쪼개져 있는 것과 같다.
3. 부모클래스가 변경되면 자식클래스 또한 전부 점검해야 한다.
4. 자식클래스가 바뀔 때, 부모클래스를 안 볼 수가 없다.
5. 버그를 찾기가 힘들다. 누구의 책임인지 확인하기가 쉽지 않다. 특히 `super`를 함께 쓰게되면 마치 저 클래스 멀리 있는 코드가 내 코드처럼 행동하면서 핑퐁을 하기 시작한다. 위로갔다 내려왔다 롤러코스터를 타게 된다.

그러므로 나는 상속보다는 `Composition` 소위 구성이라는 기능이 기본이 되어야 한다고 생각한다. `Composition`은 상속과 다르게 내부 구현을 알 수 없다. 필요한 부분에만 책임을 위임한다. 내부 소스로직이 수정되었다고 다른 소스에 문제를 일으키지 않는다. 위임한 책임만 완수하면 어떻게 바뀌던 상관이 없다. 하지만 상속은 문제를 끼칠 수 있다. 구성은 그래서 수정에 자유롭다. 아니 아예 전부 뜯어 고쳐서 새로 넣어도 된다. 그렇게 하라고 의존성 주입이 있는 거니까.

방금 구성을 사용해야 하는 다른 이유가 나왔다. 구성은 런타임에 의존성이 바뀔 수 있다. 즉, 프로그래밍이 실행 중에도 내 할일을 다시 집어넣을 수 있다는 말이다. 원래라면 하늘 아래 부모클래스는 하나밖에 가질 수 없고, 그것은 컴파일 이후에는 운명처럼 거스를 수가 없는데 말이다.


결론

글을 쓰면서 내가 틀렸던 건 아닐까 하는 생각으로 정리를 해보았으나.
틀리지 않은 것 같다. 오히려 자신감이 생긴 것 같다.

상속을 쓰기 전에 구성을 할 수 있는지 확인하자. 어떻게 해서든지 구성을 사용하려고 해보자. 코드에 더 믿음이 갈 것이다. 클래스에 final을 붙여서 상속을 막아 보자. 나 자신뿐만 아니라 사람들이 그 코드를 믿게 될 것이다. 상속보다 구성으로 짠 코드를 볼 때 더 별거 아닌 코드라고 생각하는 사람들이 많다. 그것이 목적이다. 더 별거 아닌 것처럼 보이게 더 쉽게 읽을 수 있게 말이다.

2018년 2월 6일 화요일

[clojure][designpattern] 프로토타입 패턴

프로토타입은 원형틀이라고도 볼 수 있다.
기본적으로 만들어진 틀을 사용하는 것이다.
내용에서는 회원등록양식을 사용했는데 그 틀에 대한 기본틀을 프로토타입이라 하자. 여기서는 몸무게를 적지 않으면 60킬로를 기본값으로 넣는다고 한다.
그리고 중요한 것은 clone()메소드인데 왜 중요하냐면 기존에 만들어진 틀을 아주 쉽게 복사할 수 있는 것이다.

public class RegistrationForm implements Cloneable {
  private String name = "Zed";
  private String email = "zzzed@gmail.com";
  private Date dateOfBirth = new Date(1990, 1, 28);
  private int weight = 60;
  private Gender gender = Gender.MALE;
  private Status status = Status.SINGLE;
  private List children = Arrays.asList(new Child(Gender.FEMALE));
  private double monthSalary = 1000;
  private List favouriteBrands = Arrays.asList("Adidas", "GAP");

  @Override
  protected RegistrationForm clone() throws CloneNotSupportedException {
    RegistrationFrom prototyped = new RegistrationForm();
    prototyped.name = name;
    prototyped.email = email;
    prototyped.dateOfBirth = (Date)dateOfBirth.clone();
    prototyped.weight = weight;
    prototyped.gender = gender
    prototyped.status = status;

    List childrenCopy = new ArrayList();
    for (Child c : children) {
      childrenCopy.add(c.clone());
    }
    prototyped.children = childrenCopy;
    prototyped.monthsCopy = monthSalary;

    List brandsCopy = new ArrayList();
    for (String s : favoriteBrands) {
      brandsCopy.add(s);
    }
    prototyped.favouriteBrands = brandsCopy;
    
    return prototyped;
  }
}
사용자를 만들 때마다, clone()을 호출해서 기본값으로 쓴다.
왜 clone()을 사용해야 하나? 바로 자바가 나를 죽어라 헷갈리게 한 내용인 카피 문제이다.
이것 때문에 나는 getter를 만들때도 그것이 객체라면 복사를 한다. (혹시 몰라서...)


Clojure

(def registration-prototype
  {:name "Zed"
   :email "zzzed@gmail.com"
   :date-of-birth "1970-01-01"
   :weight 60
   :gender :male
   :status :single
   :children [{:gender :female}]
   :month-solary 1000
   :brands ["Adidas" "GAP"]})

;;return new object
(assoc registration-prototype
  :name "WHO"
  :email "EMAIL@DOTCOM"
  :weight 52
  :gender :female
  :month-salary 0)
clojure는 기본적으로 불변객체이기 때문에 clone은 필요없다.
그렇다면 항상 다 모든것을 final로 만들어 복사하면 느려지지 않을까?
느리긴 한데... 아주 느리지는 않다는 것이 클로저들의 주장인 것같다.

2017년 12월 3일 일요일

[clojure][designpattern] 클로저 디자인패턴 - 전략패턴

clojure design pattern 출처::http://clojure.or.kr/docs/clojure-and-gof-design-patterns.html
2. 전략패턴(Strategy Pattern)
요약 : 이름 정렬 , 유료고객은 먼저.

(def users [{:subscription true :name "Y"} {:subscription true :name "Z"} {:subscription false :name "Z"}])

(sort (comparator (fn [u1 u2]
                    (cond
                      (= (:subscription u1) (:subscription u2))
                      (neg? (compare (:name u1) (:name u2)))

                      (:subscription u1)
                      true

                      :else
                      false)))
      users)
위 소스는 comparator를 만들고 하는 것이 아주 비슷하다.
아래 소스를 봐보자.
(sort-by (juxt (complement :subscription) :name) users) ;; forward sort
(sort-by (juxt :subscription :name) #(compare %2 %1) users) ;; reverse sort
워워 하나씩 보자.
(sort-by count ["aaa" "bb" "c"])

("c" "bb" "aaa")
juxt 이녀석은 뭘까? (juxtaposition : 병렬, 병치)
((juxt a b c) x) == [(a x) (b x) (c x)]
(for [user users]
  ((juxt :subscription :name) user))
;; ([true "Y"] [true "Z"] [false "Z"])


(for [user users]
  ((juxt (complement :subscription) :name) user))
;; ([false "Y"] [false "Z"] [true "Z"])

complement (보수 말하는건가) : 함수를 받아서 true/false의 반대값을 내보냄
(def odd?? (complement even?))
(odd?? 3)
true 
이걸 먼저 알아보자
(sort-by (juxt (complement :subscription) :name) users) ;; forward sort
user=> ((complement :subscription ) {:subscription "HI"})
false
user=> ((complement :a ) {:b "HI"})
true

두 줄로 만들어지는 것이 신기하다. juxt는 엄청난 함수 인 것 같다. 나중에 쓸일이 많을 것 같다.

2017년 11월 30일 목요일

[clojure][design pattern] 클로저 - 커맨드패턴

clojure design pattern 출처::http://clojure.or.kr/docs/clojure-and-gof-design-patterns.html#command


커맨드패턴(Command Pattern)

보면 알겠지만 커맨드(명령)을 login/logout명령으로 나눠서 따로 구현했다.
구현할 때 Command 인터페이스를 구현하여 execute(실행해라!)모든 명령을 이 인터페이스가 있는 녀석으로 구현하면
뭐든 간에 execute만 실행하면 될 것이다.

(defn login [id pass] (println "id: " id ", pass: " pass))

(defn logout [id] (println "logout: " id))

(defn execute [command]
  (do
    (println "logging")
    (command)))

;; 익명인터페이스와 동급

(execute #(login "name" "123"))  

(execute #(logout "idid"))

(defn execute [command & args]
  (apply command args))

(execute login "id" "password") 
apply를 사용하는 이유는 [command & args]에서 args는 리스트형태(?)로 반환하기 때문에 이 리스트 괄호를 깨부시고 안에 들어가서 적용(apply)해야 한다.

(defn wrong-execute [command & args]
  (command args))
(wrong-execute login "id" "password")

ArityException Wrong number of args (1) passed to: user/login  clojure.lang.AFn.throwArity (AFn.java:429)
보는바와 같이 login에는 잘못된 숫자의 매개변수가 들어간 것이다.
아마
(login ["id" "password"])
요래 들어갔을 것이다. 이렇게 실행해보자.
user=> (login ["id" "password"])
ArityException Wrong number of args (1) passed to: user/login  clojure.lang.AFn.throwArity (AFn.java:429)
똑같다 이제 apply를 해보자
user=> (apply login ["id" "password"])
id:  id , pass:  password
nil
아까 apply를 이용하면 괄호들을 깨부수고 안으로 들어(?)간다고 했다.
(apply login ["id" "password"]) == (login "id" "password")
요래 들어가는 것!!! (apply를 꼭 써야 한다.)
============================

이게 왜 중요하다는 거지?

일단 커맨트패턴이 뭔지보자. (자바의 눈으로)

execute()하나를 함께 쓰기 위해서 Command라는 인터페이스를 각각 구현하는 것이다.
그러면 login/logout을 하기 위해 login,logout클래스를 만들고 Command라는 인터페이스를 구현할 것이다.

자 그렇다면 로그인을 위한 Command 인터페이스를 클래스로 구현했다. 그리고 이제 이 클래스를 사용할 것이다.
아니!
정확히는 이 클래스에 있는 Command 안에 execute을 사용할 것이다.

이 커맨드들을 어딘가에 뒀다가 가져오려면 리턴도 되야하고 매개변수로 넣어지기도 해야 할것이다.
클래스는 그럴 수 없다.(뭔줄알고!!) 메소드를 넣을 수도 없다. 인터페이스를 넣는 것이다.
execute를 사용하기 위해!!

클로저의 눈으로 보자.
일단 클로저에서는 함수를 그냥 넘기기 때문에 개념이 아예 다르다.
그냥... 뭔 만들고 넣고 하는게 아니라...
함수를 보내고 받은걸 쓰면 된다.

뭐가 더 좋다가 아니라 이 두 개념을 마음대로 노닐 수 있는 내가 되었으면 좋겠다.

2017년 5월 9일 화요일

[java][design pattern]비지터패턴은 왜 쓰는 것일까? - 2

자 이제 비지터 패턴을 이용하면 무엇이 변하는지 공부하자.
  • Visitor가 Hair들을 방문할 수 있도록 VisitableHair 인터페이스를 만들자.
  • Hair들이 VisitableHair를 구현하도록 하자. (BoldHair, BushyHair, CurlyHair, HairyHair)
  • VisitableHair를 방문할 HairShopVisitor 인터페이스를 만들자.
  • 실제로 방문해서 일을 할 헤어디자이너들을 구현하자. (Barber, Hairdresser, Stylelist)
public interface VisitableHair {
  void accept(HairShopVisitor visitor);
  //아직 HairShopvisitor가 없어서 에러가 남
}
public class BoldHair implements VisitableHair {
  @Override
  public void accept(HairShopVisitor visitor) {
    System.out.println(this + " 아무렇게나 해주세요");
    visitor.visit(this);
  }
  @Override
  public String toString() {
    return "I have a bold Hair";
  }
} 

public class BushyHair implements VisitableHair {
  @Override
  public void accept(HairShopVisitor visitor) {
    System.out.println(this + " 아무렇게나 해주세요");
    visitor.visit(this);
  }
  @Override
  public String toString() {
    return "I have a bushy Hair";
  }
}

public class CurlyHair implements VisitableHair {
  @Override
  public void accept(HairShopVisitor visitor) {
    System.out.println(this + " 아무렇게나 해주세요");
    visitor.visit(this);
  }
  @Override
  public String toString() {
    return "I have a curly Hair";
  }
}
public class HairyHair implements VisitableHair {
  @Override
  public void accept(HairShopVisitor visitor) {
    System.out.println(this + " 아무렇게나 해주세요");
    visitor.visit(this);
  }
  @Override
  public String toString() {
    return "I have a Visitable Hair";
  }
}
이제 HairShopVisitor를 만들자.
public interface HairShopVisitor {
  void visit(BoldHair boldHair);
  void visit(BushyHair busyHair);
  void visit(CurlyHair curlyHair);
  void visit(HairyHair hairyHair);
}
HairShopVisitor를 구현해서 사용하려면 저 위에 있는 모든 객체를 구현 해놔사 사용할 수 있다.
일단 하나를 구현해보자.
public class HairShopBarberVisitor implements HairShopVisitor{
  @Override
  public void visit(BoldHair boldHair) {
    System.out.println("나는 이용사입니다. boldHair 군요. 면도해드릴게요\n");
  }
  @Override
  public void visit(BushyHair busyHair) {
    System.out.println("나는 이용사입니다. busyHair 군요. 밀어드릴게요\n");  
  }
  @Override
  public void visit(CurlyHair curlyHair) {
    System.out.println("나는 이용사입니다. curlyHair 군요. 밀어드릴게요\n");  
  }
  @Override
  public void visit(HairyHair hairyHair) {
    System.out.println("나는 이용사입니다. hairyHair 군요. 밀어드릴게요\n");  
  }
}
이렇게 하면 방문자로서 하나의 로직이 추가된 것이다. 2개를 마저 만들자.
public class HairShopHairdresserVisitor implements HairShopVisitor{
  @Override
  public void visit(BoldHair boldHair) {
    System.out.println("미용사입니다. boldHair 군요. 파마(?)해드릴게요\n");
  }
  @Override
  public void visit(BushyHair busyHair) {
    System.out.println("미용사입니다. busyHair 군요. 파마해드릴게요\n");
  }
  @Override
  public void visit(CurlyHair curlyHair) {
    System.out.println("미용사입니다. curlyHair 군요. 파마해드릴게요\n");
  }
  @Override
  public void visit(HairyHair hairyHair) {
    System.out.println("미용사입니다. hairyHair 군요. 파마해드릴게요\n");
  }
}

public class HairShopHairStylelistVisitor implements HairShopVisitor{
  @Override
  public void visit(BoldHair boldHair) {
    System.out.println("스타일리스트입니다. boldHair 군요. 스타일있게 해드릴게요\n");
  }
  @Override
  public void visit(BushyHair busyHair) {
    System.out.println("스타일리스트입니다. busyHair 군요. 스타일있게 해드릴게요\n");
  }
  @Override
  public void visit(CurlyHair curlyHair) {
    System.out.println("스타일리스트입니다. curlyHair 군요. 스타일있게 해드릴게요\n");
  }
  @Override
  public void visit(HairyHair hairyHair) {
    System.out.println("스타일리스트입니다. hairyHair 군요. 스타일있게 해드릴게요\n");
  }
}
이제 테스트를 해보자.
public static void main(String[] args) {
  VisitableHair[] variable_hair = { new BoldHair(), new BushyHair(), new CurlyHair(), new HairyHair()};
  HairShopVisitor[] artists = {new HairShopBarberVisitor(), new HairShopHairdresserVisitor(), new HairShopHairStylelistVisitor()};
  
  for(HairShopVisitor artist : artists) {
    for (VisitableHair hair : variable_hair )
      hair.accept(artist);
    }
  }
답은?
I have a bold Hair 아무렇게나 해주세요
나는 이용사입니다. boldHair 군요. 면도해드릴게요

I have a bushy Hair 아무렇게나 해주세요
나는 이용사입니다. busyHair 군요. 밀어드릴게요

I have a curly Hair 아무렇게나 해주세요
나는 이용사입니다. curlyHair 군요. 밀어드릴게요

I have a Visitable Hair 아무렇게나 해주세요
나는 이용사입니다. hairyHair 군요. 밀어드릴게요

I have a bold Hair 아무렇게나 해주세요
미용사입니다. boldHair 군요. 파마(?)해드릴게요

I have a bushy Hair 아무렇게나 해주세요
미용사입니다. busyHair 군요. 파마해드릴게요

I have a curly Hair 아무렇게나 해주세요
미용사입니다. curlyHair 군요. 파마해드릴게요

I have a Visitable Hair 아무렇게나 해주세요
미용사입니다. hairyHair 군요. 파마해드릴게요

I have a bold Hair 아무렇게나 해주세요
스타일리스트입니다. boldHair 군요. 스타일있게 해드릴게요

I have a bushy Hair 아무렇게나 해주세요
스타일리스트입니다. busyHair 군요. 스타일있게 해드릴게요

I have a curly Hair 아무렇게나 해주세요
스타일리스트입니다. curlyHair 군요. 스타일있게 해드릴게요

I have a Visitable Hair 아무렇게나 해주세요
스타일리스트입니다. hairyHair 군요. 스타일있게 해드릴게요
몇 번 더 만들어보면 손에 익지 않을까 싶다.

[java][design pattern]비지터패턴은 왜 쓰는 것일까? - 1

비지터 패턴을 쓰는 이유가 뭘까?
왜 방문을 해서 로직을 추가하는 걸까? 그냥 해당 클래스에 로직을 넣어서 쓰면 안될까?
해당 클래스를 바꾸지 못할 때 쓰는 것일까? 해당 클래스들의 자료구조는 변화하지 않을 것이고 로직은 계속 추가될 것이라면 비지터가 좋을 것 같기도 하다.
여기 머리를 자르기로 하자.
곱슬, 직모, 탈모, 대머리, 금발의 긴머리, 뽀글뽀글 등등 여러가지의 머리가 있다. 이들은 모두 미용실에가서 이렇게 말한다.
어울리게 잘라주세요
이제 미용사 성향 / 머릿카락에 따라 머리를 자를 것이다. 구현은
  1. Hair, HairShop 인터페이스 생성
  2. 각 인터페이스의 구현체 생성(BoldHair, BushyHair / Barber, Hairdresser)
  3. 그리고 테스트
// 1. 인터페이스 생성
public interface Hair { }
public interface HairShop {
  void cut(BoldHair boldHair);
  void cut(BushyHair busyHair);
  void cut(Hair hair);
}
이제 인터페이스를 구현하자.
// 2. 구현체 생성
public class BoldHair implements Hair{ }
public class BushyHair implements Hair{ }

public class Barber implements HairShop {
  @Override
  public void cut(BoldHair boldHair) {
    System.out.println("Barber : boldHair");
  }
  @Override
  public void cut(BushyHair busyHair) {
    System.out.println("Barber : BusyHair");
  }
  @Override
  public void cut(Hair hair) {
    System.out.println("Barber : Hair Interface... ");
  }
}

public class Hairdresser implements HairShop{

  @Override
  public void cut(BoldHair boldHair) {
    System.out.println("Hairdressor : BoldHair");
  }

  @Override
  public void cut(BushyHair busyHair) {
    System.out.println("Hairdressor : BushyHair");
  }

  @Override
  public void cut(Hair hair) {
    System.out.println("Hairdressor : Hair Interface... ");
  }
}
이제 테스트를 해보자.
public class Test {
  public static void main(String[] args) {
    Hair[] hairs = {new BoldHair(), new BushyHair()};
    HairShop[] artists = {new Barber(), new Hairdresser()};
 
    for (Hair hair : hairs)
      for (HairShop artist : artists)
        artist.cut(hair);
  }
}
콘솔을 확인
Barber : Hair Interface... 
Hairdressor : Hair Interface... 
Barber : Hair Interface... 
Hairdressor : Hair Interface...  
이렇게 된다. 결국 머릿카락의 정보는 가질 수 없다. 왜냐하면 자바는 싱글디스패치이기 때문인데 자세한 내용은 더블디스패치관련 글을 읽어보기를 바라면서...
여하튼 비지터패턴은 해당 클래스를 변경하지 않고 Visitor들을 추가해서 일을 할 수 있게 한다.
Visitor만 새로 만들면 hair들은 새로운 로직을 추가하지 않고 로직을 추가할 수 있게 된다.
출처:
https://dzone.com/articles/design-patterns-visitor https://sourcemaking.com/design_patterns/visitor/java/1 https://www.tutorialspoint.com/design_pattern/visitor_pattern.htm

2017년 4월 23일 일요일

[java][design pattern] 더블 디스패치 패턴은 언제 쓰일까?

더블디스패치 패턴은 대체 왜 쓰이는 걸까?
자바는 싱글디스패치만을 지원한다고 한다.
그 말은 더블디스패치를 사용하려면 패턴으로 메꿔야 한다는 말인가보다.
예전에는 싱글디스패치만으로도 만족하고 살았지만 다른 많은 언어들이 실행시키는 인스턴스와 매개변수 둘 다 타입체크를 하여 멋져졌다 하니.
우리도 패턴으로 그것이 가능함을 보여야 하지 않겠나 해서 만들어 진 것이 아닌가 하는 상상을 해본다.

일단 만들어보자. 이제부터 할 일은 이렇게 된다.
  • 인터페이스 둘을 만든다. (Format, Post)
  • 인터페이스를 구현한다. (Post - Mail,SMS / Format - JPG,XML)
  • 이 두 인터페이스를 연결한다.
  • 실행해본다.
  • 일단 왜 더블디스패치가 없이 만들면 어떻게 되는지 보자.
    1. 인터페이스 둘을 만든다.
    interface Format {}
    interface Post {}
    
    2. 인터페리스를 구현한다. (총 4개)
    public class JPG implements Format { }
    public class XML implements Format { }
    
    public class Mail implements Post { }
    public class SMS implements Post { }
    
    이제 인터페이스에 필요한 내용들 추가하고 구현도 해서 이 두 인터페이스를 연결하자.
    Post인터페이스는 JPG / XML에 따라 보내는 방식이 다를 수 있다. 그래서 그 타입에 따라 다른 일을 하도록하자.
    public interface Post {
      void send(Format format);
      void send(JPG jpg);
      void send(XML xml);
    }
    
    게다가 SMS/MAIL에 따라 다를 것이다.
    public class Mail implements Post {
    
      @Override
      public void send(Format format) {
        throw new RuntimeException("Mail.send(Format format) :: 타입을 알 수 없습니다.");
      }
    
      @Override
      public void send(JPG jpg) {
        System.out.println("Mail.send(JPG jpg)");
      }
    
      @Override
      public void send(XML xml) {
        System.out.println("Mail.send(XML xml)");
      }
    
    }
    
    public class SMS implements Post {
    
      @Override
      public void send(Format format) {
        throw new RuntimeException("SMS.send(Format format) :: 타입을 알 수 없습니다.");
      }
    
      @Override
      public void send(JPG jpg) {
        System.out.println("SMS.send(JPG jpg)");
      }
    
      @Override
      public void send(XML xml) {
        System.out.println("SMS.send(XML xml)");
      }
    
    }
    
    다 되었다. 실행해보자.
    public class Test {
      public static void main(String[] args) {
        Post post = new Mail();
        Format format = new JPG();
        post.send(new JPG());
        post.send(format);
      }
    }
    
    잘 될까?
    Mail.send(JPG jpg)
    Exception in thread "main" java.lang.RuntimeException: Mail.send(Format format) :: 타입을 알 수 없습니다.
     at designpattern.doubledispatch.post.Mail.send(Mail.java:11)
     at designpattern.doubledispatch.Test.main(Test.java:13)
    
    하나는 잘 되었고, 다른 하나는 잘 되지 않았다. 왜냐하면 메소드 오버로딩은 정적 디스패치(static dispatch)를 한다. 이 말은 런타임에 타입을 동적으로 체크하는 것이 아니라 미리 컴파일 이전에 타입을 미리 제대로 정해 놔야 한다는 말이다.
    에러가 난 곳은 정확한 타입(JPG)를 넣은 것이 아니라. Format이라는 인터페이스를 넣었다. 이것이 문제가 되는 것
    그러면 어떻게 해야 할까?
    일단 자바에서는 매개변수 타입체크를 동적으로 하지않는다고 하니까 타입체크를 두 번 하도록 해보자.
    지금부터 할일은
    1. Post 인터페이스를 매개변수가 Format인 것만 남기고 삭제한다. (어짜피 이걸로 타입체크를 못함)
    2. Format 인터페이스에 Mail,SMS(Post의 구현체 전부)를 각각 매개변수로 하여 void send(T t)형식의 메소드를 추가한다
    3. Post의 구현체에서 해당 메소드를 호출한다.
    4. 실제로 로직을 실행하는 곳은 Format의 구현체들에서 각각 구현한다.
    말이 잘 전달되지 않은 것 같은데 일단 코드를 한 번 짜보자.
    // 1. 매개변수가 Format 인것 빼고 다 삭제
    public interface Post {
      void send(Format format);
    }
    // 2. Mail,SMS를 각각 매개변수로 send라는 메소드를 추가
    public interface Format {
      /*
       * 여기에서 타입체크를 하는 것이다. 원래는 Post에서 했지만
       * 한 번 더 하기 위해, Format에서 한다.
       * 
       */
      void send(Mail mail);
      void send(SMS sms);
    }
    
    이제 인터페이스를 바꿨으니 구현체들을 바꿔보자.
    // 3. 새로 만든 메소드 호출
    public class Mail implements Post {
    
      @Override
      public void send(Format format) {
        format.send(this); // 여기서 이미 this는 타입체킹이 되어있음.
      }
    }
    
    public class SMS implements Post {
    
      @Override
      public void send(Format format) {
        format.send(this);
      }
    
    }
    
    이제 실제 로직이 들어있는 곳을 보자.
    // 4. 실제 로직이 있는 곳 변경
    public class JPG implements Format {
    
      @Override
      public void send(Mail mail) {
        System.out.println("mail send JPG");
      }
    
      @Override
      public void send(SMS sms) {
        System.out.println("SMS send JPG");
      }
    
    }
    
    public class XML implements Format {
    
      @Override
      public void send(Mail mail) {
        System.out.println("mail send XML");
      }
    
      @Override
      public void send(SMS sms) {
        System.out.println("SMS send XML");
      }
    
    }
    
    한번 실행해보자.
    public class Test {
      public static void main(String[] args) {
        Post post = new Mail();
        Format format = new JPG();
        post.send(new JPG());
        post.send(format);
      }
    }
    
    과연 결과는??
    mail send JPG
    mail send JPG
    
    성공이다. 이 패턴은 비지터 패턴과 아주 유사하다고 하는데, 비지터 패턴은 내 생각에는 클래스를 변경하지 않고 새로운 로직을 추가하기 위해 필요한 패턴인 것 같고
    더블디스패치패턴은 좀 더 일반적인 기술같다. 양쪽의 타입을 제대로 체크하기 위해 만든 기술인듯하다.

    2017년 1월 26일 목요일

    [clojure] 클로저 소개글

    사내 블로그를 적어야 할 일이 있어서(할당량...) 무엇을 적을 까 하다가
    클로저에 대해 한 번 써보았다.
    좀 약파는 글이기는 한데 재미있었다.
    http://blog.naver.com/andwise/220920969200

    2017년 1월 22일 일요일

    [clojure][design pattern] 상태패턴 , 비지터 패턴


    clojure design pattern


    출처::http://clojure.or.kr/docs/clojure-and-gof-design-patterns.html#command

    3. 상태패턴

    유료/무료 사용자 차이, 잔액 계산, 유료 전환

    JAVA 
    1.두개의상태를 enum으로 만든다.
    public enum UserState {
      SUBSCRIPTION(Integer.MAX_VALUE), NO_SUBSCRIPTION(10);
    
      private int newsLimit; 
     
      UserState(int newsLimit) { this.newsLimit = newsLimit; }
      public int getNewsLimit() { return newsLimit; }
    }
    
    2. 유저를 만든다.
    public class User {
      private int money = 0;
      private UserState state = UserState.NO_SUBSCRIPTION;
      private final static int SUBSCRIPTION_COST = 30;
    
      public List newsFeed() { // enum안에 들어있는 리미트 값으로 보여준다.
        return DB.getNews(state.getNewsLimit());
      }
    
      public void pay(int money) { //돈을 넣어서 잔액을 지불하고 임계점을 넘으면 유료회원이 된다.
        this.money += money;
        if (state == UserState.NO_SUBSCRIPTION
            && this.money >= SUBSCRIPTION_COST) {
          // buy subscription
          state = UserState.SUBSCRIPTION;
          this.money -= SUBSCRIPTION_COST;
        }
      }
    }
    
    CLOJURE multimethod
     
    (defmulti news-feed :user-state)
    
    (defmethod news-feed :subscription [user]
      (db/news-feed))
    
    (defmethod news-feed :no-subscription [user]
      (take 10 (db/news-feed))) 
    ** defmulti는 무엇을 플래그로 멀티메소드를 만들 것인가. (:user-state으로 분기한다. 
    
    (def user (atom {:name "Jackie Brown"
                     :balance 0
                     :user-state :no-subscription}))
    
    (def ^:const SUBSCRIPTION_COST 30)
    
    (defn pay [user amount]
      (swap! user update-in [:balance] + amount)
      (when (and (>= (:balance @user) SUBSCRIPTION_COST)
                 (= :no-subscription (:user-state @user)))
        (swap! user assoc :user-state :subscription)
        (swap! user update-in [:balance] - SUBSCRIPTION_COST)))
    
    (news-feed @user) ;; top 10
    (pay user 10)
    (news-feed @user) ;; top 10
    (pay user 25)
    (news-feed @user) ;; all news
    

     4. 방문자 패턴 (Visitor pattern);
    public abstract class Item{ abstract void accept(Visitor v); }
    
    class Message extends Item { void accept(Visitor v) { v.visit(this);} }
    class Activity extends Item  { void accept(Visitor v) { v.visit(this);} }
    // 비지터
    public interface visitor {
      void visit(Activity a);
      void visit(Message m);
    }
    
    class PDFVisitor implements Visitor {
      public void visit(Activity a) { System.out.println("PDFExporter.export(a) - Activity");
      public void visit(Message m) { System.out.println("PDFExporter.export(a) - Message");
    }
    class ExcelVisitor implements Visitor {
      public void visit(Activity a) { System.out.println("ExcelExporter.export(a) - Activity");
      public void visit(Message m) { System.out.println("ExcelExporter.export(a) - Message");
    }
    main {
      Item i = new Message();
      Visitor v = new PDFVisitor();
      i.accept(v);
    }
    

    더블디스패치패턴이 생겨난 이유는(추측컨데) 인자 타입체크를 하지 않기 때문이다. 예를들어
    (비지터 패턴을 사용하지 않는 경우 가정 출처링크 참고)
    Item i = new Message();
    
    i.accept()를 실행하면 그것이 Activity인지 Message인지 알 필요가 없다. 왜냐하면 이놈은 타입체크가 이루어지기 때문이다. 하지만 아래 놈은 다르다.
    Format f = new PDF(); //여기서 Format은 PDF,EXCEL,GIF를 모두 다루는 놈이라 가정하자.
    i.accept(f)
    
    될거라 생각하지 말자.
    Format f는 인자로 들어와서 타입체크가 되지 않았다. (인자는 타입체크를 하지 않는다는 것이 중요하다.) 그래서 더블디스패치로 이중 타입체크를 하는 것이다. 하는 방법은 아주 단순하다.
    i.apply(f); 
    // 만약 이런 메소드를 실행하려 하는데 i는 타입체크가 되었고 f는 인자이기 때문에 타입체크가 안되있다면 이 메소드를 바로 실행하는 것이 아니다.
    f.apply(i); // 이렇게 바꿔서 f도 타입체크를 하도록 하는 것이다. 그러면 두번 타입체크가 되는 것이다.
    
    어쨋든 Visitor패턴으로 잘 만들었다.

    clojure

    (defmulti export
      (fn [item format] [(:type item) format]))
    {:type :message :content "Say what again!"}
    {:type :message, :content "Say what again!"}
    {:type :activity :content "Quoting Ezekiel 25:17"}
    {:type :activity, :content "Quoting Ezekiel 25:17"}
    
    (defmethod export [:activity :pdf] [item format]
     (println "exporter/activity->pdf" item))
    
    (defmethod export [:activity :xml] [item format]
      (println "exporter/activity->xml" item))
    (defmethod export [:message :pdf] [item format]
      (println "exporter/message->pdf" item))
    (defmethod export [:message :xml] [item format]
      (println "exporter/message->xml" item))
    
    (defmethod export :default [item format]
      (throw (IllegalArgumentException. "not supported")))
    
    ===============================================
    (export {:type :message } :xml)
    exporter/message->xml {:type :message}
    nil
    user=> (export {:type :message } :pdf)
    exporter/message->pdf {:type :message}
    
    ===============================================
    (하지만 :pdf와 :xml 사이에는 아무런 상하 관계(hierarchy)가 존재하지 않네요. 단순히 키워드일 뿐이잖아요?)
    hierarchy만들기
    (derive ::pdf ::format)
    (derive ::xml ::format)
    (isa? ::xml ::format)
    (isa? ::pdf ::format)
    
    (defmethod export [:message ::pdf] [item format] (println ":message ::pdf" item))
    (defmethod export [:message ::format] [item format] (println ":message ::FORMAT!!!!" item))
    
    (export {:type :message :content "SAYHIHIHIHI"} ::pdf)
    :message ::pdf {:type :message, :content SAYHIHIHIHI}
    nil
    user=> (export {:type :message :content "???????????"} ::xml)
    :message ::FORMAT!!!! {:type :message, :content ???????????}
    nil
    

    ::xml을 구현하지도 않았는데 만들어졌다. FORMAT으로 거슬러 올라가서 일이 실행된것!!

    ** 더블디스페치를 지원하면 비지터패턴은 의미가 없다.




    [clojure][design pattern] 클로저 디자인패턴 - 전략패턴

    clojure design pattern
    출처::http://clojure.or.kr/docs/clojure-and-gof-design-patterns.html#command

    2. 전략패턴(Strategy Pattern)

    이름순으로 정렬(단, 유료고객은 먼저)

    (def users [{:subscription true :name "Y"} {:subscription true :name "Z"} {:subscription false :name "Z"}])
    
    (sort (comparator (fn [u1 u2]
                        (cond
                          (= (:subscription u1) (:subscription u2))
                          (neg? (compare (:name u1) (:name u2)))
    
                          (:subscription u1)
                          true
    
                          :else
                          false)))
          users)
    
    위 소스는 comparator를 만들고 하는 것이 아주 비슷하다.
    아래 소스를 봐보자.
    (sort-by (juxt (complement :subscription) :name) users) ;; forward sort
    (sort-by (juxt :subscription :name) #(compare %2 %1) users) ;; reverse sort
    
    워워 하나씩 보자.
    (sort-by count ["aaa" "bb" "c"])
    
    ("c" "bb" "aaa")
    
    juxt 이녀석은 뭘까? (juxtaposition : 병렬, 병치)
    ((juxt a b c) x) == [(a x) (b x) (c x)]
    (for [user users]
      ((juxt :subscription :name) user))
    ;; ([true "Y"] [true "Z"] [false "Z"])
    
    
    (for [user users]
      ((juxt (complement :subscription) :name) user))
    ;; ([false "Y"] [false "Z"] [true "Z"])
    

    complement (보수 말하는건가) : 함수를 받아서 true/false의 반대값을 내보냄
    (def odd?? (complement even?))
    (odd?? 3)
    true 
    
    이걸 먼저 알아보자
    (sort-by (juxt (complement :subscription) :name) users) ;; forward sort
    user=> ((complement :subscription ) {:subscription "HI"})
    false
    user=> ((complement :a ) {:b "HI"})
    true
    

    두 줄로 만들어지는 것이 신기하다. juxt는 엄청난 함수 인 것 같다. 나중에 쓸일이 많을 것 같다.