메서드 이름을 신중히 짓자.

 

같은 패키지에 속한 다른 이름들과 일관되게 짓는게 최우선 목표다. 

 

편의 메서드를 너무 많이 만들지 말자.

 

매개변수 목록은 짧게 유지하자. 4개 이하를 권장

  1. 여러 메서드로 쪼갠다.
    • 잘못하면 메서드가 많아질 수 있으나 직교성을 높여 메서드를 줄여주는 효과가 있다.
    • subList, indexOf메서드를 사용하여 부분리스트에서 인덱스를 찾는 예시
  2.  매개변수 여러 개를 묶어주는 도우미 클래스를 만드는 것이다.
    • 도우미 클래스는 정적 멤버 클래스로 둔다.
  3. 객체 생성에 사용한 빌더 패턴을 메서드 호출에 응용
  4. 매개변수의 타입으로는 클래스보다는 인터페이스가 더 낫다
  5. boolean보다는 우너소 2개짜리 열거 타입이 낫다

 

 

 

직교성 

  • 공통점이 없는 기능들이 잘 분리되어 있다.
  • 기능을 원자적으로 쪼개 제공한다.
  • API가 다루는 개념의 추상화 수준에 맞춰 원자적으로 쪼개야한다.
  • MSA 아키텍처는 직교성이 높고 모놀리식 아키텍처는 직교성이 낮다.
반응형
Posted by UIJ
  • 상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지(자기사용) 문서로 남겨야 한다.
  • 효율적인 하위 클래스를 큰 어려움 없이 만들 수 있게 하려면 클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅(hook)을 잘 선별하여 protected 메서드 형태로 공개해야할 수도 있다.
  • 상속용으로 설계한 클래스는 배포 전에 반드시 하위 클래스를 만들어 검증해야 한다. 
  • 상속용 클래스의 생성자는 직접적으로든 간접적으로든 재정의 가능 메서드를 호출해서는 안 된다. 재정의 가능 메서드를 호출하는 자기 사용 코드를 완벽히 제거해야한다.
  • private, final, static 메서드는 재정의가 불가능하니 생성자에서 안심하고 호출해도 된다.
  • 상속용으로 설계하지 않은 클래스는 상속을 금지한다.

 

clone과 readObject 모두 직접적으로든 간접적으로든 재정의 가능 메서드를 호출해서는 안 된다.

  • readObject의 경우 하위 클래스의 상태가 미처 다 역직렬화되기 전에 재정의한 메서드부터 호출하게 된다.
  • clone의 경우 하위 클래스의 clone메서드가 복제본의 상태를 (올바른 상태로) 수정하기 전에 재정의한 메서드를 호출한다. 이는 프로그램 오작동으로 이어질 수 있다.

상속을 금지하는 두 가지 방법

  • 클래스를 final로 선언한다.
  • 모든 생성자를 private이나 package-private으로 선언하고 public 정적 팩터리를 만들어준다. 정적 팩토리외에는 그 어떤 초기화 메서드도 public으로 선언해서는 안 된다.
반응형
Posted by UIJ

캡슐화를 깨는 상속을 지양하자. 

  • 상위 클래스가 릴리즈마다 변경될 경우 상속받는 하위클래스도 변경되어야하고 상위 클래스의 구현 방식에 따라 하위 클래스의 동작에 이상이 생길 수도 있다. 
  • 다음 릴리즈에 새로운 메서드가 추가될 때 하위 클래스에서 재정의하지 못한 새로운 메서드를 통해 '잘못된'객체를 컬렉션에 넣을 수 있다.
  • 메서드 재정의가 아닌 새로운 메서드를 추가할 경우에도 다음 릴리즈에 상위 클래스에 하위 클래스에서 추가한 메서드와 시그니처가 같고 반환 타입이 다르면 하위 클래스는 컴파일되지 않는다. 동일한 메서드를 재정의한게 되더라도 상위 클래스의 메서드 규약을 만족하지 못할 가능성이 있다.

새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하게 하는 컴포지션을 사용하자.

  • composition 설계 기법 : 기존 클래스가 새 클래스의 일부(component)로 구성되는 방식
  • forwarding(전달) : 새로운 클래스에 포함된 각각의 메서드는 기존 클래스에 있는 메서드 가운데 필요한 것을 호출해서 그 결과를 반환 
  • forwarding method(전달 메서드) : 전달 기법을 사용해 구현된 메서드

컴포지션을 통해 구현된 클래스는 기존 클래스의 세부 구현에 종속되지 않기 때문에 견고하다.

컴포지션과 전달의 조합은 넓은 의미로 위임(delegation)이라고 부른다. 

 

래퍼 클래스는 콜백 프레임워크와는 어울리지 않는다.

상속은 상위 클래스와 하위 클래스가 IS-A 관계일때만 사용하는 것이 좋다. 단, 상위 클래스가 계승을 고려하여 설계되었고 동일한 패키지내에 있어야 한다. 그렇지 않다면 구성과 전달 기법을 사용하는 것이 좋다. 

반응형
Posted by UIJ

불변 클래스는 서버가 내려갈 때까지(객체가 파괴되는 순간) 인스턴스 내부의 값이 절대로 바뀌지 않는 클래스다. 자바도 String, primitive types(int, char, double, float 등) 의 박싱된 클래스, BigInteger, BigDecimal 등이 자바에서 제공하는 불변 클래스다. 불변 클래스는 설계 및 구현이 쉽고 오류가 생길 가능성이 적다. *가변 클래스는 프로그램을 작성하면서 값이 변경되고 잘 못 변경된 값으로 인해 시스템 오류가 생길 수 있으나 불변 클래스는 소멸될 때까지 값이 변하지 않아 값 변경으로 인한 오류가 발생할 여지가 없다.

 

불변 클래스 생성 규칙

  • 객체의 상태를 변경하는 setter 메서드를 제공하지 않는다.
  • 클래스를 확장할 수 없도록 한다.
  • 모든 필드를 final로 선언한다.  (https://docs.oracle.com/javase/specs/jls/se17/html/jls-17.html#jls-17.5) 이는 개발자로 하여금 동기화없이 thread-safe한 불변의 객체를 구현할 수 있게해준다. 
  • 모든 필드를 private으로 선언한다.
  • 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다. 생성자, 접근자, readObject 메서드(아이템 88) 모두에서 방어적 복사를 수행해야한다.

인스턴스 자신은 수정하지 않고 새로운 인스턴스를 만들어 반환하는 프로그래밍 패턴을 함수형 프로그래밍이라 한다. 이와 달리 절차적 혹은 명령형 프로그래밍에서는 메서드에서 피연사자인 자신을 수정해 자신의 상태가 변하게 된다. 불변 객체는 thread safe하여 따로 동기화할 필요가 없다. 이러한 특성을 활용하여 불변 객체는 최대한 reuse를 하면 메모리 사용량과 가비지 컬렉션 비용이 줄어든다.(성능이 좋아진다.) 불변 클래스는 자주 사용되는 인스턴스를 정적 팩터리를 사용하여 캐싱할 수 있다. 

 

 

* 불변 객체의 단점 : 값이 다르면 반드시 독립된 객체로 만들어야한다. 불변 객체의 값의 가짓수가 많으면 이들을 만드는데 많은 비용이 든다. 그래서 BigInteger에서는 내부적으로 가변 동반 클래스를 사용하여 연산 속도를 높여준다. String 클래스는 새로운 문자열이 생성될 때마다 상수화된 문자열 객체를 만들기에 잦은 변경이 일어날 경우 가변 동반 클래스인 StringBuilder를 사용하면 된다.

 

가변 클래스도 객체가 가질 수 있는 상태의 수를 줄여 예측하기 쉽고 오류가 생길 여지를 차단하는게 좋다. 이를 위해 꼭 변경해야 할 필드를 뺀 나머지 모두를 final로 선언하자. 객체를 재활용할 목적으로 필드를 초기화하는 메서드를 제공하면 성능 이점 대신 복잡성만 커지고 오류가 발생할 확률이 커진다. 

 

클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다. 

 

 

뒷 장에서 확인할 내용

불변 객체를 자유롭게 공유할 수 있다는 점은 방어적 복사(아이템 50)도 필요 없다는 결론으로 자연스럽게 이어진다.

불변 객체는 그 자체로 실패 원자성을 제공한다.(아이템 76)

다시 말해 신뢰할 수 없는 하위 클래스의 인스턴스라고 확인되면, 이 인수들을 가변이라 가정하고 방어적으로 복사해 사용해야 한다.(아이템50)

지연 초기화(아이템 803의 예이기도 한 이 기법을 String도 사용한다.

String과 BigInteger처럼 무거운 값 객체도 불변으로 만들 수 있는지 고심해야한다. 성능 때문에 어쩔 수 없다면(아이템 67)불변 클래스와 쌍을 이루는 가변 동반 클래스를 public 클래스로 제공하도록 해야한다.

 

반응형
Posted by UIJ