본문 바로가기

기타/객체지향의 탄생(2013)

객체지향의 탄생- Factory Method

 

  1. 생성 관련 패턴(Creational Patterns)
    1. 팩토리 메소드 패턴(Factory Method)

1. 다시 사람사는 세상을 살펴보자면, 사람사는 세상은 역시 복잡다단하다. 사람이던 동물이던 사람이 만든 물건이던 모두 혼자서만 작동하는 경우는 드물고 대부분 다른 사물에 의존한다.

 

의존은 최대한 줄여야 좋다. 의존이 많을수록 의존된 사물이 잘못되었을때 상처입을 확률이 크다. 우리는 전산화를 통해서도 의존성을 줄여왔다. 인터넷 뱅킹은 사람이 발품팔아 은행가서 번호표 뽑고 기다리는 시간과 거리에 대한 의존을 줄여 주었다. 인터넷 정부는 각종 민원처리를 최대한 간소화하여 민원인이 민원처리에 대한 지식과 시간에 대한 의존을 줄여 주었다. 기타 다양한 IT서비스는 사람이 원하는 기능만 알고 나머지 복잡한 기능은 모두 숨겨서 사람사는 세상을 편하게 만들어주었다.

 

여지껏 강조했지만 객체간의 의존성도 최대한 줄여야 프로그래밍의 바람직한 덕목인 유연하고 확장성이 높고 유지보수하기 쉬운 어플리케이션을 만들 수 있다.

 

우리가 살고 있는 세상에서 전산화를 통해 의존성을 줄였듯이 객체지향에서도 의존성을 최대한 줄이기 위한 여러가지 방법이 존재한다.

 

캡슐화와 정보은닉이라는 객체지향의 기본 요소와 SRP, LSP 등의 객체지향 디자인 원칙을 통해서 의존성을 줄이는 방법을 알게되었다. 그런데 여기서 말하고자 하는 의존성은 객체 생성에 대한 의존성을 줄이는 방법에 대한 이야기이다. 팩토리패턴 이야기이다.

 

2. 옛날 강남에서 처음 신입사원으로 일을 시작했다. 강남은 으리으리하고 복잡하며 사람들도 많아서 시골뜨기 같은 나는 마냥 신기해하고 좋아했다. 그러나 강남에서의 점심시간만큼은 고역이었다. 먼저 강남의 식당가는 너무 다양해서 어느 식당가서 먹어야 할지 혼란스러웠다. 이 혼란스러움을 줄이려면 어느 식당이 강남 물가에 비해 그나마 싸고 맛있는지 정보 입수 과정이 필요했다. 좋은 식당을 다행히 찾아도 강남의 좋은 식당은 이미 근처의 수많은 직장인에게 소문이 퍼져있기 때문에 점심시간이면 직장인으로 장사진을 이루었다. 한참 기다리다가 겨우 자리를 잡더라도 많은 손님들속에 그나마 빨리 음식이 나오고 맛있게 먹을수 있는 것은 무엇인지 주문하는것도 고역이었다. 여러명이 가면 서로 주문을 맞추는것도 힘든 과정이었다. 이제 주문까지 마치고 오랜기다림끝에 음식을 먹고 나가면 귀중한 점심 휴식 시간 1시간은 금방 지나가 버리고 벌써 녹초가 된 기분이 들었다.

 

3. 3년쯤 경력이 되었을때 잠실에 홀로 파견나가 일을 한적이있다. 이곳도 강남처럼 으리으리하고 복잡하며 사람들도 많았고 점심을 어떻게 먹어야할지 고민도 되었다. 주변을 둘러보니 잠실 근처에는 큰 푸드코트가 있었다. 푸드코트에서의 점심 식사 과정은 처음에는 낮설었지만 그 효율적인 방식이 좋아서 푸드코트를 자주 찾게되었다. 먼저 카운터에 가서 어떤 요리점에서 어떤 음식을 먹을지 결정하여 주문을 한다. 그리고 전광판에 주문 코드를 응시하다가 음식이 나왔다가 반짝이면 해당 요리점에서 음식을 받는다. 먹는다. 그리고 끝. 나는 푸드코트의 엄청난 효율성에 감탄했다. 하지만 식사는 효율성만이 다는 아니라서 푸드코트의 음식이 맛있게 느껴지진 않았지만 점심을 빨리 먹고 쉬고 싶을때가 많았기 때문에 푸드코트를 자주 이용하였고 점심에 귀중한 낮잠도 잘 수 있었다.

 

4. 신입사원 시절 밥먹으러 다니느라 고생했던 강남의 많은 식당은 이렇게 표현한다.

 

[각각 곳곳에 떨어진 식당을 찾아야 한다.]

 

public abstract class Restaurant {

 

    public abstract void restaurantDescription();

    

    public void order() {

        System.out.println("주문하여 먹읍시다.");

    }

} 

[레스토랑 수퍼 클래스]

 

public class RestaurantHansik extends Restaurant {

 

    @Override

    public void restaurantDescription() {

        System.out.println("여기는 한식당 입니다.");

    }

 

} 

[한식당 클래스]

 

public class RestaurantIlsik extends Restaurant {

 

    @Override

    public void restaurantDescription() {

        System.out.println("여기는 일식당 입니다.");

    }

 

}

[일식집 클래스]

 

public class RestaurantYangsik extends Restaurant {

 

    @Override

    public void restaurantDescription() {

        System.out.println("여기는 양식당 입니다.");

    }

 

} 

[양식집 클래스]

 

public class Actor {

    Restaurant restaurant;

    

    public void restaurantSelect(String restaurantSelect) {

        // 어느 식당을 갈까.

        if(restaurantSelect.equals("한식")) {

            restaurant = new RestaurantHansik();

            restaurant.restaurantDescription();

        } else if(restaurantSelect.equals("일식")) {

            restaurant = new RestaurantIlsik();

            restaurant.restaurantDescription();

        } else if(restaurantSelect.equals("양식")) {

            restaurant = new RestaurantYangsik();

            restaurant.restaurantDescription();            

        }

        

        // 주문한다.

        restaurant.order();

    }

 

    /**

     * @param args

     */

    public static void main(String[] args) {

        Actor actor = new Actor();

        actor.restaurantSelect("일식");

    }

 

} 

[Actor는 여러 식당을 알아야 한다.]

 

[팩토리 패턴을 쓰지 않는 경우]

 

사람이 무수히 많은 강남의 식당의 위치와 맛과 가격을 기억했다가 어느 식당을 갈지 번거롭게 선택을 해야 한다. 일단 식당을 선택하는 부분만 분리하고 싶다.

 

[액터는 푸드코트에서 메뉴를 편하게 고른다.]

 

[팩토리 패턴 실행 예제 UML]

 

public abstract class FoodCourt {

    

    public void order(Restaurant restaurant) {

        restaurant.restaurantDescription();

        System.out.println("푸드코트에서 주문되었습니다.");

    }

    

    public abstract Restaurant getRestaurantFood();

} 

[푸드코트 수퍼 클래스]

 

public class FoodCourtHansik extends FoodCourt {

 

    @Override

    public Restaurant getRestaurantFood() {

        return new RestaurantHansik();

    }

 

}

[한식 주문 푸드코트]

 

public class FoodCourtIlsik extends FoodCourt {

 

    @Override

    public Restaurant getRestaurantFood() {

        return new RestaurantIlsik();

    }

 

} 

[일식 주문 푸드코트]

 

public class FoodCourtYangsik extends FoodCourt {

 

    @Override

    public Restaurant getRestaurantFood() {

        return new RestaurantYangsik();

    }

 

} 

[양식 주문 푸드코트]

 

public class Actor {

    public void restaurantSelect(FoodCourt foodCourt) {

        // 주문한다.

        foodCourt.order(foodCourt.getRestaurantFood());

    }

    

    /**

     * @param args

     */

    public static void main(String[] args) {

        Actor actor = new Actor();

        actor.restaurantSelect(new FoodCourtIlsik());

    }

} 

[메인함수와 Actor가 깔끔해졌다.]

 

푸드코트의 구조를 객체지향으로 표현하면 위와 같을 것이다. 일단 사람 객체가 푸드코트에 갔다고 생각하고 푸드코트에 입점한 식당과 메뉴를 확인한 다음 푸드코트 카운터에 주문을 신청한다.

 

사람뿐만 아니라 푸드코트 카운터도 편하게 일을한다. 푸드코트 카운터는 식당과 식당의 메뉴가 무엇인지 알 필요도 없고 관여할 필요도 없다. 그저 사람 객체 주문을 받고 선택한 식당의 메뉴를 리턴받으면 끝이다.

 

사람 객체와 푸드코트 카운터도 편해졌다. 위와 같이 객체 생성을 서브클래스에서 하게 되면 해당 객체가 사용하고자 하는 객체의 추상 클래스만 의존하면 되기 때문에 의존성을 줄이는 효과를 얻을 수 있다. 이것이 팩토리 패턴이다.

 

5. 여지껏 의존성을 줄이는 여러가지 방법, 캡슐화와 정보은닉이라는 객체지향의 기본 요소와 SRP, LSP 등의 객체지향 디자인 원칙을 배웠지만 이 경우 '런쳐' 클래스가 객체 조작을 해준다는 가정이 들어있다.

 

만약 객체 생성을 런쳐 클래스가 아닌 클라이언트 객체에서 해야 된다면 팩토리 패턴으로 의존성을 최대한 줄여야 한다. 팩토리 패턴에 쓰인 중요한 객체지향 디자인 원리는 '구상 클래스가 아닌 추상화된 클래스에 의존하라' 이다. 위의 푸드코트처럼 서브클래스에서 객체를 생성하게 위임하면 추상클래스는 쓰고자 하는 객체 패밀리의 역시 추상 클래스에만 의존할수 있기 때문에 이 디자인 원리를 지킬수 있다.

 

위 식당 관련 어플리케이션은 초기 사람 런쳐 객체가 지저분하게 모든 강남의 식당가를 알았어야 했는데, 중간에 푸드코트 역할 객체를 배치하여 일단 런쳐 객체가 하는 일을 깔끔하게 정리하였다.

 

다음 푸드코트 추상클래스 역시 식당 메뉴의 추상클래스에만 의존했기 때문에, 현실 세계의 푸드코트 카운터처럼 카운터 직원이 입점한 식당과 메뉴를 구체적으로 알고 관여할 필요 없이 신속하게 주문을 도와줄 수 있다. 팩토리 패턴은 런쳐 클래스와 클라이언트 객체 그룹의 의존성을 깔끔하게 정리한다.

 

[팩토리 패턴]

 

 

덧 ) 이 객체지향의 탄생 원고는 제가 책으로 내려다가 일단 잘 안되었는데요. 이유는 비문이 많다. 단락내 주제가 중복된다. 어떤 상황 설명을 과장한다.등 입니다. 그래도 원고를 일단 블로그에 몽땅 풀어보고 언젠가 제대로 교정해서 다시 도전할 생각입니다. 이점을 감안해서 읽고 객체지향을 이해하는데 도움이 되셨으면 좋겠습니다. 의견도 주셨으면 좋겠습니다. 원고 조금만 교정하면 괜찮을것 같은 출판사 관계자분의 피드백도 환영합니다. 매주 월요일 발행 예정입니다.