2019-07-28 TIL

2019-07-28

The 3 Types of Design Patterns All Developers Should Know (Free Code Camp blog)

https://www.freecodecamp.org/news/the-basic-design-patterns-all-developers-need-to-know/ 위 글 읽은 찌끄레기


약 26가지의 패턴이 있으며, 3가지로 구분할 수 있다.

  1. Creational : 클래스 인스턴스화를 위해 디자인 된 것들. 클래스 생성 패턴, 오브젝트 생성 패턴 둘 다 가능하다.
  2. Structural : 클래스의 구조와 합성(composition)을 다룸. 주 목표는 관련된 클래스들의 기능성을 향상하되 이것들의 composition을 변화시키지 않는 것
  3. Behavioral : 한 클래스가 다른 것들과 어떻게 커뮤니케이트하냐에 따라 디자인됨.

Type 1 : Creational - The Signleton Design Pattern

싱글턴 디자인 패턴은 Creational 패턴이며 클래스의 인스턴스는 하나만 만들고, 그 오브젝트에는 단 하나의 글로벌 access point만 제공하는 것임. 자바에서 이런 클래스로 흔히 사용되는 것은 Calendar인데, 이 클래스의 인스턴스를 만들 수 없다. 또한 `getInstance() 메서드를 사용해 이 오브젝트를 사용한다.

싱글턴 디자인 패턴은 다음을 포함한다

  1. 클래스의 단 하나의 인스턴스를 붙들고있는 private static 변수
  2. private constructor를 가지므로 어디서도 인스턴스화가 불가능하다
  3. 클래스의 단 하나의 인스턴스를 리턴하기 위한 public static 메서드

싱글턴 디자인은 여러가지 각각 다른 구현이 존재함. 그 중 세가지만 보자.

  1. Eager Instantiation
  2. Lazy Instantiation
  3. Thread-safe Instantiation

Eager Beaver ?

public class EagerSingleton {
	// 인스턴스를 만듦
	private static EagerSingleton instance = new EagerSingleton();

	// private constructor를 선언해 클래스 밖에서는 인스턴스를 만들 수 없음
	private EagerSingleton() {  }

	// 유일하게 존재하는 인스턴스를 반환
	public static EagerSingleton getInstance() {
		return instance;
	}
}

이 클래스가 어디에서도 사용되지 않는다면? 비상 대책으로 Lazy Instantiation이 있다.

Lazy Days ?

위에 것과 크게 다르지 않다. 주된 차이점은 static variable이 null로 초기화되어 선언되며, getInstance()메서드에서 인스턴스화 된다.

public class LazySingleton {
	// 인스턴스를 null로 초기화함
	private static LazySingleton instance = null;

	// private constructor 선언. 클래스 바깥에서 인스턴스화 불가.
	private LazySingleton() {  }

	// 인스턴스가 null이면 인스턴스를 만든다
	public static LazySingleton getInstance() {
		if (instance == null) {
			instance = new LazySingleton();
		}
		return instance;
	}
}

이 방법은 문제를 하나 해결하지만, 여전히 다른 문제가 있다. 두 클라이언트가 이 싱글턴 클래스에 동시(몇 millisecond 사이)에 접근한다면? 두 클라이언트의 조건문이 둘 다 true가 되고, 인스턴스가 두 개 만들어진다. 이 문제를 고치기 위해, Thread Safe 인스턴스화가 구현된다.

(Thread) Safety is Key ?

Java에서, synchronized키워드는 메서드나 오브젝트에 thread safety 구현을 위해 사용되며 동시에 한 쓰레드만 특정 리소스에 접근하게 된다. 클래스 인스턴스화를 synchronized 블럭 안에 넣음으로써 동시에 하나의 클라이언트만 이 메서드에 접근할 수 있게 된다.

public class ThreadSafeSingleton {
	// null로 선언
	private static ThreadSafeSingleton instance = null;

	// private constructor
	private ThreadSafeSingleton() {  }

	// synchronized 블럭 안에서, 인스턴스가 null이면 새 인스턴스를 만든다.
	public static ThreadSafeSingleton getInstance() {
		synchronized (ThreadSafeSingleton.class) {
			if (instance == null) {
				instance = new ThreadSafeSingleton();
			}
		}
		return instance;
	}
}

synchronized 메서드는 부하를 일으키며, 전체 동작의 퍼포먼스를 감소시킨다.

예를 들어, 인스턴스 변수가 이미 인스턴스화 된 상황이라도 어떤 클라이언트가 getInstance()메서드에 접근할 때 마다 synchronized 메서드가 실행되며 퍼포먼스가 떨어지게 된다.

부하를 줄이기 위해, double locking이 사용된다.

// double locking은 synchronized 메서드의 부하를 줄이기 위해 사용됨
public static ThreadSafeSingleton getInstanceDoubleLocking() {
	if (instance == null) {
		synchronized (ThreadSafeSingleton.class) {
			if (instance == null) {
				instance = new ThreadSafeSingleton();
			}
		}
	}
	return instance;
}

(역자 주: 인스턴스가 만들어지지 않은 경우에만 synchronized 메서드를 실행해 동시접근을 막고, 단 하나의 인스턴스만 만든다)

Type 2: Structural - The Decoration Design Pattern

Decorator 패턴을 왜, 어디서 사용해야 하는지 이해를 돕기 위해 작은 시나리오를 들려주겠다.

니가 커피샵을 운영한다고 치자. 여느 뉴비들과 마찬가지로 두 타입의 일반 커피로 시작하자. 하우스 블렌드, 다크 로스트. 당신의 결제 시스템에는 이 두 커피에 대한 하나의 클래스만 있으며 ‘음료’ 라는 추상 클래스를 상속받는다. 그리고 손님들이 와서는 커피에 설탕이나 우유를 추가하길 원하고있다!

결제시스템 담당자가 와서 시스템에 ‘커피 + 설탕’ , ‘커피 + 우유’ 서브클래스를 만들었지만 손님이 말하길,

“커피에 우유랑 설탕 추가해주세요”

???

IT 담당자는 각 커피 클래스에 ‘우유와 설탕 추가’ 서브클래스를 만들었고, 그럭저럭 괜찮은 것 같았다. 그런데 어느날 길 건너에 다른 커피샵이 생겼고, 커피 종류가 4가지에 추가 가능한 옵션이 10가지나 된다고 한다!

우리 가게도 질 수 없으니 서두러 판매할 재료를 갖추었다. 그런데 결제 시스템은?

당신은 새로운 커피들과 추가옵션에 모두 대응하도록 무한한 경우의 수 만큼의 서브클래스를 갖는 시스템을 상상했지만 만들수가 없었다. 게다가 시스템의 크기는 얼마나 클까?

적절한 결제 시스템을 만들 때라고 판단했고 새 IT 담당자를 찾았다. 그는 이렇게 말했다.

“decorator 패턴을 쓴다면 훨씬 쉽고 작은 시스템으로 가능해요.”

What on earth is that?

데코레이터 패턴은 구조적 카테고리에 속하며 실질적인 클래스의 구조를 다룬다. 상속으로든 조합으로든 혹은 둘 다 이던지. 이 디자인의 목표는 오브젝트의 기능성을 런타임 상에서 변경하는 것이다. 추상 클래스와 인터페이스들을 활용한 다른 여러 디자인 패턴 중 하나다.

public abstract class Beverage {
	private String description;
    
	public Beverage(String description) {
		super();
		this.description = description;
	}
    
	public String getDescription() {
		return description;
	}
    
	public abstract double cost();
}
public class HouseBlend extends Beverage {
	public HouseBlend() {
		super(House blend”);
	}

	@Override
	public double cost() {
		return 250;
	}
}

public class DarkRoast extends Beverage {
	public DarkRoast() {
		super(Dark roast”);
	}

	@Override
	public double cost() {
		return 300;
	}
}
public abstract class AddOn extends Beverage {
	protected Beverage beverage;

	public AddOn(String description, Beverage bev) {
		super(description);
		this.beverage = bev;
	}

	public abstract String getDescription();
}
public class Sugar extends AddOn {
	public Sugar(Beverage bev) {
		super(Sugar, bev);
	}

	@Override
	public String getDescription() {
		return beverage.getDescription() +with Mocha;
	}

	@Override
	public double cost() {
		return beverage.cost() + 50;
	}
}

public class Milk extends AddOn {
	public Milk(Beverage bev) {
		super(Milk, bev);
	}

	@Override
	public String getDescription() {
		return beverage.getDescription() +with Milk;
	}

	@Override  public double cost() {
		return beverage.cost() + 100;
	}
}
public class CoffeeShop {
	public static void main(String[] args) {
		HouseBlend houseblend = new HouseBlend();
		System.out.println(houseblend.getDescription() +:+ houseblend.cost());

		Milk milkAddOn = new Milk(houseblend);
		System.out.println(milkAddOn.getDescription() +:+ milkAddOn.cost());

		Sugar sugarAddOn = new Sugar(milkAddOn);
		System.out.println(sugarAddOn.getDescription() +:+ sugarAddOn.cost());
	}
}

최종 결과:

(…후략)


Minchang Kim
Minchang Kim
웹/앱 개발자 김민창입니다! 좋은 하루 되세요!