최종 정리
1. Singleton 패턴
기본 구조
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
...
}
해당 코드는 멀티 스레드 환경에서 문제가 발생할 수 있다.
해결 1.
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
getInstance() 메소드에 synchronized 키워드를 사용한다.
해결 2.
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
정적으로 Singleton 객체를 생성한다. → 리소스 낭비 문제 발생!
해결 3?
public class Singleton {
private static Singleton uniqueInstance = null;
private Singleton() {}
public static Singleton getInstance() {
if(uniqueInstance == null) {
synchronized (Singleton.class) {
if(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
1. 변수가 초기화 되었는지 확인한다.
2. lock을 획득한다.
3. 변수가 이미 초기화 되었는지 다시 확인한다. 다른 스레드가 먼저 잠금을 획득한 경우, 이미 초기화를 수행했을 수 있다. 그렇다면 초기화된 변수를 반환하자.
4. 초기화되지 않았다면 변수를 초기화하고 반환하자.
- 언뜻보면 이전 알고리즘이 문제에 대한 효율적인 해결책으로 보인다. 그러나 이 기술에는 미묘한 문제가 많이 있기때문에 피해야 한다.
- 어떤 경우에는 스레드 A가 값이 초기화되지 않았음을 알아채고 lock을 획득하고 값 초기화를 시작한다.
- 일부 프로그래밍 언어의 의미로 인해 컴파일러에서 생성된 코드는 A가 초기화 수행을 완료하기 전에 부분적으로 생성된 객체를 가리키도록 공유 변수를 업데이트할 수 있다.
- 스레드 B는 공유 변수가 촉기화되었음을 확인하고 해당 값을 반환한다. 스레드 B는 값이 이미 초기화되었다고 생각하기 때문에 잠금을 획득하지 않는다. 스레드 A가 초기화를 완료하기 전에 변수를 사용하면 충돌할 가능성이 높다.
- J2SE 1.4 및 이전 버전에서 이중 확인 잠금을 사용할 때의 위험 중 하나는 작동하는 것처럼 보일 수 있다는 것이다.
- 컴파일러, 스케줄러에 의한 스레드 인터리브 및 기타 동시 시스템 활동의 특성에 따라 이중 확인 잠금의 잘못된 구현으로 인한 실패는 간헐적으로만 발생할 수 있다.
- 오류를 재현하는 것은 어려울 수 있다.
해결 3. 옳은 이중 확인 잠금
public class Singleton {
private valotile static Singleton uniqueInstance = null;
private Singleton() {}
public static Singleton getInstance() {
if(uniqueInstance == null) {
synchronized (Singleton.class) {
uniqueInstance = new Singleton();
}
}
return uniqueInstance;
}
}
Singleton 인스턴스에 volatile 키워드를 사용한다.
- 성능을 고려한 완벽한 솔루션이다. 그러나 성능 문제가 없는 경우, 이중 확인 잠금은 과잉일 수 있다. 또한 최소한 Java5를 실행하고 있는지 확인해야 한다.
2. Command 패턴
public class TestDrive {
public static void main(String[] args) {
Lamp lamp = new Lamp();
Command lampOnCommand = new LampOnCommand(lamp);
Button button1 = new Button(lampOnCommand);
button1.pressed();
Alarm alarm = new Alarm();
Command alramOnCommand = new AlramOnCommand(alarm);
Button button2 = new Button(alarmOnCommand);
button2.pressed();
button2.setCommand(lampOnCommand);
button2.pressed();
}
}
3. Adaptor 패턴
public class EnumerationIterator implements Iterator {
Enumeration enum;
public EnumerationIterator(Enumeration enum) {
this.enum = enum;
}
public boolean hasNext() {
return enum.hasMoreElements();
}
public Object next() {
return enum.nextElement();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
4. Facade 패턴
public class HomeTheaterFacade {
Amplifier amp;
Tuner tuner;
DvdPlayer dvd;
CdPlayer cd;
Projector projector;
TheaterLights lights;
Screen screen;
PopcornPopper popper;
public HomeTheaterFacade(Amplifier amp, Tunner tuner,
DvdPlayer dvd, CdPlayer cd, Projectoer projector,
Screen screen, TheaterLights lights,
PopcornPopper popper) {
this.amp = amp;
this.tuner = tuner;
...
}
public void watchMovie(String movie) {
System.out.println(“Get ready to watch a movie...”);
poper.on();
popper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.setInput(dvd);
projector.wideScreenMode();
amp.on();
amp.setDvd(dvd);
amp.setSurroundSound();
amp.setVolume(5);
dvd.on();
dvd.play(movie);
}
}
5. Template 패턴
public abstract class CaffeinBeverage {
final void prepareRecipe() {
boilwater();
brew();
pourInCup();
addCondiments();
}
abstract void brew();
abstract void addCondiments();
public void boilwater() {
System.out.println("Boiling water");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
}
prepareRecipe()이 Template Method !!
CaffeubeBeverage 클래스는 추상 클래스이다.
public class Tea extends CaffeineBeverage {
public void brew() {
System.out.println("Steeping the tea");
}
public void addCondiments() {
System.out.println("Adding Lemon");
}
}
Hook Method
public class CoffeeWithHook extends CaffeineBeverage {
public void brew() {
System.out.println("Dripping Coffee through filter");
}
public void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
public boolean customerWantsCondiments() throws IOException {
String answer = getUserInput();
if (answer.toLowerCase().startsWith("y")) return true;
else return false;
}
private String getUserInput() throws IOException {
String answer = null;
System.out.println("Would you like milk and sugar with your coffee (y/n)? ");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
} catch(IOException ioe) {
System.out.println("IO error trying to read your answer");
}
if(answer == null) { return "n"; }
return answer;
}
}
6. Iterator 패턴
Aggregate 개체의 기본 표현을 노출하지 않고 Aggregate 개체의 요소에 순차적으로 액세스하는 방법을 제공합니다.
7. Composite 패턴
public class MenuTestDrive {
public static void main(String[] args) {
//중간 레벨 메뉴
MenuComponent pancakeHouseMenu = new Menu("PANCAKE HOUSE MENU", "Breakfast");
MenuComponent dinerMenu = new Menu("DINER MENU", "Lunch");
MenuComponent cafeMenu = new Menu("CAFE MENU", "Dinner");
//루트 레벨 메뉴에 중간 레벨 메뉴를 더함
MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined");
allMenus.add(pancakeHouseMenu);
allMenus.add(dinerMenu);
allMenus.add(cafeMenu);
// 중간 레벨 메뉴에 하위 레벨 메뉴를 더함
MenuComponent dessertMenu = new Menu("DESSERT MENU", "Dessert of course!");
dinerMenu.add(dessertMenu);
//Menu에 MenuItem을 더함
pancakeHouseMenu.add(new MenuItem("K&B's Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99));
pancakeHouseMenu.add(new MenuItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", false, 2.99));
pancakeHouseMenu.add(new MenuItem("Blueberry Pancakes", "Pancakes made with fresh blueberries, and blueberry syrup", true, 3.49));
pancakeHouseMenu.add(new MenuItem("Waffles", "Waffles, with your choice of blueberries or strawberries", true, 3.59));
dinerMenu.add(new MenuItem("Vegetarian BLT", "(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99));
dinerMenu.add(new MenuItem("BLT", "Bacon with lettuce & tomato on whole wheat", false, 2.99));
dinerMenu.add(new MenuItem("Soup of the day", "A bowl of the soup of the day, with a side of potato salad", false, 3.29));
dinerMenu.add(new MenuItem("Hotdog", "A hot dog, with saurkraut, relish, onions, topped with cheese", false, 3.05));
dinerMenu.add(new MenuItem("Steamed Veggies and Brown Rice", "A medly of steamed vegetables over brown rice", true, 3.99));
dinerMenu.add(new MenuItem("Pasta", "Spaghetti with Marinara Sauce, and a slice of sourdough bread", true, 3.89));
dessertMenu.add(new MenuItem("Apple Pie", "Apple pie with a flakey crust, topped with vanilla icecream", true, 1.59));
dessertMenu.add(new MenuItem("Cheesecake", "Creamy New York cheesecake, with a chocolate graham crust", true, 1.99));
dessertMenu.add(new MenuItem("Sorbet", "A scoop of raspberry and a scoop of lime", true, 1.89));
cafeMenu.add(new MenuItem("Veggie Burger and Air Fries", "Veggie burger on a whole wheat bun, lettuce, tomato, and fries", true, 3.99));
cafeMenu.add(new MenuItem("Soup of the day", "A cup of the soup of the day, with a side salad", false, 3.69));
cafeMenu.add(new MenuItem("Burrito", "A large burrito, with whole pinto beans, salsa, guacamole", true, 4.29));
Waitress waitress = new Waitress(allMenus);
waitress.printVegetarianMenu();
}
}
Composite 패턴은 2가지 형태의 방식으로 나눌 수 있다.
Composite 패턴에서 Composite 클래스는 자식들을 관리하기 위해 추가적인 메소드가 필요하다.
이러한 메소드들이 어떻게 작성되느냐에 따라, Composite 패턴은 다른 목적을 추구할 수 있다.
- For Uniformity
- 일관성을 추구하는 방식
- 자식을 다루는 메소드들을 Component에서 정의한다.
- Type Safty
- Client는 Leaf와 Composite를 다르게 취급하고 있다.
- Client에서 Leaf 객체가 자식을 다루는 메소드를 호출할 수 없기 때문에 타입에 대한 안정성을 얻게 된다.
8. State 패턴
9. Proxy 패턴
- remote proxy : 서로 다른 주소 공간에 존재하는 객체를 가리키는 로컬 환경에 존재하는 proxy
- virtual proxy : 요청이 있을 때, 필요한 고비용 객체를 생성하는 proxy
- protection proxy : 대상 객체의 접근을 제어하기 위한 proxy
Proxy Pattern
Virtual Proxy
Protection Proxy
Proxy 패턴과 Decorator 패턴은 프록시 객체를 활용하기 때문에 구현 방법이 비슷하나 사용 목적이 다르다.
- Proxy 패턴은 객체에 대한 접근을 제어하는 목적으로 사용된다.
- Decorator 패턴은 동적으로 책임을 추가하기 위한 목적으로 사용된다.
- Decorator 패턴과 Proxy 패턴은 대상 객체에 대한 참조자를 관리하므로 구조가 비슷하다. 하지만 목적은 다르다.
- Proxy는 Decorator와 달리 동적으로 어떤 기능을 추가 및 제거하지 않는다.
- Proxy 패턴의 목적은 대상 객체에 대한 접근을 제어하는 것이다.
- Proxy 패턴은 참조하는 Target이 실제 기능을 제공하지만 Decorator 패턴은 Decorator 자체에서도 기능적인 일을 담당한다.
- 그러므로 Decorator는 재귀적 합성이 중요하지만 Proxy에서는 단순이 메시지를 전달하므로 재귀적 합성은 전혀 의미가 없다.