이펙티브자바(3) - 클래스와 인터페이스

클래스와 멤버의 접근권한을 최소화하라

정보은닉의 장점

  • 여러컴포넌트를 병렬로 개발할수있어서 개발속도가 빨라진다
  • 시스템관리비용을 낮춘다. 각컴포넌트를 더 빨리 파악하여 디버깅할수있고, 다른 컴포넌트로 교체하는 부담도 적기때문이다
  • 소프트웨어 재사용성을 높인다.

private으로 설계하고 공개해야할일이 생기게되면 그부분만 public으로 설계하라는 맥락

public 클래스에서는 public 필드가아닌 접근자 메서드를 사용하라

public 클래스는 가변필드를 직접노출하면 절대 안된다.
불편필드라면 노출해도 덜위험하지만 말이다.
하지만, default 클래스나 private 중첩 클래스에서는 종종 필드를 노출하는편이 나을때도 있다.

변경 가능성을 최소화하라

불변객체는 thread-safe 하고 안전성이있다. 단순하다.
안심하고 공유할수있다. 그렇기떄문에 자주사용되는 인스턴스를 캐싱하여 같은 인스턴스를 중복생성하지않게 해주는 정적팩토리를 제공할수있다.

정적팩터리를 사용하여 여러클라이언트가 인스턴스를 공유하여 메모리사용량, 가비지컬렉션 비용이 줄어든다 등 책에서는 불변객체에대한 장점들을 나열하다가

값이다르면 반드시 독립된 객체로 만들어야한다 = ? 단점부분 이해가안간다.

다른합당한 이유가 없다면 모든필드는 private final이어야한다. (꼭 변경해야할 필드를 뺸 나머지는 말이다.)
확실한 이유가없다면 정적팩터리, 생성자 외에는 그어떤 초기화 메서드도 public으로 제공해서는 안된다.

자바빈, VO,DTO

VO = 값을담는 객체(데이터스트럭쳐) 로 하다가 이후에는 값을 가지고있는데 setter로 값을 변경할수없는 것을의미한다
값을담는 객체는 최근에 DTO라 불린다.
최대한 값객체(VO)를 만드는것이 좋다.

자바빈이 만들어진이유 = 자바빈 규약으로 맞춰서 객체를 만들면 컴포넌트로 사용하겠다 라는 규약이다. 자바빈에 따라 만든거를 컴포넌트처럼 배포가 가능했다.
재사용가능한 소프트웨어 컴포넌트를 의미한다.

property vs attribute

property = 외부에 노출된 값. getter,setter 할수있는 값
attribute = 내부의 속성(외부에서 관찰이 안됨)

상속대신 컴포지션을 사용하라

상속을 사용하게 될때, 하위클래스에서 사용할때 변수가 생기지않도록 어떻게 작동할지 잘 명시해줘야한다. 상속의 여러문제점을 알려준다.
묘안중 하나로 새로운 클래스를 만들고 private필드로 기존 클래스의 인스턴스를 참조하게 하는것이다.
이러한설계를 컴포지션이라 한다. 이는 기존클래스에 새로운 메서드가 추가되더라도 영향받지 않는다.
상속은 반드시 하위클래스가 상위클래스의 진짜 하위타입인 상황에서만 쓰여야한다.
하위클래스가 상위클래스와 is-a 관계일떄만 클래스를 상속해야한다.
is-a 관계? 차와 페라리의 관계라고 생각하면 편하다.
페라리는 차지만 차는 페라리가 아니다.
정말로 B가A인가? 자문뒤에 아닐거같다면 A를 private인스턴스로 두고 A와 다른 API를 제공해야한다. A는 B의 필수구성요소가 아니라 구현하는 방법중 하나일뿐이다.

상속은 강력하지만 캡슐화를 해친다는 문제가있다. 상속을 사용하려거든 상위클래스에서 확장을했을때 하위클래스에 어떤영향을 끼칠지도 고려하면서 써야한다는 얘기이다. 애매하다면 컴포지션을 사용하라

상속을 고려해 설계하고 문서화하라, 그러지않았다면 상속을 금지하라

상속용클래스는 재정의할수있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야한다.
( 어떤 순서로 호출하는지, 각각의 호출결과가 처리에 어떤 영향을 주는지 등)
그러지않고 메서드를 재정의하게되면 어떤일이 일어나는지 예측범위를 넘을가능성이있다.

상속용으로 설계한 클래스는 배포전에 반드시 하위클래스를 만들어 검증해야한다.
상속용 클래스의 생성자는 직,간접적으로 재정의 가능 메서드를 호출해서는 안된다.
하위클래스의 생성자가 호출되기도전에 재정의가능 메서드를 먼저 호출하기때문이다.
상속용으로 클래스를 설계할때는 엄청난 노력이 들고, 그 클래스에 안기는 제약도 상당하다

클래스를 final로 선언하던지, 모든생성자를 private이나 default로 선언하고 public 정적팩토리를 만들어주는 방법이있다.

상속용 클래스를 설계하는것은 매우 까다롭다. 어떻게 사용될지 문서로 남겨야하고, 일부메서드는 protected로 남길지 private으로 남길지, 등 고려해야할점이 많다.

상속을 구현하기위해 하위클래스 3개정도를 테스트하라고하는데 현실적인지는 잘모르겠다.

추상클래스보단 인터페이스를 사용하라

추상클래스 vs 인터페이스

추상클래스는 새로운타입을 정의하는데 커다란 제약을 안게된다.
자바는 단일상속만 지원하기때문이다.
추상클래스는 계층구조가 강요된다. 추상클래스의 구현클래스는 하위클래스가 되야만하는 구조가되어버린다.

인터페이스는 default메서드가 설계가 가능하더라도, public으로만 설계가 가능하다는 단점이있다.

public abstract class AbstractMapEntry<K,V>
        implements Map.Entry<K,V> {
    // 변경 가능한 엔트리는 이 메서드를 반드시 재정의해야 한다.
    @Override public V setValue(V value) {
        throw new UnsupportedOperationException();
    }
    
  
    
    // Map.Entry.equals의 일반 규약을 구현한다.
    @Override public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry) o;
        return Objects.equals(e.getKey(),   getKey())
                && Objects.equals(e.getValue(), getValue());
    }

    // Map.Entry.hashCode의 일반 규약을 구현한다.
    @Override public int hashCode() {
        return Objects.hashCode(getKey())
                ^ Objects.hashCode(getValue());
    }

    @Override public String toString() {
        return getKey() + "=" + getValue();
    }
}

왜 setValue 에 에러를 던지게 해놓았는가?
-> setValue메서드 자체를 사용하지않게끔 하려는 의도인것같다.

인터페이스는 구현하는쪽을 생각해 설계하라

default 메서드는 무적이 아니다.
다방면으로 고려해야할점이있고, 테스트도 해서 추가해야한다.
default메서드를 추가하는순간 오류를 야기할수도있다는점을 생각해라.(인터페이스의 원래의 목적을 해칠수있는 그림이나올수있다.)

인터페이스는 타입을 정의하는 용도로만 사용하라

잘못된 안티패턴의 예시 = 상수인터페이스 패턴
인터페이스 내부에 static final 상수들만 가득한 인터페이스 (외부인터페이스가아니라 내부구현에 해당하기때문에)
사용자에게 혼란을 야기 + 클라이언트가 해당상수들에게 종속될수있는 가능성생긴다.

상수를 공개할 목적이라면 인스턴스화 할수없는 유틸리티 클래스를 만들거나 열거타입을 만들어 공개하면된다.
인터페이스는 타입을 정의하는용도로만 사용하고 상수공개용으로 쓰지말자

좋은 웹페이지 즐겨찾기