1. 개요

이 빠른 사용방법(예제)에서는 전략 패턴 의 특수한 경우인 Null 개체 패턴을 살펴보겠습니다 . 그 목적과 실제로 사용을 고려해야 하는 경우에 대해 설명합니다.

늘 그렇듯이 간단한 코드 예제도 제공합니다.

2. 널 객체 패턴

대부분의 객체 지향 프로그래밍 언어에서는 null 참조를 사용할 수 없습니다. 이것이 우리가 종종 null 검사를 작성하도록 강요받는 이유입니다.

Command cmd = getCommand();
if (cmd != null) {
    cmd.execute();
}

경우에 따라 이러한 if 문의 수가 많아지면 코드가 보기 흉해지고 읽기 어렵고 오류가 발생하기 쉽습니다. Null 객체 패턴이 유용한 경우입니다.

Null 개체 패턴의 의도는 이러한 종류의 null 검사를 최소화하는 것입니다. 대신 null 동작을 식별하고 클라이언트 코드에서 예상하는 유형으로 캡슐화할 수 있습니다. 그런 중립적인 논리는 매우 단순합니다. 즉, 아무것도 하지 않는 것입니다. 이렇게 하면 더 이상 null 참조의 특수 처리를 처리할 필요가 없습니다.

실제로 좀 더 정교한 비즈니스 논리를 포함하는 주어진 유형의 다른 인스턴스를 처리하는 것과 동일한 방식으로 null 객체를 처리할 수 있습니다. 결과적으로 클라이언트 코드는 더 깨끗하게 유지됩니다.

null 개체에는 상태가 없어야 하므로 동일한 인스턴스를 여러 번 만들 필요가 없습니다. 따라서 우리는 종종 null 객체를 싱글톤 으로 구현 합니다 .

3. Null 객체 패턴의 UML 다이어그램

패턴을 시각적으로 살펴보겠습니다.

NOP

보시다시피 다음 참가자를 식별할 수 있습니다.

  • 클라이언트 에는 AbstractObject 인스턴스가 필요합니다.
  • AbstractObject 는 클라이언트 가 기대 하는 계약을 정의합니다 . 구현 클래스에 대한 공유 논리를 포함할 수도 있습니다.
  • RealObject 는 AbstractObject 를 구현 하고 실제 동작을 제공합니다 .
  • NullObject 는 AbstractObject 를 구현 하고 중립적인 동작을 제공합니다 .

4. 시행

이제 이론에 대한 명확한 아이디어를 얻었으니 예를 살펴보겠습니다.

메시지 라우터 애플리케이션이 있다고 상상해 보십시오. 각 메시지에는 유효한 우선 순위가 할당되어 있어야 합니다. 우리 시스템은 우선 순위가 높은 메시지를 SMS 게이트웨이로 라우팅하고 중간 우선 순위 메시지는 JMS Queue로 라우팅해야 합니다.

그러나 때때로 "정의되지 않은" 또는 비어 있는 우선순위를 가진 메시지가 애플리케이션에 올 수 있습니다. 이러한 메시지는 추가 처리에서 폐기되어야 합니다.

먼저 라우터 인터페이스를 만듭니다.

public interface Router {
    void route(Message msg);
}

다음으로 위 인터페이스의 두 가지 구현을 만들어 보겠습니다. 하나는 SMS 게이트웨이로 라우팅을 담당하고 다른 하나는 메시지를 JMS Queue로 라우팅합니다.

public class SmsRouter implements Router {
    @Override
    public void route(Message msg) {
        // implementation details
    }
}
public class JmsRouter implements Router {
    @Override
    public void route(Message msg) {
        // implementation details
    }
}

마지막으로 null 개체를 구현해 보겠습니다.

public class NullRouter implements Router {
    @Override
    public void route(Message msg) {
        // do nothing
    }
}

이제 모든 조각을 함께 모을 준비가 되었습니다. 예제 클라이언트 코드가 어떻게 보이는지 살펴보겠습니다.

public class RoutingHandler {
    public void handle(Iterable<Message> messages) {
        for (Message msg : messages) {
            Router router = RouterFactory.getRouterForMessage(msg);
            router.route(msg);
        }
    }
}

우리가 볼 수 있듯이 RouterFactory 에서 어떤 구현이 반환되는지에 관계없이 모든 Router 개체를 동일한 방식으로 처리합니다. 이를 통해 코드를 깨끗하고 읽기 쉽게 유지할 수 있습니다.

5. Null 객체 패턴을 사용하는 경우

클라이언트 가 실행을 건너뛰거나 기본 작업을 수행하기 위해 null 을 확인 하는 경우 Null 객체 패턴을 사용해야 합니다 . 이러한 경우 null 개체 내에 중립 논리를 캡슐화하고 null 값 대신 클라이언트에 반환할 수 있습니다. 이 방법으로 클라이언트의 코드는 주어진 인스턴스가 null 인지 여부를 더 이상 인식할 필요가  없습니다.

이러한 접근 방식은 Tell-Don't-Ask 와 같은 일반적인 객체 지향 원칙을 따릅니다 .

Null 객체 패턴을 언제 사용해야 하는지 더 잘 이해하기 위해 다음과 같이 정의된 CustomerDao 인터페이스 를 구현해야 한다고 상상해 봅시다 .

public interface CustomerDao {
    Collection<Customer> findByNameAndLastname(String name, String lastname);
    Customer getById(Long id);
}

대부분의 개발자는 제공된 검색 기준 과 일치하는 고객이 없는 경우 findByNameAndLastname() 에서 Collections.emptyList() 를 반환 합니다. 이것은 Null 객체 패턴을 따르는 아주 좋은 예입니다.

반대로 get ById() 는 주어진 ID를 가진 고객을 반환해야 합니다. 이 메서드를 호출하는 사람은 특정 고객 엔터티를 얻을 것으로 예상합니다. 그러한 고객이 없는 경우 제공된 ID에 문제가 있음을  알리기 위해 명시적으로 null 을 반환해야 합니다.

다른 모든 패턴과 마찬가지로 Null 개체 패턴을 맹목적으로 구현하기 전에 특정 사용 사례를 고려해야 합니다 . 그렇지 않으면 코드에서 찾기 어려운 일부 버그가 의도하지 않게 도입될 수 있습니다.

6. 결론

이 기사에서는 Null 객체 패턴이 무엇이며 언제 사용할 수 있는지 알아보았습니다. 디자인 패턴의 간단한 예제도 구현했습니다.

평소와 같이 모든 코드 샘플은 GitHub 에서 사용할 수 있습니다 .

Generic footer banner