본문 바로가기

Design Patterns

레지스트리 패턴, 특수 사례 패턴

레지스트리

다른 객체가 공용 객체와 서비스를 찾기 위해 사용하는 잘 알려진 객체

 

어떤 객체를 찾으려는 경우 찾으려는 객체와 연결된 다른 객체에서 시작해 이 연결을 따라 검색하는 것이 일반적이다. 예를들어 한 고객의 모든 주문을 찾으려면 고객 객체에서 주문을 얻는 메서드를 사용한다. 그런데 이렇게 출발점으로 사용할 적절한 객체가 없는 경우가있다.

 

레지스트리는 기본적으로 전역 객체이거나 보이는 것만큼 전역은 아닐 수 있지만 적어도 전역으로 보인다.

 

작동 원리

레지스트리의 정적 메서드를 통해서 필드에 접근한다. (필드까지 정적일 필요는 없다)

 

데이터의 범위

레지스트리의 데이터는 실행 컨텍스트별로 달라질 수 있다.

  1. 프로세스 범위의 레지스트리에서 일반적으로 사용되는 형태는 싱글턴이다. 이 경우 레지스트리 클래스는 한 레지스트리 인스턴스를 저장하는 단일 정적 필드를 포함한다. 싱글턴은 단일 스레드 어플리케이션에서 많이 사용된다. 다중 스레드의 경우 동기화를 해야하는 어려움이 있다.
  2. 스레드 범위의 레지스트리 데이터는 가장 일반적으로 사용된다. 자바의 스레드 로컬과 같이 스레드별 저장소를 제공하는 환경이 많이 있다.

 

사용시점

최후의 수단, 전역 데이터는 가능하면 사용하지 마라

 

레지스트리를 대신할 수 있는 방법

  1. 널리 사용되는 데이터를 매개변수로 전달
    • 이 방법은 몇 단계를 거쳐 전달하려면 불필요한 매개변수를 계속 전달해야 하는 어려움이 있다.
  2. 객체가 생성될 때 공용 데이터에 대한 참조를 추가

 

예제: 싱글턴 레지스트리(자바)

class Registry...

  private static Registry soleInstance = new Registry();
  protected PersonFinder personFinder = new PersonFinder();
  
  private static Registry getInstance() {
    return soleInstance;
  }
  public static PersonFinder personFinder() {
    return getInstance().personFinder;
  }

 

 

예제: 스레드로부터 안전한 레지스트리(자바)

자바는 스레드에 로컬이고 이해하기 쉽게 스레드 로컬 변수라고 하는 스레드별 저장소 변수를 제공한다.

class ThreadLocalRegistry...

  private static ThreadLocal instances = new ThreadLocal();
  
  public static ThreadLocalRegistry getInstance() {
    return (ThreadLocalRegistry) instances.get();
  }
  
  public static void begin() {
    Assert.isTrue(instances.get() == null);
    instances.set(new ThreadLocalRegistry());
  }
  public static void end() {
    Assert.notNull(getInstance());
    instances.set(null);
  }
  
  private PersonFinder personFinder = new PersonFinder();;
  
  public static PersonFinder personFinder() {
    return getInstance().personFinder;
  }

 

외부의 호출에서는 begin과 end메서드로 레지스트리 이용을 래핑한다.

  try {
    ThreadLocalRegistry.begin();
    PersonFinder f1 = ThreadLocalRegistry.personFinder();
    Person martin = Registry.personFinder().find(1);
    assertEquals("Fowler", martin.getLastName());
  } finally {
    ThreadLocalRegistry.end();
  }

 

특수 사례

특정 사례에 대해 특수한 동작을 제공하는 하위 클래스

작동원리

특정 객체의 필드(변수)가 null일 가능성이 있다면 null을 처리하는 하위클래스를 만들어서 처리하자. 예를들어 고객 객체가 있을 때 null에 대한 검사를 피하고 싶다면 null 고객 객체를 만들면 된다. 고객 객체의 모든 메서드를 가지고 특수 사례에서 일부를 재정의한다. 이후에 null인 고객이 있으면 null 고객 객체의 인스턴스를 대신 사용하면 된다.

 

사용 시점

시스템에서 특정 클래스 인스턴스에 대한 조건 검사 후 또는 null 검사 후 동일한 동작을 수행하는 위치가 여러 곳인 경우 특수사례를 사용한다.

 

예제: 간단한 null 객체(C#)

class Employee...
  public virtual String Name {
    get {return _name;}
    set {_name = value;}
  }
  private String _name;
  public virtual Decimal GrossToDate {
    get {return calculateGrossFromPeriod(0);}
  }
  public virtual Contract Contract {
    get {return _contract;}
  }
  private Contract _contract;
  
class NullEmployee : Employee, INull...
  public override String Name {
    get {return "Null Employee";}
    set {}
  }
  public override Decimal GrossToDate {
    get {return 0m;}
  }
  public override Contract Contract {
    get {return Contract.NULL;}
  }