1. 상속과 중복 코드
1.1. DRY 원칙
•
중복 코드는 변경을 방해한다.
◦
이것이 중복 코드를 제거해야 하는 가장 큰 이유다.
•
프로그램의 본질을 비즈니스와 관련된 지식을 코드로 변환하는 것이지만 이 지식은 항상 변한다.
◦
그에 맞춰 코드를 변경해야 한다.
•
중복 코드의 문제점은 수정에 필요한 노력을 몇 배로 증가시킨다는 것이다.
•
중복 여부 판단 기준은 변경으로 요구사항 수정 시 두 코드를 함께 변경한다면 중복 코드로 판단한다.
•
신뢰할 수 있고 수정하기 쉬운 소프트웨어를 만드는 방법 중 하나는 중복을 제거하는 것이다.
•
Don’t Repeat Yourself = DRY 법칙
◦
동일한 지식을 중복하지 말라
◦
한 번, 단 한번 원칙 또는 단일 지점 제어 원칙이라고도 부른다.
1.2. 상속을 이용해 중복 코드 제거하기
•
이미 존재하는 클래스와 유사한 클래스가 필요하다면 코드를 복사하지 말고 상속을 이용해 코드를 재사용하라
•
속성의 변경이나 같은 코드를 이용하는 클래스는 부모 클래스를 통해서 이를 제공 받아 중복 코드를 줄일 수 있다.
2. 취약한 기반 클래스 문제
•
상속은 자식 클래스와 부모 클래스의 결합도를 높인다.
◦
이 강한 결합으로 인해 자식 클래스는 부모 클래스의 불필요한 세부사항에 엮이게 된다.
◦
부모 클래스의 작은 변경에도 자식 클래스는 오류에 시달려야할 수 있다.
•
취약한 기반 클래스 문제 = 부모 클래스의 변경에 의해 자식 클래스가 영향을 받는 현상
◦
상속을 사용한다면 피할 수 없는 객체지향의 근본적인 취약성
•
상속을 사용하면 부모 클래스의 퍼블릭 인터페이스가 아닌 구현에도 자식 클래스가 영향받기 쉬워진다.
◦
작은 변경에도 자식 클래스가 요동칠 수 있다.
2.1. 불필요한 인터페이스 상속 문제
•
java.util.Properties와 java.util.Stack은 상속을 잘못 사용한 대표적인 예시다.
•
자바의 초기 프레임워크 개발자들은 요소의 추가/삭제 오퍼레이션을 제공하는 Vector를 재사용하기 위해 Stack을 Vector의 자식 클래스로 구현했다.
◦
Vector는 임의의 위치에 요소를 조회/추가/삭제할 수 있는 오퍼레이션을 제공하는데 비해 Stack은 맨 마지막 위치에만 요소 추가가 가능한 오퍼레이션을 제공한다.
◦
하지만 의도와는 다르게 부모 클래스의 인터페이스를 이용한다면 임의의 위치에 요소를 추가할 수 있게되므로 Stack의 규칙을 쉽게 위반하게 된다.
•
상속받은 부모 클래스의 메서드가 자식 클래스의 내부 구조에 대한 규칙을 깨트릴 수 있다.
2.2. 메서드 오버라이딩 오작용 문제
•
InstrumentedHashSet은 HashSet의 내부에 저장된 요소의 수를 셀 수 있는 기능을 추가한 클래스로서 HashSet의 자식 클래스로 구현돼 있다.
public class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
@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);
}
}
Java
복사
•
InstrumentedHashSet은 요소를 추가한 횟수를 기록하기 위해 addCount라는 인스턴스 변수를 포함시키고 있다.
InstrumentedHashSet<String> languages = new InstrumentedHashSet<>();
languages.addAll(Arrays.asList("Java", "Ruby", "Scala"));
Java
복사
•
위 코드의 실제 실행 결과는 예상과는 다른 6인데 부모 클래스인 addAll내에서 add메서드를 호출하기 때문이다.
•
자식 클래스가 부모 클래스의 메서드를 오버라이딩 할 경우 부모 클래스가 자신의 메서드를 사용하는 방법에 자식 클래스가 결합될 수 있다.
◦
죠슈아 블로치의 주장 = 클래스가 상속되기를 원한다면 상속을 위해 클래스를 설계하고 문서화해야 하며, 그렇지 않은 경우에는 상속을 금지시켜야 한다.
2.3. 부모 클래스와 자식 클래스의 동시 수정 문제
•
상속 사용 시 자식 클래스가 부모 클래스의 메서드를 오버라이딩하거나 불필요한 인터페이스를 상속받지 않아도 부모를 수정할 때 자식을 함께 수정해야할 수도 있다.
•
클래스를 상속하면 결합도로 인해 자식 클래스와 부모 클래스의 구현을 영원히 변경하지 않거나 자식 클래스와 부모 클래스를 동시에 변경하거나 둘 중 하나를 선택할 수 밖에 없다.
3. Phone 다시 살펴보기
•
취약한 기반 클래스 문제를 어느정도 완화 시키는 방법
3.1. 추상화에 의존하자
•
함께 변경이 일어날 가능성이 있는 이유 = 강하게 결합되었기 때문
•
문제를 해결하는 가장 일반적인 방법은 자식 클래스가 부모 클래스의 구현이 아닌 추상화에 의존하도록 만드는 것이다.
◦
정확하게 말하면 부모/자식 클래스 모두 추상화에 의존하도록 만든다.
•
코드 중복을 제거하기 위한 상속을 도입할 때 따르는 두 가지 원칙
◦
두 메서드가 유사하게 보인다면 차이점을 메서드로 추출하라.
▪
메세드 추출을 통해 동일한 형태로 보이도록 만들 수 있다.
◦
부모 클래스의 코드를 하위로 내리지 말고 자식 클래스의 코드를 상위로 올려라.
▪
부모 클래스의 구체적인 메서드를 자식 클래스로 내리는 것보다 자식 클래스의 추상적인 메서드를 부모 클래스로 올리는 것이 재사용성과 응집도 측면에서 더 뛰어난 결과를 얻을 수 있다.
3.2. 차이를 메서드로 추출하라
•
변하는 것으로 부터 변하지 않는 것을 분리하라 또는 변하는 부분을 찾고 이를 캡슐화하라 라는 조언을 메서드 수준에서 적용하는 것이다.
•
중복 클래스의 서로 다른 점을 찾아 동일한 이름을 가진 메서드를 부모로 올리고 중복코드를 부모에 구현한다.
3.3. 추상화가 핵심이다
•
공통 코드를 이동 시킨 후 각 클래스는 서로 다른 변경의 이류를 가진다는 것에 주목하자
◦
이는 단일 책임의 원칙을 준수하기 때문에 응집도가 올라간다.
•
추상화에 의존한다면 자식 클래스는 부모 클래스 구현에 의존하지 않는다.
◦
때문에 부모 클래스의 변경은 자식에게 전파되지 않아 낮은 결합도를 유지하게 된다.
•
또한 추상화에 의존하면 새로운 전략을 추가하기 쉬워진다.
◦
이는 개방-폐쇄 원칙을 준수하게 된다.
•
이는 모두 추상화에 의존하며 얻을 수 있는 장점들이다.
3.4. 의도를 드러내는 이름 선택하기
•
추상 클래스는 Abstract를 구체적인 전략은 자신이 어떤 역할을 하는 자식 클래스인지 클래스 명에서 드러내도록 하자
4. 차이에 의한 프로그래밍
•
상속이 강력한 이유는 익숙한 개념을 이용해 새로운 개념을 쉽고 빠르게 추가할 수 있기 때문이다.
•
이처럼 기존 코드와 다른 부분만 추가해 기능을 확장하는 걸 차이에 의한 프로그래밍이라 부른다.
◦
목표는 중복을 제거하고 재사용을 하는 것이다.
◦
프로그래밍 세계에서 코드 중복은 악의 근원이다.
•
코드 중복을 줄이는건 재사용을 넘어 버그가 존재하지 않게한다.
•
객체지향에서 중복 코드 제거의 가장 유명한 방법 중 하나는 상속이다.
◦
상속의 오용과 남용은 확장과 이해가 어려운 코드를 만들기 때문에 필요한 경우에만 사용하자
•
중복을 제거하기 위해서는 코드를 재사용 가능한 단위로 분해하고 재구성해야 한다.
•
코드를 재사용하기 위해서는 중복 코드를 제거해서 하나의 모듈로 모아야 한다.