티스토리 뷰

JAVA

static factory method (정적 팩토리 메소드)

修人事待天命♡ 2018. 4. 4. 09:05

static factory method (정적 팩토리 메소드)

장점

1. static factory method 에는 이름이 있다.

2. 생성자와는 다르게 호출할 때마다 새로운 객체를 생성할 필요가 없다.

3. 반환값 자료형의 하위 자료형 객체를 반환할 수 있다.

4. 형인자 자료형(parameterized type) 객체를 만들 때 편하다.


단점

1. static factory method 만 있는 클래스를 만들면 생기는 가장 큰 문제

    - public이나 protected로 선언된 생성자가 없으므로 하위클래스를 만들 수 없다

2. static factory method 가 다른 정적 메서드와 확연히 구분되지 않는다.


effective java 책을 보면 규칙1에 static factory method에 대해서 설명하고 있다. 하지만 불행하게도 그 내용이 이해가 가질 않아서 그동안 블로그에 글로 올리고 싶어도 이게 무슨 말을 하는 것인지 잘 이해가 가질 않아 올리지 못 하고, 그냥 책의 굵은 글씨만 추려서 블로그에 올려둔상태였다. (한달은 이상태로 지낸듯 하다.) ^_^;;

하지만 오늘 이해가 가지 않는 부분들을 훌륭하신 개발자분들이 적어 놓은 글을 구글링해서 이해한 부분을 적어보고자 한다.

시작!

static factory method

장점1. static factory method 에는 이름이 있다.


//평범하게 new를 사용해서 객체를 생성
public class RobotTest {
    public static void main(String[] args) {
       	Robot alphaRobot = new Robot("Alpha");
        Robot betaRobot = new Robot("Beta", 2);// 개발자에게 혼돈을 줄 수 있다.
        Robot gammaRobot = new Robot(2,"Gamma");
    }
}

public class Robot {
    private String name;//이름
    private int arm;//팔
    private int leg;//다리
    // 로봇 클래스 생성자
    Robot(String name) {//이름만 있는 로봇
        this.name = name;
    }
    Robot(String name, int arm) {//이름과 팔이 있는 로봇
        this.name = name;
        this.arm = arm;
    }
//	Robot(String name, int leg) {//이름과 다리가 있는 로봇, 시그니처가 중복되어 만들지 못 함.
//	    this.name = name;
//	    this.leg = leg;
//	}
    //자바에는 "클래스에 시그니처 별로 하나의 생성자만 넣을 수 있다." 라는 제약이 있다.
    //이런 자바의 제약을 회피하기 위해서 아래와 같은 생성자를 만들었다.( 이런 생성자 만들면 안돼요!)
    Robot(int leg, String name) {//이름과 다리가 있는 로봇
        this.leg = leg;
        this.name = name;
    }
}

자바에서 시그니처란?

메소드를 생성하기 위한 규칙들을 중 메소드의 이름과 파라미터 만을  메소드의 시그니처(Method Signature)라고 한다.

(갑자기 시그니처라니? effective java 책에 보면 시그니처별로 하나의 생성자만 넣을 수 있다. 라는 부분이 나온다. 처음 읽었을 때는 음??음??

뭐? 싸인?서명? 갑자이 왠 시그니처? 라는 생각을 했었다. 나라면 이렇게 번역했을 거다. 클래스에는 생성자의 이름과 파라미터의 타입이 같은 생성자는 2개이상 만들 수 없다. )


이러한 문제점을 static factory method는 해결 할 수가 있다.


public class RobotTest {
    public static void main(String[] args) {
        Robot alphaRobot = Robot.getInstanceAlphaRobot();
        Robot betaRobot = Robot.getInstanceBetaRobot();
        Robot gammaRobot = Robot.getInstanceGammaRobot();
        System.out.println(alphaRobot.toString());
        System.out.println(betaRobot.toString());
        System.out.println(gammaRobot.toString());
    }
}
public class Robot {
    private String name;// 이름
    private int arm;// 팔
    private int leg;// 다리
    private static final Robot alphaRobot = new Robot("Alpha");
    private static final Robot betaRobot = new Robot("Beta", 2);
    private static final Robot gammaRobot = new Robot(2,"Gamma");
    // 로봇 클래스 생성
    private Robot(String name) {//이름만 있는 로봇
        this.name = name;
    }
    private Robot(String name, int arm) {//이름과 팔이 있는 로봇
        this.name = name;
	this.arm = arm;
    }
    private Robot(int leg, String name) {//이름과 다리가 있는 로봇
        this.leg = leg;
        this.name = name;
    }
    public static Robot getInstanceAlphaRobot() {
        return alphaRobot;
    }
    public static Robot getInstanceBetaRobot() {
        return betaRobot;
    }
    public static Robot getInstanceGammaRobot() {
        return gammaRobot;
    }
    @Override
    public String toString() {
        String format = "이 로봇의 이름은 %5s이고 팔은 %s 개, 다리는 %s 개만큼 완성되었습니다.";
        return String.format(format , this.name, this.arm, this.leg);
    }
}

<출력 결과>


생성자를 private으로 제한하여 외부에서 객체 생성을 할 수 없게 만들었다. 그리고 인스턴스를 취득할 때는 Robot클래스에서 제공한 static method를 사용해서 취득했다.

이렇게 하면 이 Robot클래스를 사용하는 개발자는 메소드에 이름을 주었기 때문에 생성자로 인스턴스를 얻을 때처럼의 혼란스러움은 피할 수 있겠다.

그러므로 static factory method를 사용하면 가독성이 좋다는 이야기였다.


장점2. 생성자와는 다르게 호출할 때마다 새로운 객체(instance)를 생성할 필요가 없다.

public static Boolean valueOf(boolean b) {
  return b ? Boolean.TRUE : Boolean.FALSE;
}

이 메소드는 Primitive Type boolean의 값을 Boolean클래스에 대한 Reference Type으로 변환한다. 객체를 생성하지 않는 좋은 사례라고 Effective Java에서는 소개하고 있다.

객체를 생성하지 않는 방법은 당연히 객체를 만드는 비용이 클 때 적용하면 성능을 크게 개선할 수 있다. 하기와 같은 싱글턴(singleton)패턴처럼 말이다. 싱글턴 패턴을 사용하면 기존에 만들어져 있는 객체를 반환하는 방식을 통해 불필요한 객체 생성을 피할 수 있고, 이런 경우에는 equals(Object) 대신에 == 를 통해서 비교 할 수 있으므로 성능 향상도 기대할 수 있다.

public class Robot {
    private String name;// 이름
    private static final Robot alphaRobot = new Robot("Alpha");

    private Robot(String name) {//외부에서 객체를 생성할 수 없고, 상속할 수도 없다.
         this.name = name;
    }
    public static Robot getInstanceAlphaRobot() {
        return alphaRobot;
    }
}


장점3. 반환값 자료형의 하위 자료형 객체를 반환할 수 있다.


//서비스 제공자 인터페이스
public interface Provider {
    Service newService();
}
//============================================================
//서비스 인터페이스
public interface Service {
    // 서비스의 고유한 메소드들이 이 자리에 온다.
}
//============================================================
//서비스 등록과 접근에 사용되는 객체 생성 불가능 클래스
public class Services {
    private Services() {
    } // 객체 생성 방지
    // 서비스 이름과 서비스를 맵에 보관
    private static final Map providers = new ConcurrentHashMap();
    public static final String DEFAULT_PROVIDER_NAME = "";
    //제공자 등록 API
    public static void registerDefaultProvider(Provider p) {
        registerProvider(DEFAULT_PROVIDER_NAME, p);
    }
    public static void registerProvider(String name, Provider p) {
        providers.put(name, p);
    }
    // 서비스 접근 API
    public static Service newInstance() {
        return newInstance(DEFAULT_PROVIDER_NAME);
    }
    public static Service newInstance(String name) {
        Provider p = providers.get(name);
        if (p == null){
            throw new IllegalArgumentException("No provider registered with name: " + name);
        }      
        return p.newService();
    }
}
//=============================================================
public class Test {
    public static void main(String[] args) {
        // Providers would execute these lines
        Services.registerDefaultProvider(DEFAULT_PROVIDER);
        Services.registerProvider("comp", COMP_PROVIDER);
        Services.registerProvider("armed", ARMED_PROVIDER);
        // Clients would execute these lines
        Service s1 = Services.newInstance();
        Service s2 = Services.newInstance("comp");
        Service s3 = Services.newInstance("armed");
        System.out.printf("%s, %s, %s%n", s1, s2, s3);
    }
    private static Provider DEFAULT_PROVIDER = new Provider() {
        public Service newService() {
            return new Service() {
                @Override
                public String toString() {
                     return "Default service";
                }
            };
        }
    };
    private static Provider COMP_PROVIDER = new Provider() {
        public Service newService() {
            return new Service() {
                @Override
                public String toString() {
                    return "Complementary service";
                }
            };
        }
    };
    private static Provider ARMED_PROVIDER = new Provider() {
        public Service newService() {
            return new Service() {
                @Override
                public String toString() {
                    return "Armed service";
                }
            };
        }
    };
}


댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함