본문 바로가기

CS ﹒ Algorithm/DesignPatterns

디자인 패턴 (2) Factory-Method Pattern : 팩토리 메서드 패턴

팩토리 메서드 패턴의 클래스 다이어그램

 

 

Factory Method Pattern?

 

팩토리 메서드 패턴은 인스턴스를 만드는 책임을 추상 클래스(혹은 추상 인터페이스)의 책임으로 감싼 것이다.

그렇다.. 이렇게 말하면 무슨 말인지 알 수가 없다. 지식 자랑글일 뿐.

 

직접 눈으로 보는 것이 빠르다, 설명은 뒤로 미루자.

 

 

 

 

 

딱히 쓸모 없는 Animal이라는 클래스가 존재한다.

이 클래스는 그냥 봐도 크게 쓸모가 없는데, 심지어 필드값에 따라 메서드가 추가되는 끔찍한 구조를 가지고 있으며 언제 생성자 또한 마찬가지이다.

 

아마도 디자인 패턴에 관심을 가지고 있는 단계라면 OCP(Open-Close Principle, 개방-폐쇄 원칙)에 대해서는 알고 있을 것이다. 그리고 이 클래스는 OCP를 완전히 무시하고 있다.

개방 폐쇄 원칙이 무엇인가?

 

소프트웨어 개체는 확장에는 열려있어야 하지만 수정에 대해서는 닫혀있어야 한다는 것이다.

" 엥..? run에 메서드 더 쓰는 건 확장 아닌가? "

 

 

 

 

이런 게 개방 폐쇄 원칙이다.

JDBC는 다양한 데이터베이스로 확장될 수 있지만, 그렇다고 JDBC의 코드를 추가하거나 수정하지 않는다.

 

혹시나 OCP가 헷갈리는 사람이 있을까봐 옆으로 샜다.

이제 끔찍한 Aniaml 클래스를 분해해보자.

 

 

 

 

1. Animal2에서 지저분한 검증 로직과 run 호출 시의 조건문 등 계속해서 추가되어야 하는 부분들은 모두 제거했다.

 

 

 

2. 이름에서 눈치챘겠지만, Factory Method Pattern에서 말하는 Factory Method는 바로 createAnimal이다.

계속해서 추가되거나 변경될 수 있는 공통 검증사항은 추상 클래스인 AnimalMaker가 담당하게 되었고, 각자 다른 값을 가져야하는 메서드는 각 클래스에서 구현할 것이다.

 

 

 

 

3. species를 받아서 조건문을 통해 자동으로 Rabbit이나 Turtle를 만들어주는 것도 방법이지만 나는 괜히 조건문이 싫어서 이런 방법을 택했다. 참고로 이런식으로 사용하는 아주아주 익숙한 클래스가 있다. 끝까지 읽으면 알게될 것이다.

 

 

 

4. 이제 공통된 필드와 다른 메서드, 상태를 가진 Rabbit, Turtle 클래스가 만들어졌다.

이제 다시 한 번 팩토리 메서드를 살펴보자. 

 

 

5. 만약 AnimalMaker의 구현체인 TurtleMaker가 createAnimal("복동이")를 호출한다고 가정해보자.

우선 해당 메서드에서 파라미터 값을 검증한 뒤, getAnimal을 호출할 것이다.

getAnimal은 TurtleMaker에 구현되어 있고, Turtle 클래스를 구현한다.

 

6. 이제 검증 로직이 계속해서 늘어나도, 구현체마다 같은 메서드를 구현하는 방식이 모두 달라도 Animal2 클래스는 전혀 수정하지 않아도 된다. 그리고 유저는 자신이 선택해야하는 부분을 제외하고는 건드릴 필요가 없어졌다.

 

 

 

우리는 이런 형태를 이미 진작에 본 적이 있을 것이다. (스프링을 배웠다면)

 

 

 

 

1
2
3
4
5
        BeanFactory xmlFactory = new ClassPathXmlApplicationContext("config.xml");
        String xml = xmlFactory.getBean("hello",String.class);
        
        BeanFactory javaFactory = new AnnotationConfigApplicationContext(Config.class);
        String class = javaFactory.getBean("hello",String.class);
cs

 

바로 빈 팩토리다.

빈 팩토리는 우리가 만든 AnimalMaker의 역할로써 각각 TurtleMaker, RabbitMaker를 생성해주는 것과 동일하고, getBean은 파라미터에 따라 다른 객체를 리턴해주는 createAnimal과 같다.

 

내가 BeanFactory 예제와 다른 순서로 작성해놔서 헷갈릴 수 있는데, 펼쳐서 생각해보자.

 

 

1
2
3
4
5
6
7
8
9
AnimalMaker animalMaker = new RabbitMaker();
 
Animal2 rabbit = animalMaker.getAnimal("춘식이");
 
 
 
AnimalMaker animalMaker = new TurtleMaker();
 
Animal2 turtle = turtleMaker.getAnimal("옥순이");
cs

 

이제 정말 같은 원리라는 것을 이해할 수 있을 것이다. 

 

 

스프링에서도 쓰는 건 이제 알겠는데, 그래서 왜 쓰는 것이고 장점은 무엇일까?

그것은 위에서 언급했던 OCP 때문이다. 기본적인 틀만 같다면 이제 Animal2 클래스는 끝없이 다양한 형태로 구현할 수 있으며, 구현된 클래스를 수정하거나 삭제해도 다른 곳에 전혀 영향을 주지 않는다.

또한 위 예제는 엄청나게 짧기 때문에 체감하기 힘들지만, 원래 Animal 클래스의 코드가 10배 길었다고 상상해보자. 이렇게 분리함으로써 가독성과 유지보수성에서도 이점을 얻을 수 있을 것이다.

 

팩토리 메서드 패턴은 다양하게 활용할 수 있는 디자인 패턴 중 하나이기 때문에 이해라도 하고 넘어가면 언젠가 도움이 될 것이다. (자바 11부터는 인터페이스에 private과 default 접근 지시자를 설정할 수 있게 되면서 더욱 편리해졌다.)