티스토리 뷰
‘개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴' 책을 보고 정리한 글입니다.
빌딩의 장비들의 전원을 관리하는 제어 프로그램을 생각해보자.
이 프로그램을 만들기 위해 개별 장비의 전원을 켜고 끄는 기능을 제공하는 인터페이스를 정의하고, 장비 별로 알맞은 콘크리트 클래스를 구현했다.
개별 장비가 아닌 장비들을 하나로 묶어서 관리할 수 있도록 하기 위해 다음과 같이 DeviceGroup 클래스를 추가하였다.
위 타입을 이용해서 장비들의 전원을 제어하는 코드는 다음과 같이 Device 타입과 DeviceList 타입을 구분해서 처리할 것이다.
public class PowerController {
public void turnOn(Long deviceId) {
Device device = findDeviceById(deviceId);
device.turnOn();
}
// turnGroupOn()과 turnOn()은 개별/그룹 차이를 빼면 동일한 기능이다.
public turnGroupOn(Long groupId) {
DeviceGroup group = findGroupById(groupId);
group.turnAllOn();
}
}
위 코드의 단점은 PowerController 입장에서 봤을 때 장비나 장비 그룹의 전원을 켜는 동작은 동일한 동작임에도 불구하고 Device와 DeviceGroup을 구분해서 처리해야 한다는 점이다.
전원 켜고/끄는 기능 외에 소비 전력 측정과 같은 새로운 기능이 추가될 경우 PowerController 클래스에는 turnOn() / turnGroupOn() 처럼 거의 동일한 메서드가 추가된다.
거의 동일한 코드가 중복된다는 점은 결국 복잡도를 높여서 코드의 수정이나 확장을 어렵게 만든다.
컴포지트 패턴이 이 단점을 해소하기 위해 사용된다.
컴포지트 패턴은 이 문제를 전체-부분을 구성하는 클래스가 동일 인터페이스를 구현하도록 만듦으로써 해결한다.
위 그림은 앞서 있는 그림의 구조에 컴포지트 패턴을 적용한 결과다.
여기서 DeviceGroup 클래스는 개별 Device를 하나의 그룹으로 묶어주는데, Aircon 클래스나 Light 클래스처럼 DeviceGroup 클래스도 Device 인터페이스를 상속받는 것을 알 수 있다.
즉, 부분(AirCon, Light)과 전체(DeviceGroup)를 한 개의 인터페이스로 추상화한 것이다.
컴포지트의 책임
- 컴포넌트 그룹을 관리한다.
- 컴포지트에 기능 실행을 요청하면, 컴포지트는 포함하고 있는 컴포넌트들에게 기능 실행 요청을 위임한다.
다음은 DeviceGroup을 구현한 코드다.
import java.util.ArrayList;
import java.util.List;
public class DeviceGroup implements Device {
private List<Device> devices = new ArrayList<>();
public void addDevice(Device d) {
devices.add(d);
}
public void removeDevice(Device d) {
devices.remove(d);
}
@Override
public void turnOn() {
for(Device device : devices) {
device.turnOn(); // 관리하는 Device 객체들에게 실행 위임
}
}
@Override
public void turnOff() {
for(Device device : devices) {
device.turnOff(); // 관리하는 Device 객체들에게 실행 위임
}
}
}
다음 코드에서 addDevice()와 removeDevice()는 DeviceGroup이 관리할 Device 객체들의 목록을 관리한다.
turnOn()과 turnOff()는 DeviceGroup이 관리하고 있는 Device 객체들에게 기능 실행을 위임한다.
이는, 아래 코드처럼 DeviceGroup 객체에 Device 객체를 등록한 뒤에 DeviceGroup 객체의 turnOn()을 호출하면, 등록되어 있는 모든 Device 객체의 turnOn()이 호출된다는 것을 뜻한다.
Device device1 = null;
Device device2 = null;
DeviceGroup group = new DeviceGroup();
group.addDevice(device1);
group.addDevice(device2);
group.turnOn(); // device1과 device2의 turnOn() 실행
이제, DeviceGroup 클래스는 Device 타입이 되므로, 전원 제어 기능을 제공하는 PowerController 클래스는 Device 타입과 DeviceGroup 타입을 구분할 필요 없이 다음과 같이 Device 타입만을 이용해서 전원 관리를 할 수 있게 된다.
public class PowerController {
public void turnOn(Long deviceId) {
Device device = findDeviceById(deviceId);
device.turnOn();
}
}
컴포지트 패턴을 사용할 때의 또 다른 장점은 컴포지트 자체도 컴포넌트이기 때문에, 컴포지트에 다른 컴포지트를 등록할 수 있다는 것이다.
각 층의 Light 객체를 모은 DeviceGroup 객체를 생성하고 다시 각 층 별로 생성된 DeviceGroup 객체를 모아서 하나의 DeviceGroup 객체에 등록하면, 전 층의 Light 객체를 켜거나 끄는 기능을 구현할 수 있게 된다.
컴포지트 패턴 구현의 고려 사항
컴포넌트를 관리하는 인터페이스를 어디서 구현할지에 대한 여부다.
컴포넌트 패턴의 장점 중 하나는 클라이언트가 컴포지트와 컴포넌트를 구분하지 않고 컴포넌트 인터페이스만으로 프로그래밍할 수 있게 돕는다는 점인데, 앞서 예제에서는 컴포지트인 DeviceGroup에 인터페이스를 정의했다.
따라서 Device 그룹을 만들어야 하는 코드는 DeviceGroup 타입에 직접 접근해야 하는 상황이 발생한다.
Device 타입에 컴포넌트를 관리하는 인터페이스를 추가하면, 클라이언트 입장에서 DeviceGroup 타입을 사용하지 않고도 그룹을 생성할 수 있게 된다.
컴포지트가 아닌 클래스에서 addDevice()와 removeDevice() 기능이 정상적으로 동작하면 안 된다.
이런 상황이 발생하지 않도록 Device 타입의 두 메서드에 익셉션을 발생시키는 기본 구현을 추가하고, DeviceGroup 클래스에서 알맞게 재정의하도록 구현할 수 있다.
익셉션을 발생시키는 방법보다 조금 더 나은 방법은 컴포넌트를 추가할 수 있는지의 여부를 판단해 주는 기능을 Device 타입에 정의하는 것이다.
예를 들어, 다음과 같이 Device 타입에 canContain() 메서드를 추가하는 것이다.
public abstract class Device {
// 생략
public boolean canContain(Device device) {
return false;
}
}
GroupDevice 에서는 canContain() 리턴값을 true로 재정의해준다.
'JAVA > 디자인 패턴' 카테고리의 다른 글
Iterator 패턴 (0) | 2022.03.08 |
---|---|
널(Null) 객체 패턴 (0) | 2022.03.07 |
추상 팩토리(Abstract Factory) 패턴 (0) | 2022.03.06 |
파사드(Facade) 패턴 (0) | 2022.03.05 |
옵저버(Observer) 패턴 (0) | 2022.03.03 |
- Total
- Today
- Yesterday
- Spring Boot
- BOJ
- 이팩티브 자바
- Olympiad
- C++
- 백준
- Algorithm
- Effective Java
- 알고리즘
- Kotlin
- 정규표현식
- JPA
- 디자인패턴
- node.js
- 이펙티브 자바
- AWS
- BAEKJOON
- 디자인 패턴
- 코테
- kotest
- MSA
- 클린 아키텍처
- 테라폼
- 프로그래머스
- 클린 코드
- 객체지향
- kkoon9
- programmers
- Spring
- Java
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |