본문으로 건너뛰기

Object

절차지향

  • 프로세스와 데이터를 별도의 모듈에 위치시키는 방식을 절차적 프로그래밍이라고 부른다.(26p)
  • 모든 처리가 하나의 클래스 안에 위치하고 나머지 클래스는 단지 데이터 역할만 수행한다.(26p)
  • 절차적 프로그래밍의 세상에서 데이터의 변경은로 인한 영향을 지역적으로 고립시키기 어렵다.(26p)
  • 모든 처리가 하나의 클래스에 위치하기 때문에 여러 데이터 클래스 중 하나만 변경되더 라고 변경이 처리 클래스의 변경을 부른다.(26p)
    • 변경하기 쉬운 설계는 한 번에 하나의 클래스만 변경할 수 있는 설계다.

객체지향 프로그래밍

  • 보통 객체지향 프로그래밍을 작성할 때 가장 먼저 고려하는 것은 어떤 클래스가 필요한지 고민한다.(40p)
    • 하지만 진정한 객체지향은 클래스가 아닌 객체에 초점을 맞출 때 얻을 수 있다.
    • 즉 어떤 클래스가 필요한지가 아니라 어떤 객체가 필요한지 고민하라.

역할, 책임, 협력

협력

  • 객체지향 시스템은 자율적인 객체들의 공동체다.
  • 메시지 전송은 객체 사이의 협력을 위해 사용할 수 있는 유일한 커뮤니케이션 수단이다.(49p)
    • 메시지를 수신한 객체는 스스로의 결정에 따라 메서드를 실행해 요청에 응답한다.
    • 수신된 메시지를 처리하기 위한 자신만의 방법을 메서드라고 부른다.
  • 객체를 자율적으로 만드는 가장 기본적인 방법은 내부 구현을 캡슐화하는 것이다.
    • 캡슐화를 통해 변경에 대한 파급효과를 제한할 수 있다.

자율적인 객체는 자신에게 할당된 책임을 수행하는 중에 필요한 정보를 알지 못하거나 외부의 도움이 필요한 경우 적절한 객체에게 메시지를 전송해서 협력을 요청한다.

책임

  • 책임이란 객체에 의해 정의되는 응집도 있는 행위의 집합이다.
  • 즉 객체의 책임은 무엇을 알고 있는가무엇을 할 수 있는가로 구성된다.

무엇을 알고 있는가

  • 사적인 정보에 관해 아는 것
  • 관련된 객체에 관해 아는 것
  • 자신이 유도하거나 계산할 수 있는 것에 관해 아는 것

무엇을 할 수 있는가

  • 객체를 생성하거나 계산을 수행하는 등의 스스로 하는 것
  • 다른 객체의 행동을 시작시키는 것
  • 다른 객체의 활동을 제어하고 조절하는 것

역할

  • 객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합을 역할이라고 부른다.
  • 역할을 구현하는 가장 일반적인 방법은 추상 클래스인터페이스를 사용하는 것이다.
  • 협력 관점에서 추상 클래스와 인터페이스는 구체 클래스들이 따라야하는 책임의 집합을 서술한 것이다.

응집도와 결합도

  • 응집도와 결합도는 변경과 깊은 관련이 있다.
  • 어떤 설계를 쉽게 변경할 수 있다다는 것은 높은 응집도와 낮은 결합도를 가지고 있다는 뜻이다.
  • 높은 응집도와 낮은 결합도를 얻기 위해서는 캡슐화가 핵심이다.
    • 객체는 자신이 어떤 데이터를 가지고 있는지 내부에 캡슐화하고 외부에 공개해서는 안된다.
    • 속성의 가시성을 private으로 설정했다고 해도 접근자와 수정자를 통해 속성을 외부로 공개하면 캡슐화를 위반한 것이다.

응집도

  • 응집도는 변경이 발생할 때 모듈 내부에서 발생하는 변견의 정도로 측정할 수 있다.
  • 하나의 변경을 수용하기 위해 모듈 전체가 변경되면 응집도가 높은 것
  • 하나의 변경을 수용하기 위해 다수의 모듈이 함께 변경돼야 한다면 응집도가 낮은 것이다.

응집도의 높고 낮음

  • 클래스가 하나 이상의 이유로 변경돼야 한다면 응집도가 낮은 것이다. 변경의 이유를 기준으로 클래스를 분리한다.
  • 응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함께 초기화 한다.
  • 모든 메서드가 모든 인스턴스 변수를 사용한다면 응집도가 높다고 볼 수 있다.
  • 153p

결합도

  • 한 모듈이 변경되기 위해서 다른 모듈의 변경을 요구하는 정도로 측정할 수 있다.

결합도의 높고 낮음

  • 한 객체가 다른 객체에 대해 알고 있는 정보(지식)의 양으로 결합도가 결정된다.
  • 객체 A가 다른 객체 B의 접근자 메서드를 사용하고 있다면 결합도가 높을 수 있다.
    • 객체는 자율적인 존재여야 한다.
    • A 객체가 수행하고 있는 책임을 데이터를 저장하고 있는 객체 B에게 넘겨주면 결합도를 낮출 수 있다.

의존성과 결합도

  • 의존성은 두 요소 사이의 관계 유무를 설명한다.
    • 의존성이 존재한다 또는 의존성이 존재하지 않는다.
  • 결합도는 두 요소 사이에 존재하는 의존성의 정도를 상대적으로 표현한다.
    • 결합도가 강하다 또는 결합도가 느슨하다.

추상화에 의존하라

  • 추상화란 세부사항, 구조를 좀 더 명확하게 이해하기 위해 특정 절차나 물체를 의도적으로 생략하거나 감춤으로써 복잡도를 극복하는 방법이다.
  • 따라서 대상에 대해 알아야하는 지식의 양을 줄여 결합도를 느슨하게 유지할 수 있다.
  • 목록에서 아래쪽으로 갈수록 클라이언트가 알아야하는 지식의 양이 적어진다.
    • 구체 클래스 의존성
    • 추상 클래스 의존성
    • 인터페이스 의존성

new의 위험성

  • new를 잘못 사용하면 클래스 간의 결합도가 강해진다.
  • new 연산자를 사용하기 위해서는 구체 클래스의 이름을 직접 기술해야 한다.
    • 이는 추상화에 의존하는 것이 아닌 구체 클래스에 의존할 수밖에 없기 때문에 결합도가 강해진다.
  • new 연산자를 사용하면 어떤 인자를 이용해 클래스의 생성자를 호출해야 하는지 알아야한다.
    • 즉 클라이언트가 알아야하는 지식의 양이 늘어나기 때문에 결합도가 강해진다.
    • 구체 클래스를 생성하기 위해 필요한 인자로 사용되는 구체 클래스와의 의존성도 만들어 진다.
  • 해결법
    • 인스턴스를 생성하는 로직과 생성된 인스턴스를 사용하는 로직을 분리한다.
    • 의존성 해결 방법을 이용해 외부에서 인스턴스를 전달 받아 사용하고 직접 생성하지 않는다.
  • new가 항상 위험한 것은 아니다.
    • 문제는 객체 생성이 아니라 부적절한 곳에서 객체를 생성한다는 것이다.
    • 유연하고 재사용 가능한 설계를 원한다면 객체에 대한 생성과 사용을 분리해야 한다.
    • 클래스 안에서 객체의 인스턴스를 직접 생성하는 것이 유용한 경우도 있다.
    • 협력하는 기본 객체를 설정하고 싶은 경우가 이러한 경우에 해당된다.
    • 설계는 트레이드오프 활동이다.
    • 결합도와 사용성을 저울질한다.

의존성

  • 의존성은 두 요소 사이의 관계 유무를 설명한다.
  • 의존성이 존재한다 또는 의존성이 존재하지 않는다.
  • 의존성이라는 말 속에는 어떤 객체가 변경될 때 그 객체에게 의존하는 다른 객체도 함께 변경될 수 있다는 사실이 내포돼 있다.(16p)
    • 그렇다고 객체 사이의 의존성을 완전히 없애라는 것이 아니다.
    • 객체지향 설계는 서로 의존하면서 협력하는 객체들의 공동체를 구축하는 것이다.
    • 최소한의 의존성만 유지하고 불필요한 의존성을 제거하라는 의미

결합도와의 차이

  • 결합도는 두 요소 사이에 존재하는 의존성의 정도를 상대적으로 표현한다.
    • 결합도가 강하다 또는 결합도가 느슨하다.

명시적인 의존성

  • 의존성 대상을 생성자의 인자로 전달받는 방법
  • 퍼블릭 인터페이스에 의존성을 드러낸다.
  • 의존성은 명시적으로 표현해야 한다.
    • 그래야 퍼블릭 인터페이스를 통해 컴파일 타임 의존성을 적절한 런타임 의존성으로 교체할 수 있다.

숨겨진 의존성

  • 의존성 대상을 생성자 내부에서 직접 생성하는 방법
  • 퍼블릭 인터페이스에 의존성을 감춘다.
  • 의존성을 파악하기 위해 내부 구현을 직접 살펴볼 수밖에 없다.

의인화

  • 비록 현실에서는 수동적인 존재라고 하더라도 일단 객체지향 세계에 들어오면 모든 것이 능동적이고 자율적인 존재로 바뀐다.(33p)
  • 레베카 워프스브록은 능동적이고 자율적인 존재로 소프트웨어 객체를 설계하는 원칙을 의인화라고 부른다.(33p)

패턴

  • 객체에게 책임을 할당할 때 해당 책임을 어떤 객체에게 할당하면 좋을까?
  • 이런 경우 도움이 되는 패턴들을 알아보자.

정보 전문가 패턴

  • INFORMATION EXPERT 패턴이란 객체에게 책임을 할당할 때 가장 기본이 되는 책임 할당 원칙이다.
  • 책임을 수행하는데 필요한 정보를 가지고 있는 객체에게 책임을 할당하는 것이 정보 전문가 패턴이다.
  • 여기서 정보라는 단어를 사용했는데 이는 데이터와는 다르다.
    • 책임을 수행하는 객체가 정보를 알고 있다고해서 그 정보를 저장하고 있을 필요는 없다.
    • 객체는 해당 정보를 제공할 수 있는 다른 객체를 알고 있거나 필요한 정보를 계산해서 제공할 수 있다.

CREATOR 패턴

  • 객체를 생성할 책임을 어떤 객체에게 할당할지에 대한 지침
  • 객체 A를 생성할 때 아래의 조건을 최대한 많이 만족시키는 B에게 객체 생성 책임을 할당하라
    • B가 A 객체를 포함하거나 참조한다.
    • B가 A 객체를 기록한다.
    • B가 A 객체를 긴밀하게 사용한다.
    • B가 A 객체를 초기화 하는 데 필요한 데이터를 가지고 있다.(정보 전문가 패턴)
  • 이미 결합돼 있는 객체에게 생성 책임을 할당하는 작업은 설계의 전체적인 결합도에 영향을 미치지 않는다.
  • 즉 CREATOR 패턴은 이미 존재하는 객체 사이의 관계를 이용하기 때문에 낮은 결합도를 유지할 수 있게 한다.

디미터 법칙

  • 182p
  • 디미터 법칙은 낯선 자에게 말하지 말라 또는 오직 인접한 이웃하고만 말하라라고 말할 수 있다.
  • 자바에서는 오직 하나의 도트만 사용하라라고 말할 수 있다.
  • 여기서 이웃 아래와 같다.
    • this 객체
    • 메서드의 매개변수
    • this의 속성
    • this의 속성인 컬렉션의 요소
    • 메서드 내에서 생성된 지역 객체

디미터 법칙을 위반하는 전형적인 코드

public class ReservationAgency {
public Reservation reserve(Screening screeing, Customer customer, int adienceCount) {
screeing.getMovie().getDiscountConditions();
}
}
  • ReservationAgency과 Screening은 이웃이다.
    • 메서드의 매개변수이기 때문
  • ReservationAgency과 DiscountCondition은 이웃이 아니다.
  • 디미터 법칙 위해
    • screeing이 이웃(Movie)의 이웃(DiscountConditions)에 접근하고 있다.
    • screeing이 낯선 자(DiscountConditions)에게 말하고 있다.
    • 두 개의 도트를 사용하고 있다.

디미터 법칙 위배

  • 디비터 법칙을 위배하는 것은 클라이언트에게 구현을 노출한다는 의미다.
  • 이는 높은 결합도는 초래한다.

해결

  • 이웃의 내부 구조를 묻지말고 이웃에게 직접 자신의 책임을 수행하도록 시키지자.

묻지 말고 시켜라

  • 186p

원칙의 함정

  • 무작정 디미터 법칙와 묻지 말고 시켜라를 적용하면 응집도가 낮은 객체가 넘쳐날 것이다.

메시지, 메서드

메시지

  • 객체가 다른 객체와 협력하기 위해 사용하는 의사소통 메커니즘
  • 객체의 오퍼레이션이 실행되도록 요청하는 것을 메시지 전송이라고 한다.
  • 메시지와 메서드를 구분하는 것이 매우 중요하다.(49p)
    • 메시지와 메서드의 구분에서부터 다형성의 개념이 출발하기 때문이다.

퍼블릭 인터페이스

  • 오퍼레이션의 집합

오퍼레이션

  • 퍼블릭 인터페이스의 포함된 메시지를 오퍼레이션이라고 한다.
  • 오퍼레이션은 수행가능한 어떤 행동에 대한 추상화다.
  • 즉 오퍼레이션은 내부의 구현 코드를 제외한 시그니처를 의미한다.

메서드

  • 메시지에 응답하기 위해 실제로 실행되는 코드 블록을 의미한다.
  • 메서드는 오퍼레이션의 구현이다.
  • 동일한 오퍼레이션이라도 메서드는 다를 수 있다.

시그니처

  • 메서드의 이름과 인자를 포함한다.
  • 대부분의 언어가 반환 타입을 시그니처에 포함하지 않지만 포함하는 언어도 존재한다.

명령 쿼리 분리 원칙

  • 202p
  • 어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능 모듈을 루틴이라고 부른다.
  • 루틴은 다시 프로시저함수로 구분된다.
    • 프로시저: 부수효과를 발생시킬 수 있지만 값을 반환할 수 없다.
    • 함수: 값을 반환할 수 있지만 부수효과를 발생시킬 수 없다.
  • Command-Query 분리 원칙은 프로시저와 함수를 부르는 또 다른 이름이다.
    • Command가 프로시저의 다른 이름이다.
    • Query가 함수의 다른 이름이다.

장점

  • 명령과 쿼리를 구분하면 객체의 부수효과를 제어하기 수월해진다.
  • 쿼리는 객체의 상태를 변경하지 않기 때문에 몇 번이고 반복적으로 호출하더라도 상관없다.
    • 명령이 개입되지 않는 한 쿼리의 값은 변경되지 않아 결과 예측이 쉬워진다.
  • 쿼리를 순서와 횟수에 상관없이 호출할 수 있다.

OCP(개방 폐쇄 원칙)

  • 소프트웨어 객체는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.
  • OCP는 사실 런타임 의존성과 컴파일 타임 의존성에 관한 이야기다.
    • 런타임 의존성은 실행 시에 협력에 참여하는 객체들 사이의 관계다
    • 컴파일 타임 의존성은 코드에서 드러나는 클래스 사이의 관계다.
    • 유연하고 재사용 가능한 설계에서 런타임 의존성과 컴파일 타임 의존성은 서로 다른 구조를 가진다.
  • OCP는 컴파일 타임 의존성을 유지하면서 런타임 의존성의 가능성을 확장하고 수정할 수 있는 구조다.

OCP의 핵심

  • 추상화의 의존하는 것

DIP(의존성 역전 원칙)

  • 유연하고 재사용 가능한 설계를 원한다면 모든 의존성의 방향이 추상 클래스나 인터페이스와 같은 추상화를 따라야 한다.
  • 구체 클래스는 의존성의 시작점이어야 한다. 의존성의 목적지가 돼서는 안 된다.
  • 이를 의존성 역전 원칙이라고 부른다.
    • 상위 수준의 모듈을 하위 수준의 모듈에 의존해서는 안 된다. 둘 모두 추상화의 의존해야 한다.
    • 추상화는 구체적인 사항에 의존해서는 안 된다. 구체적인 사항은 추상화에 의존해야 한다.

역전이라는 단어가 사용된 이유

전통적인 소프트웨어 개발 방법에서 상위 수준의 모듈이 하위 수준의 모듈에 의존하는 경향이 있었기 때문이다.

유연성

  • 305P
  • 유연하고 재사용 가능한 설계란 런타임 의존성과 컴파일 타임 의존성의 차이를 인식하고 코드 구조를 설계하는 것을 의미한다.
    • 코드 상에 표현된 정적인 클래스의 구조와 실행 시점의 동적인 객체 구조가 다르다는 사실이 이해를 어렵게 만든다.
  • 유연성이 항상 좋은 것은 아니다.
  • 유연한 설계라는 이면에는 복잡한 설계라는 의미가 숨어있다.
  • 변경은 예상이 아니라 현실이여야 한다.
    • 미래에 변경이 일어날지도 모른다는 막연한 불안감은 불필요하게 복잡한 설계를 낳는다.
    • 아직 일어나지 않은 변경은 변경이 아니다.

DRY 원칙

  • 309p
  • Don't Repeat Yourself
  • 중복 코드를 반복하지 마라!
  • 중복 코드가 가지는 가장 큰 문제는 코드를 수정하는 데 필요한 노력을 몇 배로 증가시킨다
    • 먼저 중복 코드를 찾는다.
    • 모든 코드를 일관되게 수정한다.

중복 여부 판단

  • 코드가 중복되었을 때 이것이 진짜 중복인지 가짜 중복인지 판단하는 방법
  • 변경이 일어 났을 때 두 코드를 함께 수정해야 한다면 이 코드는 중복이다.
  • 중복 코드는 코드의 모양이 결정하는 것이 아니라 변경에 반응하는 방식이 결정한다.

상속

  • 상속은 DRY 원칙을 지킬 수 있는 수단이다.
  • 하지만 설계는 트레이드오프 활동이다. 상속은 코드의 재사용을 위해 캡슐화를 희생한다.

상속과 결합도

  • 상속은 부모 클래스와 자식 클래스 사이에 강한 결합을 만들어낸다.

  • 상속을 이용해 코드를 재사용하기 위해서는 부모 클래스의 개발자가 세웠던 가정이나 추론 과정을 정확하게 이해해야 한다.

  • 이것은 자식 클래스 작성자가 부모 클래스의 구현 방법에 대한 정확한 지식을 가져야 한다는 것을 의미한다.

  • 자식 클래스가 부모 클래스의 구현 세부사항에 의존하도록 만들어 캡슐화를 약화시킨다.

취약한 기반 클래스

  • 자식 클래스가 부모 클래스의 변경에 취약해지는 현상을 의미한다.

super

  • 자식 클래스에서 super의 참조를 이용해 부모 클래스의 메서드를 직접 호출할 경우 결합도가 강해진다.
  • super 호출을 제거할 수 있는 방법을 찾아보자.

불필요한 인터페이스 상속 문제

  • 상속받은 부모 클래스의 메서드가 자식 클래스의 내부 구조에 대한 규칙을 깨트릴 수 있다

  • 자바 초기에 상속을 잘못 사용한 대표적인 사례 java.util.Stack을 보자

  • Stack은 나중에 추가된 요소가 가장 늦게 추출되는 LIFO 자료 구조인 스택을 구현한 클래스다.

  • Stack은 Vector를 재사용하기 위해 Stack을 Vector의 자식 클래스로 구현했다.

  • 그러나 Vector에는 임의의 위치에서 요소를 조회하고, 추가하고, 삭제할 수 있는 퍼블릭 인터페이스가 존재한다.

  • 따라서 Stack에게 상속된 Vector의 퍼블릭 인터페이스를 이용해 임의의 위치에서 요소를 추가하고 삭제해 Stack의 규칙을 쉽게 위반할 수 있다.

Stack<String> stack = new Stack<String>();
stack.push("1st");
stack.push("2nd");
stack.push("3rd");
stack.add(0, "4th"); // 임의의 위치에 원소를 삽입하는 것이 가능하다.

메서드 오버라이딩 오작용 문제

  • 자식 클래스가 부모 클래스의 메서드를 오버라이딩할 때 자식 클래스가 부모 클래스의 메서드 호출 방법에 영향을 받는 문제
  • 책에서는 HashSet은 상속받은 InstrumentedHashSet을 예로 들었다.
public class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;

public InstrumentedHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}

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

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

public int getAddCount() {
return addCount;
}
}
@Test
@DisplayName("InstrumentedHashSet 테스트")
void testAddAll() {
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(List.of("Snap", "Crackle", "Pop"));
Assertions.assertThat(s.getAddCount()).isEqualTo(6);
}

부모 클래스와 자식 클래스의 동시 수정 문제

  • 부모 클래스와 자식 클래스 사이의 개념적인 결합으로 인해 부모 클래스를 변경할 때 자식 클래스도 함께 변경해야 한다.

합성

  • 347p
  • 상속과 합성은 객체지향 프로그래밍에서 가장 널리 사용되는 코드 재사용 기법이다.
  • 합성은 전체를 표현하는 객체가 부분을 표현하는 객체를 포함해서 부분 객체의 코드를 재사용한다.
  • 상속에서 부모 클래스와 자식 클래스 사이의 의존성은 컴파일타임에 해결되지만 합성은 두 객체 사이의 의존성은 런타임에 해결된다.

상속과 비교

  • 합성은 구현에 의존하지 않는다는 점에서 상속과 다르다.
    • 상속은 자식이 부모의 내부 구현에 대해 상세히 알아야하기 때문에 강한 결합이 생긴다.
  • 상속은 내부에 포함된 객체의 구현이 아닌 퍼블릭 인터페이스에 의존한다.
    • 따라서 객체의 내부 구현이 변경되어도 영향을 최소화할 수 있다.
  • 따라서 코드 재사용을 위해 객체 합성이 클래스 상속보다 더 좋은 방법이다.
    • 구현에 대한 의존성을 인터페이스에 대한 의존성으로 변경하여 결합도를 느슨하게 한다.
  • 코드 재사용 관점에서 상속을 화이트 박스 재사용이라고 하며 합성을 블랙박스 재사용이라 한다.

불필요한 인터페이스 상속 문제 해결

  • 앞서 보았던 Stack을 합성을 이용해 다시 구현해 보자.

public class Stack<E> {
private Vector<E> elements = new Vector<>();

public E push(E item) {
elements.addElement(item);
return item;
}


public E pop() {
if (elements.isEmpty()) {
throw new EmptyStackException();
}
return elements.remove(elements.size() - 1);
}

}
  • 상속을 사용한 기존 Stack 대신 Vector를 포함한다.
  • 상속에서는 불필요한 인터페이스 상속 문제 때문에 임의 위치의 원소를 삭제하거나 삽입하는게 가능했지만 합성을 이용하면 그런 불필요한 인터페이스를 가지지 않는다.

메서드 오버라이딩 오작용 문제 해결

  • 예제의 InstrumentedHashSet을 상속에서 합성으로 변경하면 메서드 오버라이딩 오작용 문제를 해결할 수 있다.
  • InstrumentedHashSet이 내부에 HashSet을 인스턴스로 가지고 HashSet의 퍼블릭 인터페이스만 사용해서 InstrumentedHashSet을 구현하면 된다.

해결 코드

public class InstrumentedHashSet<E> {
private int addCount = 0;
private Set<E> set;

public InstrumentedHashSet(Set<E> set) {
this.set = set;
}

public boolean add(E e) {
addCount++;
return set.add(e);
}

public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return set.addAll(c);
}

public int getAddCount() {
return addCount;
}

}
  • 하지만 InstrumentedHashSet은 HashSet의 모든 퍼블릭 인터페이스를 그대로 제공해야 한다.
  • 따라서 아래와 같은 코드를 작성한다.
public class InstrumentedHashSet<E> implements Set<E> {
private int addCount = 0;
private Set<E> set;

public InstrumentedHashSet(Set<E> set) {
this.set = set;
}

@Override
public boolean add(E e) {
addCount++;
return set.add(e);
}

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

public int getAddCount() {
return addCount;
}

@Override
public Spliterator<E> spliterator() {
return Set.super.spliterator();
}

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

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

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

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

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

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

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

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

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

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

@Override
public void clear() {
set.clear();
}
}
  • Set의 오퍼레이션을 오버라이딩한 인스턴스 메서드 안에서 set 인스턴스에게 동일한 메서드를 그대로 전달한다.
    • 이를 포워딩이라고 부른다.
  • 포워딩은 기존 클래스의 인터페이스를 그대로 외부에 제공하면서 구현에 대한 결합 없이 일부 작동 방식을 변경할 때 유용한 기법이다.