상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 한다. 클래스의 API로 공개된 메서드에서 클래스 자신의 또 다른 메서드를 호출할 수도 있다. 또한 그 메서드가 재정의 가능 메서드(protected, public 중 final이 아닌 모든 메서드)라면 그 사실을 호출하는 메서드의 API 설명에 적시해야 한다. API 문서의 메서드 설명 끝에 종종 Implementation Requirements로 시작하는 절이 있다. 이 절은 그 메서드의 내부 동작 방식을 설명하는 곳이다. 이 절은 메서드 주석에 @implSpec 태그를 붙여주면 자바독 도구가 생성해준다. 해당 설명에 따르면 iterator 메서드..
상속보다는 컴포지션을 사용하라 상속은 코드를 재사용하는 강력한 수단이지만, 잘못 사용하면 오류를 내기 쉬운 소프트웨어를 만들게 된다. 같은 프로그래머가 통제하는 패키지 안에서 상속한다면 안전할 수 있지만, 다른 패키지의 구체 클래스를 상속하는 것을 위험하다. (인터페이스 상속이 아닌 클래스 상속) 상속의 문제점 상속은 상위 클래스의 설계에 따라 캡슐화를 깨뜨리는 등의 하위 클래스에 문제를 일으킬 수 있다. 예를 들어 HashSet을 사용하는 프로그램에서 성능을 높이기 위해 처음 생성된 이후 원소가 몇개 더해졌는지 등의 기능을 추가했다고 생각해보자. import java.util.Collection; import java.util.HashSet; import java.util.List; public cla..
변경 가능성을 최소화하라 String, BigInteger, BigDecimal 등은 자바 플랫폼 라이브러리에서 불변 클래스로 정의 된다. 불변 클래스란 인스턴스 내부 값을 수정할 수 없는 클래스를 의미한다. 불변 클래스는 가변 클래스보다 설계 및 구현이 쉬우며, 오류가 생길 여지가 적고 훨씬 안전하다. 불변 클래스 생성 규칙 객체의 상태를 변경하는 메서드(Setter)를 제공하지 않는다. 클래스를 확장할 수 없도록 한다. 상속을 막는 대표적인 방법은 클래스를 final로 선언하는 것이며, 다른 방법도 있다. 모든 필드를 final로 선언한다. 시스템이 강제하는 기능을 이용해 설계자의 의도를 드러낸다. 새로 생성된 인스턴스를 동기화 없이 다른 스레드로 건네도 문제없이 동작하게끔 보장하는 데도 필요하다. ..
public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 class Point { public double x; public double y; } 다음과 같은 클래스 설계는 퇴보한 클래스이다. 이런 클래스는 데이터 필드에 직접 접근할 수 있기 때문에 캡슐화의 이점을 제공하지 못한다. API를 수정하지 않고는 내부 표현을 바꿀 수 없고, 외부 접근이 가능하기 때문에 불변식을 보장할 수 없으며, 외부에서 필드에 접근할 때 부수 작업을 수행할 수도 없다. class Point { private double x; private double y; public Point(double x, double y) { this.x = x; this.y = y; } // Getter & Setter } Ge..
클래스와 멤버의 접근 권한을 최소화하라 잘 설계된 컴포넌트는 내부 데이터와 내부 구현 정보를 외부 컴포넌트로부터 잘 숨긴 컴포넌트이다. 모든 내부 구현을 완벽히 숨기고, 구현과 API를 깔끔히 분리하는 것이 중요하다. 이를 정보 은닉 혹은 캡슐화라고 한다. 정보 은닉의 장점 여러 컴포넌트를 병렬로 개발할 수 있기 때문에 시스템 개발 속도를 높인다. 디버깅과 교체에 용이하여 시스템 관리 비용을 낮춘다. 다른 컴포넌트에 영향을 주지 않기 때문에 제작 난이도를 낮추고 성능 최적화와 SW 재사용에 도움을 준다. 접근 제어자 자바에서는 정보 은닉을 위해 접근을 제어하는 메커니즘을 제공한다. public : 모든 곳에 접근할 수 있다. (공개 API - 영원한 관리 필요) protected : package-pri..
Comparable을 구현할지 고려하라 객체를 정렬하기 위한 인터페이스로 자바에선 Comparable 인터페이스와 Comparator 인터페이스 두 가지가 존재 한다. 정확히는 정렬하기 위한 인터페이스라기 보단, 객체를 비교할 수 있는 인터페이스라고 보는 것이 바람직하다. public class PointTest { public static void main(String[] args) { Point a = new Point(1, 2); Point b = new Point(2, 3); } static class Point { int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } } 다음과 같은 Point 객체는 부등호 비교뿐만 아니라 객체를..
clone 재정의는 주의해서 진행하라 /** * A class implements the Cloneable interface to * indicate to the {@link java.lang.Object#clone()} method that it * is legal for that method to make a * field-for-field copy of instances of that class. * * Invoking Object's clone method on an instance that does not implement the * Cloneable interface results in the exception * CloneNotSupportedException being thrown. * *..
toString을 항상 재정의하라 Object의 기본 메서드 중 toString은 재정의하지 않으면 phone@adbbd처럼 단순히 클래스명@해시코드를 표현한다. toString의 규약은 모든 하위 클래스에서 이 메서드를 재정의하라이다. toString 메서드는 객체를 println, printf, +, assert, 디버거 등 을 출력할 때 자동으로 호출된다. 즉, 직접 호출하지 않아도 사용되는 경우가 많다. 따라서 재정의는 필수적이며, 재정의 시에 객체가 가진 주요 정보를 모두 반환하는 것이 올바르다. /** * Returns a string representation of this collection. The string * representation consists of a list of the ..