2019-07-28
https://www.freecodecamp.org/news/the-basic-design-patterns-all-developers-need-to-know/ 위 글 읽은 찌끄레기
약 26가지의 패턴이 있으며, 3가지로 구분할 수 있다.
싱글턴 디자인 패턴은 Creational 패턴이며 클래스의 인스턴스는 하나만 만들고, 그 오브젝트에는 단 하나의 글로벌 access point만 제공하는 것임. 자바에서 이런 클래스로 흔히 사용되는 것은 Calendar인데, 이 클래스의 인스턴스를 만들 수 없다. 또한 `getInstance() 메서드를 사용해 이 오브젝트를 사용한다.
싱글턴 디자인 패턴은 다음을 포함한다
싱글턴 디자인은 여러가지 각각 다른 구현이 존재함. 그 중 세가지만 보자.
public class EagerSingleton {
// 인스턴스를 만듦
private static EagerSingleton instance = new EagerSingleton();
// private constructor를 선언해 클래스 밖에서는 인스턴스를 만들 수 없음
private EagerSingleton() { }
// 유일하게 존재하는 인스턴스를 반환
public static EagerSingleton getInstance() {
return instance;
}
}
이 클래스가 어디에서도 사용되지 않는다면? 비상 대책으로 Lazy Instantiation이 있다.
위에 것과 크게 다르지 않다. 주된 차이점은 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 인스턴스화가 구현된다.
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 메서드를 실행해 동시접근을 막고, 단 하나의 인스턴스만 만든다)
Decorator 패턴을 왜, 어디서 사용해야 하는지 이해를 돕기 위해 작은 시나리오를 들려주겠다.
니가 커피샵을 운영한다고 치자. 여느 뉴비들과 마찬가지로 두 타입의 일반 커피로 시작하자. 하우스 블렌드, 다크 로스트. 당신의 결제 시스템에는 이 두 커피에 대한 하나의 클래스만 있으며 ‘음료’ 라는 추상 클래스를 상속받는다. 그리고 손님들이 와서는 커피에 설탕이나 우유를 추가하길 원하고있다!
결제시스템 담당자가 와서 시스템에 ‘커피 + 설탕’ , ‘커피 + 우유’ 서브클래스를 만들었지만 손님이 말하길,
“커피에 우유랑 설탕 추가해주세요”
IT 담당자는 각 커피 클래스에 ‘우유와 설탕 추가’ 서브클래스를 만들었고, 그럭저럭 괜찮은 것 같았다. 그런데 어느날 길 건너에 다른 커피샵이 생겼고, 커피 종류가 4가지에 추가 가능한 옵션이 10가지나 된다고 한다!
우리 가게도 질 수 없으니 서두러 판매할 재료를 갖추었다. 그런데 결제 시스템은?
당신은 새로운 커피들과 추가옵션에 모두 대응하도록 무한한 경우의 수 만큼의 서브클래스를 갖는 시스템을 상상했지만 만들수가 없었다. 게다가 시스템의 크기는 얼마나 클까?
적절한 결제 시스템을 만들 때라고 판단했고 새 IT 담당자를 찾았다. 그는 이렇게 말했다.
“decorator 패턴을 쓴다면 훨씬 쉽고 작은 시스템으로 가능해요.”
데코레이터 패턴은 구조적 카테고리에 속하며 실질적인 클래스의 구조를 다룬다. 상속으로든 조합으로든 혹은 둘 다 이던지. 이 디자인의 목표는 오브젝트의 기능성을 런타임 상에서 변경하는 것이다. 추상 클래스와 인터페이스들을 활용한 다른 여러 디자인 패턴 중 하나다.
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());
}
}
최종 결과:
(…후략)