ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [생성 패턴] Factory Method
    CS/디자인패턴 2021. 5. 18. 21:49
    클래스 인스턴스의 생성과 사용 부분을 분리하여 관리하자.

     

    팩토리 메소드의 기본 구조는 아래와 같습니다.

     

    Factory Method Pattern

    UML 다이어그램을 봐도 실제로 어떻게 동작하는 건지 잘 이해가 어려운데요.... 저만 그런가요..? 그래서 예제를 살펴보면서 어떤 구조이고, 어떤 장점이 있는지 살펴보려고 합니다.

     

    아래와 같이 동일한 Interface를 가진 세 가지 Parser(Response, Error, Feedback Parser)를 만들었다고 가정해봅시다.

    public interface Parser {
        public String parse();
    }
    
    public class ResponseParser implements Parser {
        @Override
        public String parse() {
            return "Response Message";
        }
    }
    
    public class ErrorParser implements Parser {
        @Override
        public String parse() {
            return "Error Message";
        }
    }
    
    public class FeedbackParser implements Parser {
        @Override
        public String parse() {
            return "Feedback Message";
        }
    }

    이 파서를 이용하는 사용자는 자신의 상황에 따라서 ResponseParser를 사용할 수도 있고, ErrorParser 혹은 FeedbackParser를 사용해야 할 수도 있습니다. 이를 if구문을 이용해서 각 상황에 필요한 파서를 제공받도록 아래와 같이 코드를 작성했습니다.

    public class DisplayService {
        public void display(String type) {
            Parser parser;
    
            if (type == 'res') {
                parser = new ResponseParser();
            } else if (type == 'err') {
                parser = new ErrorParser();
            } else if (type == 'feedback') {
                parser = new FeefbackParser();
            }
    
            String msg = parser.parse();
            System.out.println(msg);
        }
    }

    어떤 파서가 필요한지 명시적으로 호출하는 이 상황이 불편하게 느껴집니다(난 그냥 Parser가 필요한 것뿐인데...). 또한 파서의 종류가 늘어난다면? 혹은 줄어든다면? 그때마다 파서를 할당하는 부분에 수정해야 하는데, 지금이야 display()가 굉장히 간단하니 어떤 부분을 수정해야 하는지 명확하지만, display()가 복잡하면 복잡할수록 수정해야 하는 부분을 찾기 어려울 수 있습니다.

     

    어떻게 해야 할까... 객체지향의 원칙 SOLID에서 OCP를 떠올려봅시다.

    • OCP: 변경이 자주 일어나는 부분을 따로 관리하자.

     

    사실, 굳이 SOLID 원칙이 아니더라도 무엇인가 자주 변경되는 부분을 따로 떼어내는 것은 유지보수 측면에서 좋은 효과를 얻을 수 있습니다. 그러니 저 부분을 따로 떼어 함수화 하여 아래와 같은 구조로 display()를 작성할 수 있습니다.

    public class DisplayService {
        public void display(String type) {
            Parser parser = getParser(type);
            String msg = parser.parse();
            System.out.println(mgs);
        }
    
        public Parser getParser(String type) {
            if (type == 'res') {
                return new ResponseParser();
            } else if (type == 'err') {
                return new ErrorParser();
            } else if (type == 'feedback') {
                return new FeedbackParser();
            }
        }
    }

    새로운 파서가 추가되거나 삭제될 때마다 수정해야 하는 코드가 명확해졌고, display() 입장에서도 파서를 사용하여 parse()를 한다는 간단한 사실만 표현할 수 있게 되었습니다. 

     

    현재까지의 구조는 다음 그림과 같은 모습입니다. 

    • Product = Parser
    • ConcreateProduct = ResponseParser, ErrorParser, FeedbackParser
    • Creator = DisplayService

     

    아직까지는 함수로만 따로 분리시킨 것에 불과하고 디자인 패턴이라 하기엔 아직 부족합니다. 이제 DisplayService가 Parser와 마찬가지로 Response, Error, Feedback으로 구분된다고 생각해봅시다.

    public class ResponseDisplayService {
        public void display() {
            Parser parser = new ResponseParse();
            String msg = parser.parse();
            System.out.println(msg);
        }
    }
    
    public class ErrorDisplayService {
        public void display() {
            Parser parser = new ErrorParse();
            String msg = parser.parse();
            System.out.println(msg);
        }
    }
    
    public class FeedbackDisplayService {
        public void display() {
            Parser parser = new FeedbackParser();
            String msg = parser.parse();
            System.out.println(msg);
        }
    }

    이 코드가 틀리다고 할 수는 없지만, display()의 내용이 파서 생성자만 제외하고 같은 코드입니다. 만약 display()에 어떤 작업을 추가한다고 생각하면, 모든 DisplayService()를 돌아다니며 해당 작업을 추가해야 하는 문제가 있습니다.

     

    공통의 동작을 정의하기 위해서 상속 구조를 사용할 수 있고, DisplayService를 다음과 같이 만들 수 있습니다.

    public abstract class DisplayService {
        public void display() {
            Parser parser = getParser();
            String msg = parser.parse();
            System.out.println(msg);
        }
    
        public abstract Parser getParser();
    }

    여기서 핵심은 추상 함수를 사용하여 파서가 생성되는 부분에 파서가 반환될 것이라는 사실만 알려주고, 실제 파서의 생성은 각 하위 클래스에서 담당한다는 것입니다.

    public class ResponseDisplayService extends DisplayService {
        @Override
        public Parser getParser() {
            return new ResponseParser();
        }
    }
    
    public class ErrorDisplayService extends DisplayService {
        @Override
        public Parser getParser() {
            return new ErrorParser();
        }
    }
    
    public class FeedbackDisplayService extends DisplayService {
        @Override
        public Parser getParser() {
            return new FeedbackParser();
        }
    }

    여기까지 작성된 코드를 다이어그램으로 그리면 아래와 같은 모습이 됩니다.

    맨 처음 보여준 그림과 동일한 구조가 되었습니다.

    'CS > 디자인패턴' 카테고리의 다른 글

    [생성 패턴] Singleton  (0) 2021.05.25
    [생성 패턴] Builder  (0) 2021.05.21
    [생성 패턴] Prototype  (0) 2021.05.20
    [생성 패턴] Abstract Factory Method  (0) 2021.05.19
    [디자인 패턴] 개요  (0) 2021.05.17

    댓글

Designed by Tistory.