반응형

이 글은 Effective Java를 참조하여 작성하였습니다.


클래스를 통해 객체를 만드는 방법은 생성자를 이용하는 것입니다. 하지만 public으로 선언된 정적 팩토리 메서드를 추가하는 방법으로도 객체를 생성할 수 있습니다. 여기서 말하는 정적 팩토리 메서드는 디자인패턴에 팩토리 메서드 개념과 다릅니다.


생성자 대신 정적 팩토리 메서드를 사용하는 첫 번째 장점은 생성자와는 달리 정적 팩토리 메서드에는 이름이 있다는 것입니다. 즉 이름이 있기 때문에 네이밍을 잘 한다면 사용하기 쉽고 가독성도 높아집니다. 생성자가 다양하다면 각각의 생성자 용도를 기억하기 힘들 것이고 (아니 절대로 불가능 할 것이다) API가 없다면 제대로 파악하기 힘들것입니다. 하지만 정적 팩토리 메서드에는 이름이 있으므로 이런 문제는 생기지 않기 때문에 같은 시그니처를 가지는 생성자를 여러개 정의해야 할 경우에는 생성자들을 정적 팩토리 메서드로 바꾸고 네이밍에 신경 써야 합니다.


정적 팩토리 메서드?? 그러면 그게 뭔데 ??

Object object = new Object(); 와 같은 구문을 이용해서

일반적으로 객체를 생성할 때는 생성자를 사용할 것입니다. 


Static Factory Method 는 public static method 로서 외부 클래스에서 바로 접근할 수 있는 method인것이죠. static의 개념이 없다면 static을 먼저 검색하고 오세요~!

class Human{

String name;

int age;

int height;

int weight;

//Constructor

public Human(String name, int age, int height, int weight)

{

this.name=name;

this.age=age;

this.height=height;

this.weight=weight;

}

public Human(String name, int age)

{

this.name=name;

this.age=age;

}

//Static static Factory Method

public Human allInfoHuman() {

return new Human("sw",26,179,75);

}

public static Human portionHuman() {

return new Human("sw",26);

}

}



두번째 장점은 생성자와는 달리 호출할 때마다 새로운 객체를 생성할 필요가 없다는 것입니다. 이는 경량 패턴과 유사한데 동일한 객체가 요청되는 일이 잦고, 객체 생성시 비용이 클 경우 적용하면 성능을 크게 개선시킬 수 있습니다. 사실 위 예제 소스에서도 new연산을 계속 쓰고 있습니다. 아래에 보는 예제 소스와 같이 미리 만들어둔 객체를 리턴할 수 있기 때문에 new와 같은 비용이 큰 연산의 횟수를 줄일 수 있습니다.


public static final BigInteger ZERO = new BigInteger(new int[0], 0);


private final static int MAX_CONSTANT = 16;

private static BigInteger posConst[] = new BigInteger[MAX_CONSTANT+1];

private static BigInteger negConst[] = new BigInteger[MAX_CONSTANT+1];


static {

    /* posConst에 1 ~ 16까지의 BigInteger 값을 담는다. */

    /* negConst에 -1 ~ -16까지의 BigInteger 값을 담는다. */

}


public static BigInteger valueOf(long val) {

    // 미리 만들어둔 객체를 리턴한다

    if (val == 0)

        return ZERO;

    if (val > 0 && val <= MAX_CONSTANT)

        return posConst[(int) val];

    else if (val < 0 && val >= -MAX_CONSTANT)

        return negConst[(int) -val];


    // 새로운 객체를 만들어 리턴한다

    return new BigInteger(val);

}



세번째 장점은 생성자와 달리 반환값 자료형의 하위 자료형 객체를 반환할 수 있다는 것입니다. 즉 반환되는 객체의 클래스를 훨씬 유연하게 결정할 수 있는 것이죠. 음.. 생성자는 리턴값이 없는데 정적 팩토리 메서드는 메서드입니다. 메서드라는 이름이 붙여진 이유에 대해 잠시 생각해 보면 리턴값이 있다는 것이고, 그 리턴값이 생성자처럼 자기 자신만이 아닌 하위 객체도 리턴할 수 있다는 뜻입니다. 


 package effectiveJava;


public class Regulation {

public static void main() {

Human h = Human.setSexInstance("man");

h.getInfo();

}

}


abstract class Human {

String name;

public abstract void getInfo();


public static Human setSexInstance(String sex) {

if (sex == "man")

return new Man();

else

return new Woman();

}

}


class Man extends Human {

public void getInfo() {

System.out.println("I'm man");

}

}


class Woman extends Human {

public void getInfo() {

System.out.println("I'm woman");

}

}


마지막 장점은 형인자 자료형 객체를 만들 때 편리하다는 것입니다. 이부분은 JDK1.7이후부터는 의미가 없을 듯 하네요


Map<String, List<String>> map = new HashMap<String, List<String>>();

이와 같은 소스를

public static <K,V> HashMap<K,V> newInstance() {

       return new HashMap<K,V>();

}

와 같은 메서드가 있으면 

Map<String, List<String>> map = HashMap.newInstance()와 같이 줄여서 사용할 수 있다는 것인데 JDK1.7부터는


Map<String, List<String>> map = new HashMap<>();을 지원하니까 말이죠


지금까지 장점에 대해 알아보았는데요 장점이 있다면 단점도 역시 존재합니다.


첫번째 단점은 critical하다고 생각되는데 정적 팩토리 메서드만 있는 클래스를 만든다면 public, protected로 선언된 생성자가 없기 때문에 하위 클래스를 만들 수 없다는 것입니다. 예시로 자바 컬렉션 프레임워크에 포함된 기본 구현 클래스들의 하위 클래스는 만들 수 없다는 예시가 있네요!


두번째 단점은 정적 팩토리 메서드가 다른 정적 메서드와 확실하게 구분되지 않는다는 것입니다. 물론 API문서를 본다면 생성자와 다른 메서드는 뚜렷하게 구별되지만 정적 팩토리 메서드는 그렇지 않습니다. 때문에 더더욱 메서드 네이밍을 잘해야 겠죠.


하지만 정적 팩토리 메서드가 주는 장점은 분명 있으며, 생성자와 용도가 같다고 생각하면 큰 오산입니다. 그 차이와 장단점을 이해해서 정적 팩토리 메서드가 효과적인 경우 고려해보고 무조건적으로 public 생성자를 만드는 것을 삼가해야 합니다.


반응형
반응형

다형성


OOP에서의 다형성은 여러 가지 형태를 가질 수 있는 능력을 말하며, 자바에서는

한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현했다.


즉 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 했다는 것이다.


class Home{

  String address;

  boolean isVisit;

  

  void rest();

  void eat(); 

}

class Room extends Home{

  String roomName;

  

  void sleep();

}


Home 과 Room은 서로 상속관계에 있으며 다음과 같이 사용할 수 있다.

Home home = new Room();

Room room = new Room();


이 코드에서 Room인스턴스 2개를 생성하고 참조변수 home,room이 생성된 인스턴스를 하나씩 참조하도록 했다.

이 경우 인스턴스가 Room타입이여도 참조변수 home은 room의 모든 멤버를 사용할 수 없다. 즉, 둘 다 같은 타입의 인스턴스지만 참조변수의

타입에 따라 사용할 수 있는 멤버의 갯수가 달라지는 것이다. 


반대로 Room r = new Home()은 불가능하다. 왜냐하면 실제 인스턴스 r이 사용할수 있는 멤버 갯수가 실제 인스턴스인 Home의 갯수보다 더 많기 때문이다.

참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 하는 것이다. 왜냐하면 클래스는 상속을 통해 확장되는 것이지

축소될 수는 없기 때문이다. 조상 인스턴스의 멤버 개수는 자손 인스턴스의 멤버 개수보다 항상 적거나 같기 때문에 반대의 경우는 불가하다.


그렇다면 어차피 조상타입으로 선언하면 조상타입의 멤버만 참조가능할텐데 왜 일부 멤버만을 사용하도록 하는 것일까??


그 이유는 참조변수도 상속관계에 있는 클래스 사이에서는 형변환이 가능하기 때문이다. 자손타입의 변수를 조상타입으로 조상타입의 변수를 자손타입으로 변환할 수 있다.

따라서 모든 객체는 Object클래스 타입으로 형 변환이 가능하다.


형 변환에서 자손 -> 조상을 Up-casting

           조상 -> 자손을 Down-casting 이라고 하며 up은 생략이 가능하지만 down-casting은 형변환 생략이 불가하다.


그 이유는 조상타입 참조변수가 참조하는 것은 조상타입이거나 자손타입일 것이다. 즉 참조변수가 다룰 수 있는 멤버의 개수가 실제 인스턴스가 가지고 있는 멤버 개수보다 적을 것이 분명하므로 문제가 되지 않기 때문이다.

반응형

+ Recent posts