'싱글톤'에 해당되는 글 1건

  1. 2009.12.14 자바 클래스를 싱글 패턴으로 구현하기

생성자 대신 스태틱 팩토리 메소드를 고려하라

스태틱 팩토리 메소드는 단순히 자신이 정의된 클래스의 인스턴스를 리턴하는 메소드로 public static으로 정의한다. 기본타입 boolean의 래퍼클래스인 boolean에 1.4 배포판부터 추가된 Boolean.valueOf(boolean b) 메소드는 스태틱 메소드의 좋은 예이다.

  • 스태틱 팩토리 메소드는 생성자와 달리 알맞은 이름을 줄 수 있다.
  • 스태틱 팩토리 메소드는 생성자와 달리 호출될 때마다 새로은 객체를 생성하지 않아도 된다.
  • 생성자는 자신이 정의된 클래스의 인스턴스만 리턴할 수 있지만, 스태틱 팩토리 메소드는 자신이 선언된 것과 같은 타입의 인스턴스는 모두 리턴할 수 있다.
  • 스태틱 팩토리 메소드의 가장 큰 단점은 이 메소드를 정의한 클래스가 public 이나 protected 생성자를 제공하지 않으면, 다른 클래스가 이 클래스를 상속 받을 수 없다는 것이다.
  • 스태틱 팩토리 메소드의 두번째 단점은 다른 스태틱 메소드와의 차이를 명시 할 수 없다는 것이다.

Foo.java 서비스 제공자 프레임워크 예제
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
 

//서비스 제공자 프레임워크 예제
public abstract class Foo {
        //문자열 키와 구현 클래스의 class 객체를 결합시킨 맵
        private static Map implementations = null;
        private static ResourceBundle classNames;
        
        //처음 호출되었을때 맵을 초기화 한다.
        private static synchronized void initMapIfNecessary() {
               if( implementations == null ) {
                       implementations = new HashMap();
               }
               
               //속성 파일에서 키와 구현 클래스 이름을 가져온다.
               //Class.forName을 써서 클래스 이름으로부터 Class 객체를
               //생성하고 Map에 저장한다.
               
               String firstFoo = getValue("firstFoo");
               String secondFoo = getValue("secondFoo");
               String thirdFoo = getValue("thirdFoo");
               
               try {
                       Class obj1 = Class.forName(firstFoo);
                       Class obj2 = Class.forName(secondFoo);
                       Class obj3 = Class.forName(thirdFoo);
                       implementations.put("firstFoo", obj1);
                       implementations.put("secondFoo", obj2);
                       implementations.put("thirdFoo", obj3);
                       
               } catch (ClassNotFoundException e) {
                       System.out.println("Class not found");
               }
        }
        
        private static String getValue(String s) {
               String value = classNames.getString(s);
               return value;
        }
        
        static {
               try {
                       classNames = ResourceBundle.getBundle("TableText");                  
               } catch (java.util.MissingResourceException e) {
                       System.out.println("Resource File not Found");
                       System.exit(1);
               }
        }
        
        public static Foo getInstance(String key) {
               initMapIfNecessary();
               Class c = (Class) implementations.get(key);
               System.out.println(c + " " + "returned");
               if (c == null)
                       return new DefalutFoo();
               try {
                       return (Foo)c.newInstance();
               } catch (Exception e) {
                       return new DefalutFoo();
               }
        }
}

 

Private 생성자를 써서 싱글톤을 유지하라

싱글톤(singleton)이란 정확히 하나의 인스턴스만 만들어지는 클래스로 원래부터 유일할 수밖에 없는 비디오 출력이나 파일 시스템과 같은 시스템 구성요소들을 주로 표현한다.

싱글톤은 두가지 방법으로 구현 할수 있다. 두 방법 모두 생성자를 private으로 정의하고 클라이언트가 이 클래스의 유일한 인스턴스에 접근 할 수 있는 public static 멤버를 제공한다.

public static final 필드로 구현한 싱글톤
//public static final 필드로 구현한 싱글톤
public class Elvis {
        public static final Elvis INSTANCE = new Elvis();
        
        private Elvis() {
               //... 코드 생략
        }
        
        //... 이하 생략
        
}

Elvis 클래스의 private 생서자는 public static final 멤버 필드인 Elvis.INSTANCE가 초기화 될 때 딱 한번만 호출된다. 싱글톤을 유지 하려면 public이나 protected 생성자를 두지 말아야 한다.

스태틱 팩토리 메소드로 구현한 싱글톤
//스태틱 팩토리 메소드로 구현한 싱글톤
public class Elvis {
        private static final Elvis INSTANCE = new Elvis();
        
        private Elvis() {
               //... 코드 생략
        }
        
        public static Elvis getInstance() {
               return INSTANCE;
        }
        
        //... 이하 생략
        
}

스태틱 팩토리 메소드인 Elvis.getInstance는 호출될 때마다 새로운 Elvis 인스턴스를 생성하지 않고 계속 같은 객체 참조를 리턴한다.

 

Priavet 생성자로 인스턴스를 만들지 못하게 하라

가끔 static 메소드와 static 필드로만 이루어진 클래스를 만들어야 할때가 있다. 이때에는 private 생성자 하나만 만들어 주면 된다.

//인스턴스를 만들 필요가 없는 유틸리티 클래스
public class UtilityClass {
        //private 생성자를 하나 만들어 컴파일러가 자동으로 기본 생성자를
        //추가 하지 못하게 한다.
        private UtilityClass() {
               //이 생성자는 호출되지 않는다.
        }
        
        //...이하 생략
        
}

Utility 클래스의 생성자는 이 클래스 밖에서 접근할 수 없다. 따라서 utility 클래스 내부에서 생성자를 호출하지 않는 다면 , 절대 인스턴스가 생성되는 일은 없다.

 

쓸데없는 객체를 중복 생성하지 마라

동등한 기능을 하는 객체는 필요할때마다 생성하는 것보다 한 객체를 재사용하는 것이 더 낫다. 다음과 같은 코드는 만들면 안된다.

String s = new String("바보"); //이렇게 만들지 말것!

다음은 가변 객체를 잘못 사용하고 있는 예제이다.

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
 
public class Person {
        private final Date birthDate;
        //다른 필드는 생략했다.
        
        public Person(Date birthDate) {
               this.birthDate = birthDate;
        }
        
        //절대 이렇게 하지 말 것!
        public boolean isBabyBoomer() {
               Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
               gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
               Date boomStart = gmtCal.getTime();
               gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
               Date boomEnd = gmtCal.getTime();
               return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) < 0;
        }
}

isBabyBoomer 메소드를 호출할때마다 Calendar 인스턴스하나, TimeZone 인스턴스하나, Date 인스턴스 두개가 쓸데 없이 계속 생성된다. isBabyBoomer 메소드는 내용이 변하지 않기 때문에 static 초기화를 사용하여 다음과 같이 해결한다.

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
 
public class Person {
        private final Date birthDate;
        //다른 필드는 생략했다.
        
        public Person(Date birthDate) {
               this.birthDate = birthDate;
        }
        
        /**
         * 베이비 붐의 시작 및 종료 일자
         */
        private static final Date BOOM_START;
        private static final Date BOOM_END;
        
        static {
               Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
               gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
               BOOM_START = gmtCal.getTime();
               gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
               BOOM_END = gmtCal.getTime();  
        }
        
        public boolean isBabyBoomer() {
               return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0;
        }
}

 

쓸모 없는 객체 참조는 제거하라

다음 예제를 살펴보자.

import java.util.EmptyStackException; 

//어디선가 메모리가 새고(memory leak) 있다.
public class Stack {
        private Object[] elements;
        private int size = 0;
        
        public Stack(int initialCapacity) {
               this.elements = new Object[initialCapacity];
        }
        
        public void push(Object e) {
               ensureCapacity();
               elements[size++] = e;
        }
        
        public Object pop() {
               if(size ==0)
                       throw new EmptyStackException();
               return elements[--size];
        }
        
        /**
         * 최소한 하나의 구성요소를 담을 수 있는 여유를 두어야 하며,
         * 여유가 없을 때 구성요소를 담는 배열의 크기를 두배로 늘린다.
         */
        public void ensureCapacity() {
               if(elements.length == size) {
                       Object[] oldElements = elements;
                       elements = new Object[2*elements.length+1];
                       System.arraycopy(oldElements, 0, elements, 0, size);
               }
        }
}

elements 배열의 “유효부분(active portion)”을 벗어난 배열 구성요소가 저장하고 있는 참조들은 쓸모 없는 참조들이다. 팝(pop)된 객체들은 다른 프로그램에서 더 이상 참조하지 않더라도 이 객체들은 가비지 컬렉션 대상이 되지 않는다. 가비지 컬렉터가 있는 언어에서 메모리 누수현상은 매우 위험한 것이다. 이런문제는 쓸모없는 참조에 null을 대입해 버리면 된다. 따라서 Stack 클래스의 pop메소드는 다음과 같이 수정되어야 한다.

public Object pop() {
       if(size ==0)
	       throw new EmptyStackException();
       Object result =  elements[--size];
       elements[size] = null; //쓸모없는 참조를 없앤다.
       return result;
}
* 가비지 컬렉션

자바 플랫폼의 모든 배열과 객체들은 힘(heap)이라는 메모리 공간에 저장된다. New 키워드를 쓸때마다 힙에 새로운 메모리가 객체마다 할당된다. 이때 할당된 메모리를 가비지 컬렉터가 알아서 반환해준다. 보통 프로그래머들은 메모리를 반환할 때, 대상 객체 참조에 null을 대입하거나 System.gc()를 호출하는 방법을 사용한다. 물른, System.gc()를 호출했다고 해서 가비지 컬렉션이 바로 실행된다는 보장은 없다.

* 종료자

Object 클래스에는 protected 메소드인 finalize라는 메소드가 있다. 자바의 모든 클래스는 이메소드를 재정의 할 수 있다. 특정 객체에 실제로 호출되는 finalize 메소드를 이 객체의 종료자(finalizer)라고 한다. 시스템은 가비지 컬렉터가 객체를 파괴하기 전에 반드시 종료자를 호출한다. 하지만, 가비지 컬렉터가 객체를 언제 파괴할지 알수 없기 때문에 종료자도 언제 호출될지 알수 없고, 아예 호출되지 않을수도 있다.

//종료가 가디언 구현패턴
public class Foo {
        //이 객체는 오직 자신을 포함하는 Foo객체의 종료처리를 수행한다.
        private final Object finalizerGuradin = new Object() {
               protected void finalize() throws Throwable {
                       System.out.println("Outer Foo object finalized");
               }
        };
        
        public static void main(String args[]) {
               Foo foo = new Foo();
               //강제 종료자 수행
               foo = null;
               System.gc();
        }
}

 

EFFECTIVE JAVA
카테고리 컴퓨터/IT
지은이 Joshua Bloch (대웅, 2003년)
상세보기

'개발자 센터 > Java' 카테고리의 다른 글

자바 메소드 작성시 알아야 할 것  (0) 2009.12.16
자바 프로그래밍 일반  (1) 2009.12.14
Posted by 피곤키오
,