우리는 보통 클래스의 인스턴스를 생성자로 얻는다.
하지만 생성자를 사용하여 얻는 방법 대신, 정적 팩터리 메서드로 얻을 수도 있다.
정적 팩터리 메서드란 static factory method로, 해당 클래스의 인스턴스를 반환하는 역할을 한다.
그렇다면 어떤 경우에 static factory method를 사용할 수 있을까?
생성자를 통하여 객체의 특성을 묘사하기는 쉽지 않다.
하나 생각을 해보자.
static public class People{
private Long age;
private Boolean isMan;
public People(Long age, Boolean isMan){
this.age = age;
this.isMan = isMan;
}
}
People이라는 클래스는 나이와 남자,여자 여부를 나타내는 두가지의 파라미터를 가지는 상황을 가정하였다.
public class Item1 {
static public class People{
private Long age;
private Boolean isMan;
public People(Long age, Boolean isMan){
this.age = age;
this.isMan = isMan;
}
public static People man(Long age){
return new People(age, true);
}
}
public static void main(String[] args) {
People man1 = new People(24L,true); //생성자 방법
People man2 = People.man(24L); //정적 팩터리 메서드 방법
}
}
이런 상황에서 new People(24L, true)
이랑 People.man(24L)
이랑 뭐가더 '남자'라는 객체를 잘 설명할 것인가?
후자일 것이다.
위의 경우에서도 미리 static으로 24세의 남자라는 객체를 만들어 놓았기 때문에 매번 객체를 생성하지 않아도 된다.
이렇게 되면 객체를 싱글톤으로 만들 수 있고, 또다른 인스턴스를 만드는 일도 하지 않을 수 있다.
위의 예시는 잘못된 설명입니다. (지적감사합니다 ㅎㅎ)People.man(24L);
을 반환하면, 매번 새로운 People 객체가 생성이 되므로, 싱글톤이 아닙니다.
만일 내가 24살의 남성을 항상 반환하고 싶다면,
private static People manOf24Years = new People(24L, true);
public static void main(String[] args) {
People man2 = People.manOf24Years;
}
처럼 되어야 겠죠.
모든 코드는 다음과 같습니다.
public class Item1 {
static public class People{
private Long age;
private Boolean isMan;
public People(Long age, Boolean isMan){
this.age = age;
this.isMan = isMan;
}
private static People manOf24Years = new People(24L, true);
public static People man(Long age){
return new People(age, true);
}
}
public static void main(String[] args) {
People man1 = new People(24L,true); //생성자 방법
People man2 = People.manOf24Years; //정적 팩터리 메서드 방법
}
}
chatGPT를 통해 좋은 예시를 가져왔다.
interface Shape {
void draw();
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
public class ShapeFactory {
public static Shape createShape(String shapeType) {
if ("circle".equalsIgnoreCase(shapeType)) {
return new Circle();
} else if ("rectangle".equalsIgnoreCase(shapeType)) {
return new Rectangle();
} else {
throw new IllegalArgumentException("Invalid shape type");
}
}
public static void main(String[] args) {
Shape circle = ShapeFactory.createShape("circle");
circle.draw(); // 출력: Drawing a circle
Shape rectangle = ShapeFactory.createShape("rectangle");
rectangle.draw(); // 출력: Drawing a rectangle
}
}
Shape라는 인터페이스를 두고,
Circle 과 Rectangle이 해당 인터페이스를 구현한다.
그리고 static factory method인 createShape()
메서드로 Shape의 하위 객체들인 Circle
과 Rectangle
을 반환하고 있다.
즉 인터페이스인 Shape를 정적 팩터리 메서드의 반환타입으로 사용하여 클라이언트 코드는 어떤 구현 객체가 반환될지 몰라도 된다는 장점이 있다.
정적 팩터리 메서드로 하위 타입 객체를 반환하는 대표적인 예는 'Collections' 클래스이다.
이 클래스는 인스턴스화가 불가능하며, List,Map,Set 구현체들을 정적 팩터리 메서드로 얻을 수 있다.
여기서 List, Set 인터페이스는 Collections를 상속하지만, Map은 Collections을 상속하지 않는다.
그 이유는 List, Set이 공통부분이 있기 때문이다. (비어있거나 추가하거나..)
아무쪼록, Collection 프레임 워크 인터페이스에서 구현한 여러 구현체들을 Collections 클래스의 정적 팩터리 메서드로 반환할 수 있게 된 것이다.
공식 문서 -> https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html
JAVA 8 이전에는 인터페이스에 정적 메서드를 선언할 수 없어서 다음과 같이 작성했다고 한다.
public interface MyInterface {
void doSomething();
// 동반 클래스
class Helper {
public static void staticMethod() {
// 정적 메서드의 구현 내용
System.out.println("정적 메서드 호출");
}
}
}
이를 다음 처럼 호출한다.
public class Main {
public static void main(String[] args) {
// 정적 메서드 호출
MyInterface.Helper.staticMethod();
}
}
하지만 자바8 이후부터 인터페이스에 정적메서드를 직접 선언할 수 있게되면서
public interface MyInterface {
void doSomething();
// 정적 메서드 선언
static void staticMethod() {
// 정적 메서드의 구현 내용
System.out.println("정적 메서드 호출");
}
}
직접 인터페이스 내에서 정적 메서드를 선언하고,
public class Main {
public static void main(String[] args) {
// 정적 메서드 호출
MyInterface.staticMethod();
}
}
이렇게 호출할 수 있게 되었다.
대표적인 예가 EnumSet 클래스이다.
이 클래스는 파라미터의 원소의 수에 따라 두가지 하위 클래스중 하나의 인스턴스를 반환한다.
사실 당연한 말이라고 생각했다.
어차피 반환하는 형태는 클래스의 상위 타입으로 반환할 수 있으니까 말이다.
근데 뭔가 이상한 말이 많아서.. 죽 훑고 넘어갔다..
정적 팩터리 메서드로 인스턴스를 반환하게 만들면서, 생성자를 private하게 막아 놓으니.. 그렇게 되면서 해당 클래스를 상속한 클래스는 부모 클래스의 생성자를 호출할 길이 없으므로 하위 클래스를 만들 수가 없다는 점이 큰 단점이다.