[Java] Design Pattern: Singleton Pattern

da-nyee·2020년 8월 5일
0

TIL

목록 보기
3/4
post-thumbnail

Design Pattern에는 여러 방법들이 존재한다. 먼저 Singleton Pattern에 대해 알아보자.

☕ 싱글톤 패턴(Singleton Pattern)이란?

  • Java 디자인 패턴(Design Pattern) 중 하나이다.
  • 싱글톤 패턴을 따르는 클래스는 생성자가 여러차례 호출되더라도 실제로 생성되는 객체는 하나이다.
  • 최초 생성 이후에 호출되는 생성자는 최초 생성자가 생성한 객체를 리턴한다.
  • 주로 공통된 객체를 여러개 생성해 사용하는 DBCP(Database Connection Pool)과 같은 상황에서 많이 사용된다.

즉, 하나의 객체만을 생성해 이후에 호출된 곳에서는 생성된 객체를 반환하여 프로그램 전반에서 하나의 인스턴스만을 사용하게 하는 패턴이다.

👉 PC 자원은 한정되어 있기 때문에 인스턴스가 남용되는 건 바람직하지 않다. 하나의 자원으로 모두가 공유해 사용해야 하는 경우 이 패턴은 유용한 방법이 될 수 있다.

📄 사용법 및 예제 코드

public class Printer {
    	private static Printer printer = null;
    
    	private Printer(){}
    
    	public static Printer getInstance() {
    		if(printer == null) {
    			printer = new Printer();
    		}
    		return printer;
    	}
    
    	public void print(String input) {
    		System.out.println(input);
    	}
}
  • 기본 생성자에 private를 사용하여, 클래스 외부에서 클래스 멤버 변수에 직접 접근해 생성자를 생성할 수 없도록 설정했다. 따라서, 클래스 외부에서는 getInstance() 멤버 함수를 통해서만 생성자를 생성할 수 있다.
  • getInstance()에서 기존에 생성된 객체가 없다면 인스턴스가 생성된다.
  • getInstance()에서 기존에 생성된 객체가 있다면 생성되어 있는 인스턴스를 리턴하는 형태로, 프로그램 전반에 걸쳐 하나의 인스턴스를 유지한다.

👉 static 공유 자원 !!

📑 문제점

싱글톤 패턴을 멀티 쓰레드 환경에서 사용하는 경우 문제점이 존재한다.

멀티 쓰레드 환경에서 안전하지 않다.

public static Printer getInstance() {
    	if(printer == null) {
    	    	printer = new Printer();
    	}
    	return printer;
}
  • 위의 코드가 여러 쓰레드가 공유되고 있는 상황에서 실행된다면 조건문이 동시에 두 번 이상 돌 수 있다.
  • 이런 경우, 하나의 인스턴스가 아닌 여러개의 인스턴스가 생성되는 위험이 발생할 수 있다.

인스턴스가 상태를 유지해야 할 때, 고려할 점이 더 많다.

public class Printer {
    	private static Printer printer = null;
    	private int count = 0;
    
    	private Printer(){}
    
    	public static Printer getInstance() {
    		if(printer == null) {
    			printer = new Printer();
    		}
    		return printer;
    	}
    
    	public void print(String input) {
    		count++;
    		System.out.println(input + "count : "+ count);
    	}
}
  • 위 코드에서 count 값이 서로 다른 쓰레드에서 공유되고 있다면 값이 일관되지 않는 문제점이 발생할 수 있다.

📑 문제점 해결

멀티 쓰레드 환경에서 문제점을 해결할 수 있는 방법은 다음과 같다.

정적 변수에 인스턴스를 생성하여 바로 초기화 하는 방법

public class Printer {
    	private static Printer printer = new Printer();
    	private static int count = 0;
    
    	private Printer(){}
    
    	public static Printer getInstance() {
    		return printer;
    	}
    
    	public synchronized static void print(String input) {
    		count++;
    		System.out.println(input + "count : "+ count);
    	}
}
  • static 키워드로 인해 기존에 조건문에서 체크하던 부분이 제거된다.
  • 객체 생성 자체는 로드 시점에서 결정되어 하나의 객체만을 사용하나, count에 접근하는 것은 각 쓰레드에서 동시에 접근하기 때문에 쓰레드마다 변수 값이 달라진다.
  • synchronized 키워드로 여러 쓰레드에서 동시 접근하는 것을 막아 이를 해결할 수 있다.

인스턴스를 생성하는 메서드에 동기화 하는 방법

public interface Printer {
    	public void print(String input);
}
    
--------------------------------------------
    
public class RealPrinter implements Printer {
    	private static Printer printer = null;
    
    	private RealPrinter() {
    	}
    
    	public synchronized static Printer getInstance() {
    		if (printer == null)
    			printer = new RealPrinter();
    		return printer;
    	}
    
    	@Override
    	public void print(String input) {
    		System.out.println(input);
    	}
}
    
--------------------------------------------
    
public class UsePrinter {
    	public void doSomething(Printer printer) {
    		printer.print("fakeGet");
    	}
}
    
--------------------------------------------
    
public class FakePrinter implements Printer {
    	private String str;
    
    	public void print(String str) {
    		this.str = str;
    	}
    
    	public String get() {
    		return str;
    	}
}
    
--------------------------------------------
    
class UsePrinterTest {
    	@Test
    	void testdoSomething() {
    		FakePrinter fake = new FakePrinter();
    		UsePrinter use = new UsePrinter();
    		use.doSomething(fake);
    		assertThat("fakeGet").isEqualTo(fake.get());
    	}
}
  • 위의 코드와 같이 모의 객체를 통해 테스트 하고자 한다면 interface를 사용하는 것이 좋으나, static 타입의 method를 만든다면 위와 같은 테스트는 할 수 없다.

👉 interface는 static method를 가질 수 없기 때문이다.

📑 문제점 요약

  • 싱글톤 패턴은 프로그램 전체에서 하나의 객체만을 공통으로 사용하고 있기 때문에 각 객체간의 1. 결합도가 높아지고 2. 변경에 유연하게 대처하기 어렵다. 해당 객체가 변경되면 이를 참조하고 있는 모든 값들이 변경되어야 하기 때문이다.
  • 멀티 쓰레드 환경에서 어느정도 대처가 가능하나, 고려할 점이 많아 사용하기 어렵다.
  • 프로그램 전반에서 필요한 부분에만 사용하면 장점이 있다. 그러나, 그 포인트를 잡기 어렵다.

💡 결론

싱글톤 패턴을 적절하게 활용하면 좋지만, 남용될 여지가 많다.

  • 단일 쓰레드(Single Thread) 환경에서 사용하는 경우
    • static 키워드를 통해 관리해야 한다.
    • static 키워드를 사용하면 클래스 로딩 단계에서 바로 초기화 된다.
    • 이 경우에도 테스트를 위한 모의 객체를 생성하는 등 다른 목적으로 사용할 필요가 있다면 멀티 쓰레드 환경에서 사용하는 것처럼 관리하면 된다.
  • 멀티 쓰레드(Multi Thread) 환경에서 사용하는 경우
    • synchronized 키워드를 통해 관리해야 한다.
    • 다양한 변화에 대응하기 위해 interface 형태로 관리하면 좋다.


🔎 참고자료

profile
매일매일을 소중하게 ✨

0개의 댓글