[소프트웨어] 객제지향 프로그래밍 디자인 패턴

디자인 패턴

코드의 중복이 존재하는 경우 공통된 부분을 추출하여 별도로 정의하면 재사용 가능하게 만들 수 있다. 이는 변하지 않는 작업 속에서 변하는 부분을 관심사로 선택하여 변하는 부분과 변하지 않는 부분을 분리하는 것이다. 변하지 않는 부분은 재사용을 위해, 변하는 부분은 변하지 않는 부분과 분리하여 관리하기 위해 디자인 패턴을 적용한다.

객체지향 프로그래밍의 특징인 캡슐화, 상속, 다형성을 어떻게 활용하여 변하는 부분을 변하지 않는 부분으로부터 어떻게 분리할 것인가에 대한 고민을 하면 된다. 변하지 않는 부분과 변하는 부분을 서로 분리시켜야 코드 상 의존성을 없애고 기능 변경에 대한 확장성을 부여할 수 있다.

재사용과 분리 목적 뿐만 아니라 개방 폐쇄 원칙(OCP)을 만족하는 소프트웨어 설계를 위해 디자인 패턴을 선택할 수 있다. 작업의 순서는 동일하지만 특정 작업에서의 구체적인 알고리즘(로직)이 변경되는 경우 템플릿 메서드 패턴 또는 전략 패턴을 사용한다.


템플릿 메서드 패턴

템플릿 메서드 패턴(template method pattern)이란 상속을 통해 기능을 변경하는 디자인 패턴이다. 변하는 기능을 상위 클래스(주로 추상 클래스)의 추상 메서드로 선언하고 구체적인 기능을 하위 클래스에서 정의한다.

변하는 기능은 구현 내용이 없는 추상 메서드로 선언하고 변하지 않는 작업의 순서(일련의 메서드 호출)는 추상 클래스의 변하지 않는 템플릿 메서드에 정의한다. 즉, 템플릿 메서드 패턴에서는 상위 클래스에 알고리즘의 기본 구조를 정의하고 구체적인 구현은 하위 클래스에서 정의하게 함으로써 변경을 캡슐화한다.

추상 클래스의 구현 클래스(구체 클래스)에서는 템플릿 메서드 자체가 아닌 템플릿 메서드 내에서 호출하는 메서드들 중 변하는 메서드(추상 메서드)만 오버라이딩함으로써 기능을 재정의한다.


팩토리 메서드 패턴

팩토리 메서드 패턴(factory method pattern)이란 객체 생성 기능을 하는 클래스를 정의하고 어떤 객체를 생성할지(인스턴스화 할지)는 하위 클래스가 결정하게 하는 디자인 패턴이다. 팩토리 메서드 패턴은 템플릿 메서드 패턴의 한 종류이다.

객체 생성을 위해 클래스의 생성자를 직접 호출하는 대신 클래스의 생성자를 호출하여 객체를 반환하는 역할을 하는 팩토리 메서드를 정의한다. 팩토리 메서드로부터 반환되는 객체를 제품(product)이라고 하며 팩토리 메서드가 정의된 클래스를 생산자(creator)라고 한다. 즉, 팩토리 메서드는 제품 객체를 생성하여 반환하며 생산자의 팩토리 메서드를 호출하여 반환되는 제품 객체를 사용한다.

생산자 클래스에는 팩토리 메서드와 변하지 않는 작업의 순서(일련의 메서드 호출, 비즈니스 로직)가 정의되어 있다. 비즈니스 로직 수행 메서드에서는 팩토리 메서드를 호출하여 반환받은 제품 객체의 기능을 호출하는 것이다.

팩토리 메서드가 반환하는 제품 객체는 제품 인터페이스 구현 객체이며 제품 인터페이스는 제품 객체가 구현해야 할 메서드를 정의한다. 생산자 클래스의 상속과 메서드 재정의를 통해 팩토리 메서드의 기능을 변경함으로써 반환되는 제품의 클래스(객체 타입)가 변경되도록 만들고 이로 인해 제품 구현체가 변경됨에 따라 제품의 기능이 변경된다. 생산자 클래스의 구현체를 선택함으로써 반환되는 제품 객체가 달라지게 되고 이에 따라 구체적인 기능이 변경되는 것이다.

  • 생산자 클래스 구현체 선택 -> 제품 클래스 구현체 선택 -> 제품 기능 결정

팩토리 메서드가 어떤 제품 객체를 반환하는지(어떤 제품 구현 객체의 구현 메서드를 사용할지)에 대한 결정은 클라이언트에서 이루어진다. 클라이언트에서 생산자 구현체를 결정한 후 팩토리 메서드를 호출하면 제품 구현체가 결정되어 반환되고, 반환된 제품 객체의 기능 메서드를 호출하여 사용한다.

상속을 통해 기능(어떤 제품 구현 객체를 반환할지)을 변경하며 변하는 기능을 상위 클래스(주로 추상 클래스)의 추상 메서드(팩토리 메서드)로 선언하고 구체적인 기능을 하위 클래스에서 정의한다는 점에서 템플릿 메서드 패턴으로 볼 수 있다.


전략 패턴 (Strategy Pattern)

전략 패턴(strategy pattern)이란 인터페이스와 합성을 통해 기능을 확장하는 디자인 패턴이다. 변하는 기능을 인터페이스의 메서드로 만들고 구체적인 기능을 인터페이스 구현 클래스에서 정의한다.

변하지 않는 작업의 맥락(context)을 위한 컨텍스트 객체를 정의하고 그 맥락 속에서 변하는 부분을 추출 및 분리하여 별도의 클래스(전략 클래스)를 생성한다. 변하지 않는 작업 속에서 변하는 기능(알고리즘)을 별도로 추출해 낸 것이 바로 전략 클래스이다. 따라서 기능 별로 전략 클래스가 정의되고 기능 별 전략 클래스들은 하나의 인터페이스를 구현한다. 컨텍스트 내에 정의되어 있는 여러 기능 중 변경될 수 있는(또는 변경이 필요한) 기능을 분리시킨다.

인터페이스를 사용함으로써 구체적인 클래스를 알 필요 없이 서로 다른 기능을 가지고 있는 전략 클래스의 교체가 가능하다. 이와 같이 전략 패턴에서는 컨텍스트로부터 구체적인 전략을 독립적으로 유지함으로써 변하지 않는 부분으로부터 변하는 부분을 분리하고, 변하지 않는 작업의 흐름(컨텍스트)과 기존의 전략(알고리즘)에 영향을 주지 않으면서 새로운 기능을 수행하는 전략을 추가할 수 있다.


전략 패턴에서 관심사 및 역할의 분리와 DI

컨텍스트 객체는 작업 수행을 위해 어떤 전략을 사용할지(어떤 알고리즘을 동작시킬지)에 대해서는 관여하지 않으며 클라이언트에서 전략을 결정한 후 컨텍스트 객체에 전달만 한다. 변경되는 인터페이스 구현체 결정은 컨텍스트 객체 내에서 하는 것이 아니며 이미 결정된 인터페이스 구현체를 컨텍스트가 주입받게 하고 컨텍스트 객체는 주입받은 인터페이스 구현체의 메서드를 단순히 호출하기만 한다. 즉, 컨텍스트 객체에서 변경되는 부분을 누가 어떻게 결정할 것인가는 컨텍스트 객체로의 의존 관계 주입(dependency injection)과 관련이 있다.

변하는 객체의 생성 및 초기화와 컨텍스트 객체로의 전달 및 주입을 담당하는 역할을 또다른 객체에게 위임할 수 있으며 이런 역할을 하는 객체가 팩토리 객체이다. 팩토리 객체는 인터페이스 구현 객체를 결정한 후 컨텍스트 객체에 주입함으로써 인터페이스 구현 객체와 컨텍스트 객체간 간 의존 관계가 결정된(의존성이 주입된) 컨텍스트 객체를 반환하는 역할을 한다.

관심사와 역할을 분리하여 정리하면 다음과 같다.

  1. 팩토리 객체
    • 인터페이스 구현 객체 생성
    • 변하지 않는 알고리즘이 정의된 컨텍스트 객체와 변하는 인터페이스 구현 객체 간 의존 관계 결정 (의존성 주입)
    • 의존 관계가 결정된(의존성이 주입된) 컨텍스트 객체 생성 및 반환
  2. 컨텍스트 객체: 의존성을 주입 받은 인터페이스 구현 객체의 기능 호출
  3. 인터페이스 구현 객체 (전략 객체): 변하는 기능(구체적인 구현)을 정의
  4. 클라이언트 객체: 팩토리 객체로부터 컨텍스트 객체를 반환 받아 컨텍스트 객체의 기능을 사용


템플릿 메서드 패턴과 전략 패턴

템플릿 메서드 패턴은 상속을 기반으로 하므로 알고리즘의 변경은 하위 클래스에서 기능 재정의를 통해 이루어진다. 따라서 템플릿 메서드 패턴은 클래스 레벨에서 작동하므로 컴파일 시점에 이미 객체 관계가 결정되는 정적인 특성을 가진다. 기능을 변경하려면 클래스 간 상속 관계를 변경한 후 컴파일해야 한다.

전략 패턴은 합성(composition)을 기반으로 하므로 알고리즘의 변경은 기존 기능을 새로운 기능으로 교체하는 것을 통해 이루어진다. 따라서 전략 패턴은 객체 레벨에서 작동하므로 런타임 시점에 객체의 행위를 변경할 수 있는 동적인 특성을 가진다. 기능을 변경하려면 서로 다른 객체를 주입하는 로직(조건에 따라 서로 다른 인터페이스 구현체를 생성하여 생성자의 인자로 전달하는 것)이 실행되도록 하면 된다.

Comments