
다행이도 이번 제목은 그렇게 이질감이 들지는 않는다.
조금 가볍게 생각해볼수 있는 Item인것 같다.
우리는 객체 지향 프로그래밍(Object Oriented Programming : OOP) 이라는 단어를 참 많이 들어봤다.
당시는 마구 잡이식 개발 방식에서 goto문을 사용하지 않기로 약속한 정차 지향 방법론이 주를 이루던 때였다.
하지만 코드 수정이 어려워 재사용이 힘들다는 점에서 너무 큰 문제점을 가지고 있었고,
결국 코드를 재사용 하기 좋게 붙였다 뗏다 할 수 있는 모듈(객체 : Object) 단위로 개발하자는 의견이 나론다.
그때부터 큰 변화의 바람이 불기 시작했고,
바로 그것이 오늘날까지 강력하게 영향을 발휘하고 있는 객체 지향 방법론이다.
결과적으로 OOP의 핵심은 코드의 재사용이다.
이 목적을 위해서 우리는,
일단 어떤 틀의 형태인 클래스를 만들고,
그를 통해 인스턴스를 만들어 사용하는 방식을 취하기로 했다.
그런데 간혹가다 단순히 정적 메서드와 정적 필드 (쉽게 말해 응용 가능성이 없는),
만을 담은 클래스를 만들고 싶을 때가 있다.
마치 C언어에서의 함수(Function)처럼 말이다
사실 객체 지향 방법론의 사고방식에서 벗어난 행동이라 부정적으로 보일수 있지만, 나름 필요할 때가 있다.
정적인 클래스가 도움이 될 때
java.lang.Math나 java.util.Arrays처럼,
기본 타입 값이나 배열 관련 메서드들을 모아놓을때java.util.Collections처럼 특정 인터페이스의 구현체들에 대하여,
그것들을 생성해주는 정적 팩토리 메서드들을 모아놓을 때final 클래스와 관련한 메서드들을 모아놓을 때
어쨋든 OOP에서도 그런게 필요한 경우가 있다는 말이다.
여기서 문제는, 인스턴스를 만들어 쓰지 않을 정적 클래스라면
아예 인스턴스를 못 만들게 막아줘야 한다는 것이다.
// Util이지만 생성자가 없음
public class ImageUtility {
private static String IMAGE_DATE_FORMAT = "yyyyMMddHHmm";
public static String makeImageFileNm(String imgFileNm) {
return imgFileNm + "_" + new SimpleDateFormat(IMAGE_DATE_FORMAT).format(new Date());
}
}
생성자를 명시하지 않으면 컴파일러가 자동으로 매개변수를 받지 않는 public 생성자를 만든다. 이때 사용자는 이 생성자가 자동으로 생성된 것인지 구분할 수 없으며,
// 다음과 같이 사용하기를 바랬으나,
ImageUtility.makeImageFileNm("test", ".png");
// 생성자를 생성해서 사용할 수도 있음
ImageUtility imageUtility = new ImageUtility();
String imageFileNm = imageUtility.makeImageFileNm("test", ".png");
이처럼 의도치 않게 인스턴스화할 수 있게된 클래스들도 발생한다.
// Util이지만 생성자가 없음
abstract class ImageUtility {
private static String IMAGE_DATE_FORMAT = "yyyyMMddHHmm";
public static String makeImageFileNm(String imgFileNm) {
return imgFileNm + "_" + new SimpleDateFormat(IMAGE_DATE_FORMAT).format(new Date());
}
}
public class ItemImageUtility extends ImageUtility {...}
// 추상클래스이기 때문에 생성자 생성 불가
// ImageUtility imageUtility = new ImageUtility();
//상속받은 클래스에서 생성자 호출 가능
ItemImageUtility itemImageUtility = new ItemImageUtility();
이 방법 역시도 별다른 움이 되지는 못한다.
그것을 상속받아 하위 클래스로 만든다음 인스턴스화 하면 그만이기 때문이다.
추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없으며,
오히려 추상클래스로 만들었다가는, 그걸 본 개발자들이 '아, 이건 상속받아서 인스턴스화 해서 쓰라는건가?'라고 생각할 수도 있다.
위 문제를 해결하는 방법은 그렇게 어렵지 않다.
private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다.
public class ImageUtility {
// 기본 생성자가 만들어지는 것을 방어(인스턴스화 방지용)
private ImageUtility() {
throw new AssertionError();
}
}
private 생성자이므로 클래스 외부에서는 접근할 수 없으며, 내부에서 실수로 생성자를 호출하는 경우에 대응하기 위해 AssertionError 예외처리를 했다.
하지만, 생성자가 있는데 호출할 수 없는 것은 직관적이지 않으므로, 적절한 주석을 다는 것을 권장한다.
또한, private 생성자는 상속도 불가능하다. 모든 생성자는 상위 클래스의 생성자를 호출하게 되는데, 이를 private 선언으로 하위 클래스가 상위 클래스의 생성자에 접근을 못해 상속이 불가능하다.
인스턴스화를 하지 않기위해 private 생성자가 선언되어 있고, 배열 관련 메서드들이 모여있다.
Math에 대한 기본 타입(PI, E)이나 관련 메서드들이 모여있다.
특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(팩터리)를 모아놓을 수 있다. (java8부터 이런 메서드를 인터페이스에 넣을 수 있다.
final class를 상속해서 하위 클래스에 메서드를 넣는 것은 불가능하므로, final 클래스와 관련 메서드들을 모아놓을때도 사용한다.