본문 바로가기

Design Pattern

[디자인 패턴_Again] Factory Pattern. - (1) -

팩토리 패턴은 싱글턴 패턴과 함께 이미 상당히 널리 쓰이고 있는
디자인 패턴입니다.

이 팩토리 메소드 패턴에는 3가지 유형이 있습니다.

헤드퍼스트 디자인 패턴의 글을 인용해보면

심플팩토리와 추상 팩토리 패턴 그리고 팩토리 메소드 패턴입니다.

이 책에서는 엄격히 얘기하면 심플 팩토리는 패턴은 아니라고 합니다..

하지만 아마 가장 많이 쓰이는 방식이 이 심플 팩토리가 아닐까 싶습니다.

그래서 팩토리 패턴은 3번에 나눠서 내용을 정리하려고 합니다.

하나에 전부 정리하려니 은근 빡세서요 -_-;

일단 심플 팩토리 먼저 정리해보겠습니다.

이 패턴은 추상화를 이용합니다. 그래서 하나의 구현 클래스에 집중되어 있는 의존도를

끊어버리는 것이죠. 결국 "나는 상세 구현 클래스가 무엇인지는 관심없다."

입니다.

아래와 같은 클래스를 보겠습니다.


public abstract class Animal {
 String name;
 
 public abstract void sing();
}

추상클래스 Animal을 만들고 이것을 상속해서 개,고양이,오리 클래스를 만듭니다.

public class Cat extends Animal {

 @Override
 public void sing() {
  System.out.println("야옹!");  
 }
}

--------------------------------------
public class Dog extends Animal {

 @Override
 public void sing() {
  System.out.println("멍!");  
 }
}
---------------------------------------
public class Duck extends Animal {

 @Override
 public void sing() {
  System.out.println("꽥!");
 }
}

그리고 이 동물들이 소리를 내도록 하는 클래스가 있습니다.

public class AnimalSing {
 String animal;
 Animal a;
 
 public AnimalSing(String animal) {
  this.animal = animal;
 }
 
 public void listen() {
  if("dog".equals(animal)) {
   a = new Dog();
   a.sing();
  } else if("cat".equals(animal)) {
   a = new Cat();
   a.sing();
  } else if("duck".equals(animal)) {
   a = new Duck();
   a.sing();
  } else
   System.out.println("이 동물은 뭔가요?");
 }

}
그래도 상속도 이용했고 해서 구현이 되어있지만, 문제는 AnimalSong의 listen 메소드입니다.
저정도의 클래스 갯수라면 AnimalSing 클래스에서 저렇게 되어 있는 것도 나쁘지는 않을 것입니다. 무조건 패턴화 시킨다고 좋은 것은 아니니까요.

어쩔때는 불필요하게 복잡도를 증가시키는 경우도 있습니다.

하지만 동물클래스가 엄청 많다거나
혹은 이제 더 이상 고양이는 쓰지 않는다고 해서 위 메소드에서 고양이 부분을 지워야 한다는등의
작업 요건이 생기게 되면 (그리고 이런 변화는 항상 생기게 마련이죠..)
문제가 생깁니다.

물론 어디선가는 위의 코드처럼 각각의 동물 클래스를 생성해서 넘겨주는 부분은 필요합니다.

하지만 그 부분이 위의 AnimalSing 클래스는 아니라는 거죠.

AnimalSing 클래스의 listen메서드는 말그대로 Animal (상세가 뭔지는 몰라도) 을 상속한 동물 클래스들의 소리만 들려주면 됩니다. 그 동물들이 뭐냐는 것까지 AnimalSing 클래스가 판단해서 생성 할 필요까지는 없는거죠. 위의 경우 단순히 sing()메서드만을 호출하기 때문에 간단해 보이지만 이런 내용이 복잡해져서 길고 긴 코드가 listen메서드에 들어가 있다면

단순히 고양이 클래스를 생성하는 부분을 수정하려 했다가 다른 동물들의 소리까지 못 듣게 되버리는 상황이 발생 할 수도 있습니다.

원래 더욱 안 좋은 케이스의 코드는 Animal이라는 추상 클래스조차 사용하지 않은 것이지만
(어떤 케이스일까요..?)
거기부터 얘기를 하면 리팩토링에 대한 얘기가 되어버리기 때문에 ... 그냥 Animal 클래스를 사용해서 상속을 이용해 구현해놓은 코드를 예제로 만들어 보았습니다.

그래서 사용하는 것이 심플 팩토리입니다.

공장 내부에서 무엇을 하는지는 모릅니다. 그냥 Animal이 올 것이라는 것만 알죠.
그리고 그 Animal은 sing()이라는 메서드를 가지고 있고, 받는 입장에서는 (AnimalSong)
그냥 그 sing메서드를 호출하면 됩니다. 이렇게 함으로써 AnimalSong 클래스와
Dog,Cat,Duck 클래스들은 의존성이 약해지게 되는 것입니다.

Animal 이라는 추상클래스 덕분에요.

심플 팩토리는 아래와 같이 작성 될 것입니다.

public class AnimalFactory {
 
 public Animal getAnimal(String animal) {
  if("dog".equals(animal)) {
   return new Dog();
  } else if("cat".equals(animal)) {
   return new Cat();
  } else if("duck".equals(animal)) {
   return new Duck();
  } else
   return null;
 }
}

이름을 받아서 해당되는 클래스의 객체를 넘겨줍니다. Animal형이죠.

사용되는 부분은

public class AnimalSing {
 String animal;
 Animal a;
 
 public AnimalSing(String animal) {
  this.animal = animal;
 }
 
 public void listen() {
  AnimalFactory af = new AnimalFactory();
  Animal a = af.getAnimal(animal);
  a.sing();
 }
}
이런식으로 구현됩니다. 중요한 것은 이 클래스에서는 Cat이나 Dog나 Duck에 대해서는
아무것도 모른다는 것입니다. 단지 넘어올 타입이 Animal이라는 것만 알고 있으면 되는거죠.

만약에 sing()메서드의 시그너쳐가 변하게 되면 당연히 이 부분도 수정이 되어야 하지만
그렇게 인터페이스(추상클래스와 인터페이스를 모두 지칭합니다)가 수정되는 경우는 어쩔 수 없습니다. (그래서 인터페이스 설계가 중요합니다.)

public class Test {
 public static void main(String[] args) {
  AnimalSing as = new AnimalSing("dog");
  as.listen();
 }
}
멍!


간단한 테스트 코드는 위와 같습니다.

위의 경우 name이 미리 정의되어 있는 단어가 안들어가면(dog,duck.cat 이외의 단어) 사실 예외가 발생합니다. 이런 경우 enum을 사용하면 도움이 될 수 있습니다.