본문 바로가기

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

객체지향의 탄생-Template Method Pattern

 

  1. 템플릿 메소드 패턴(Template Method)

1. 나는 군대를 전산 장비 정비 하사로 오랫동안 보냈다. 나는 어느 초대형 전산 장비를 정비하는 특기를 수행했다. 만약 5분이상 장비가 멈추면 대한민국 안보가 구멍날 정도로 중요한 장비였다. 처음 내가맡은 역할은 조수였다. 팔뚝에 굵은 근육이 인상적인 젊은 중사/상사 또는 나이 지긋한 잔뼈가 굵으신 원사/준위들이 대형 장비를 어두운 구석에서 정비하면 나는 옆에서 트러블 라이트라는 전구를 들고 고참이 능숙하게 정비하는 모습을 보좌했다. 때로 고참들은 인상을 쓰고 입에 담배를 피면서 멋지게 장비를 정비하곤 했는데 그때 담배연기 참는 모습을 내색하지 않고 조용히 보좌해야 했던 시절은 지금에 와서 아늑한 추억이 되었다.

 

군대시절 정비사로 일하면서 많이 느끼고 배웠던 것은 군대는 작업을 수행할때 유독 매뉴얼, 절차대로 수행하는것을 중요하게 강조한다는 것이다. 예를들어 한달에 두어번 전산 장비 전체를 OFF하고 예방 정비를 수행하는 '정비의 날' 이 있다. 정비의 날에는 장비의 어느 요소를 어떻게 정비할것인지 매뉴얼로 완전하게 정리 되어 있어 정비 부사관들은 이 매뉴얼대로 예방정비를 수행했다. 이런 체계적인 예방정비 활동 덕분에 대부분 365일 장애없는 장비 운영을 지원할수 있었다.

 

내가 조수 딱지를 떼고 처음 책임지고 당직근무를 스던 어느 때였다. 당직근무 섰을때 만약 전산 장비에 장애가 생긴다면 나는 반드시 5분내에 해결해야 했다. 처음에는 긴장하여 장애시 절차를 달달 외우고 다녔는데 이제 말년이 되니깐 느긋해져서 부하사병과 라면을 먹고 TV보면서 썰~을 풀고 있었다.

 

그때 갑자기 장비실에서 달려오는 모니터 사병, "김하사님 큰일 났습니다. 노 데이터(시스템 다운)" 입니다. TV보며 간식 먹던 나는 이 충격적인 정보를 받아들이는데 잠시 숨이 꽉 막히는 느낌이었다. 정신을 차려보니 장비앞에 서 있었다. '어떻게 해야 되지..어떻게…'

 

처음 당직근무를 책임지고 슬때 달달외우던 절차를 떠올렸다. 그래..1. 에러 로그를 확인한다. 2. 대부분의 경우 리부팅 하면 해결되는데 이 사항에 되는지 확인한다. 3. 정해진 절차에 의해 리부팅을 수행한다. 4. 리부팅 하는 동안 정비반장님께 보고한다. 5. 리부팅 후 정상작동되는지 정해진 절차에 의거 골고루 확인한다. 6. 복구 했음을 정비반장님께 보고한다. 7. 에러 발생원인과 복구 상황을 사령부에 보고한다. 다행히 이 절차를 달달 외우고 있었던 덕분에 무사히 해결했다.

 

템플릿 메소드 패턴은 군대 시절 달달 외웠던 업무 매뉴얼과 같다.

 

[장애등의 비상상활 발생시 완전한 매뉴얼이 있으면 매뉴얼대로 실행하면 된다.]

 

2. 만약 노데이터 발생시 정형화된 해결 방법이 매뉴얼화 되지 않았다면 당시 나처럼 경험부족한 초급 간부나 모니터 사병은 당황하여 어쩔줄 몰라서 그 긴장되는 상황을 해결하기 쉽지 않았을 것이다. 그래서 심지어는 국가안보에 구멍이 뚫리는 심각한 상황도 발생했을지 모른다.

 

3. 매뉴얼이 철저하게 정리되어 있고 철저하게 교육이 되어 있기 때문에 왠만한 위기 상황은 어린 초급간부나 사병도 충분히 해결할 수 있었다.

 

군대 시절 능숙하지 않은 사병도 쉽게 임무 수행하도록 도와주는 매뉴얼은 요즘 IT기업에서 많이 쓰이는 프레임워크와 비슷하다. 프레임워크는 군대시절 매뉴얼처럼 정해진 틀을 만들어놓고 개발자가 편리하게 활용할만 라이브러리를 만든 다음, 개발자가 이 틀을 준수하면서 로직을 작성하게끔 유도한다. 그래서 초급 개발자라도 정해진 규칙을 자연스럽게 지키게 유도하기 때문에 요즘 전산 개발 현장에서는 대부분 프레임워크를 쓴다.

 

프레임워크에 특히 템플릿 메소드 패턴이 유익하게 쓰인다. 프레임워크 최상위 추상 클래스에 로직을 어떻게 진행할지 명시만 해놓고 개발자가 구현할 구상 클래스에서 새부 알고리즘을 구현하는 방법이 전형적인 템플릿 메소드 패턴이다.

 

[비상복구메뉴얼은 해야할 역할을 메소드 형태로 기술한다. 그리고 비상복구절차 같은 로직 흐름을 기술한 메소드에서 '해야할 역할을 명시한 메소드' 들을 활용하여 로직을 구성한다. 해야할 역할을 명시한 메소드는 인터페이스가 될수도 있고, 상속받는 클래스가 같은 로직을 쓴다면 일부는 미리 구현해도 좋다. '비상복구메뉴얼실행' 클래스는 위에 정의된 메소드를 실제로 구현하는 역할을 한다.]

 

4. 당시 군대 전산 장비의 장애 발생시 해결하는 방법은 당직근무자 마다 틀릴것이다.

 

[같은 처리 과정이더라도 초보인 김하사와 베테랑 김상사가 처리하는 방식은 다르다.]

 

잘 못하는 경우도 있고 잘 해결하는 경우도 있을것인데 해결 방법이나 순서가 틀리더라도 대부분 처리 절차는 비슷하게 흐른다. 이 비슷한 순서를 좀더 체계적이고 효율적으로 정리하여 매뉴얼로 만들면 객체지향 디자인패턴 템플릿 메소드 패턴의 모습과 같다.

 

public abstract class BasicEmergencyManual {

 

    public void recoverProcess() {

        

        confirmErrorLog(); // 에러 로그 확인

        

        boolean rebootResult = executeReboot(); // 리부팅 실행

        

        if(rebootResult) {

            boolean checkResult = normalCheck(); // 정상 작동 확인

            

            if(checkResult) {

                writeReport(); // 보고서 제출

            } else {

                // TODO 상부 처리

            }

        } else {

            callMainternance(); // 전문 정비사 호출

        }

        

        if(finalCheck()) {

            System.out.println("복구 절차 완료");

        }

    }

    

    // 에러 로그 확인

    abstract protected void confirmErrorLog();

    

    // 리부팅 실행

    abstract protected boolean executeReboot();

    

    // 정상 작동 확인

    abstract protected boolean normalCheck();

    

    // 보고서 제출

    abstract protected void writeReport();

    

    // 전문 정비사 호출

    abstract protected void callMainternance();

 

    // 마무리 체크

    protected boolean finalCheck() {

        return true;

    }

}

[비상 정비 매뉴얼, 구체적인 액션은 추상 메소드로 구현하여 상속받는 클래스에서 구현하도록 되어 있다.]

 

public class ManualExecutePeople_Rookie extends BasicEmergencyManual {

 

    @Override

    protected void confirmErrorLog() {

        System.out.println("에러 로그를 확인하는 로직..");

        System.out.println("근데 뭘 확인해야 할지 햇갈리네..");

    }

 

    @Override

    protected boolean executeReboot() {

        boolean result = false;

        

        System.out.println("장비가 고장날때는 리부팅한다..");

        System.out.println("근데 떨린다..");

        

        int tensionCount = 99; // 떨리는 정도

        if(tensionCount > 100) {

            result = false;

        } else {

            result = true;

        }

        

        return false;

    }

 

    @Override

    protected boolean normalCheck() {

        System.out.println("리부팅 후 장비 정상 유무를 체크한다..");

        System.out.println("근데 이게 제대로 돌아가는지 햇갈린다..");

        return true;

    }

 

    @Override

    protected void writeReport() {

        System.out.println("상부에 보고한다..");

        System.out.println("긴장해서 말을 더듬거린다..");

    }

 

    @Override

    protected void callMainternance() {

        System.out.println("전문 정비사 호출을 요청한다.");

    }

}

[비상복구메뉴얼을 상속받은 루키 클래스는 추상 메소드를 구현하지만, 루키답게 어설프다.

 

public class ManualExecutePeople_Veteran extends BasicEmergencyManual {

    

    @Override

    protected void confirmErrorLog() {

        System.out.println("에러 로그를 확인하는 로직..");

        System.out.println("빠르게 확인..");

    }

 

    @Override

    protected boolean executeReboot() {

        boolean result = false;

        

        System.out.println("장비가 고장날때는 리부팅한다..");

        System.out.println("눈 감고도 하지..");

        

        int tensionCount = 99; // 떨리는 정도

        if(tensionCount > 100) {

            result = false;

        } else {

            result = true;

        }

        

        return false;

    }

 

    @Override

    protected boolean normalCheck() {

        System.out.println("리부팅 후 장비 정상 유무를 체크한다..");

        System.out.println("다 정상이군..");

        return true;

    }

 

    @Override

    protected void writeReport() {

        System.out.println("상부에 보고한다..");

        System.out.println("문제 없습니다~!..");

    }

 

    @Override

    protected void callMainternance() {

        System.out.println("전문 정비사 호출을 요청한다.");

    }

 

}

[비상복구메뉴얼을 상속받은 베테랑 클래스는 비상복구메뉴얼 대로 잘 실행한다.]

 

public class TemplatePatternMain {

 

    public static void main(String[] args) {

        BasicEmergencyManual manualRookieRun = new ManualExecutePeople_Rookie();

        manualRookieRun.recoverProcess(); // 루키가 실행하면 불안

        

        BasicEmergencyManual manualVeteranRun = new ManualExecutePeople_Veteran();

        manualVeteranRun.recoverProcess(); // 베테랑이 실행하면 걱정 없

        

    }

}

[런쳐 클래스]

 

이와 같이 추상 클래스에서는 알고리즘이 해야될 일과 흐름만 명시하고 그 실제적인 구현은 구상 클래스에서 하도록 유도한다.

 

public abstract class BasicEmergencyManual {

 

    public void recoverProcess() {

        ……

        

        if(finalCheck()) {

            System.out.println("복구 절차 완료");

        }

    }

    

    …..

 

    // 마무리 체크

    protected boolean finalCheck() {

        return true;

    }

} 

 

후크 메소드는 추상 클래스에서 선언만하고 구현은 구상 클래스에서 해도 좋고 안해도 좋은 메소드이다. 후크 메소드를 사용하는 이유는 구상 클래스에 따라 알고리즘의 특정 부분을 선택적으로 사용할수 있게 도와주는 메소드이다. 예를 들어 위의 그림과 같이 사령부 보고는 해도 좋고 안해도 좋은 경우라면 후크 메소드를 사용하면 된다.

 

5. 템플릿 메소드 패턴은 우리가 의도하던 하지 않았던 많이 쓰이는 패턴이다. 특히 프레임워크처럼 정형화된 코딩을 개발자에게 유도해야 할 경우 많이 쓰인다. 매뉴얼처럼 원하는 형식으로 로직의 흐름을 유도하기 때문에 여러 개발자들과 협업 할 때 일관성 있는 개발을 유도하기 좋은 패턴이다.

 

그래서 프레임워크에서 로직이 처리되는 전체적인 알고리즘 흐름은 언제나 프레임워크 설계자가 의도한대로 제어된다. 대신 프레임워크에서 명시한 알고리즘 각 단계의 새부 로직은 프레임워크를 바탕으로 코딩하는 개발자 마음대로 할 수 있다. 결국 프레임워크 설계자도 좋고 개발자도 좋은 어플리케이션 개발 환경을 구축할 수 있다.

 

템플릿 메소드 패턴을 유심히 보면 추상 클래스에서 구상 클래스의 로직을 호출하지 구상 클래스에서 상위 클래스를 직접 호출하진 않는다. 이것은 헐리우드 원칙이라고 불리는 추상 클래스가 구상 클래스를 직접 호출해야 한다는 디자인원칙을 지킨다.

 

예를들어 추상 클래스가 구상 클래스에 의존하고 구상 클래스는 다시 추상클래스에 의존하고 마치 스파게티면처럼 의존도가 꼬인다면 어플리케이션을 분석하고 개선하고 유지보수하는데 어려움을 겪을 것이다. 하지만 헐리우드 원칙을 지키기 때문에 이런 의존성 부패라는 문제를 개선할수 있고 템플릿 메소드 패턴을 잘 사용하면 자연스럽게 헐리우드 원칙을 지킬 수 있다.

 

[템플레이트 패턴 원본]

 

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