본문으로 바로가기

https://www.inflearn.com/course/디자인-패턴

 

코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의

디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할

www.inflearn.com

 

객체 생성 관련 디자인 패턴

 싱글톤 패턴

 싱글톤 패턴 1부 - 싱글톤 패턴을 가장 단순히 구현하는 방법

- 싱글톤 패턴 : 인스턴스를 오직 한개만 제공하는 클래스

- 시스템 런타임, 환경 세팅에 대한 정보 등, 인스턴스가 여러개 일 때 문제가 생길 수 있는 경우가 있다. 인스턴스를 오직 한개만 만들어 제공하는 클래스가 필요하다.

 

double checked locking 사용하기
private static volatile Settings instance;

public static Settings getInstance() {
	if (instance == null){
    	synchronized (Settings.class){
        	if ( instance == null ) {
			instance = new Settings();
            }
            }
        }
    }
    return instance;
 }

  > 자바 1.4에서는 작동 안함

  > volatile 변수 사용으로 복잡함

 

static inner 클래스 사용하기 - 권장하는 방법
private static class SettingsHolder {
	private static final Settings INSTANCE = new Settings();
}

public static Settings getInstance(){
	return SettingsHolder.INSTANCE;
}
홀더 클래스가 가지고 있는 인스턴스는 어째서 바깥메서드에서 호출을 할 때 생성이 되는건가요?


static으로 선언된 필드는 설명드린대로 어떤 클래스가 처음 로딩이 될 때 정적인 메모리 공간에 만들어지는데, holder가 가지고 있는 클래스가 로딩이 되는 시점 자체은 getInstance()를 호출했을 때니까, getInstacne()가 아닌 다른 메소드 (holder를 참조하지 않는)를 사용할 경우에는 해당 클래스를 로딩할 필요가 없으니 결과적으로 SettingsHolder가 가지고 있는 static한 인스턴스도 만들어지지 않는거죠.

static inner classs는 별도로 분리되어있는 파일에 있는 클래스라고 생각하시면 좀 더 쉬울 수 있겠네요.

 

 

싱글톤  깨기
리플렉션 사용
Settings settings = Settings.getInstance();
Constructor<Settings> constructor = Settings.class.getDeclaredConstructor();
constructor.setAccessible(true);
Settings settings1 = constructor.newInstance();
직렬화 & 역직렬화 사용하기
Settings settings = Settings.getInstance();
Settings settings1 = null;

try ( ObjectOutput out = new ObjectOutputStream(new FileOutputStream("settings.obj"))){
    out.writeObject(settings);
}
try (ObjectInput in = new ObjectInputStream(new FileInputStream("settings.obj"))){
      settings1 = (Settings)in.readObject();
}

> 직렬화 객체에  protected Object readResolve() 구현

 

리플렉션을 막는 싱글톤 구현 방법

- ENUM 사용하기

 > 리플렉션으로 절때 뚫을 수 없음

   - "Cannot reflectively create enum objects"

 > enum의 단점은 클래스를 로딩하는 순간 미리 만들어지는 객체임, Enum만 상속 받을 수 있음

 

결과적으로 Lazy Loading 및 class 상속을 위해 static innerclass 인 Holder를 쓰는 게 Singleton 구현에 이상적임

 

스프링 에서의 싱글톤 패턴
Runtime runtime = Runtime.getRuntime();
runtime.maxMemory();
runtime.freeMemory();

 

팩토리 메소드 패턴

패턴 소개

- 구체적으로 어떤 인스턴스를 만들지는 서브 클래스가 정한다.

- 다양한 구현체 (Product)가 있고, 그중에서 특정한 구현체를 만들 수 잇는 다양한 팩토리 (Creator)를 제공할 수 있다.

- 장점 : OCP - 확장에는 열려있고, 변경에는 닫혀있어야한다.

- 자바 11 부터는 인터페이스에 private 메소드 추가가 가능하다.

   -> 자바 8 에서는 추상클래스를 두어 추상메소드를 구현한뒤 상속하여 해결한다.

 

자바와 스프링에서 찾아보는 패턴

- SimpleFactory 패턴은 if else 등으로 객체 생성

- 스프링에서의 BeanFactory도 팩토리 메소드 패턴과 동일하다.

  > beanfactory를 인터페이스라 하고, AnnotationConfigApplicationContext 등이 팩토리 구현체

  > 이에 따라 반환하는 applicationContext가 달라진다.
    +) 그에 따른 concrete object도 달라짐

 

 

빌더 패턴

패턴 소개

- 단순 수정자에 의해 생성할 경우, 불완전한 인스턴스가 생성됨

- 생성자에 의해 만들려면 생성자가 다양해짐

  > 사용하는 측에서 어느걸 써야할지 헷갈리게 됨

- 동일한 프로세스를 거쳐 다양한 구성의 인스턴스를 만들는 방법

- 디렉터를 통해 변수 없이 호출만드로 하는 경우도 있음

- 빌더 인터페이스 반환

 > 구현체에서 구현
 > 필드를 채우고 구현체를 반환하다.

- 장점

 > 복잡한 객체를 순서를 강제하여 만들 수 있다. (생성자는 순서를 강제할 수 없다.)

 > 디렉터 사용시, 생성자를 통해 필드에 빌더를 설정해주는데, 다양한 빌더를 사용할 수 있어서 기능변경에 용이하다.

- 단점
 > 클라이언트에서 디렉터와 빌드를 반드시 생성해야함

 > 구조가 기존에 비해 복잡해짐

 

자바와 스프링에서 찾아 보는 패턴

- StringBuilder : synchronized X

- StreamBuilder : Generic 연관

- lomBok : builder annotation

- UriComponent

 

프로토 타입 패턴

패턴 소개

- 기존 인스턴스를 복제하여 새로운 인스턴스를 만드는 방법

- 복제 기능을 갖추고 있는 기존 인스턴스를 프로토타입으로 사용해 새 인스턴스를 만들 수 있다.

- DB 조회, API 호출에 따른 객체를 생성하여 복제할 때 사용하는게 좋다.

- Object Class의 Clone을 그대로 사용 ( + Clonable 인터페이스 구현 )

 -> 기본적으로 Shallow Copy 이기 때문에, Deep copy를 진행하려면 Clone 메소드를 override 하여 진행

-  List (Collections)은 Clonable 상속 안함
 -> arrayList는 clonable 상속해서 clone 가능
 -> List의 경우, new ArrayList<>(list) 와 같이 생성자를 통해 복제

- 장점
 -> 복잡한 객체를 만든느 과정을 숨길 수 있다.

 -> 기존 객체를ㄹ 복제하는 과정이 새 인스턴스를 만드는것보다 비용 (시각 또는 메모리) 적인 면에서 효율적일 수도 있다.

 -> 추상적입 타입을 리턴할 수 있다.

- 단점
 -> 복잡한 객체를 만드는 과정보다 복잡할 수 있다. (특히 순환 참조