Chain of Responsibility 패턴이란.

어떤 프로세스가 일어났을때 그 프로세스를 처리할 책임을 적당한 대상한테 넘겨주게 됩니다. Exception을 throw하는것도 비슷한 원리인데, Chain of responsibility pattern은 예외 만이 아니라 사건에 대한 처리역할을 적당한 대상에게 가도록 해주는 기법입니다. 이에 더하여 발생한 이벤트를 체인에 속한 여러 처리자에 걸쳐 처리할 수 있습니다.

Chain of Responsibility 패턴의 장점

요청을 보낸 쪽하고 받는쪽을 분리시킬 수 있습니다.

이 패턴은 하나의 클래스의 인스턴스들간의 체인이라기보다는 여러 클래스간에 걸쳐 이루어지는 일이기 때문에 구조가 다른 클래스에 대해서 낮은 결합도로 동일한 이벤트에 대한 핸들링을 가능하게 한다는 점에서 주목할 만 합니다.

사슬에 들어가는 객체를 바꾸거나 순서를 바꿈으로써 역할을 동적으로 추가/제거 할 수 있습니다.
상황에 따라 동적으로 핸들러를 추가하거나 제거할 수 있으며, 이러한 변화가 전체구조에 아무런 영향을 주지 않는다는 점에서 객체지향적인 목적을 달성한다고 볼 수 있습니다. 해당 패턴을 적용하게된 결정적인 이유가 바로 이때문 이었습니다.

보통은 일련의 프로세스에 대해 처리 클래스를 여러개 정의해두고 한데 묶어둔 후 사건이 일어나면 맨 앞의 클래스에게 '이거 처리해줘' 하고 넘겨주는식으로 체인의 스타트를 끊습니다. 그러면 묶어둔 순서대로 프로세스를 넘겨 받으면서 처리가 가능한 클래스를 만나면 해당 클래스가 처리를 하고 아니라면 다음 클래스에게 토스토스 하면서 프로세스를 처리하게 되는 것 입니다.

객체에서는 사슬의 구조를 몰라도 되고 그 사슬에 들어 있는 다른 객체에 대한 직접적인 레퍼런스를 가질 필요도 없기 때문에 객체를 단순하게 만들 수 있다는 장점이 있습니다.

Chain of Responsibility 패턴의 단점

요청이 반드시 수행된다는 보장이 없습니다.

만약 체인이 적절하게 구성되어 있지 않다면 프로세스가 사슬의 끝을 만날때 까지 처리 되지 않을 수도 있다는 것입니다. 반드시 프로세스가 처리 될 수 있도록 적절히 사슬의 순서를 구성해야 합니다.

요청을 처리하는데 걸리는 시간을 정확히 예측하기 함들다는 단점이 있습니다.

장점으로 언급되었던 구성의 유연함이 바로 단점이 되는 순간입니다. 실제로 요청을 처리해줄 객체가 체인의 어느 위치에 있을지는 동적으로 변동 될 수 있기 때문에 실시간 시스템과 같이 시간예측이 중요한 경우 이 패턴의 적용을 재검토 해야 할 것입니다.

실행시에 과정을 살펴보거나 디버깅하기가 힘들 수 있다는 단점이 있습니다.

Chain of Responsibility 패턴의 적용

요구사항)
본 시스템의 가정 데이터 업로드 시 업로드 되는 데이터의 정합성을 검증하기 위한 프로세스로 정합성에는 데이터의 정합성, 디멘젼 코드 정합성, 손익항목의 정합성이 검증되어야 했습니다. 모든 검증은 업로드 되는 순간 모모두 검증되어야 하며, 검증 결과를 출력해야 합니다.

그림 1-1 클래스 다이어그램

샘플 소스코드

AbstractOrgDimensionValidator.java

package BP;

import java.util.List;
import java.util.Map;

public abstract class AbstractOrgDimensionValidator {

  /** 다음 실행할 검증기 */
    private AbstractOrgDimensionValidator next;

    /** 업로드 상태 */
    private List<String> uploadStatus; 

    /** 
     * @return 다음 실행할 검증기 존재 여부
     */
    private boolean hasNext() {
        return this.next != null;
    }

    /**
     * @param next
     */
    protected void setNext(AbstractOrgDimensionValidator next) {
        this.next = next;
    }

    /**
     * @param uploadStatus
     */
    protected void setUploadStatus(List<String> uploadStatus) {
        this.uploadStatus  = uploadStatus;
    }

    /**
     * @param orgRecord 
     */
    protected void executeNext(List<Map<String, String>> dataset) {
        if (hasNext()) {
            this.next.setUploadStatus(uploadStatus);
            this.next.validate(dataset);
        }
    }    

    /**
     * 각 검증기 내부에서 구현해야할 검증부
     * @param dataset
     */
    abstract void validate(List<Map<String, String>> dataset);  
}

OrgDatasetValidator.java

/**
 * 조직별 데이터셋 검증기
 */
package BP;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class OrgDatasetValidator {
 /**
  * @param args
  */
 public static void main(String[] args) {
  //검증에 사용 할 데이터셋과 업로드 상태를 정의하고 생성한다. 
  List<Map<String, String>> orgDataset = new ArrayList<Map<String,String>>();
  List<String> uploadStatus = new ArrayList<String>();
  orgDataset = makeValidateData(orgDataset);

        // 코드 검증기
        OrgDimensionCodeValidator codeValidator = new OrgDimensionCodeValidator();

        // 손익항목 검증기
        OrgDimensionPlItemValidator plItemValidator = new OrgDimensionPlItemValidator();
        plItemValidator.setNext(codeValidator);

        // 조직 검증기
        OrgDimensionOrganizationValidator organizationValidator = new OrgDimensionOrganizationValidator();
        organizationValidator.setNext(plItemValidator);
        organizationValidator.setUploadStatus(uploadStatus);

        organizationValidator.validate(orgDataset);

 }

 public static List<Map<String, String>> makeValidateData(List<Map<String, String>> orgDataset){

  Map<String, String> dataMap1 = new HashMap<String, String>();
  Map<String, String> dataMap2 = new HashMap<String, String>();
  Map<String, String> dataMap3 = new HashMap<String, String>();
  dataMap1.put("Code", "C0201");
  dataMap2.put("Code", "C0202");
  dataMap3.put("Code", "C0203");
  dataMap1.put("Org", "본부");
  dataMap2.put("Org", "담당");
  dataMap3.put("Org", "지역단");
  dataMap2.put("Pl", "원수보험료");
  dataMap1.put("Pl", "손해액");
  dataMap3.put("Pl", "평가금액");

  orgDataset.add(dataMap1);
  orgDataset.add(dataMap2);
  orgDataset.add(dataMap3);

  return orgDataset;
 }

}

OrgDimensionCodeValidator.java

/**
 * 조직별 가정 코드 검증기
 */

package BP;

import java.util.List;
import java.util.Map;

public class OrgDimensionCodeValidator extends AbstractOrgDimensionValidator{

 @Override
 public void validate(List<Map<String, String>> dataset) {
        List<Map<String, String>> recordList = dataset;
        for (Map<String, String> orgRecord : recordList) {

            String data = orgRecord.get("Code");
            if (data != null) {
                System.out.println("코드 "+ data +"이(가) 검증되었습니다.");
            }
        }
        this.executeNext(dataset);
    }  

}
OrgDimensionOrganizationValidator.java

/**
 * 조직별 가정 디멘전 조직 검증기
 *
 */
public class OrgDimensionOrganizationValidator extends AbstractOrgDimensionValidator {

 @Override    
 public void validate(List<Map<String, String>> dataset){
         List<Map<String, String>> recordList = dataset;
         for (Map<String, String> orgRecord : recordList) {

             String data = orgRecord.get("Org");
             if (data != null) {
                 System.out.println("조직 "+ data +"이(가) 검증되었습니다.");
             }
         }
         this.executeNext(dataset);
     }  

}

OrgDimensionPlItemValidator.java

/**
 * 조직별 가정 디멘전 손익항목 검증기
 */
public class OrgDimensionPlItemValidator extends AbstractOrgDimensionValidator {

 @Override
 public void validate(List<Map<String, String>> dataset) {
        List<Map<String, String>> recordList = dataset;
        for (Map<String, String> orgRecord : recordList) {

            String data = orgRecord.get("Pl");
            if (data != null) {
                System.out.println("손익항목 "+ data +"이(가) 검증되었습니다.");
            }
        }
        this.executeNext(dataset);
 }    

}

실행 결과

결론

이상으로 COR패턴에 대해 살펴보고, 적용 결과를 출력해 보았습니다. 이번 프로젝트의 특성 상 검증모듈이 다양했었고 그 결과출력 또한 정확해야 했기에 위 패턴은 요구사항에 부합했고 타당했다고 생각합니다. 앞으로 다양한 프로젝트 경험을 통해 이와같은 패턴의 적절한 사용이 많아 졌으면 좋겠습니다.

namoosori
안녕하세요. 나무소리 입니다. 나무소리는 넥스트리(주)의 교육 브랜드 입니다.넥스트리가 지난 20년 동안 쌓아온 개발 및 교육 경험들을 나무소리를 통해 많은 분들과 공유 하려고 합니다.앞으로 저희 나무소리를 통해 보다 나은 교육을 경험 하실 수 있도록 구성원 모두 최선을 다하겠습니다.