7월 8 일 -컴포지션

Yullgiii·2024년 7월 8일
0
post-thumbnail

컴포지션(Composition)

개요

컴포지션(Composition)은 기존 클래스가 새로운 클래스의 구성 요소가 되는 개념으로, 상속의 단점을 보완할 수 있다. 상속은 부모 클래스의 특성을 재정의하여 자식 클래스에 맞게 재사용하는 데 유용하지만, 유연성을 해칠 수 있는 단점이 있다. 반면, 컴포지션은 새로운 클래스를 생성하여 기존 클래스의 인스턴스를 참조하는 방식으로 더 유연하고 캡슐화에 유리한 장점을 제공한다.

상속(Inheritance)의 단점

  • 캡슐화 위반: 자식 클래스가 부모 클래스의 내부 구현에 의존하게 되어 변경에 취약하다.
  • 유연하지 못한 설계: 상속 구조가 변경되면 전체 구조에 영향을 미치기 쉬움.
  • 다중상속 불가능: Java에서는 다중상속이 지원되지 않아 하나의 부모 클래스만 상속 가능.

오류의 예시

다음 예제는 CustomList에 요소를 몇 번 추가했는지 count 변수로 체크하여 출력하는 예제이다.

import java.util.ArrayList;
import java.util.Collection;

public class CustomList<E> extends ArrayList<E> {
    private int count = 0;

    @Override
    public boolean add(E e) {
        count++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        count += c.size();
        return super.addAll(c);
    }

    public int getCount() {
        return count;
    }
}

하지만 실제로 사용해보면 예상과 다른 결과가 나올 수 있다.

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        CustomList<String> customList = new CustomList<>();
        List<String> test = Arrays.asList("a", "b", "c");
        customList.addAll(test);

        System.out.println(customList.getCount()); // 출력: 6
    }
}

이 예제에서는 add 메소드와 addAll 메소드 모두 호출되어 count 값이 예상보다 크게 나온다. 이는 addAll 메소드 내에서 add 메소드를 호출하기 때문이다.

컴포지션(Composition)으로 개선하기

상속 대신 컴포지션을 사용하여 위 문제를 해결할 수 있다. CustomList는 ForwardingList 클래스를 사용하여 컴포지션을 구현한다.

ForwardingList 클래스

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class ForwardingList<E> implements List<E> {
    private final List<E> list;

    public ForwardingList(List<E> list) {
        this.list = list;
    }

    @Override
    public int size() {
        return list.size();
    }

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return list.contains(o);
    }

    @Override
    public Iterator<E> iterator() {
        return list.iterator();
    }

    @Override
    public Object[] toArray() {
        return list.toArray();
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return list.toArray(a);
    }

    @Override
    public boolean add(E e) {
        return list.add(e);
    }

    @Override
    public boolean remove(Object o) {
        return list.remove(o);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return list.containsAll(c);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        return list.addAll(c);
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        return list.addAll(index, c);
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return list.removeAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return list.retainAll(c);
    }

    @Override
    public void clear() {
        list.clear();
    }

    @Override
    public E get(int index) {
        return list.get(index);
    }

    @Override
    public E set(int index, E element) {
        return list.set(index, element);
    }

    @Override
    public void add(int index, E element) {
        list.add(index, element);
    }

    @Override
    public E remove(int index) {
        return list.remove(index);
    }

    @Override
    public int indexOf(Object o) {
        return list.indexOf(o);
    }

    @Override
    public int lastIndexOf(Object o) {
        return list.lastIndexOf(o);
    }

    @Override
    public ListIterator<E> listIterator() {
        return list.listIterator();
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        return list.listIterator(index);
    }

    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        return list.subList(fromIndex, toIndex);
    }
}

CustomList 클래스

import java.util.Collection;
import java.util.List;

public class CustomList<E> extends ForwardingList<E> {
    private int count = 0;

    public CustomList(List<E> list) {
        super(list);
    }

    @Override
    public boolean add(E e) {
        count++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        count += c.size();
        return super.addAll(c);
    }

    public int getCount() {
        return count;
    }
}

수정된 테스트 코드

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        CustomList<String> customList = new CustomList<>(new ArrayList<>());
        List<String> test = Arrays.asList("a", "b", "c");
        customList.addAll(test);

        System.out.println(customList.getCount()); // 출력: 3
    }
}

So...

컴포지션은 기존 클래스의 기능을 확장하면서도 상속의 단점을 피할 수 있는 강력한 기법이다. 새로운 클래스에서 기존 클래스를 private 필드로 참조하여 필요한 메소드를 호출하는 방식으로, 유연한 설계와 캡슐화를 유지할 수 있다. 상속을 사용해야 하는 경우는 IS-A 관계가 성립할 때로 제한하고, 그 외의 경우에는 컴포지션을 사용하는 것이 더 좋은 설계가 될 수 있다.

profile
개발이란 무엇인가..를 공부하는 거북이의 성장일기 🐢

0개의 댓글