헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

프록시는 접근을 제어하고 관리한다.

다양한 변형 프록시가 존재한다.

프록시 패턴의 정의

특정 객체로의 접근을 제어하는 대리인을 제공한다.

  • 원격 프록시를 써서 원격 객체로 접근 제어
  • 가상 프록시로 생성하기 힘든 자원으로 접근 제어
  • 보호 프록시로 접근 권한이 필요한 자원으로 접근 제어

 

자바는 java.lang.reflect 패키지를 제공한다. 이 패키지 기능을 사용하여 프록시 기능을 구현할 수 있다.

이렇게 만들어진 프록시는 런타임 중 생성되서, 동적 프록시(dynamic proxy)라 한다.

 

리플랙션 패키지를 사용하면 자바에서 Proxy 클래스를 생성해 주므로 필요한 정보만 전달해주면 된다.

InvocationHandler 가 핵심이다.

 

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;

public class ProtectProxyTest {
	static interface Person{
		String getName();
		String getGender();
		String getInterests();
		int getGeekRating();
		
		void setName(String name);
		void setGender(String gender);
		void setInterests(String interests);
		void setGeekRating(int rating);
	}
	
	static class PersonImpl implements Person{
		String name;
		String gender;
		String interests;
		int rating;
		int ratingCount = 0;
		
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public String getGender() {
			return gender;
		}
		@Override
		public int getGeekRating() {
			if(ratingCount == 0 ) return 0;
			return (rating/ratingCount);
		}
		public void setGender(String gender) {
			this.gender = gender;
		}
		public String getInterests() {
			return interests;
		}
		public void setInterests(String interests) {
			this.interests = interests;
		}
		
		@Override
		public void setGeekRating(int rating) {
			this.rating = rating;
			ratingCount++;
		}
		
	}
	static class OwnerInvocationHandler implements InvocationHandler{
		Person person;
		
		public OwnerInvocationHandler(Person person) {
			this.person = person;
		}
		@Override
		//proxy객체참조, method 객체가 호출한 메서드 정보, args 메서드 인자정보
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			try {
				if(method.getName().startsWith("get")) {
					return method.invoke(person, args);
				}else if(method.getName().startsWith("setGeekRating")) {
					//나한테 평가는 불가
					throw new IllegalAccessException();
				}else if(method.getName().startsWith("set")) {
					return method.invoke(person, args);
				}
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
			return null;
		}
	}
	static class NonOwnerInvocationHandler implements InvocationHandler{
		Person person;
		
		public NonOwnerInvocationHandler(Person person) {
			this.person = person;
		}
		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			try {
				if(method.getName().startsWith("get")) {
					return method.invoke(person, args);
				}else if(method.getName().startsWith("setGeekRating")) {
					return method.invoke(person, args);
				}else if(method.getName().startsWith("set")) {
					//내것 아니니까 수정 불가
					throw new IllegalAccessException();
				}
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
			return null;
		}
	}
	
	static Person getOwnerProxy(Person person) {
		return (Person)Proxy.newProxyInstance(//정적 메서드
				person.getClass().getClassLoader(),//클래스 로더
				person.getClass().getInterfaces(),//프록시에서 구현할 모든 인터페이스
				new OwnerInvocationHandler(person));//InvocationHandler 구현한 클래스
	}
	static Person getNonOwnerProxy(Person person) {
		return (Person)Proxy.newProxyInstance(
				person.getClass().getClassLoader(),
				person.getClass().getInterfaces(),
				new NonOwnerInvocationHandler(person));
	}
	
	
	static Person getPersonFromDatabase(String name) {
		return (Person)datingDB.get(name);
	}
	
	static void initializeDatabase() {
		Person joe = new PersonImpl();
		joe.setName("김자바");
		joe.setInterests("자동차, 컴퓨터, 음악");
		joe.setGeekRating(7);
		datingDB.put(joe.getName(), joe);

		Person kelly = new PersonImpl();
		kelly.setName("박자바");
		kelly.setInterests("웹쇼핑, 영화, 음악");
		kelly.setGeekRating(6);
		datingDB.put(kelly.getName(), kelly);
	}
	
	static HashMap<String, Person> datingDB = new HashMap<String, Person>();
	
	public static void main(String[] args) {
		initializeDatabase();
		drive();
	}

	static void drive() {
		Person joe = getPersonFromDatabase("김자바");
		
		Person ownerProxy = getOwnerProxy(joe);//프록시 생성
		
		System.out.println("이름은 " + ownerProxy.getName());
		ownerProxy.setInterests("볼링, 바둑");
		System.out.println("본인 프록시에 관심 사항을 등록합니다.");
		try {
			ownerProxy.setGeekRating(10);
		} catch (Exception e) {
			System.out.println("본인 프록시에 괴짜 지수를 매길 수 없습니다.");
		}
		System.out.println("괴짜 지수 " + ownerProxy.getGeekRating());

		Person nonOwnerProxy = getNonOwnerProxy(joe);// 프록시 생성
		
		System.out.println("이름은 " + nonOwnerProxy.getName());
		try {
			nonOwnerProxy.setInterests("볼링, 바둑");
		} catch (Exception e) {
			System.out.println("타인 프록시에는 관심 사항을 등록할 수 없습니다.");
		}
		nonOwnerProxy.setGeekRating(3);
		System.out.println("타인 프록시에 괴짜 지수를 매깁니다.");
		System.out.println("괴짜 지수 " + nonOwnerProxy.getGeekRating());

	}

}
이름은 김자바
본인 프록시에 관심 사항을 등록합니다.
본인 프록시에 괴짜 지수를 매길 수 없습니다.
괴짜 지수 7
이름은 김자바
타인 프록시에는 관심 사항을 등록할 수 없습니다.
타인 프록시에 괴짜 지수를 매깁니다.
괴짜 지수 1

핵심 정리

프록시 패턴을 사용하면 객체에 대리인을 내세워서 클라이언트 접근을 제어할 수 있다.

원격 프록시는 클라이언트와 원격 객체 사이 데이터 전달을 관리

가상 프록시는 생성비용이 큰 객체 접근을 제어(지연로딩)

보호 프록시는 권한을 확인하여 객체 접근을 제어

이외 도 다양한 프록시 변형이 존재한다. 캐시 서버도 프록시 일종이다.

자바에는 동적 프록시 기능이 내장되어 있다. 

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

전략 패턴과 상태 패턴은 용도가 다를 뿐 그 설계는 비슷하다.

상태 패턴은 내부 상태를 바꿔 객체가 행동을 바꿀 수 있도록 한다.

public class StateTest {
	static class GumballMachine{
		//상태 표현 상수
		final static int SOLD_OUT = 0;
		final static int NO_QUARTER = 1;
		final static int HAS_QUARTER = 2;
		final static int SOLD = 3;
		
		//현재 상태 변수
		int state  = SOLD_OUT;
		int count = 0 ;
		
		public GumballMachine(int count) {
			this.count= count;
			if(count>0) {
				state = NO_QUARTER;
			}
		}
		
		public void insertQuarter() {
			if(state == HAS_QUARTER) {
				System.out.println("동전은 한 개만 넣어 주세요.");
			}else if (state == NO_QUARTER) {
				state = HAS_QUARTER;
				System.out.println("동전을 넣으셨습니다.");
			}else if (state == SOLD_OUT) {
				System.out.println("매진되었습니다. 다음 기회에 이용해 주세요.");
			}else if (state == SOLD) {
				System.out.println("알맹이를 내보내고 있습니다.");
			}
		}
		
		public void ejectQuarter() {
			if(state == HAS_QUARTER) {
				System.out.println("동전이 반환됩니다.");
				state = NO_QUARTER;
			}else if (state == NO_QUARTER) {
				state = HAS_QUARTER;
				System.out.println("동전을 넣어 주세요");
			}else if (state == SOLD_OUT) {
				System.out.println("동전을 넣지 않으셨습니다. 동전이 반환되지 않습니다.");
			}else if (state == SOLD) {
				System.out.println("이미 알맹이를 뽑으셨습니다.");
			}
		}
		
		public void turnCrank() {
			if(state == SOLD) {
				System.out.println("손잡이는 한 번만 돌려 주세요.");
			}else if (state == NO_QUARTER) {
				System.out.println("동전을 넣어 주세요.");
			}else if (state == SOLD_OUT) {
				System.out.println("매진되었습니다.");
			}else if (state == HAS_QUARTER) {
				System.out.println("손잡이를 돌리셨습니다.");
				state = SOLD;
				dispense();
			}
		}
		public void dispense() {
			if(state == SOLD) {
				System.out.println("알맹이를 내보내고 있습니다.");
				if(0 == (count -= 1)) {
					System.out.println("더 이상 알맹이가 없습니다.");
					state = SOLD_OUT;
				} else {
					state = NO_QUARTER;
				}
			}else if (state == NO_QUARTER) {
				System.out.println("동전을 넣어 주세요.");
			}else if (state == SOLD_OUT) {
				System.out.println("매진입니다.");
			}else if (state == HAS_QUARTER) {
				System.out.println("알맹이를 내보낼 수 없습니다.");
			}
		}
		public void refill(int numGumBalls) {
			this.count = numGumBalls;
			state = NO_QUARTER;
		}
		public String toString() {
			StringBuffer result = new StringBuffer();
			result.append("\nMighty Gumball, Inc.");
			result.append("\nJava-enabled Standing Gumball Model #2004\n");
			result.append("Inventory: " + count + " gumball");
			if (count != 1) {
				result.append("s");
			}
			result.append("\n갬블머신 현재 상태 ");
			if (state == SOLD_OUT) {
				result.append("매진");
			} else if (state == NO_QUARTER) {
				result.append("동전 투입 대기 중");
			} else if (state == HAS_QUARTER) {
				result.append("손잡이 돌리기 대기 중");
			} else if (state == SOLD) {
				result.append("알맹이 배출 중");
			}
			result.append("\n");
			return result.toString();
		}
	}
	public static void main(String[] args) {
		GumballMachine gumballMachine = new GumballMachine(5);

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.ejectQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.ejectQuarter();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);
	}
}
Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 5 gumballs
갬블머신 현재 상태 동전 투입 대기 중

동전을 넣으셨습니다.
손잡이를 돌리셨습니다.
알맹이를 내보내고 있습니다.

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
갬블머신 현재 상태 동전 투입 대기 중

동전을 넣으셨습니다.
동전이 반환됩니다.
동전을 넣어 주세요.

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
갬블머신 현재 상태 동전 투입 대기 중

동전을 넣으셨습니다.
손잡이를 돌리셨습니다.
알맹이를 내보내고 있습니다.
동전을 넣으셨습니다.
손잡이를 돌리셨습니다.
알맹이를 내보내고 있습니다.
동전을 넣어 주세요

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 2 gumballs
갬블머신 현재 상태 손잡이 돌리기 대기 중

동전은 한 개만 넣어 주세요.
동전은 한 개만 넣어 주세요.
손잡이를 돌리셨습니다.
알맹이를 내보내고 있습니다.
동전을 넣으셨습니다.
손잡이를 돌리셨습니다.
알맹이를 내보내고 있습니다.
더 이상 알맹이가 없습니다.
매진되었습니다. 다음 기회에 이용해 주세요.
매진되었습니다.

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 0 gumballs
갬블머신 현재 상태 매진

각 행동마다 상태를 전이하는 부분이 있다.

그리고 행동(메서드) 마다 상태별 경우의 수에 대한 코드가 있다.

public class StatePattern {
	static interface State {
		void insertQuarter();

		void ejectQuarter();

		void turnCrank();

		void dispense();

		void refill();
	}
	
	static abstract class BaseState implements State{
		GumballMachine gumballMachine;
		public BaseState(GumballMachine gumballMachine) {
			this.gumballMachine = gumballMachine;
		}
	}
	
	static class NoQuarterState extends BaseState{
		public NoQuarterState(GumballMachine gumballMachine) {
			super(gumballMachine);
		}
		public void insertQuarter() {
			System.out.println("동전을 넣으셨습니다.");
			gumballMachine.setState(gumballMachine.getHasQuarterState());
		}
		public void ejectQuarter() {
			System.out.println("동전을 넣어 주세요");
		}
		public void turnCrank() {
			System.out.println("동전을 넣어 주세요.");
		}
		public void dispense() {
			System.out.println("동전을 넣어 주세요.");
		}
		public void refill() {
		}
		@Override
		public String toString() {
			return "동전 기다리는 중";
		}
	}
	static class HasQuarterState extends BaseState{
		public HasQuarterState(GumballMachine gumballMachine) {
			super(gumballMachine);
		}
		public void insertQuarter() {
			System.out.println("동전은 한 개만 넣어 주세요.");
		}
		public void ejectQuarter() {
			System.out.println("동전이 반환됩니다.");
			gumballMachine.setState(gumballMachine.getNoQuarterState());
		}
		public void turnCrank() {
			System.out.println("손잡이를 돌리셨습니다.");
			gumballMachine.setState(gumballMachine.getSoldState());
		}
		public void dispense() {
			System.out.println("알맹이를 내보낼 수 없습니다.");
		}
		public void refill() {
		}
		@Override
		public String toString() {
			return "손잡이 돌기는 것 기다리는 중";
		}
	}
	static class SoldOutState extends BaseState{
		public SoldOutState(GumballMachine gumballMachine) {
			super(gumballMachine);
		}
		public void insertQuarter() {
			System.out.println("매진되었습니다. 다음 기회에 이용해 주세요.");
		}
		public void ejectQuarter() {
			System.out.println("매진되었습니다.");
		}
		public void turnCrank() {
			System.out.println("손잡이를 돌렸지만, 알맹이는 없습니다.");
		}
	 
		public void dispense() {
			System.out.println("알맹이를 내보낼 수 없습니다.");
		}
		
		public void refill() {
			gumballMachine.setState(gumballMachine.getNoQuarterState());
		}
		@Override
		public String toString() {
			return "매진";
		}
	}
	static class SoldState extends BaseState{
		public SoldState(GumballMachine gumballMachine) {
			super(gumballMachine);
		}
		public void insertQuarter() {
			System.out.println("알맹이를 내보내고 있습니다.");
		}
		public void ejectQuarter() {
			System.out.println("이미 알맹이를 뽑으셨습니다.");
		}
		public void turnCrank() {
			System.out.println("손잡이는 한 번만 돌려 주세요.");
		}
		public void dispense() {
		}
		public void refill() {
			gumballMachine.releaseBall();
			if (gumballMachine.getCount() > 0) {
				gumballMachine.setState(gumballMachine.getNoQuarterState());
			} else {
				System.out.println("이런, 알맹이가 다 떨어졌어요");
				gumballMachine.setState(gumballMachine.getSoldOutState());
			}
		}
		@Override
		public String toString() {
			return "알맹이 내보내는 중";
		}
	}
	
	
	static class GumballMachine{
		State state;
		int count = 0;
		
		final State soldOutState;
		final State noQuarterState;
		final State hasQuarterState;
		final State soldState;
		
		public GumballMachine(int numberGumballs) {
			soldOutState = new SoldOutState(this);
			noQuarterState = new NoQuarterState(this);
			hasQuarterState = new HasQuarterState(this);
			soldState = new SoldState(this);
			
			this.count = numberGumballs;
			state = numberGumballs > 0 ? noQuarterState : soldOutState;
		}
		void setState(State state) {
			this.state = state;
		}
	    public State getState() {
	        return state;
	    }

	    public State getSoldOutState() {
	        return soldOutState;
	    }

	    public State getNoQuarterState() {
	        return noQuarterState;
	    }

	    public State getHasQuarterState() {
	        return hasQuarterState;
	    }

	    public State getSoldState() {
	        return soldState;
	    }
		
		public void insertQuarter() {
			state.insertQuarter();
		}
		
		public void ejectQuarter() {
			state.ejectQuarter();
		}
		
		public void turnCrank() {
			state.turnCrank();
			state.dispense();
		}
		
		public void refill(int count) {
			this.count += count;
			System.out.println("겜볼 기계가 방금 리필되었습니다. 개수는 다음과 같습니다." + this.count);
			state.refill();
		}
		
		public void releaseBall() {
			System.out.println("알맹이가 슬롯에서 굴러 나옵니다....");
			if (count > 0) {
				count = count - 1;
			}
		}
		public int getCount() {
			return count;
		}
		
		public String toString() {
			StringBuffer result = new StringBuffer();
			result.append("\n겜블 머신_");
			result.append("\n자바 기반 Gumball Model #2004");
			result.append("\n항목: " + count + " 알맹이");
			if (count != 1) {
				result.append("들");
			}
			result.append("\n");
			result.append("머신 상태 :" + state + "\n");
			return result.toString();
		}
	}
	public static void main(String[] args) {
		GumballMachine gumballMachine = new GumballMachine(2);

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		
		gumballMachine.refill(5);
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);
	}
}

상태 인터페이스로 추상화 다형성을 이용

각 상태의 행동을 별개의 클래스로 국지화

앞으로 생길 수 있는 새로운 상태에 대해 OCP를 준수할 수 있다.

 

상태 패턴의 정의

객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있다.

마치 객체의 클래스가 바뀌는 것과 같은 결과

 

상태 패턴과 전략 패턴은 비슷하나 용도가 다르다.

상태 패턴은 상태 객체에 일련 행동이 캡슐화, Context 객체에서 여러 상태 객체 중 한 객체에게 모든 행동을 위임한다.

클라이언트는 상태 객체를 몰라도 된다.

전략 패턴은 클라이언트가 Context에게 어떤 전략 객체를 사용할지를 지정한다. 주로 런타임 환경에서 객체를 변경할 수 있는 유연성을 제공하는 용도로 쓰인다.

 

상태 패턴은 객체에 수많은 조건문을 넣는 대신 상태 패턴을 사용한다고 생각하면 된다.

 

상태 패턴에서 상태 전환을 Context에서 해도, 상태 클래스에서 해도 상관없다. 다만, 상태 전환 코드를 상태  클래스에 넣으면 상태 클래스 간 의존성이 생긴다. 이 문제로 위 예제에선 겜블머신에 게터 메소드를 사용했다.

 

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

서로 다는 방식으로 저장한 것을 공통으로 다루고 싶다.

import java.util.*;
import java.util.Map.Entry;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class IteratorTest {
	static Supplier<IntStream> supply(){
		return ()-> new Random().ints(10, 1, 100);
	}
	
	static class ClassArr<E> implements Iterable<E>{
		E[] dataArr;
		
		public ClassArr(E[] dataArr) {
			this.dataArr = (E[]) dataArr;
		}

		@Override
		public Iterator<E> iterator() {
			return new Iterator<E>() {
				int cursor = 0;
				public boolean hasNext() {
					return cursor < dataArr.length;
				}
				public E next() {
					return dataArr[cursor++];
				}
			};
		}
		
	}
	
	static class MapClass implements Iterable<Map.Entry<Object, List<Integer>>>{
		Map<Object, List<Integer>> map = new HashMap<>();

		public MapClass() {
			map= supply().get().boxed().collect(Collectors.groupingBy(n->n));
		}
		
		@Override
		public Iterator<Entry<Object, List<Integer>>> iterator() {
			return map.entrySet().iterator();
		}

	}

	
	static class ArrClass implements Iterable<Integer>{
		int[] intArr = supply().get().toArray();

		@Override
		public Iterator<Integer> iterator() {
			return new Iterator<Integer>() {
				int cursor = 0;
				public Integer next() {
					return intArr[cursor++];
				}
				public boolean hasNext() {
					return cursor<intArr.length;
				}
			};
		}
	}
	static class ListClass implements Iterable<Integer>{
		List<Integer> intList = supply().get().boxed().toList();
		@Override
		public Iterator<Integer> iterator() {
			return intList.iterator();
		}
		
	}
	
	public static void main(String[] args) {
		String[] test = {"배열은","향상된","반복문","사용가능"};
		//배열 향상 포문 가능
		for(String data : test) {
			System.out.print(data+" ");
		}
		System.out.println();
		//어떠한 클래스도 Iterable만 알맞게 구현하면 전부 향상 포문 가능하다.
		for(String data : new ClassArr<>(test)) {
			System.out.print(data+" ");
		}
		System.out.println();
		for(Integer val : new ArrClass()) {
			System.out.print(val+" ");
		}
		System.out.println();
		for(Integer val :new ListClass()) {
			System.out.print(val+" ");
		}
		System.out.println();
		for(Entry en : new MapClass()) {
			System.out.print(en + " ");
		}
	}
}
배열은 향상된 반복문 사용가능 
배열은 향상된 반복문 사용가능 
61 13 57 51 30 49 25 5 48 48 
34 91 69 72 70 1 18 5 26 55 
49=[49] 17=[17] 97=[97] 35=[35] 52=[52] 53=[53] 56=[56] 40=[40] 45=[45] 15=[15]

반복자 패턴 알아보기

반복자 패턴은 Iterator 인터페이스에 의존한다

인터페이스만 있으면 배열, 리스트, 해시테이블 모든 컬렉션을 순회할 있다.

반복자 패턴의 정의

집합체 내에서 어떤 식으로 일이 처리되는지 전혀 모르는 상태에서 안에 들어있는 모든 항목을 대상으로 반복 작업을 수행할 있다.

 

또한 서로 다른 종류의 집합체라도 Iterator 구현해 리턴하면 같은 방법으로 순회를 있다.

집합체는 요소 순회만 Iterator에게 위임하고, 내부 자료구조 관리만 잘하면 된다.

컴포지트 패턴의 정의

반복자 패턴만으론 처리하기 어려워 메뉴 관리에 도움되는 컴포지트 패턴 도임

객체를 트리구조로 구성해서 부분-전체 계층구조를 구현한다. 컴포지트 패턴을 사용하면 클라이언트에서 개별 객체와 복합 객체를 똑같은 방법으로 다룰 수 있다.

 

import java.util.ArrayList;
import java.util.List;

public class CompositeTest {
	//구성요소
	static abstract class MenuComponent{
		public void add(MenuComponent menuComponent) {
			throw new UnsupportedOperationException();
		}
		public void remove(MenuComponent menuComponent) {
			throw new UnsupportedOperationException();
		}
		public MenuComponent getChild(int i) {
			throw new UnsupportedOperationException();
		}
		public String getName() {
			throw new UnsupportedOperationException();
		}
		public String getDescription() {
			throw new UnsupportedOperationException();
		}
		public double getPrice() {
			throw new UnsupportedOperationException();
		}
		public boolean isVegetarian() {
			throw new UnsupportedOperationException();
		}
		public void print() {
			throw new UnsupportedOperationException();
		}
	}
	//잎
	static class MenuItem extends MenuComponent{
		String name;
		String description;
		boolean vegetarian;
		double price;
		
		public MenuItem(String name, String description, boolean vegetarian, double price) {
			this.name = name;
			this.description = description;
			this.vegetarian = vegetarian;
			this.price = price;
		}
		
		public String getName() {
			return name;
		}
		
		public String getDescription() {
			return description;
		}
		
		public boolean isVegetarian() {
			return vegetarian;
		}
		
		public double getPrice() {
			return price;
		}
		
		public void print() {
			System.out.print("  "+getName());
			if(isVegetarian()) {
				System.out.print("(v)");
			}
			System.out.println(", "+getPrice());
			System.out.println("    -- "+getDescription());
		}
		
	}
	//노드
	static class Menu extends MenuComponent{
		List<MenuComponent> menuComponents = new ArrayList<>();
		String name;
		String description;
		
		public Menu(String name, String description) {
			this.name = name;
			this.description = description;
		}
		
		public void add(MenuComponent menuComponent) {
			menuComponents.add(menuComponent);
		}
		public void remove(MenuComponent menuComponent) {
			menuComponents.remove(menuComponent);
		}
		public MenuComponent getChild(int i) {
			return menuComponents.get(i);
		}
		
		public String getName() {
			return name;
		}
		public String getDescription() {
			return description;
		}
		
		public void print() {
			System.out.print("\n"+getName());
			System.out.println(", "+getDescription());
			System.out.println("----------------------");
			
			for(MenuComponent menuComponent : menuComponents) {
				menuComponent.print();
			}
			
		}
	}
	
	static class Waitress{
		MenuComponent allmenus;

		public Waitress(MenuComponent allmenus) {
			this.allmenus = allmenus;
		}
		
		public void printMenu() {
			allmenus.print();
		}
	}
	
	
	public static void main(String[] args) {
		MenuComponent phMenu = new Menu("팬케이크 하우스 메뉴", "아침 메뉴");
		MenuComponent dinerMenu = new Menu("객체마을 식당 메뉴", "점심 메뉴");
		MenuComponent cafeMenu = new Menu("카페 메뉴", "저녁 메뉴");
		MenuComponent dessertMenu = new Menu("디저트 메뉴", "디저트를 즐겨 보에쇼");
		
		MenuComponent allMenu = new Menu("전체 메뉴", "전체 메뉴");
		
		allMenu.add(phMenu);
		allMenu.add(dinerMenu);
		allMenu.add(cafeMenu);
		
		phMenu.add(
				new MenuItem("K&B 팬케이크 아침정식"
				,"팬케이크에 스크램블에그와 토스트", true, 2.99));
		phMenu.add(
				new MenuItem("레귤러 팬케이크 아침정식"
				,"팬케이크에 계란 후라이와 소세지", false, 2.99));
		phMenu.add(
				new MenuItem("블루베리 팬케이크"
				,"팬케이크와 블루베리, 블루베리 시럽", true, 3.49));
		phMenu.add(
				new MenuItem("와플"
				,"와플과 블루베리 또는 딸기를 드립니다.", true, 3.59));
		
		dinerMenu.add(
				new MenuItem("채식주의자용 BLT"
				, "통밀 위에 콩고기 베이컨, 상추, 토마토를 얹은 메뉴", true, 2.99));
		
		dinerMenu.add(
				new MenuItem("BLT"
				, "통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴", false, 2.99));
		dinerMenu.add(
				new MenuItem("오늘의 스프"
				, "감자 샐러드를 곁들인 오늘의 스프", true, 2.99));
		dinerMenu.add(
				new MenuItem("핫도그"
				, "사워크라우트, 갖은 양념, 양파, 치즈가 곁들여진 핫도그", true, 2.99));
		dinerMenu.add(
				new MenuItem("파스타"
				, "마리나라 소스 스파게티, 효모빵도 드립니다.", true, 3.89));
		dinerMenu.add(dessertMenu);
		
		
		dessertMenu.add(
				new MenuItem("애플 파이"
				, "바삭바삭한 크러스트에 바닐라 아이스크림이 얹혀 있는 애플 파이", true, 1.59));
		dessertMenu.add(
				new MenuItem("치즈케이크"
				, "초콜릿 그레이엄 크러스트 위에 부드러운 뉴욕 치즈케이크", true, 1.99));
		dessertMenu.add(
				new MenuItem("소르베"
				, "라스베리와 라임의 절묘한 조화", true, 1.89));
		
		cafeMenu.add(
				new MenuItem("베지 버거와 에어 프라이"
				, "통밀빵, 상추, 토마토, 감자 튀김이 첨가된 베지 버거", true, 1.59));
		cafeMenu.add(
				new MenuItem("오늘의 스프"
				, "통밀빵, 상추, 토마토, 감자 튀김이 첨가된 베지 버거", false, 0.69));
		cafeMenu.add(
				new MenuItem("투리토"
				, "통 핀토콩과 살사, 구아카몰이 곁들여진 푸짐한 부리토", true, 0.89));
		
		Waitress waitress = new Waitress(allMenu);
		waitress.printMenu();
	}
}
전체 메뉴, 전체 메뉴
----------------------

팬케이크 하우스 메뉴, 아침 메뉴
----------------------
  K&B 팬케이크 아침정식(v), 2.99
    -- 팬케이크에 스크램블에그와 토스트
  레귤러 팬케이크 아침정식, 2.99
    -- 팬케이크에 계란 후라이와 소세지
  블루베리 팬케이크(v), 3.49
    -- 팬케이크와 블루베리, 블루베리 시럽
  와플(v), 3.59
    -- 와플과 블루베리 또는 딸기를 드립니다.

객체마을 식당 메뉴, 점심 메뉴
----------------------
  채식주의자용 BLT(v), 2.99
    -- 통밀 위에 콩고기 베이컨, 상추, 토마토를 얹은 메뉴
  BLT, 2.99
    -- 통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴
  오늘의 스프(v), 2.99
    -- 감자 샐러드를 곁들인 오늘의 스프
  핫도그(v), 2.99
    -- 사워크라우트, 갖은 양념, 양파, 치즈가 곁들여진 핫도그
  파스타(v), 3.89
    -- 마리나라 소스 스파게티, 효모빵도 드립니다.

디저트 메뉴, 디저트를 즐겨 보에쇼
----------------------
  애플 파이(v), 1.59
    -- 바삭바삭한 크러스트에 바닐라 아이스크림이 얹혀 있는 애플 파이
  치즈케이크(v), 1.99
    -- 초콜릿 그레이엄 크러스트 위에 부드러운 뉴욕 치즈케이크
  소르베(v), 1.89
    -- 라스베리와 라임의 절묘한 조화

카페 메뉴, 저녁 메뉴
----------------------
  베지 버거와 에어 프라이(v), 1.59
    -- 통밀빵, 상추, 토마토, 감자 튀김이 첨가된 베지 버거
  오늘의 스프, 0.69
    -- 통밀빵, 상추, 토마토, 감자 튀김이 첨가된 베지 버거
  투리토(v), 0.89
    -- 통 핀토콩과 살사, 구아카몰이 곁들여진 푸짐한 부리토

 

 

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

알고리즘을 캡슐화하는 패턴

 

public class TemplateMethodTest {
	static class Coffee{
		void prepareRecipe() {
			boilWater();
			brewCoffeeGrinds();
			purInCup();
			addSugarAndMilk();
		}
		
		public void boilWater() {
			System.out.println("물 끊이는 중");
		}
		public void brewCoffeeGrinds() {
			System.out.println("필터로 커피를 우려내는 중");
		}
		public void purInCup() {
			System.out.println("컵에 따르는 중");
		}
		public void addSugarAndMilk() {
			System.out.println("설탕과 우유를 추가하는 중");
		}
	}
	static class Tea {
		void prepareRecipe() {
			boilWater();
			steepTeaBag();
			purInCup();
			addLemon();
		}
		public void boilWater() {
			System.out.println("물 끊이는 중");
		}
		public void steepTeaBag() {
			System.out.println("찻잎을 우려내는 중");
		}
		public void addLemon() {
			System.out.println("레몬을 추가하는 중");
		}
		public void purInCup() {
			System.out.println("컵에 따르는 중");
		}
	}
	public static void main(String[] args) {
		Coffee coffee = new Coffee();
		Tea tea = new Tea();
		coffee.prepareRecipe();
		tea.prepareRecipe();
		
	}
}
물 끊이는 중
필터로 커피를 우려내는 중
컵에 따르는 중
설탕과 우유를 추가하는 중
물 끊이는 중
찻잎을 우려내는 중
컵에 따르는 중
레몬을 추가하는 중

알고리즘이 거의 같다. 두 클래스 공통된 부분을 추상화하는 것이 좋겠다.

public class TemplateMethodTest2 {
	
	//더 추상화 할 요소는?
	abstract static class CaffeineBeverage{
		abstract void prepareRecipe();
		public void boilWater() {
			System.out.println("물 끊이는 중");
		}
		public void purInCup() {
			System.out.println("컵에 따르는 중");
		}
	}
	
	static class Coffee extends CaffeineBeverage{
		void prepareRecipe() {
			boilWater();
			brewCoffeeGrinds();
			purInCup();
			addSugarAndMilk();
		}
		
		
		public void brewCoffeeGrinds() {
			System.out.println("필터로 커피를 우려내는 중");
		}
		
		public void addSugarAndMilk() {
			System.out.println("설탕과 우유를 추가하는 중");
		}
	}
	static class Tea extends CaffeineBeverage{
		void prepareRecipe() {
			boilWater();
			steepTeaBag();
			purInCup();
			addLemon();
		}
		public void steepTeaBag() {
			System.out.println("찻잎을 우려내는 중");
		}
		public void addLemon() {
			System.out.println("레몬을 추가하는 중");
		}
	}
}

더 추상화할 여지가 있다. 단순히 메서드 이름이 아니라 동작 자체를 보면 추상화할 것이 더 존재함을 알 수 있다.

무언가를 우려낸다는 행위와, 무언가를 첨가하는 것

public class TemplateMethodTest3 {
	//abstract 메서드가 없더라도 abstract를 붙여 인스턴스화를 방지할 수 있다.
	abstract static class CaffeineBeverage{
    	//핵심 템플릿 메서드 final
		final void prepareRecipe() {
			 boilWater();
			 brew();
			 purInCup();
			 addCondiments();
		}
		//약간의 동작 차이만을 보이므로 재정의를 강제하도록 abstract 키워드를 붙인다.
		protected abstract void addCondiments();
		protected abstract void brew();
		public void boilWater() {
			System.out.println("물 끊이는 중");
		}
		public void purInCup() {
			System.out.println("컵에 따르는 중");
		}
	}
	
	static class Coffee extends CaffeineBeverage{
		@Override
		public void addCondiments() {
			System.out.println("설탕과 우유를 추가하는 중");
		}
		@Override
		public void brew() {
			System.out.println("필터로 커피를 우려내는 중");
		}
	}
	static class Tea extends CaffeineBeverage{
		@Override
		protected void addCondiments() {
			System.out.println("레몬을 추가하는 중");
		}
		@Override
		protected void brew() {
			System.out.println("찻잎을 우려내는 중");
			
		}
	}
}

템플릿 메소드 패턴 알아보기

템플릿 메소드는 알고리즘의 각 단계를 정의하며, 서브 클래스에서 일부 단계를 구현할 수 있도록 유도한다.

핵심은 템플릿 메소드를 final로 선언해 각 알고리즘의 순서를 통제한다.

 

템플릿 메소드 패턴의 장점 

템플릿 메소드에서 알고리즘을 독점해 처리한다.

알고리즘이 한 군데 모여 있다. 

 

템플릿 메소드 패턴의 정의

알고리즘의 골격을 정의한다.

템플릿 메소드를 사용하면 알고리즘의 일부 단계를 서브클래스에서 구현할 수 있다.

알고리즘의 구조를 그대로 유지하면서 알고리즘의 특정 단계를 서브클래스에서 재정의할 수 있다.

abstract class AbstractClass{
    final void templateMethod() {
        primitiveOperation1();
        primitiveOperation2();
        concreteOperation();
        hook();
    }
    public void hook() {}
    public void concreteOperation() {}
    protected abstract void primitiveOperation2();
    protected abstract void primitiveOperation1();
}

템플릿 메소드 후크 알아보기

hook 메서드는 구상메서드지만, 구현한게 아무것도(혹은 거의없는) 없다.

hook 메서드는 알고리즘 사이사이에 마음것 위치할 있다.

서브클레스에서 필요 오버라이드해서 사용할 목적이다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import designpattern.headfirst.chapter8.TemplateMethodTest3.Coffee;
import designpattern.headfirst.chapter8.TemplateMethodTest3.Tea;

public class TemplateMethodTest4 {
	abstract static class CaffeineBeverageWithHook{
		final void prepareRecipe() {
			 boilWater();
			 brew();
			 purInCup();
			 if(customerWantsCondiments()) {
				 addCondiments();
			 }
		}
		boolean customerWantsCondiments() {
			return true;
		}
		protected abstract void addCondiments();
		protected abstract void brew();
		public void boilWater() {
			System.out.println("물 끊이는 중");
		}
		public void purInCup() {
			System.out.println("컵에 따르는 중");
		}
	}
	
	static class CoffeeWithHook extends CaffeineBeverageWithHook{
		@Override
		public void addCondiments() {
			System.out.println("설탕과 우유를 추가하는 중");
		}
		@Override
		public void brew() {
			System.out.println("필터로 커피를 우려내는 중");
		}
		
		@Override
		public boolean customerWantsCondiments() {
			String answer = getUserInput();
			
			if(answer.equalsIgnoreCase("y")) {
				return true;
			}else {
				return false;
			}
		}
		private String getUserInput() {
			String answer = null;
			System.out.print("커피에 우유와 설탕을 넣을까요?(y/n)");
			try{
				BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
				answer = br.readLine();
			}catch(IOException e) {
				System.out.println(e.getCause());
			}
			if(answer == null) {
				return "no";
			}
			return answer;
		}
	}
	static class TeaWithHook extends CaffeineBeverageWithHook{
		@Override
		protected void addCondiments() {
			System.out.println("레몬을 추가하는 중");
		}
		@Override
		protected void brew() {
			System.out.println("찻잎을 우려내는 중");
			
		}
		@Override
		public boolean customerWantsCondiments() {
			String answer = getUserInput();
			if(answer.equalsIgnoreCase("y")) {
				return true;
			}else {
				return false;
			}
		}
		private String getUserInput() {
			String answer = null;
			System.out.println("차에 레몬을 넣을까요? (y/n) ");
			try{
				BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
				answer = br.readLine();
			}catch(IOException e) {
				System.out.println(e.getCause());
			}
			if(answer == null) {
				return "no";
			}
			return answer;
		}
	}
	
	public static void main(String[] args) {
		Tea tea = new Tea();
		Coffee coffee = new Coffee();
		tea.prepareRecipe();
		coffee.prepareRecipe();
		
		TeaWithHook teaWithHook = new TeaWithHook();
		CoffeeWithHook coffeeWithHook = new CoffeeWithHook();
		
		System.out.println("\n홍차 준비중...");
		teaWithHook.prepareRecipe();
		System.out.println("\n커피 준비중...");
		coffeeWithHook.prepareRecipe();
	}
}
물 끊이는 중
찻잎을 우려내는 중
컵에 따르는 중
레몬을 추가하는 중
물 끊이는 중
필터로 커피를 우려내는 중
컵에 따르는 중
설탕과 우유를 추가하는 중

홍차 준비중...
물 끊이는 중
찻잎을 우려내는 중
컵에 따르는 중
차에 레몬을 넣을까요? (y/n) 
n

커피 준비중...
물 끊이는 중
필터로 커피를 우려내는 중
컵에 따르는 중
커피에 우유와 설탕을 넣을까요?(y/n)y
설탕과 우유를 추가하는 중

할리우드 원칙

먼저 연락하지 마세요. 저희가 연락 드리겠습니다.

 

의존성 부패를 방지하기 위함

의존성 부패는 고수준 구성요소가 저수준 구성 요소에 의존하고, 저수준 구성 요소는 다시 고수준 구성 요소에 의존하고를 반복하는 것 

순환 의존성

 

할리우드 원칙을 사용하면, 저수준 구성 요소가 시스템에 접속할 수는 있지만 언제, 어떻게 구성 요소를 사용할지는 고수준 구성 요소가 결정한다.

, 고수준 구성 요소가 저수준 구성 요소에게 "먼저 연락하지 마세요. 제가 먼저 연락 드리겠습니다." 라고 얘기하는 것과 같다.

할리우드 원칙과 템플릿 메소드 패턴

할리우드 원칙과 템플릿 메소드 패턴의 관계는 쉡게 있다.

템플릿 메소드 패턴을 써서 디자인하면 자연스럽게 서브클래스에게 "우리가 연락할 테니까 먼저 연락하지 "라고 얘기하는 셈이다.

 

자바 API 템플릿 메소드 패턴 알아보기

    public static void sort(Object[] a) {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a); //일부를 위임
        else
            ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
    }

    /** To be removed in a future release. */
    private static void legacyMergeSort(Object[] a) {
        Object[] aux = a.clone();
        mergeSort(aux, a, 0, a.length, 0);
    }

Arrays.sort 는 알고리즘을 구현하고, 일부 단계를 서브클래스에서 구현하라는 템플릿 메서드 정의와 완전히 같지는 않다.

자바에서 모든 배열이 정렬 기능을 사용할 수 있도록 만들기 위해 정적 메서드 sort를 만들고 대소를 비교하는 부분은 배열 타입 객체에서 구현하도록 만들었다.

이런 점에서 sort()메소드 구현 자체는 템플릿 메소드 패턴의 기본 정신에 충실하다.

Arrays의 서브클래스를 만들어야 한다는 제약조건이 없어 더 유연하기도 하다.

단점

	public class ArraysSortTest {
	    static class Dummy {}
	    
	    public static void main(String[] args) {
	        Dummy[] dumArr = {new Dummy(), new Dummy()};
	        Arrays.sort(dumArr);
	    }
	}
	
	Exception in thread "main" java.lang.ClassCastException: class designpattern.headfirst.chapter8.ArraysSortTest$Dummy cannot be cast to class java.lang.Comparable (designpattern.headfirst.chapter8.ArraysSortTest$Dummy is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
	 at java.base/java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:320)
	 at java.base/java.util.ComparableTimSort.sort(ComparableTimSort.java:188)
	 at java.base/java.util.Arrays.sort(Arrays.java:1041)
	 at designpattern.headfirst.chapter8.ArraysSortTest.main(ArraysSortTest.java:10)
	
	sort 메서드에서 해당 객체 배열이 Comparable 을 구현했는지 컴파일러에서 체크를 할 수 없어 런타임 에러를 유발한다…

핵심 정리

템플릿 메소드는 알고리즘의 단계를 정의하며 일부 단계를 서브 클래스에서 구현하도록 할 수 있다.

템플릿 메소드 패턴은 코드 재사용에 도움된다.

후크 메서드는 서브 클래스에서 선택적으로 재정의한다.

할리우드 원칙에 의하면, 저수준 모듈은 언제 어떻게 호출할지는 고수준 모듈에서 결정해야 한다.

실전에서 교과서적으로 패턴과 반드시 일치하지 않을 수 있다. 

팩토리 메소드 패턴은 템플릿 메소드 패턴의 특화 버전이다.

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

어댑터 생각해보기

일상 생활에서 어댑터란 무언가를 호환되도록 도와준다.

한국의 220V 콘센트를 일본의 110V 콘센트와 호환되게 해주는 어댑터가 그 예다.

 

코드에서도 어댑터 패턴은 이 역할을 한다.

기존 시스템과 업체에서 제공한 코드가 있다고 치자. 이 사이를 어댑터 코드로 연결지어주면, 두 코드는 코드 변경이 없다.

어댑터 사용 방법 알아보기

package designpattern.headfirst.chapter7;

public class AdapterTest {
	//기존 코드
	static interface Duck{
		void quack();
		void fly();
	}
	static class MallardDuck implements Duck{
		@Override
		public void quack() {
			System.out.println("꽥!");
		}
		@Override
		public void fly() {
			System.out.println("날고 있어요!!");
		}
	}
	
	//새로 제공된 코드
	static interface Turkey{
		void gobble();
		void fly();
	}
	static class WildTurkey implements Turkey{
		@Override
		public void gobble() {
			System.out.println("골골");
		}
		@Override
		public void fly() {
			System.out.println("짧은 거리를 날고 있어요!");
		}
	}
	
	//어댑터
	static class TurkeyAdapter implements Duck{
		Turkey turkey;
		
		public TurkeyAdapter(Turkey turkey) {
			this.turkey = turkey;
		}

		public void quack() {
			turkey.gobble();
		}

		public void fly() {
			for (int i = 0; i < 5; i++) {
				turkey.fly();
			}
		}
	}
	static class DuckAdapter implements Turkey{
		Duck duck;
		public DuckAdapter(Duck duck) {
			this.duck = duck;
		}
		public void gobble() {
			duck.quack();
		}
		public void fly() {
			duck.fly();
		}
	}
	
	public static void main(String[] args) {
		Duck duck = new MallardDuck();
		Turkey turkey = new WildTurkey();
		Duck turkeyAdapter = new TurkeyAdapter(turkey);
		Turkey duckAdapter = new DuckAdapter(duck);
		
		System.out.println("칠면조---");
		turkey.gobble();
		turkey.fly();
		System.out.println("\n오리---");
		testDuck(duck);
		System.out.println("\n칠면조 어댑터---");
		testDuck(turkeyAdapter);
		System.out.println("\n오리 어댑터---");
		testTurkey(duckAdapter);
		testTurkey(turkey);
		
		
	}
	static void testTurkey(Turkey turkey){
		turkey.gobble();
		turkey.fly();
	}
	static void testDuck(Duck duck) {
		duck.quack();
		duck.fly();
	}
}
칠면조---
골골
짧은 거리를 날고 있어요!

오리---
꽥!
날고 있어요!!

칠면조 어댑터---
골골
짧은 거리를 날고 있어요!
짧은 거리를 날고 있어요!
짧은 거리를 날고 있어요!
짧은 거리를 날고 있어요!
짧은 거리를 날고 있어요!

오리 어댑터---
꽥!
날고 있어요!!
골골
짧은 거리를 날고 있어요!

어댑터 패턴 알아보기

public class 어댑터패턴 {
	static interface Adaptee {
		void 적응이필요해();
	}
	static class AdapteeClass implements Adaptee{
		public void 적응이필요해() {
			System.out.println("적응이 필요한 코드 뭉치");
		}
	}
	//타겟 인터페이스
	static interface Origin{
		void 원본코드();
	}
	static class OriginClass implements Origin{
		public void 원본코드() {
			System.out.println("원본 코드");
		}
	}
	//핵심
	static class OriginAdapter implements Origin{
		Adaptee adaptee; // 어댑티 객체 인스턴스가 있다.
		
		public OriginAdapter(Adaptee adaptee) {
			this.adaptee = adaptee;
		}
		@Override
		public void 원본코드() {
			adaptee.적응이필요해();
		}
	}
	public static void main(String[] args) {
		Origin origin = new OriginClass();
		Adaptee adaptee = new AdapteeClass();
		Origin originAdapter = new OriginAdapter(adaptee);
		action(origin);
		action(originAdapter);
	}
	//기존에 클라이언트가 사용하던 원본 코드는 변경이 없다.
	static void action(Origin origin) {
		origin.원본코드();
	}
}
원본 코드
적응이 필요한 코드 뭉치

클라이언트는 타겟 인터페이스로 다루니 코드 변경이 없다.

어댑터는 반드시 적응 시켜야할 어댑티 메서드와 일대일 매칭되리란 보장이 없다.

클라이언트는 중간에 어댑터가 있다는 사실도 모른다.

 

어댑터 패턴의 정의

특정 클래스 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환한다.

호환되지 않는 인터페이스를 사용하는 클라이언트를 그대로 활용할 수 있다. 

객체 어댑터와 클래스 어댑터

자바의 경우 단일 상속 언어로 클래스 어댑터를 쓰지 않는다.

클래스 어댑터는 다중 상속이 가능한 언어에서 쓰일 수 있다.

 

객체 어댑터는 구성관계를 사용하므로 어떤 언어도 상관없다. 단순히 호출을 전달만 하면 된다.

 

실전 사례

Enumberation , Iterator

public class EnumIterAdapter {
	static class EnumerationIterator implements Iterator<Object>{
		Enumeration<?> enumeration;
		public EnumerationIterator(Enumeration<?> enumeration) {
			this.enumeration = enumeration;
		}
		@Override
		public boolean hasNext() {
			return enumeration.hasMoreElements();
		}
		@Override
		public Object next() {
			return enumeration.nextElement();
		}
		/*Enumeration는 원래 없던 기능이라 구현이 불가능하다. 
		 * 이처럼 어댑터는 반드시 일대일 대응되리란 보장이 없다. */
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}
	static class IteratorEnumeration implements Enumeration<Object>{
		Iterator<?> iterator;
		public IteratorEnumeration(Iterator<?> iterator) {
			this.iterator = iterator;
		}
		@Override
		public boolean hasMoreElements() {
			return iterator.hasNext();
		}
		@Override
		public Object nextElement() {
			return iterator.next();
		}
	}
}

퍼사드 패턴

기존 복잡한 시스템을 단순한 인터페이스로 변경하는 패턴

쓰기 쉬운 인터페이스를 제공하는 퍼사드 클래스를 구현하는 핵심

 

감싸고 있는 클래스 수가 퍼사드, 어댑터를 구분짓는 요소가 아니다.

어댑터가 여러 클래스를 적응시킬 수도 있고, 하나의 복잡한 인터페이스를 하나의 퍼사드로 처리할 수도 있다.

package designpattern.headfirst.chapter7;

public class FacadeTest {
	static class PopcornPopper {
		String description;

		public PopcornPopper(String description) {
			this.description = description;
		}
		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void pop() {
			System.out.println(description + " popping popcorn!");
		}
		public String toString() {
			return description;
		}
	}

	static class Projector {
		String description;
		StreamingPlayer player;

		public Projector(String description, StreamingPlayer player) {
			this.description = description;
			this.player = player;
		}
		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void wideScreenMode() {
			System.out.println(description + " in widescreen mode (16x9 aspect ratio)");
		}
		public void tvMode() {
			System.out.println(description + " in tv mode (4x3 aspect ratio)");
		}
		public String toString() {
			return description;
		}
	}

	static class StreamingPlayer {
		String description;
		int currentChapter;
		Amplifier amplifier;
		String movie;

		public StreamingPlayer(String description, Amplifier amplifier) {
			this.description = description;
			this.amplifier = amplifier;
		}

		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void play(String movie) {
			this.movie = movie;
			currentChapter = 0;
			System.out.println(description + " playing \"" + movie + "\"");
		}
		public void play(int chapter) {
			if (movie == null) {
				System.out.println(description + " can't play chapter " + chapter + " no movie selected");
			} else {
				currentChapter = chapter;
				System.out.println(description + " playing chapter " + currentChapter + " of \"" + movie + "\"");
			}
		}
		public void stop() {
			currentChapter = 0;
			System.out.println(description + " stopped \"" + movie + "\"");
		}
		public void pause() {
			System.out.println(description + " paused \"" + movie + "\"");
		}
		public void setTwoChannelAudio() {
			System.out.println(description + " set two channel audio");
		}
		public void setSurroundAudio() {
			System.out.println(description + " set surround audio");
		}
		public String toString() {
			return description;
		}
	}

	static class CdPlayer {
		String description;
		int currentTrack;
		Amplifier amplifier;
		String title;

		public CdPlayer(String description, Amplifier amplifier) {
			this.description = description;
			this.amplifier = amplifier;
		}
		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void eject() {
			title = null;
			System.out.println(description + " eject");
		}
		public void play(String title) {
			this.title = title;
			currentTrack = 0;
			System.out.println(description + " playing \"" + title + "\"");
		}
		public void play(int track) {
			if (title == null) {
				System.out.println(description + " can't play track " + currentTrack + ", no cd inserted");
			} else {
				currentTrack = track;
				System.out.println(description + " playing track " + currentTrack);
			}
		}
		public void stop() {
			currentTrack = 0;
			System.out.println(description + " stopped");
		}
		public void pause() {
			System.out.println(description + " paused \"" + title + "\"");
		}
		public String toString() {
			return description;
		}
	}

	static class TheaterLights {
		String description;

		public TheaterLights(String description) {
			this.description = description;
		}
		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void dim(int level) {
			System.out.println(description + " dimming to " + level + "%");
		}
		public String toString() {
			return description;
		}
	}

	static class Tuner {
		String description;
		Amplifier amplifier;
		double frequency;

		public Tuner(String description, Amplifier amplifier) {
			this.description = description;
		}
		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void setFrequency(double frequency) {
			System.out.println(description + " setting frequency to " + frequency);
			this.frequency = frequency;
		}
		public void setAm() {
			System.out.println(description + " setting AM mode");
		}
		public void setFm() {
			System.out.println(description + " setting FM mode");
		}
		public String toString() {
			return description;
		}
	}

	static class Screen {
		String description;

		public Screen(String description) {
			this.description = description;
		}
		public void up() {
			System.out.println(description + " going up");
		}
		public void down() {
			System.out.println(description + " going down");
		}
		public String toString() {
			return description;
		}
	}

	static class Amplifier {
		String description;
		Tuner tuner;
		StreamingPlayer player;

		public Amplifier(String description) {
			this.description = description;
		}
		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void setStereoSound() {
			System.out.println(description + " stereo mode on");
		}
		public void setSurroundSound() {
			System.out.println(description + " surround sound on (5 speakers, 1 subwoofer)");
		}
		public void setVolume(int level) {
			System.out.println(description + " setting volume to " + level);
		}
		public void setTuner(Tuner tuner) {
			System.out.println(description + " setting tuner to " + tuner);
			this.tuner = tuner;
		}
		public void setStreamingPlayer(StreamingPlayer player) {
			System.out.println(description + " setting Streaming player to " + player);
			this.player = player;
		}
		public String toString() {
			return description;
		}
	}
	//퍼사드
	static class HomeTheaterFacade {
		Amplifier amp;
		Tuner tuner;
		StreamingPlayer player;
		CdPlayer cd;
		Projector projector;
		TheaterLights lights;
		Screen screen;
		PopcornPopper popper;

		public HomeTheaterFacade(Amplifier amp, Tuner tuner, StreamingPlayer player, Projector projector, Screen screen,
				TheaterLights lights, PopcornPopper popper) {
			this.amp = amp;
			this.tuner = tuner;
			this.player = player;
			this.projector = projector;
			this.screen = screen;
			this.lights = lights;
			this.popper = popper;
		}

		public void watchMovie(String movie) {
			System.out.println("Get ready to watch a movie...");
			popper.on();
			popper.pop();
			lights.dim(10);
			screen.down();
			projector.on();
			projector.wideScreenMode();
			amp.on();
			amp.setStreamingPlayer(player);
			amp.setSurroundSound();
			amp.setVolume(5);
			player.on();
			player.play(movie);
		}

		public void endMovie() {
			System.out.println("Shutting movie theater down...");
			popper.off();
			lights.on();
			screen.up();
			projector.off();
			amp.off();
			player.stop();
			player.off();
		}

		public void listenToRadio(double frequency) {
			System.out.println("Tuning in the airwaves...");
			tuner.on();
			tuner.setFrequency(frequency);
			amp.on();
			amp.setVolume(5);
			amp.setTuner(tuner);
		}

		public void endRadio() {
			System.out.println("Shutting down the tuner...");
			tuner.off();
			amp.off();
		}
	}
	public static void main(String[] args) {
		Amplifier amp = new Amplifier("Amplifier");
		Tuner tuner = new Tuner("AM/FM Tuner", amp);
		StreamingPlayer player = new StreamingPlayer("Streaming Player", amp);
		CdPlayer cd = new CdPlayer("CD Player", amp);
		Projector projector = new Projector("Projector", player);
		TheaterLights lights = new TheaterLights("Theater Ceiling Lights");
		Screen screen = new Screen("Theater Screen");
		PopcornPopper popper = new PopcornPopper("Popcorn Popper");
 
		HomeTheaterFacade homeTheater = 
				new HomeTheaterFacade(amp, tuner, player, 
						projector, screen, lights, popper);
 
		homeTheater.watchMovie("Raiders of the Lost Ark");
		System.out.println("=========================");
		homeTheater.endMovie();
	}
}

퍼사드 패턴의 정의

서브시스템에 있는 일련의 인터페이스를 통합 인터페이스로 묶어준다.

 

최소 지식 원칙(==디메테르 원칙)

객체 사이의 상호 작용은 될 수 있으면 긴밀한 사이에서만 허용

어떤 객체든 그 객체와 상호작용하는 클래스의 수와 상호작용 방식에 주의를 기울여야 한다.

시스템의 한 부분을 변경했을 때 다른 부분까지 줄줄이 고쳐야하는 상황이라면, 최소 지식 원칙이 잘 지켜지지 않고 있는 것

public class 최소지식 {
	static class A1{
		A2 a2 = new A2();
		void callA1() {System.out.println("A1 야호!");}
		void callA2() {a2.callA2();}
		void callA3() {a2.a3.callA3();}
	}
	static class A2{
		A3 a3 = new A3();
		void callA2() {System.out.println("A2 야호!");}
	}
	static class A3{
		void callA3() {System.out.println("A3 야호!");}
	}
	public static void main(String[] args) {
		A1 a1 = new A1();
		a1.callA1();
		a1.callA2();
		a1.a2.callA2();
		a1.callA3();
		a1.a2.a3.callA3();
	}
	
}

핵심정리

인터페이스가 맞지 않으면 어댑터를 쓰면 된다.

큰 인터페이스와 여러 인터페이스를 단순하게 바꾸거나 통합해야 하면 퍼사드를 쓰면 된다.

 

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

메소드 호출을 캡슐화

커맨드 패턴은 작업을 요청하는 쪽과 작업을 처리하는 쪽을 분리할 수 있다.

 

커맨드 객체는 특정 객체에 관한 특정 작업 요청을 캡슐화한다.

커맨드 패턴 소개

구성요소 클라이언트, 인보커, 커맨드, 리시버

클라이언트는 커맨드 객체를 생성한다.

커맨드 객체 안에는 리시버가 있다.

리시버는 실제로 동작을 수행하는 역할을 한다.

커맨드 객체는 인보커에 저장된다. 

인보커는 클라이언트 요청으로 저장된 커맨드 객체를 실행한다.

커맨드 객체에 동작은 리시버가 한다.

 

public class CommandTest {
	//리시버
	static class Light {
		public Light() {	}
		public void on() {
			System.out.println("Light is on");
		}
		public void off() {
			System.out.println("Light is off");
		}
	}
	static  class GarageDoor {
		public GarageDoor() {
		}

		public void up() {
			System.out.println("Garage Door is Open");
		}

		public void down() {
			System.out.println("Garage Door is Closed");
		}

		public void stop() {
			System.out.println("Garage Door is Stopped");
		}

		public void lightOn() {
			System.out.println("Garage light is on");
		}

		public void lightOff() {
			System.out.println("Garage light is off");
		}
	}
	
	//커맨드
	@FunctionalInterface
	static interface Command{
		void execute();
	}
	static class LightOnCommand implements Command{
		Light light;
		
		public LightOnCommand(Light light) {
			this.light = light;
		}
		public void execute() {
			light.on();
		}
	}
	static class GarageDoorOpenCommand implements Command{
		GarageDoor garageDoor;
		public GarageDoorOpenCommand(GarageDoor garageDoor) {
			this.garageDoor = garageDoor;
		}
		@Override
		public void execute() {
			garageDoor.up();
		}
	}
	//인보커
	static class SimpleRemoteControl{
		Command slot;
		public SimpleRemoteControl() {	}
		
		public void setCommand(Command slot) {
			this.slot = slot;
		}
		public void buttonWasPressed() {
			slot.execute();
		}
	}
	
	//클라이언트(편의상 main이 이 역할), 커맨드 객체 생성, 인보커에게 커맨드 객체 전달
	public static void main(String[] args) {
		//인보커, 내부에 커맨드 객체를 저장하고, 다시 요청이 오면 커맨드 객체를 실행
		SimpleRemoteControl remoteControl = new SimpleRemoteControl();
		//리시버, 실제 동작을 수행하는 객체
		Light light = new Light();
		//커맨드, 외부와  내부에 리시버에게 동작을 위임한다.
		LightOnCommand lightOnCommand = new LightOnCommand(light);
		//인보커에 커맨드 저장
		remoteControl.setCommand(lightOnCommand);
		//클라이언트가 저장한 커맨드 객체 실행 요청, 이 메서드는 단순히 리시버에 동작을 위임한다.
		remoteControl.buttonWasPressed();
		
		GarageDoor garageDoor = new GarageDoor();
		GarageDoorOpenCommand garageDoorOpenCommand = new GarageDoorOpenCommand(garageDoor);
		
		remoteControl.setCommand(garageDoorOpenCommand);
		remoteControl.buttonWasPressed();
	}

}
Light is on
Garage Door is Open

커맨드 패턴의 정의

요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화할 수 있다.

요청을 따로 큐 같은 자료구조에 저장해 실행 전에 작업 취소 같은 기능 구현도 가능하다.

import java.util.*;

public class 심플커맨드 {
	static interface Command{
		void execute();
	}
	static class Invoker{
		//큐 예시
		private final Queue<Command> commands = new LinkedList<>();
		public void addCommand(Command command) {
			this.commands.add(command);
		}
		public void action() {
			if(!commands.isEmpty())
				commands.poll().execute();
			else System.out.println("더 이상의 요청은 없어요!");
		}
	}
	public static void main(String[] args) {
		Invoker invoker = new Invoker();
		invoker.addCommand(()->System.out.println("커맨드 객체는 요청을 캡슐화합니다."));
		invoker.addCommand(()->System.out.println("커맨드 객체는 요청을 캡슐화합니다."));
		
		invoker.action();
		invoker.action();
		invoker.action();
	}
}
커맨드 객체는 요청을 캡슐화합니다.
커맨드 객체는 요청을 캡슐화합니다.
더 이상의 요청은 없어요!

 

켜고 끄는 슬롯이 존재하는 커맨드 패턴 구현

public class CommandTest2 {
    // 리시버
    static class Light {
        String location = "";
        public Light(String location) {
            this.location = location;
        }
        public void on() {
            System.out.println(location + " light is on");
        }
        public void off() {
            System.out.println(location + " light is off");
        }
    }


    static class TV {
        String location;
        int channel;
        public TV(String location) {
            this.location = location;
        }
        public void on() {
            System.out.println("TV is on");
        }
        public void off() {
            System.out.println("TV is off");
        }
        public void setInputChannel() {
            this.channel = 3;
            System.out.println("Channel is set for VCR");
        }
    }
    static class Stereo {
        String location;
        public Stereo(String location) {
            this.location = location;
        }
        public void on() {
            System.out.println(location + " stereo is on");
        }
        public void off() {
            System.out.println(location + " stereo is off");
        }
        public void setCD() {
            System.out.println(location + " stereo is set for CD input");
        }
        public void setDVD() {
            System.out.println(location + " stereo is set for DVD input");
        }
        public void setRadio() {
            System.out.println(location + " stereo is set for Radio");
        }
        public void setVolume(int volume) {
            // code to set the volume
            // valid range: 1-11 (after all 11 is better than 10, right?)
            System.out.println(location + " stereo volume set to " + volume);
        }
    }
    static class Hottub {
        boolean on;
        int temperature;
        public Hottub() {
        }
        public void on() {
            on = true;
        }
        public void off() {
            on = false;
        }
        public void bubblesOn() {
            if (on) {
                System.out.println("Hottub is bubbling!");
            }
        }
        public void bubblesOff() {
            if (on) {
                System.out.println("Hottub is not bubbling");
            }
        }
        public void jetsOn() {
            if (on) {
                System.out.println("Hottub jets are on");
            }
        }
        public void jetsOff() {
            if (on) {
                System.out.println("Hottub jets are off");
            }
        }
        public void setTemperature(int temperature) {
            this.temperature = temperature;
        }
        public void heat() {
            temperature = 105;
            System.out.println("Hottub is heating to a steaming 105 degrees");
        }
        public void cool() {
            temperature = 98;
            System.out.println("Hottub is cooling to 98 degrees");
        }
    }
    static class CeilingFan {
        String location = "";
        int level;
        public static final int HIGH = 2;
        public static final int MEDIUM = 1;
        public static final int LOW = 0;
        public CeilingFan(String location) {
            this.location = location;
        }
        public void high() {
            // turns the ceiling fan on to high
            level = HIGH;
            System.out.println(location + " ceiling fan is on high");
        }
        public void medium() {
            // turns the ceiling fan on to medium
            level = MEDIUM;
            System.out.println(location + " ceiling fan is on medium");
        }
        public void low() {
            // turns the ceiling fan on to low
            level = LOW;
            System.out.println(location + " ceiling fan is on low");
        }
        public void off() {
            // turns the ceiling fan off
            level = 0;
            System.out.println(location + " ceiling fan is off");
        }
        public int getSpeed() {
            return level;
        }
    }
    static class GarageDoor {
        String location;
        public GarageDoor(String location) {
            this.location = location;
        }
        public void up() {
            System.out.println(location + " garage Door is Up");
        }
        public void down() {
            System.out.println(location + " garage Door is Down");
        }
        public void stop() {
            System.out.println(location + " garage Door is Stopped");
        }
        public void lightOn() {
            System.out.println(location + " garage light is on");
        }
        public void lightOff() {
            System.out.println(location + " garage light is off");
        }
    }


    // 커맨드
    @FunctionalInterface
    static interface Command {
        void execute();
    }
    static class NoCommand implements Command {
        public void execute() {
        }
    }
    static class LightOnCommand implements Command {
        Light light;
        public LightOnCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.on();
        }
    }
    static class LightOffCommand implements Command {
        Light light;
        public LightOffCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.off();
        }
    }
    static class GarageDoorUpCommand implements Command {
        GarageDoor garageDoor;
        public GarageDoorUpCommand(GarageDoor garageDoor) {
            this.garageDoor = garageDoor;
        }
        public void execute() {
            garageDoor.up();
        }
    }
    static class GarageDoorDownCommand implements Command {
        GarageDoor garageDoor;
        public GarageDoorDownCommand(GarageDoor garageDoor) {
            this.garageDoor = garageDoor;
        }
        public void execute() {
            garageDoor.up();
        }
    }
    static class StereoOnWithCDCommand implements Command {
        Stereo stereo;
        public StereoOnWithCDCommand(Stereo stereo) {
            this.stereo = stereo;
        }
        public void execute() {
            stereo.on();
            stereo.setCD();
            stereo.setVolume(11);
        }
    }
    static class StereoOffCommand implements Command {
        Stereo stereo;
        public StereoOffCommand(Stereo stereo) {
            this.stereo = stereo;
        }
        public void execute() {
            stereo.off();
        }
    }
    static class CeilingFanOnCommand implements Command {
        CeilingFan ceilingFan;
        public CeilingFanOnCommand(CeilingFan ceilingFan) {
            this.ceilingFan = ceilingFan;
        }
        public void execute() {
            ceilingFan.high();
        }
    }
    static class CeilingFanOffCommand implements Command {
        CeilingFan ceilingFan;
        public CeilingFanOffCommand(CeilingFan ceilingFan) {
            this.ceilingFan = ceilingFan;
        }
        public void execute() {
            ceilingFan.off();
        }
    }
    static class LivingroomLightOnCommand implements Command {
        Light light;
        public LivingroomLightOnCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.on();
        }
    }
    static class LivingroomLightOffCommand implements Command {
        Light light;
        public LivingroomLightOffCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.off();
        }
    }
    static class HottubOnCommand implements Command {
        Hottub hottub;
        public HottubOnCommand(Hottub hottub) {
            this.hottub = hottub;
        }
        public void execute() {
            hottub.on();
            hottub.heat();
            hottub.bubblesOn();
        }
    }
    static class HottubOffCommand implements Command {
        Hottub hottub;
        public HottubOffCommand(Hottub hottub) {
            this.hottub = hottub;
        }
        public void execute() {
            hottub.cool();
            hottub.off();
        }
    }
    // 인보커
    static class RemoteControl {
        Command[] onCommands;
        Command[] offCommands;
        public RemoteControl() {
            onCommands = new Command[7];
            offCommands = new Command[7];
            Command noCommand = new NoCommand();
            for (int i = 0; i < offCommands.length; i++) {
                onCommands[i] = noCommand;
                offCommands[i] = noCommand;
            }
        }
        public void setCommand(int slot, Command onCommand, Command offCommand) {
            onCommands[slot] = onCommand;
            offCommands[slot] = offCommand;
        }
        public void onButtonWasPushed(int slot) {
            onCommands[slot].execute();
        }
        public void offButtonWasPushed(int slot) {
            offCommands[slot].execute();
        }
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("\n------ 리모컨 ------\n");
            for (int i = 0; i < offCommands.length; i++) {
                sb.append("[slot" + i + "]" + onCommands[i].getClass().getSimpleName());
                sb.append("    " + offCommands[i].getClass().getSimpleName()+"\n");
            }
            return sb.toString();
        }
    }
    // 클라이언트, 커맨드 객체 생성, 인보커에게 커맨드 객체 전달
    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl();
         
        Light livingRoomLight = new Light("Living Room");
        Light kitchenLight = new Light("Kitchen");
        CeilingFan ceilingFan= new CeilingFan("Living Room");
        GarageDoor garageDoor = new GarageDoor("Garage");
        Stereo stereo = new Stereo("Living Room");
       
        LightOnCommand livingRoomLightOn =
                new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff =
                new LightOffCommand(livingRoomLight);
        LightOnCommand kitchenLightOn =
                new LightOnCommand(kitchenLight);
        LightOffCommand kitchenLightOff =
                new LightOffCommand(kitchenLight);


        CeilingFanOnCommand ceilingFanOn =
                new CeilingFanOnCommand(ceilingFan);
        CeilingFanOffCommand ceilingFanOff =
                new CeilingFanOffCommand(ceilingFan);


        GarageDoorUpCommand garageDoorUp =
                new GarageDoorUpCommand(garageDoor);
        GarageDoorDownCommand garageDoorDown =
                new GarageDoorDownCommand(garageDoor);


        StereoOnWithCDCommand stereoOnWithCD =
                new StereoOnWithCDCommand(stereo);
        StereoOffCommand  stereoOff =
                new StereoOffCommand(stereo);


        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
        remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
        remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff);
        remoteControl.setCommand(3, stereoOnWithCD, stereoOff);


        System.out.println(remoteControl);


        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        remoteControl.onButtonWasPushed(1);
        remoteControl.offButtonWasPushed(1);
        remoteControl.onButtonWasPushed(2);
        remoteControl.offButtonWasPushed(2);
        remoteControl.onButtonWasPushed(3);
        remoteControl.offButtonWasPushed(3);
    }
}
------ 리모컨 ------
[slot0]LightOnCommand LightOffCommand
[slot1]LightOnCommand LightOffCommand
[slot2]CeilingFanOnCommand CeilingFanOffCommand
[slot3]StereoOnWithCDCommand StereoOffCommand
[slot4]NoCommand NoCommand
[slot5]NoCommand NoCommand
[slot6]NoCommand NoCommand
Living Room light is on
Living Room light is off
Kitchen light is on
Kitchen light is off
Living Room ceiling fan is on high
Living Room ceiling fan is off
Living Room stereo is on
Living Room stereo is set for CD input
Living Room stereo volume set to 11
Living Room stereo is off

즐겨찾기 처럼 슬롯에 저장해둔 커맨드 객체는 언제든지 호출할 수 있다.

4~6 슬롯에 NoCommand 객체는 아무런 일도 안한다. 일종의 Null 객체로 이렇게 구현해두면 호출하는 쪽에서 Null 걱정을 을 할필요가 없다.

 

실행 했던 커맨드 되돌리기 

public class CommandTest3 {
    // 리시버
    static class Light {
        String location = "";
        public Light(String location) {
            this.location = location;
        }
        public void on() {
            System.out.println(location + " light is on");
        }
        public void off() {
            System.out.println(location + " light is off");
        }
    }
    static class TV {
        String location;
        int channel;
        public TV(String location) {
            this.location = location;
        }
        public void on() {
            System.out.println("TV is on");
        }
        public void off() {
            System.out.println("TV is off");
        }
        public void setInputChannel() {
            this.channel = 3;
            System.out.println("Channel is set for VCR");
        }
    }
    static class Stereo {
        String location;
        public Stereo(String location) {
            this.location = location;
        }
        public void on() {
            System.out.println(location + " stereo is on");
        }
        public void off() {
            System.out.println(location + " stereo is off");
        }
        public void setCD() {
            System.out.println(location + " stereo is set for CD input");
        }
        public void setDVD() {
            System.out.println(location + " stereo is set for DVD input");
        }
        public void setRadio() {
            System.out.println(location + " stereo is set for Radio");
        }
        public void setVolume(int volume) {
            // code to set the volume
            // valid range: 1-11 (after all 11 is better than 10, right?)
            System.out.println(location + " stereo volume set to " + volume);
        }
    }
    static class Hottub {
        boolean on;
        int temperature;
        public Hottub() {
        }
        public void on() {
            on = true;
        }
        public void off() {
            on = false;
        }
        public void bubblesOn() {
            if (on) {
                System.out.println("Hottub is bubbling!");
            }
        }
        public void bubblesOff() {
            if (on) {
                System.out.println("Hottub is not bubbling");
            }
        }
        public void jetsOn() {
            if (on) {
                System.out.println("Hottub jets are on");
            }
        }
        public void jetsOff() {
            if (on) {
                System.out.println("Hottub jets are off");
            }
        }
        public void setTemperature(int temperature) {
            this.temperature = temperature;
        }
        public void heat() {
            temperature = 105;
            System.out.println("Hottub is heating to a steaming 105 degrees");
        }
        public void cool() {
            temperature = 98;
            System.out.println("Hottub is cooling to 98 degrees");
        }
    }
    static class CeilingFan {
        public static final int HIGH = 3;
        public static final int MEDIUM = 2;
        public static final int LOW = 1;
        public static final int OFF = 0;
        String location;
        int speed;
        public CeilingFan(String location) {
            this.location = location;
            speed = OFF;
        }
        public void high() {
            speed = HIGH;
            System.out.println(location + " ceiling fan is on high");
        }
        public void medium() {
            speed = MEDIUM;
            System.out.println(location + " ceiling fan is on medium");
        }
        public void low() {
            speed = LOW;
            System.out.println(location + " ceiling fan is on low");
        }
        public void off() {
            speed = OFF;
            System.out.println(location + " ceiling fan is off");
        }
        public int getSpeed() {
            return speed;
        }
    }
    static class GarageDoor {
        String location;
        public GarageDoor(String location) {
            this.location = location;
        }
        public void up() {
            System.out.println(location + " garage Door is Up");
        }
        public void down() {
            System.out.println(location + " garage Door is Down");
        }
        public void stop() {
            System.out.println(location + " garage Door is Stopped");
        }
        public void lightOn() {
            System.out.println(location + " garage light is on");
        }
        public void lightOff() {
            System.out.println(location + " garage light is off");
        }
    }
    // 커맨드
    static interface Command {
        void execute();
        void undo();
    }
    static class NoCommand implements Command {
        public void execute() {
        }
        public void undo() {
        }
    }
    static class LightOnCommand implements Command {
        Light light;
        public LightOnCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.on();
        }
        public void undo() {
            light.off();
        }
    }
    static class LightOffCommand implements Command {
        Light light;
        public LightOffCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.off();
        }
        @Override
        public void undo() {
            light.on();
        }
    }
    static class GarageDoorUpCommand implements Command {
        GarageDoor garageDoor;
        public GarageDoorUpCommand(GarageDoor garageDoor) {
            this.garageDoor = garageDoor;
        }
        public void execute() {
            garageDoor.up();
        }
        @Override
        public void undo() {
            garageDoor.down();
        }
    }
    static class GarageDoorDownCommand implements Command {
        GarageDoor garageDoor;
        public GarageDoorDownCommand(GarageDoor garageDoor) {
            this.garageDoor = garageDoor;
        }
        public void execute() {
            garageDoor.down();
        }
        public void undo() {
            garageDoor.up();
        }
    }
    static class StereoOnWithCDCommand implements Command {
        Stereo stereo;
        public StereoOnWithCDCommand(Stereo stereo) {
            this.stereo = stereo;
        }
        public void execute() {
            stereo.on();
            stereo.setCD();
            stereo.setVolume(11);
        }
        @Override
        public void undo() {
            stereo.off();
        }
    }
    static class StereoOffCommand implements Command {
        Stereo stereo;
        int prev;
        public StereoOffCommand(Stereo stereo) {
            this.stereo = stereo;
        }
        public void execute() {
            stereo.off();
        }
        @Override
        public void undo() {
            stereo.on();
            stereo.setCD();
            stereo.setVolume(11);
        }
    }
    static class CeilingFanHighCommand implements Command {
        CeilingFan ceilingFan;
        int prevSpeed;
        public CeilingFanHighCommand(CeilingFan ceilingFan) {
            this.ceilingFan = ceilingFan;
        }
        public void execute() {
            prevSpeed = ceilingFan.getSpeed();
            ceilingFan.high();
        }
        public void undo() {
            if (prevSpeed == CeilingFan.HIGH) {
                ceilingFan.high();
            } else if (prevSpeed == CeilingFan.MEDIUM) {
                ceilingFan.medium();
            } else if (prevSpeed == CeilingFan.LOW) {
                ceilingFan.low();
            } else if (prevSpeed == CeilingFan.OFF) {
                ceilingFan.off();
            }
        }
    }
    static class CeilingFanMediumCommand implements Command {
        CeilingFan ceilingFan;
        int prevSpeed;
        public CeilingFanMediumCommand(CeilingFan ceilingFan) {
            this.ceilingFan = ceilingFan;
        }
        public void execute() {
            prevSpeed = ceilingFan.getSpeed();
            ceilingFan.medium();
        }
        public void undo() {
            if (prevSpeed == CeilingFan.HIGH) {
                ceilingFan.high();
            } else if (prevSpeed == CeilingFan.MEDIUM) {
                ceilingFan.medium();
            } else if (prevSpeed == CeilingFan.LOW) {
                ceilingFan.low();
            } else if (prevSpeed == CeilingFan.OFF) {
                ceilingFan.off();
            }
        }
    }
    static class CeilingFanLowCommand implements Command {
        CeilingFan ceilingFan;
        int prevSpeed;
        public CeilingFanLowCommand(CeilingFan ceilingFan) {
            this.ceilingFan = ceilingFan;
        }
        public void execute() {
            prevSpeed = ceilingFan.getSpeed();
            ceilingFan.low();
        }
        public void undo() {
            if (prevSpeed == CeilingFan.HIGH) {
                ceilingFan.high();
            } else if (prevSpeed == CeilingFan.MEDIUM) {
                ceilingFan.medium();
            } else if (prevSpeed == CeilingFan.LOW) {
                ceilingFan.low();
            } else if (prevSpeed == CeilingFan.OFF) {
                ceilingFan.off();
            }
        }
    }
    static class CeilingFanOffCommand implements Command {
        CeilingFan ceilingFan;
        int prevSpeed;
        public CeilingFanOffCommand(CeilingFan ceilingFan) {
            this.ceilingFan = ceilingFan;
        }
        public void execute() {
            prevSpeed = ceilingFan.getSpeed();
            ceilingFan.off();
        }
        public void undo() {
            if (prevSpeed == CeilingFan.HIGH) {
                ceilingFan.high();
            } else if (prevSpeed == CeilingFan.MEDIUM) {
                ceilingFan.medium();
            } else if (prevSpeed == CeilingFan.LOW) {
                ceilingFan.low();
            } else if (prevSpeed == CeilingFan.OFF) {
                ceilingFan.off();
            }
        }
    }
    static class LivingroomLightOnCommand implements Command {
        Light light;
        public LivingroomLightOnCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.on();
        }
        public void undo() {
            light.off();
        }
    }
    static class LivingroomLightOffCommand implements Command {
        Light light;
        public LivingroomLightOffCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.off();
        }
        public void undo() {
            light.on();
        }
    }
    static class HottubOnCommand implements Command {
        Hottub hottub;
        public HottubOnCommand(Hottub hottub) {
            this.hottub = hottub;
        }
        public void execute() {
            hottub.on();
            hottub.heat();
            hottub.bubblesOn();
        }
        public void undo() {
            hottub.cool();
            hottub.off();
        }
    }
    static class HottubOffCommand implements Command {
        Hottub hottub;
        public HottubOffCommand(Hottub hottub) {
            this.hottub = hottub;
        }
        public void execute() {
            hottub.cool();
            hottub.off();
        }
        @Override
        public void undo() {
            hottub.on();
            hottub.heat();
            hottub.bubblesOn();
        }
    }
   
    // 인보커
    static class RemoteControlWithUndo {
        Command[] onCommands;
        Command[] offCommands;
        Command undoCommand;
        public RemoteControlWithUndo() {
            onCommands = new Command[7];
            offCommands = new Command[7];
            Command noCommand = new NoCommand();
            for (int i = 0; i < offCommands.length; i++) {
                onCommands[i] = noCommand;
                offCommands[i] = noCommand;
            }
            undoCommand = noCommand;
        }
        public void setCommand(int slot, Command onCommand, Command offCommand) {
            onCommands[slot] = onCommand;
            offCommands[slot] = offCommand;
        }
        public void onButtonWasPushed(int slot) {
            onCommands[slot].execute();
            undoCommand = onCommands[slot];
        }
        public void offButtonWasPushed(int slot) {
            offCommands[slot].execute();
            undoCommand = offCommands[slot];
        }
        public void undoButtonWasPushed() {
            undoCommand.undo();
        }
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("\n------ 리모컨 ------\n");
            for (int i = 0; i < offCommands.length; i++) {
                sb.append("[slot" + i + "]" + onCommands[i].getClass().getSimpleName());
                sb.append("    " + offCommands[i].getClass().getSimpleName() + "\n");
            }
            sb.append("[undo] " + undoCommand.getClass().getSimpleName() + "\n");
            return sb.toString();
        }
    }
    // 클라이언트, 커맨드 객체 생성, 인보커에게 커맨드 객체 전달
    public static void main(String[] args) {
        RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
        Light livingRoomLight = new Light("Living Room");
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        System.out.println(remoteControl);
        remoteControl.undoButtonWasPushed();
        remoteControl.offButtonWasPushed(0);
        remoteControl.onButtonWasPushed(0);
        System.out.println(remoteControl);
        remoteControl.undoButtonWasPushed();
        CeilingFan ceilingFan = new CeilingFan("Living Room");
        CeilingFanMediumCommand ceilingFanMedium = new CeilingFanMediumCommand(ceilingFan);
        CeilingFanHighCommand ceilingFanHigh = new CeilingFanHighCommand(ceilingFan);
        CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);
        remoteControl.setCommand(0, ceilingFanMedium, ceilingFanOff);
        remoteControl.setCommand(1, ceilingFanHigh, ceilingFanOff);
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        System.out.println(remoteControl);
        remoteControl.undoButtonWasPushed();
        remoteControl.onButtonWasPushed(1);
        System.out.println(remoteControl);
        remoteControl.undoButtonWasPushed();
    }
}
Living Room light is on
Living Room light is off
------ 리모컨 ------
[slot0]LightOnCommand LightOffCommand
[slot1]NoCommand NoCommand
[slot2]NoCommand NoCommand
[slot3]NoCommand NoCommand
[slot4]NoCommand NoCommand
[slot5]NoCommand NoCommand
[slot6]NoCommand NoCommand
[undo] LightOffCommand
Living Room light is on
Living Room light is off
Living Room light is on
------ 리모컨 ------
[slot0]LightOnCommand LightOffCommand
[slot1]NoCommand NoCommand
[slot2]NoCommand NoCommand
[slot3]NoCommand NoCommand
[slot4]NoCommand NoCommand
[slot5]NoCommand NoCommand
[slot6]NoCommand NoCommand
[undo] LightOnCommand
Living Room light is off
Living Room ceiling fan is on medium
Living Room ceiling fan is off
------ 리모컨 ------
[slot0]CeilingFanMediumCommand CeilingFanOffCommand
[slot1]CeilingFanHighCommand CeilingFanOffCommand
[slot2]NoCommand NoCommand
[slot3]NoCommand NoCommand
[slot4]NoCommand NoCommand
[slot5]NoCommand NoCommand
[slot6]NoCommand NoCommand
[undo] CeilingFanOffCommand
Living Room ceiling fan is on medium
Living Room ceiling fan is on high
------ 리모컨 ------
[slot0]CeilingFanMediumCommand CeilingFanOffCommand
[slot1]CeilingFanHighCommand CeilingFanOffCommand
[slot2]NoCommand NoCommand
[slot3]NoCommand NoCommand
[slot4]NoCommand NoCommand
[slot5]NoCommand NoCommand
[slot6]NoCommand NoCommand
[undo] CeilingFanHighCommand
Living Room ceiling fan is on medium

여러개 커맨드 한번에 실행

    static class MacroCommand implements Command{
        Command[] commands;
       
        public MacroCommand(Command[] commands) {
            this.commands = commands;
        }
        public void execute() {
            for(Command command : commands) {
                command.execute();
            }
        }
        public void undo() {
            for(Command command : commands) {
                command.undo();
            }
        }
    }




    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl();
        Light light = new Light("Living Room");
        TV tv = new TV("Living Room");
        Stereo stereo = new Stereo("Living Room");
        Hottub hottub = new Hottub();


        LightOnCommand lightOn = new LightOnCommand(light);
        StereoOnCommand stereoOn = new StereoOnCommand(stereo);
        TVOnCommand tvOn = new TVOnCommand(tv);
        HottubOnCommand hottubOn = new HottubOnCommand(hottub);
        LightOffCommand lightOff = new LightOffCommand(light);
        StereoOffCommand stereoOff = new StereoOffCommand(stereo);
        TVOffCommand tvOff = new TVOffCommand(tv);
        HottubOffCommand hottubOff = new HottubOffCommand(hottub);
        Command[] partyOn = { lightOn, stereoOn, tvOn, hottubOn};
        Command[] partyOff = { lightOff, stereoOff, tvOff, hottubOff};


        MacroCommand partyOnMacro = new MacroCommand(partyOn);
        MacroCommand partyOffMacro = new MacroCommand(partyOff);


        remoteControl.setCommand(0, partyOnMacro, partyOffMacro);


        System.out.println(remoteControl);
        System.out.println("--- Pushing Macro On---");
        remoteControl.onButtonWasPushed(0);
        System.out.println("--- Pushing Macro Off---");
        remoteControl.offButtonWasPushed(0);
    }
------ 리모컨 ------
[slot0]MacroCommand MacroCommand
[slot1]NoCommand NoCommand
[slot2]NoCommand NoCommand
[slot3]NoCommand NoCommand
[slot4]NoCommand NoCommand
[slot5]NoCommand NoCommand
[slot6]NoCommand NoCommand
[undo] NoCommand
--- Pushing Macro On---
Living Room light is on
Living Room stereo is on
TV is on
Channel is set for VCR
Hottub is heating to a steaming 105 degrees
Hottub is bubbling!
--- Pushing Macro Off---
Living Room light is off
Living Room stereo is off
TV is off
Hottub is cooling to 98 degrees

커맨드 객체 배열을 한 번더 커맨드 객체로 캡슐화하여한 번에 요청으로 일련의 행동을 수행 할 수 있다.

 

핵심요약

커맨드 패턴을 사용하면 요청하는 객체와 요청을 수행하는 객체를 분리할 수 있다.

핵심은 커맨드 객체이며, 커맨드 객체는 실제 행동을 수행하는 리시버를 캡슐화한다.

 

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

싱글턴 패턴은 특정 클래스에 객체 인스턴스가 하나만 만들어지도록 해 주는 패턴

 

어플리케이션에서 하나만 있어도 잘 돌아가는 객체들이 있다. 스레드 풀, 캐시, 설정, 로그 등 객체들

이런 객체들은 오히려 2개 이상이면 오동작할 수 있다.

 

고전적인 싱글턴 패턴 구현법

이 방식을 절대로 쓰면 안된다. 

public class ChocolateBoilerTest {
	//고전적인 싱글턴, 문제를 멀티스레딩 시 발생
	static class ChocolateBoiler{
		private boolean empty;
		private boolean boiled;
		//static
		private static ChocolateBoiler boiler;
		//생성자 private 이 클래스에서만 접근가능
		private ChocolateBoiler() {
			empty = true;
			boiled = false;
		}
		//static 
		public static ChocolateBoiler getInstance() {
			//이렇게 객체를 필요할 때 생성하는 방법을 지연로딩이라 한다.
			if(boiler == null) {
				boiler = new ChocolateBoiler();
			}
			return boiler;
		}
		public void fill() {
			if(isEmpty()) {
				empty = false;
				boiled = false;
			}
		}
		public void drain() {
			if(!isEmpty()&& isBoiled()) {
				empty = true;
			}
		}
		public void boil() {
			if(!isEmpty()&& !isBoiled()) {
				boiled = true;
			}
		}
		public boolean isEmpty() {
			return empty;
		}
		public boolean isBoiled() {
			return boiled;
		}
	}
}

싱글턴 패턴의 정의

싱글턴 패턴은 클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공한다.

 

인스턴스는 그 클래스에서 관리하면 된다. 

인스턴스는 어디서는 접근할 수 있도록 전역 접근 지점을 제공한다.

지연 로딩 구현 방식은 비용이 큰 객체에 적용하면 유용하다.

 

멀티쓰레드 문제

객체가 여러 개 생길 수있다.

 

멀티쓰레딩 문제 해결하기

synchronized 키워드

		//가장 간단한 방법, 성능 저하 약 100배, 이유는 매번 호출 마다 동기화하기 때문
		public static synchronized ChocolateBoiler getInstance() {
			if(boiler == null) {
				boiler = new ChocolateBoiler();
			}
			return boiler;
		}

synchronized  키워드를 추가하면 한 스레드가 메서드 사용을 끝내기 전까지 다른 쓰레드는 기다려야 한다.

 

이 방법에 문제는 객체 생성 처음에만 유효할 뿐 이후 호출에는 전혀 쓸모없는 오버헤드를 유발한다.

 효율적으로 멀티스레딩 문제 해결하기

현재 이 방법이 부하가 크지 않다면, 그대로 사용해도 무방하다.

 

처음부터 초기화

	static class ChocolateBoiler{
		private boolean empty;
		private boolean boiled;
		//시작시 바로JVM이 정적 초기화
		//단점으로 지연로딩을 원한다면 불가능하다.
		private static ChocolateBoiler boiler = new ChocolateBoiler();

DCL(Double-Checked Locking) 사용

처음에만 동기화하고 이후 동기화 안하게 된다.

	static class ChocolateBoiler{
		private boolean empty;
		private boolean boiled;
		//volatile, 객체 생성 시 CPU 캐시되는 것을 방지하기 위함
		private static volatile ChocolateBoiler boiler;
		
		private ChocolateBoiler() {
			empty = true;
			boiled = false;
		}
		
		//DCL 이중확인 락 방법, 맨 처음에만 동기화하고 이 후 동기화 안함.
		public static ChocolateBoiler getInstance() {
			if(boiler == null) {
				synchronized (ChocolateBoiler.class) {
					if(boiler == null) {
						boiler = new ChocolateBoiler();
					}
				}
			}
			return boiler;
		}

이 방법은 JDK1.4 이전 버전에선 정상동작을 보장못한다. volatile 키워드를 써도 동기화가 제대로 안 된다.

 

궁극의 해결법 enum

enum을 사용하면, 동기화 문제, 클래스 로딩 문제, 리플렉션, (역)직렬화 문제 등 모든 문제가 해결된다.

public class ChocolateBoilerTest5 {
	static enum ChocolateBoiler{
		BOILER;
		private boolean empty;
		private boolean boiled;
		
		private ChocolateBoiler() {
			empty = true;
			boiled = false;
		}
		
		public void fill() {
			if(isEmpty()) {
				empty = false;
				boiled = false;
			}
		}
		public void drain() {
			if(!isEmpty()&& isBoiled()) {
				empty = true;
			}
		}
		public void boil() {
			if(!isEmpty()&& !isBoiled()) {
				boiled = true;
			}
		}
		public boolean isEmpty() {
			return empty;
		}
		public boolean isBoiled() {
			return boiled;
		}
	}
}

핵심 정리

  • 싱글턴 패턴을 적용하면, 그 클래스 객체는 한 개만 존재한다.
  • 객체는 어디서든 접근하도록 해야한다.
  • 생성자는 private
  • 클래스 로더가 여러 개 있으면 여러 개 인스턴스가 생길 수 있어 주의해야 한다
  • enum 지원하기 시작한 jdk 1.5 이후 부턴 싱글턴 생성 시 enum을 쓰면 된다.

source.zip
0.00MB

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

객체 인스턴스를 만드는 작업이 항상 공개되어야 하는 것은 아니며, 오히려 모든 것을 공개했다가는 결합 문제가 생길 있다는 사실을 배운다

'new'연산자를 사용한다면 '구상' 떠올려라.

객체 생성 시점에는 구상 클래스에 의존할 밖에 없다.

객체 생성은 추상적인(미완성인) 클래스는 인스턴스화를 없기 때문이다.

 

팩토리메서드

하나의 타입으로 다룰 있다지만, 어떤 구상 클래스로 인스턴스화 할지는 제각각이다.

 

코드를 보면, 런타임 시점에 값을 받아 그에 맞는 구상 클래스 객체를 생성한다.

이런 코드는 변경하거나 확장할 코드를 다시 확인하고 새로운 코드를 추가하거나 기존 코드를 제거해야 한다.

, 코드가 변경에 취약하고, 찾은 변경은 버그의 확률을 높힌다.

 

new 자체는 자바에 필수 연산자다. 당연히 안쓸 없다.

사실 진짜 문제는 "변화"

변화하는 무엇가 때문에 new 조심해서 사용해야 한다.

 

인터페이스에 맞춰서 코딩하는 것은 변화에 다형성으로 대응하기 위함이다. new 연산자는 다형성 대상이 못된다. 무조건 구상 클래스에 의존할 수 밖에 없다. 변경에 닫힌 코드가 된다.

 

팩토리메서드

위 처럼 자주 변하는 부분을 캡슐화해 변하는 부분을 특정했다.

 

객체 생성 부분 캡슐화하기

객체를 생성하는 코드 부분만 뽑아내 객체 생성만을 담당하는 별도 클래스를 만든다.

 

 

객체 생성을 처리하는 클래스를 팩토리라고 부른다

이제 새로운 객체를 직접 만들지 않고 팩토리 메서드를 호출해 처리한다.

이상 어떤 구상 클래스를 인스턴스화 할지 고민하지 않아도 된다.

 

import java.util.ArrayList;
import java.util.List;

public class SimpleFactoryTest {
	static abstract class Pizza{
		String name;
		String dough;
		String sauce;
		List<String> toppings = new ArrayList<>();
		
		public String getName() {
			return name;
		}
		
		public void prepare() {	System.out.println("준비중 ... " + name);	}
		public void bake() {System.out.println("굽는 중 ... "+ name);	}
		public void cut() {	System.out.println("짜르는 중 ... " + name);	}
		public void box() {	System.out.println("포장 중 ... "+ name);	}
		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append("---- " +name+" ----\n")
				.append(dough+"\n")
				.append(sauce+"\n");
			toppings.forEach(topping -> sb.append(topping+"\n"));
			return sb.toString();
		}
	}
	
	static class PepperoniPizza extends Pizza {
		public PepperoniPizza() {
			name = "페퍼로니 피자";
			dough = "크러스트";
			sauce = "마리나라 소스";
			toppings.add("슬라이스 페퍼로니");
			toppings.add("슬라이스 양파");
			toppings.add("모짜렐라 치즈");
		}
	}
	static class ClamPizza extends Pizza {
		public ClamPizza() {
			name = "조개 피자";
			dough = "씬 크러스트";
			sauce = "마늘 소스";
			toppings.add("조개");
			toppings.add("모짜렐라 치즈");
		}
	}
	static class VeggiePizza extends Pizza {
		public VeggiePizza() {
			name = "야채 피자";
			dough = "크러스트";
			sauce = "마리나라 소스";
			toppings.add("슈레드 모짜랄라");
			toppings.add("파마산");
			toppings.add("다신 양파");
			toppings.add("슬라이스 버섯");
			toppings.add("슬라이스 피망");
			toppings.add("슬라이스 블랙 올리브");
		}
	}
	static class CheesePizza extends Pizza {
		public CheesePizza() {
			name = "피즈 피자";
			dough = "크러스트";
			sauce = "마리나라 피자 소스";
			toppings.add("생 모짜렐라");
			toppings.add("파마산");
		}
	}
	
	static class SimplePizzaFactory {
		public Pizza createPizza(String type) {
			Pizza pizza = null;
			if (type.equals("cheese")) {
				pizza = new CheesePizza();
			} else if (type.equals("pepperoni")) {
				pizza = new PepperoniPizza();
			} else if (type.equals("clam")) {
				pizza = new ClamPizza();
			} else if (type.equals("veggie")) {
				pizza = new VeggiePizza();
			}
			return pizza;
		}
	}
	
	static class PizzaStore{
		SimplePizzaFactory pizzaFactory;

		public PizzaStore(SimplePizzaFactory pizzaFactory) {
			this.pizzaFactory = pizzaFactory;
		}
		
		public Pizza orderPizza(String type) {
			Pizza pizza;
			pizza = pizzaFactory.createPizza(type);
			
			pizza.prepare();
			pizza.bake();
			pizza.cut();
			pizza.box();
			
			return pizza;
		}
		
	}
	public static void main(String[] args) {
		SimplePizzaFactory factory = new SimplePizzaFactory();
		PizzaStore store = new PizzaStore(factory);

		Pizza pizza = store.orderPizza("cheese");
		System.out.println("주문하신 피자 " + pizza.getName() + "\n");
		System.out.println(pizza);
 
		pizza = store.orderPizza("veggie");
		System.out.println("주문하신 피자 " + pizza.getName() + "\n");
		System.out.println(pizza);
	}
	
}
준비중 ... 피즈 피자
굽는 중 ... 피즈 피자
짜르는 중 ... 피즈 피자
포장 중 ... 피즈 피자
주문하신 피자 피즈 피자

---- 피즈 피자 ----
크러스트
마리나라 피자 소스
생 모짜렐라
파마산

준비중 ... 야채 피자
굽는 중 ... 야채 피자
짜르는 중 ... 야채 피자
포장 중 ... 야채 피자
주문하신 피자 야채 피자

---- 야채 피자 ----
크러스트
마리나라 소스
슈레드 모짜랄라
파마산
다신 양파
슬라이스 버섯
슬라이스 피망
슬라이스 블랙 올리브

심플 팩토리를 정적 메소드로 정의할 수도 있다. 정적 팩토리라 불르고, 팩토리 인스턴스 생성 없이 바로 객체를 생성할 수 있다는 장점이 있지만, 서브 클래스를 만들어서 객체 생성 메소드의 행동을 변경할 수 없다는 단점이 있다.

 

'간단한 팩토리' 정의

심플 팩토리는 디자인 패턴이라기 보다는 프로그래밍에서 자주 쓰이는 관용구에 가깝다.

하지만, 자주 쓰이는 기법이다.

패턴이 아니라고 해서 중요하지 않은 것은 아니다.

 

 

다양한 팩토리 만들기

import java.util.ArrayList;
import java.util.List;

public class FactoryMethodTest {
	static abstract class Pizza{
		String name;
		String dough;
		String sauce;
		List<String> toppings = new ArrayList<>();
		
		public String getName() {	return name;	}
		
		public void prepare() {
			System.out.println("준비중 ... " + name);
			System.out.println("도우를 돌리는 중 ...");
			System.out.println("소스를 뿌리는 중 ...");
			System.out.println("토핑을 올리는 중 ...");

			toppings.forEach(topping->System.out.println(" "+topping));

		}
		public void bake() {System.out.println("175도에서 25분 간 굽기 ... ");	}
		public void cut() {	System.out.println("피자를 사선으로 짜르기 ... ");}
		public void box() {	System.out.println("피자 상자에 담기 ... ");	}
		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append("---- " +name+" ----\n")
				.append(dough+"\n")
				.append(sauce+"\n");
			toppings.forEach(topping -> sb.append(topping+"\n"));
			return sb.toString();
		}
	}
	
	//추상 팩터리
	abstract static class PizzaStore{
		public Pizza orderPizza(String type) {
			Pizza pizza;
			pizza = createPizza(type);
			
			pizza.prepare();
			pizza.bake();
			pizza.cut();
			pizza.box();
			
			return pizza;
		}
		//객체 생성을 서브클래스로 위임
		protected abstract Pizza createPizza(String type);
	}
	
	static class NYPizzaStore extends PizzaStore{
		@Override
		protected Pizza createPizza(String type) {
			Pizza pizza = null;
			if (type.equals("cheese")) {
				pizza = new NYStyleCheesePizza();
			} else if (type.equals("pepperoni")) {
				pizza = new NYStylePepperoniPizza();
			} else if (type.equals("clam")) {
				pizza = new NYStyleClamPizza();
			} else if (type.equals("veggie")) {
				pizza = new NYStyleVeggiePizza();
			}
			return pizza;
		}
		
		static class NYStylePepperoniPizza extends Pizza {
			public NYStylePepperoniPizza() {
				name = "NYStyle 페퍼로니 피자";
				dough = "크러스트";
				sauce = "마리나라 소스";
				toppings.add("슬라이스 페퍼로니");
				toppings.add("슬라이스 양파");
				toppings.add("모짜렐라 치즈");
			}
		}
		static class NYStyleClamPizza extends Pizza {
			public NYStyleClamPizza() {
				name = "NYStyle 조개 피자";
				dough = "씬 크러스트";
				sauce = "마늘 소스";
				toppings.add("조개");
				toppings.add("모짜렐라 치즈");
			}
		}
		static class NYStyleVeggiePizza extends Pizza {
			public NYStyleVeggiePizza() {
				name = "NYStyle 야채 피자";
				dough = "크러스트";
				sauce = "마리나라 소스";
				toppings.add("슈레드 모짜랄라");
				toppings.add("파마산");
				toppings.add("다신 양파");
				toppings.add("슬라이스 버섯");
				toppings.add("슬라이스 피망");
				toppings.add("슬라이스 블랙 올리브");
			}
		}
		static class NYStyleCheesePizza extends Pizza {
			public NYStyleCheesePizza() {
				name = "NYStyle 피즈 피자";
				dough = "크러스트";
				sauce = "마리나라 피자 소스";
				toppings.add("생 모짜렐라");
				toppings.add("파마산");
			}
		}
	}
	
	static class ChicagoPizzaStore extends PizzaStore{
		@Override
		protected Pizza createPizza(String type) {
			Pizza pizza = null;
			if (type.equals("cheese")) {
				pizza = new ChicagoStyleCheesePizza();
			} else if (type.equals("pepperoni")) {
				pizza = new ChicagoStylePepperoniPizza();
			} else if (type.equals("clam")) {
				pizza = new ChicagoStyleClamPizza();
			} else if (type.equals("veggie")) {
				pizza = new ChicagoStyleVeggiePizza();
			}
			return pizza;
		}
		
		static class ChicagoStylePepperoniPizza extends Pizza {
			public ChicagoStylePepperoniPizza() {
				name = "ChicagoStyle 페퍼로니 피자";
				dough = "크러스트";
				sauce = "마리나라 소스";
				toppings.add("슬라이스 페퍼로니");
				toppings.add("슬라이스 양파");
				toppings.add("모짜렐라 치즈");
			}
			@Override
			public void cut() {
				System.out.println("네모난 모양으로 피자 자르기");
			}
		}
		static class ChicagoStyleClamPizza extends Pizza {
			public ChicagoStyleClamPizza() {
				name = "ChicagoStyle 조개 피자";
				dough = "씬 크러스트";
				sauce = "마늘 소스";
				toppings.add("조개");
				toppings.add("모짜렐라 치즈");
			}
		}
		static class ChicagoStyleVeggiePizza extends Pizza {
			public ChicagoStyleVeggiePizza() {
				name = "ChicagoStyle 야채 피자";
				dough = "크러스트";
				sauce = "마리나라 소스";
				toppings.add("슈레드 모짜랄라");
				toppings.add("파마산");
				toppings.add("다신 양파");
				toppings.add("슬라이스 버섯");
				toppings.add("슬라이스 피망");
				toppings.add("슬라이스 블랙 올리브");
			}
		}
		static class ChicagoStyleCheesePizza extends Pizza {
			public ChicagoStyleCheesePizza() {
				name = "ChicagoStyle 피즈 피자";
				dough = "크러스트";
				sauce = "마리나라 피자 소스";
				toppings.add("생 모짜렐라");
				toppings.add("파마산");
			}
		}
	}
	
	static class CaliforniaPizzaStore extends PizzaStore{
		@Override
		protected Pizza createPizza(String type) {
			Pizza pizza = null;
			if (type.equals("cheese")) {
				pizza = new CaliforniaStyleCheesePizza();
			} else if (type.equals("pepperoni")) {
				pizza = new CaliforniaStylePepperoniPizza();
			} else if (type.equals("clam")) {
				pizza = new CaliforniaStyleClamPizza();
			} else if (type.equals("veggie")) {
				pizza = new CaliforniaStyleVeggiePizza();
			}
			return pizza;
		}
		
		static class CaliforniaStylePepperoniPizza extends Pizza {
			public CaliforniaStylePepperoniPizza() {
				name = "CaliforniaStyle 페퍼로니 피자";
				dough = "크러스트";
				sauce = "마리나라 소스";
				toppings.add("슬라이스 페퍼로니");
				toppings.add("슬라이스 양파");
				toppings.add("모짜렐라 치즈");
			}
		}
		static class CaliforniaStyleClamPizza extends Pizza {
			public CaliforniaStyleClamPizza() {
				name = "CaliforniaStyle 조개 피자";
				dough = "씬 크러스트";
				sauce = "마늘 소스";
				toppings.add("조개");
				toppings.add("모짜렐라 치즈");
			}
		}
		static class CaliforniaStyleVeggiePizza extends Pizza {
			public CaliforniaStyleVeggiePizza() {
				name = "CaliforniaStyle 야채 피자";
				dough = "크러스트";
				sauce = "마리나라 소스";
				toppings.add("슈레드 모짜랄라");
				toppings.add("파마산");
				toppings.add("다신 양파");
				toppings.add("슬라이스 버섯");
				toppings.add("슬라이스 피망");
				toppings.add("슬라이스 블랙 올리브");
			}
		}
		static class CaliforniaStyleCheesePizza extends Pizza {
			public CaliforniaStyleCheesePizza() {
				name = "CaliforniaStyle 피즈 피자";
				dough = "크러스트";
				sauce = "마리나라 피자 소스";
				toppings.add("생 모짜렐라");
				toppings.add("파마산");
			}
		}
	}
	
	public static void main(String[] args) {
		//추상화 타입
		PizzaStore nyStore = new NYPizzaStore();
		PizzaStore chicagoPizzaStore = new ChicagoPizzaStore();
		System.out.println(orderPizza(nyStore,"cheese"));
		System.out.println(orderPizza(chicagoPizzaStore,"cheese"));
	}
	
	//추상화된 타입으로 다루기
	static Pizza orderPizza(PizzaStore pizzaStore, String type) {
		return pizzaStore.orderPizza(type);
	}
}
준비중 ... NYStyle 피즈 피자
도우를 돌리는 중 ...
소스를 뿌리는 중 ...
토핑을 올리는 중 ...
 생 모짜렐라
 파마산
175도에서 25분 간 굽기 ... 
피자를 사선으로 짜르기 ... 
피자 상자에 담기 ... 
---- NYStyle 피즈 피자 ----
크러스트
마리나라 피자 소스
생 모짜렐라
파마산

준비중 ... ChicagoStyle 피즈 피자
도우를 돌리는 중 ...
소스를 뿌리는 중 ...
토핑을 올리는 중 ...
 생 모짜렐라
 파마산
175도에서 25분 간 굽기 ... 
피자를 사선으로 짜르기 ... 
피자 상자에 담기 ... 
---- ChicagoStyle 피즈 피자 ----
크러스트
마리나라 피자 소스
생 모짜렐라
파마산

 

팩토리를 추상화하여 추상화 타입으로 만들고 객체 생성 부분을 서브클래스로 위임했다.

팩토리 메서드 패턴의 정의

객체를 생성할 때 필요한 인터페이스를 만든다. 어떤 클래스의 인스턴를 만들지는 서브클래스에서 결정한다.

 

모든 팩토리 패턴은 객체 생성을 캡슐화한다

팩토리 메소드 패턴은 서브클래스에서 어떤 클래스를 만들지 결정함으로써 객체 생성을 캡슐화한다.

 

의존성 뒤집기 원칙

DIP(Dependency Inversion Principle)

디자인 원칙
추상화된 것에 의존하게 만들고 구상 클래스에 의전하지 않게 만든다.

 

의존성 뒤집기 원칙에서는 추상화를 더 강조

고수준 구성 요소가 저수준 구성 요소에 의존하면 안 되며, 항상 추상화에 의존하게 만들어야 한다.

의존성 뒤집기

 

의존성 뒤집기 원칙을 지키는 방법

  • 변수에 구성 클래스의레퍼런스를 저장하지 말기
    new 연산자로 구상클래스에 의존하지 말고, 팩토리를 사용
  • 구상 클래스에서 유도된 클래스를 만들지 말기
    Interface나 abstract class 처럼 추상화된 것으로부터 클래스를 만들어야 한다
  • 베이스 클래스에 이미 구현된 메서드 오버라이드하지 말기
    재정의 시 완전한 추상화가 안된다. 정말 공통적인 것만 베이스 클래스에 있어야한다.

실전에선 위 원칙을 완벽히 준수하기는 힘들다. 하지만, 알고 요령을 피우는 것과 모르고 지키기 않는 것은 차이가 있다.

 

 

 

추상 팩토리 패턴

public class AbstractFactory {
	public static void main(String[] args) {
		PizzaStore nyStore = new NYPizzaStore();
		PizzaStore chicagoStore = new ChicagoPizzaStore();
 
		Pizza pizza = nyStore.orderPizza(PizzaStore.PizzaType.CHEESE);
		System.out.println("Ethan 주문한  " + pizza + "\n");
 
		pizza = chicagoStore.orderPizza(PizzaStore.PizzaType.CHEESE);
		System.out.println("Joel 주문한  " + pizza + "\n");

		pizza = nyStore.orderPizza(PizzaStore.PizzaType.CLAM);
		System.out.println("Ethan 주문한  " + pizza + "\n");
 
		pizza = chicagoStore.orderPizza(PizzaStore.PizzaType.CLAM);
		System.out.println("Joel 주문한  " + pizza + "\n");

		pizza = nyStore.orderPizza(PizzaStore.PizzaType.PEPPERONI);
		System.out.println("Ethan 주문한  " + pizza + "\n");
 
		pizza = chicagoStore.orderPizza(PizzaStore.PizzaType.PEPPERONI);
		System.out.println("Joel 주문한  " + pizza + "\n");

		pizza = nyStore.orderPizza(PizzaStore.PizzaType.VEGGIE);
		System.out.println("Ethan 주문한  " + pizza + "\n");
 
		pizza = chicagoStore.orderPizza(PizzaStore.PizzaType.VEGGIE);
		System.out.println("Joel 주문한  " + pizza + "\n");
	}
	
	/*야채*/
	static interface Veggies{
		String toString();
	}
	static class BlackOlives implements Veggies {
		public String toString() {
			return "검정 올리브";
		}
	}
	static class Eggplant implements Veggies {
		public String toString() {
			return "가지";
		}
	}
	static class Garlic implements Veggies {
		public String toString() {
			return "마늘";
		}
	}
	static class Mushroom implements Veggies {
		public String toString() {
			return "버섯";
		}
	}
	static class Onion implements Veggies {
		public String toString() {
			return "양파";
		}
	}
	static class RedPepper implements Veggies {
		public String toString() {
			return "빨간 피망";
		}
	}
	static class Spinach implements Veggies {
		public String toString() {
			return "Spinach";
		}
	}
	/*치즈*/
	static interface Cheese {
		public String toString();
	}
	static class MozzarellaCheese implements Cheese {
		public String toString() {
			return "슈레드 모짜렐라 치즈";
		}
	}
	static class ParmesanCheese implements Cheese {
		public String toString() {
			return "슈레드 파마산 치즈";
		}
	}
	static class ReggianoCheese implements Cheese {
		public String toString() {
			return "레지아노 치즈";
		}
	}
	/*조개*/
	static interface Clam {
		public String toString();
	}
	static class FreshClams implements Clam {
		public String toString() {
			return "생물 조개";
		}
	}
	static class FrozenClams implements Clam {
		public String toString() {
			return "냉동 조개";
		}
	}
	/*도우*/
	static interface Dough {
		public String toString();
	}
	static class ThickCrustDough implements Dough {
		public String toString() {
			return "두툼하고 빠삭한 스타일 도우";
		}
	}
	static class ThinCrustDough implements Dough {
		public String toString() {
			return "얇고 빠삭한 도우";
		}
	}
	/*소스*/
	static interface Sauce {
		public String toString();
	}
	static class MarinaraSauce implements Sauce {
		public String toString() {
			return "마리나라 소스";
		}
	}
	static class PlumTomatoSauce implements Sauce {
		public String toString() {
			return "이태리 토마토 소스";
		}
	}
	/*페퍼로니*/
	static interface Pepperoni {
		public String toString();
	}
	static class SlicedPepperoni implements Pepperoni {
		public String toString() {
			return "얇게 썰은 페퍼로니";
		}
	}

	/* 핵심  추상 팩토리 정의*/
	static interface PizzaIngredientFactory {
		public Dough createDough();
		public Sauce createSauce();
		public Cheese createCheese();
		public Veggies[] createVeggies();
		public Pepperoni createPepperoni();
		public Clam createClam();
	}
	//시카고는 내륙이라 냉동 조개를 씀
	static class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
		public Dough createDough() {
			return new ThickCrustDough();
		}
		public Sauce createSauce() {
			return new PlumTomatoSauce();
		}
		public Cheese createCheese() {
			return new MozzarellaCheese();
		}
		public Veggies[] createVeggies() {
			return new Veggies[] { new BlackOlives(), new Spinach(), new Eggplant() };
		}
		public Pepperoni createPepperoni() {
			return new SlicedPepperoni();
		}
		public Clam createClam() {
			return new FrozenClams();
		}
	}
	//뉴욕은 연안이라 생물 조개 사용
	static class NYPizzaIngredientFactory implements PizzaIngredientFactory {
		public Dough createDough() {
			return new ThinCrustDough();
		}
		public Sauce createSauce() {
			return new MarinaraSauce();
		}
		public Cheese createCheese() {
			return new ReggianoCheese();
		}
		public Veggies[] createVeggies() {
			return new Veggies[] { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
		}
		public Pepperoni createPepperoni() {
			return new SlicedPepperoni();
		}
		public Clam createClam() {
			return new FreshClams();
		}
	}
	
	/*피자 군*/
	abstract static class Pizza{
		//팩토리를 사용하여 거의 동일하나 다른 스타일의 피자를 생산할 수 있다.
		PizzaIngredientFactory ingredientFactory;
		String name;
		
		Dough dough;
		Sauce sauce;
		Veggies[] veggies;
		Cheese cheese;
		Pepperoni pepperoni;
		Clam clam;
		
		abstract void prepare();
		
		void bake() {	System.out.println("25분 간 굽습니다.");	}
		void cut() {System.out.println("대각선으로 피자를 썰어줍니다.");}
		void box() {System.out.println("피자박스에 피자를 옯겨 포장합니다.");}
		
		void setName(String name) {
			this.name = name;
		}
		String getName() {
			return name;
		}
		public String toString() {
			StringBuilder result = new StringBuilder();
			result.append("---- " + name + " ----\n");
			if (dough != null) {
				result.append(dough);
				result.append("\n");
			}
			if (sauce != null) {
				result.append(sauce);
				result.append("\n");
			}
			if (cheese != null) {
				result.append(cheese);
				result.append("\n");
			}
			if (veggies != null) {
				for(Veggies veggie : veggies) {
					result.append(veggie+", ");
				}
				result.delete(result.length()-2, result.length());
				result.append("\n");
			}
			if (clam != null) {
				result.append(clam);
				result.append("\n");
			}
			if (pepperoni != null) {
				result.append(pepperoni);
				result.append("\n");
			}
			return result.toString();
		}
	}
	
	static class CheesePizza extends Pizza {
	 
		public CheesePizza(PizzaIngredientFactory ingredientFactory) {
			super.ingredientFactory = ingredientFactory;
		}
	 
		void prepare() {
			System.out.println("준비중..." + name);
			dough = ingredientFactory.createDough();
			sauce = ingredientFactory.createSauce();
			cheese = ingredientFactory.createCheese();
		}
	}
	static class ClamPizza extends Pizza {
		PizzaIngredientFactory ingredientFactory;
	 
		public ClamPizza(PizzaIngredientFactory ingredientFactory) {
			this.ingredientFactory = ingredientFactory;
		}
	 
		void prepare() {
			System.out.println("준비중..." + name);
			dough = ingredientFactory.createDough();
			sauce = ingredientFactory.createSauce();
			cheese = ingredientFactory.createCheese();
			clam = ingredientFactory.createClam();
		}
	}
	static class PepperoniPizza extends Pizza {
		PizzaIngredientFactory ingredientFactory;
	 
		public PepperoniPizza(PizzaIngredientFactory ingredientFactory) {
			this.ingredientFactory = ingredientFactory;
		}
	 
		void prepare() {
			System.out.println("준비중..." + name);
			dough = ingredientFactory.createDough();
			sauce = ingredientFactory.createSauce();
			cheese = ingredientFactory.createCheese();
			veggies = ingredientFactory.createVeggies();
			pepperoni = ingredientFactory.createPepperoni();
		}
	}
	static class VeggiePizza extends Pizza {
	 
		public VeggiePizza(PizzaIngredientFactory ingredientFactory) {
			super.ingredientFactory = ingredientFactory;
		}
	 
		void prepare() {
			System.out.println("준비중..." + name);
			dough = ingredientFactory.createDough();
			sauce = ingredientFactory.createSauce();
			cheese = ingredientFactory.createCheese();
			veggies = ingredientFactory.createVeggies();
		}
	}

	
	
	
	public abstract static class PizzaStore {
		//타입 안정성을 위해 인자를 enum으로 받기
		public enum PizzaType{
			CHEESE, VEGGIE, CLAM, PEPPERONI
		}
		//팩토리 메서드 패턴, 객체 생성을 서브타입에 맡긴다.
		protected abstract Pizza createPizza(PizzaType item);
		
		public Pizza orderPizza(PizzaType type) {
			Pizza pizza = createPizza(type);
			System.out.println("--- Making a " + pizza.getName() + " ---");
			pizza.prepare();
			pizza.bake();
			pizza.cut();
			pizza.box();
			return pizza;
		}
	}
	static class ChicagoPizzaStore extends PizzaStore {
		protected Pizza createPizza(PizzaType item) {
			Pizza pizza = null;
			PizzaIngredientFactory ingredientFactory =
			new ChicagoPizzaIngredientFactory();//팩터리
			
			switch (item) {
			case CHEESE:
				pizza = new CheesePizza(ingredientFactory);
				pizza.setName("시카고 스타일 치즈 피자");
				break;
			case VEGGIE:
				pizza = new VeggiePizza(ingredientFactory);
				pizza.setName("시카고 스타일 야채 피자");
				break;
			case CLAM:
				pizza = new ClamPizza(ingredientFactory);
				pizza.setName("시카고 스타일 조개 피자");
				break;
			case PEPPERONI:
				pizza = new PepperoniPizza(ingredientFactory);
				pizza.setName("시카고 스타일 페퍼로니 피자");
				break;
			default:
				throw new IllegalArgumentException();
			}
			return pizza;
		}

	}
	static class NYPizzaStore extends PizzaStore {
		 
		protected Pizza createPizza(PizzaType item) {
			Pizza pizza = null;
			PizzaIngredientFactory ingredientFactory =
			new ChicagoPizzaIngredientFactory();//팩터리
			
			switch (item) {
			case CHEESE:
				pizza = new CheesePizza(ingredientFactory);
				pizza.setName("뉴욕 스타일 치즈 피자");
				break;
			case VEGGIE:
				pizza = new VeggiePizza(ingredientFactory);
				pizza.setName("뉴욕 스타일 야채 피자");
				break;
			case CLAM:
				pizza = new ClamPizza(ingredientFactory);
				pizza.setName("뉴욕 스타일 조개 피자");
				break;
			case PEPPERONI:
				pizza = new PepperoniPizza(ingredientFactory);
				pizza.setName("뉴욕 스타일 페퍼로니 피자");
				break;
			default:
				throw new IllegalArgumentException();
			}
			return pizza;
		}
	}
}
--- Making a 뉴욕 스타일 치즈 피자 ---
준비중...뉴욕 스타일 치즈 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Ethan 주문한  ---- 뉴욕 스타일 치즈 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈


--- Making a 시카고 스타일 치즈 피자 ---
준비중...시카고 스타일 치즈 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Joel 주문한  ---- 시카고 스타일 치즈 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈


--- Making a 뉴욕 스타일 조개 피자 ---
준비중...뉴욕 스타일 조개 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Ethan 주문한  ---- 뉴욕 스타일 조개 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈
냉동 조개


--- Making a 시카고 스타일 조개 피자 ---
준비중...시카고 스타일 조개 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Joel 주문한  ---- 시카고 스타일 조개 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈
냉동 조개


--- Making a 뉴욕 스타일 페퍼로니 피자 ---
준비중...뉴욕 스타일 페퍼로니 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Ethan 주문한  ---- 뉴욕 스타일 페퍼로니 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈
검정 올리브, Spinach, 가지
얇게 썰은 페퍼로니


--- Making a 시카고 스타일 페퍼로니 피자 ---
준비중...시카고 스타일 페퍼로니 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Joel 주문한  ---- 시카고 스타일 페퍼로니 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈
검정 올리브, Spinach, 가지
얇게 썰은 페퍼로니


--- Making a 뉴욕 스타일 야채 피자 ---
준비중...뉴욕 스타일 야채 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Ethan 주문한  ---- 뉴욕 스타일 야채 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈
검정 올리브, Spinach, 가지


--- Making a 시카고 스타일 야채 피자 ---
준비중...시카고 스타일 야채 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Joel 주문한  ---- 시카고 스타일 야채 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈
검정 올리브, Spinach, 가지

추상 팩토리 패턴은 구상 클래스에 의존하지 않고 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생상하는 인터페이스를 제공한다. 구상 클래스는 서브 클래스에서 만든다.

 

 

 

추상 팩토리 패턴과 팩토리 메소드 패턴 비교

팩토리 메소드는 상속으로 객체를 만든다. 

추상 팩토리는 객체(팩토리) 구성으로 객체를 만든다.

 

팩토리 메소드는 클라이언트와 구상 형식을 분리하는 역할이 목적

추상 팩토리는 일련 연관된 클래스(추상 또는 인터페이스) 군을 하나로 묶은 형식을 제공한다. 다만, 클래스 추가 시 모든 구상 팩토리가 영향을 받는다.

 

핵심 정리

팩토리를 쓰면 객체 생성을 캡슐화할 수 있다.

심플 팩토리는 패턴이 아니라 기법이다.

팩토리 메소드 패턴은 상속으로 객체 생성을 서브 클래스에게 위임한다.

추상 팩토리 패턴은 객체 구성을 활용해 팩토리 인터페이스에 선언한 메소드에서 객체 군을 생성한다.

모든 팩토리는 구상 클래스 의존성을 줄여 느슨한 결합을 도와준다.

 

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

기존코드를 바꾸지 않고 객체에 새로운 임무를 추가할 수 있다.

 

상속으로 기능을 추가하기 VS 구성(데코레이터)으로 기능 추가하기

상속으로 서브 클래스에 기능을 구현하려면, 모든 서브 클래스가 오버라이드 해야 한다. 따라서 기존 코드를 수정할 수 밖에 없다.

클래스가 많아지면 일부 서브 클래스에는 적합하지 않은 불필요한 기능까지 상속받는다.

상속으로 기능 추가는 컴파일 시점에 완전히 결정된다. 

구성으로 기능을 추가하면, 기존 코드를 건들지 않고, 기능을 추가할 수 있다. 

기존 코드를 건드리지 않으므로, 버그 발생 범위가 제한된다. 

구성은 런타임 시점에 기능을 동적으로 변경할 수 있다.

 

자바는 단일 상속 언어로 일반적으로 구성이 옳다고 여겨진다.

 

OCP 살펴보기

디자인 원칙
클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다.

풀어 말하면, 기존 코드를 수정하지 않으면서 기능 추가가 가능해야 한다는 것

이 점에서 데코레이터 패턴은 OCP를 완전히 준수한다.

원 코드는 건들지 않은채(클라이언트 입장에선 데코레이터 존재조차 모른다) 기능을 추가할 수 있다.

코드에서 확장해야 부분을 선택할 때는 세심한 주의를 기울여야 한다.

무조건  OCP 적용한다면 괜히 쓸데없는 일을 하며 시간을 낭비할 있다. 또한 필요 이상으로 복잡하고 이해하기 힘든 코드를 만들게 되는 부작용이 발생할 있다.

 

데코레이터 패턴 살펴보기

데코레이터의 슈퍼클래스는 자신이 장식하고 있는 객체의 슈퍼클래스와 같다.

객체를 여러 개의 데코레이터로 감쌀 있다.

데코레이터는 자신이 감싸고 있는 객체와 같은 슈퍼 클래스를 가지고 있기에 원래 객체가 들어갈 자리에 데코레이터 객체를 넣어도 상관없다.

데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 말고도 추가 작업을 수행할 있다.

객체는 언제든지 감쌀 있으모로 실행 중에 필요한 데코레이터를 마음대로 적용할 있다.

 

데코레이터 패턴 예시

public class DecoratorTest {
	//Compenet
	static abstract class Beverage{
		String decription = "제목 없음";
		public String getDecription() {
			return decription;
		}
		public abstract double cost();
	}
	/**
	 * Decorator
	 * 형태만 (다형성) 맞추기 위해 상속(구현)하지만,
	 * 행동은 구성와 위임으로 런타임 시 동적으로 변경 가능하다.
	 */
	static abstract class CondimentDecorator extends Beverage{
		//다형성
		Beverage beverage;
		@Override
		public abstract String getDecription();
	}
	
	
	static class Espresso extends Beverage{
		public Espresso() {
			super.decription = "에스프레소";
		}
		@Override
		public double cost() {
			return 1.99;
		}
	}
	
	static class HouseBlend extends Beverage{
		public HouseBlend() {
			super.decription = "하우스 블렌드 커피";
		}
		@Override
		public double cost() {
			return .89;
		}
	}
	static class DarkRoast extends Beverage{
		public DarkRoast() {
			super.decription = "다크로스트 커피";
		}
		@Override
		public double cost() {
			return .99;
		}
	}
	static class Decaf extends Beverage{
		public Decaf() {
			super.decription = "디카페인 커피";
		}
		@Override
		public double cost() {
			return 1.05;
		}
	}
	
	static class Mocha extends CondimentDecorator{
		public Mocha(Beverage beverage) {
			super.beverage = beverage;
		}
		@Override
		public String getDecription() {
			return beverage.getDecription() + ", 모카";
		}
		@Override
		public double cost() {
			return beverage.cost()+ .20;
		}
	}
	static class Milk extends CondimentDecorator{
		public Milk(Beverage beverage) {
			super.beverage = beverage;
		}
		@Override
		public String getDecription() {
			return beverage.getDecription() + ", 우유";
		}
		@Override
		public double cost() {
			return beverage.cost()+.10;
		}
	}
	static class Whip extends CondimentDecorator{
		public Whip(Beverage beverage) {
			super.beverage = beverage;
		}
		@Override
		public String getDecription() {
			return beverage.getDecription() + ", 휘핑크림";
		}
		@Override
		public double cost() {
			return beverage.cost()+.10;
		}
	}
	static class Soy extends CondimentDecorator{
		public Soy(Beverage beverage) {
			super.beverage = beverage;
		}
		@Override
		public String getDecription() {
			return beverage.getDecription() + ", 두유";
		}
		@Override
		public double cost() {
			return beverage.cost()+.15;
		}
	}
	
	public static void main(String[] args) {
		Beverage beverage = new Espresso();
		System.out.println(beverage.getDecription()+" $"+beverage.cost());
		
		Beverage beverage2 = new DarkRoast();
		beverage2 = new Mocha(beverage2);
		beverage2 = new Mocha(beverage2);
		beverage2 = new Whip(beverage2);
		System.out.println(beverage2.getDecription()+" $"+beverage2.cost());
		
		Beverage beverage3 = new HouseBlend();
		beverage3 = new Soy(beverage3);
		beverage3 = new Mocha(beverage3);
		beverage3 = new Whip(beverage3);
		System.out.println(beverage3.getDecription()+" $"+beverage3.cost());
	}
}
에스프레소 $1.99
다크로스트 커피, 모카, 모카, 휘핑크림 $1.49
하우스 블렌드 커피, 두유, 모카, 휘핑크림 $1.34

java.io 클래스와 데코레이터 패턴

java.io 패키지는 데코레이터 패턴을 바탕으로 만들어졌다.

io 패키지에서 FilterInputStream, FilterOutputStream을 서브 클래스들이 데코레이터 패턴이 적용된 클래스이다.

따라서 FilterInputStream, FilterOutputStream 서브클래스들은 단속으로 사용할 수 없다.

문자열 기반 IO스트림은 FilterWriter, FilterReader 도  똑같다.

 

데코레이터의 단점은 사용 시 클래스가 많아진다. 그리고 중첩 사용 시 중첩 구조를 파악하기 힘든다.

 

자바 I/O 데코레이터 만들기

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
package designpattern.structural.decorator;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class IOStream {
	public static void main(String[] args) throws FileNotFoundException {
		
		try(InputStream in = 
				new UpperCaseInputStream(
				new BufferedInputStream(
                //자기에 맞는 프로젝트명 패키지 경로 파일 입력 필요
				new FileInputStream("src/designpattern/structural/decorator/text.txt")));)
		{
			StringBuilder sb = new StringBuilder();
			for(int c=0; (c=in.read()) != -1;) {
				sb.append((char)c);
			}
			System.out.println(sb);
			
		} catch (IOException e) {	}
	}
	
	static class UpperCaseInputStream extends FilterInputStream{

		public UpperCaseInputStream(InputStream in) {
			super(in);
		}
		@Override
		public int read() throws IOException {
			int data = in.read();
			return data == -1 ? data : Character.toUpperCase(data);
		}
	}
}

결과

LOREM IPSUM IS SIMPLY DUMMY TEXT OF THE PRINTING AND TYPESETTING INDUSTRY. LOREM IPSUM HAS BEEN THE INDUSTRY'S STANDARD DUMMY TEXT EVER SINCE THE 1500S, WHEN AN UNKNOWN PRINTER TOOK A GALLEY OF TYPE AND SCRAMBLED IT TO MAKE A TYPE SPECIMEN BOOK. IT HAS SURVIVED NOT ONLY FIVE CENTURIES, BUT ALSO THE LEAP INTO ELECTRONIC TYPESETTING, REMAINING ESSENTIALLY UNCHANGED. IT WAS POPULARISED IN THE 1960S WITH THE RELEASE OF LETRASET SHEETS CONTAINING LOREM IPSUM PASSAGES, AND MORE RECENTLY WITH DESKTOP PUBLISHING SOFTWARE LIKE ALDUS PAGEMAKER INCLUDING VERSIONS OF LOREM IPSUM.

핵심 정리

디자인 유연성 면에선 상속보단 구성(과 위임)이 더 좋다. (OCP)

기존 코드 수정 없이 행동을 확장할 수 있다.

구성은 런타임 시점에 기능을 동적으로 변경할 수 있다.

인터페이스로 설계 시 클라이언트는 데코레이터 존재를 알 수 없다. 

데코레이터 패턴을 사용하면 코드가 다소 복잡해진다.

 

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

 

옵저버 패턴(Observer Pattern)

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 갱신되는 방식으로 일대다 의존성을 정의한다.

 

신문사와 잡지 관계와 유사하다.

구독자가 신문사에 구독 신청, 신간이 나올때마다 배달 받는다.

구독자가 해지 신청을 하면 이상 받지 않는다.

신문사가 Subject(주제)

구독자가 Observer(관찰자)

 

import java.util.ArrayList;
import java.util.List;

public class ObserverEx01 {
	static interface Subject<T>{
		//옵저버 등록
		void addObserver(Observer<T> observer);
		//옵저서 삭제
		void removeObserver(Observer<T> observer);
		//상태 변화 시 알림
		void notityObservers();
		
		T getData();
	}
	
	//핵심 관찰 대상1
	static class News<T> implements Subject<T>{
		//이 주제를 관찰할 옵저버 목록
		List<Observer<T>> observers = new ArrayList<>();
		//옵저버들의 핵심 관찰 데이터
		T news;
		
		public T getNews() {
			return this.news;
		}
		public void setNews(T news) {
			this.news = news;
			notityObservers();
		}
		public void addObserver(Observer<T> observer) {
			observers.add(observer);
		}
		public void removeObserver(Observer<T> observer) {
			observers.add(observer);
		}
		public void notityObservers() {
			for(Observer<T> observer : observers)
				observer.update(this);
		}
		public T getData() {
			return news;
		}
		public String toString() {
			return "News 구독자 : "+observers.toString();
		}
	}
	//핵심 관찰 대상1
	static class Magazine<T> implements Subject<T>{
		//이 주제를 관찰할 옵저버 목록
		List<Observer<T>> observers = new ArrayList<>();
		//옵저버들의 핵심 관찰 데이터
		T magazine;
		
		public T getMagazine() {
			return this.magazine;
		}
		public void setMagazine(T magazine) {
			this.magazine = magazine;
			notityObservers();
		}
		public void addObserver(Observer<T> observer) {
			observers.add(observer);
		}
		public void removeObserver(Observer<T> observer) {
			observers.add(observer);
		}
		public void notityObservers() {
			for(Observer<T> observer : observers)
				observer.update(this);
		}
		public T getData() {
			return magazine;
		}
		public String toString() {
			return "Magazine 구독자 : "+observers.toString();
		}
	}
	
	static interface Observer<T>{
		//업데이터 받는 방식, Pull방식
		void update(Subject<T> subject);
	}
	static class Person<T> implements Observer<T>{
		String name;
		//갱신 받은 데이터 목록
		List<T> updateData = new ArrayList<>();
		
		public Person(String name) {
			super();
			this.name = name;
		}
		public void update(Subject<T> subject) {
			updateData.add(subject.getData());
		}
		@Override
		public String toString() {
			return name+" "+updateData.toString();
		}
	}
	//테스트
	public static void main(String[] args) {
		Observer<String> person1 = new Person<>("홍길동");
		Observer<String> person2 = new Person<>("임꺽정");
		Observer<String> person3 = new Person<>("김자바");
		
		//대한잡지
		Magazine<String> magazine = new Magazine<>();
		//민국뉴스
		News<String> news = new News<>();
		
		magazine.addObserver(person1);
		magazine.addObserver(person2);
		
		news.addObserver(person2);
		news.addObserver(person3);
		
		System.out.println(magazine);
		System.out.println(news);
		
		magazine.setMagazine("1월 호 잡지 출판");
		news.setNews("속보... 주저리주저리");
		
		magazine.setMagazine("2월 호 잡지 출판");
		news.setNews("오늘 날씨 정보");
		
		System.out.println(person1);
		System.out.println(person2);
		System.out.println(person3);
		
	}
}
Magazine 구독자 : [홍길동 [], 임꺽정 []]
News 구독자 : [임꺽정 [], 김자바 []]
홍길동 [1월 호 잡지 출판, 2월 호 잡지 출판]
임꺽정 [1월 호 잡지 출판, 속보... 주저리주저리, 2월 호 잡지 출판, 오늘 날씨 정보]
김자바 [속보... 주저리주저리, 오늘 날씨 정보]

Subject 인터페이스의 필수 메서드는 옵저버 등록/삭제, 갱신 이 3가지다.

Observer 인터페이스 구현은 나중에 Subject를 구독할 가능성이 있어도 구현하는 것이 좋다.

 

구조상 한개의 주제에 다수의 옵저버가 연결되어있다. 그래서 일대다 관계인 것

 

옵저버 패턴에서는 주제가 상태를 저장하고 제어한다. 반면에 옵저버는 상태를 사용하지만, 반드시 소유할 필요는 없다. 때문에 주제에서 상태가 바뀌었다는 사실을 알려주길 기다리는 의존적인 성질을 가지게 된다.

 

느슨한 결합의 위력

느슨한 결합은 객체들이 상호작용할 수 있지만, 서로를 잘 모르는 관계를 말한다. 

인터페이스에 맞게 개발하는 것을 의미한다.

 

디자인 원칙
상호작용하는 객체 사이에는 가능하면 느슨한 결합을 사용해야 한다.

가상 스테이션 구현하기

import java.util.ArrayList;
import java.util.List;

public class WheatherStationTest {
	static interface Subject{
		void registerObserver(Observer o);//등록
		void removeObserver(Observer o);  //삭제
		void notifyObservers();// 알림
	}
	
	@FunctionalInterface
	static interface Observer{
		void update();
	}
	
	static interface DisplayElement{
		void display();
	}
	
	
	static class WeatherData implements Subject{
		private List<Observer> observers;
		private float temperature;
		private float humidity;
		private float pressure;

		public WeatherData() {
			observers = new ArrayList<>();
		}
		public void registerObserver(Observer o) {
			observers.add(o);
		}
		public void removeObserver(Observer o) {
			observers.remove(o);
		}
		public void notifyObservers() {
			observers.forEach((ob)->ob.update());
		}
		public void measurementsChanged() {
			notifyObservers();
		}
		public void setMeasurements(float temperature, float humidity, float pressure) {
			this.temperature = temperature;
			this.humidity = humidity;
			this.pressure = pressure;
			measurementsChanged();
		}
		public float getTemperature() {
			return temperature;
		}
		public float getHumidity() {
			return humidity;
		}
		public float getPressure() {
			return pressure;
		}
	}
	
	static class CurrentConditionsDisplay implements Observer, DisplayElement{
		private float temperature;
		private float humidity;
		private WeatherData weatherData;

		public CurrentConditionsDisplay(WeatherData weatherData) {
			this.weatherData = weatherData;
			weatherData.registerObserver(this);
		}

		@Override
		public void update() {
			this.temperature = weatherData.getTemperature();
			this.humidity = weatherData.getHumidity();
			display();
		}

		@Override
		public void display() {
			System.out.println("현재 상태: 온도 " +temperature + "F, 습도 "+humidity + "%");
		}
		
	}
	static class StatisticsDisplay implements Observer, DisplayElement {
		private float maxTemp = 0.0f;
		private float minTemp = 200;
		private float tempSum= 0.0f;
		private int numReadings;
		private WeatherData weatherData;

		public StatisticsDisplay(WeatherData weatherData) {
			this.weatherData = weatherData;
			weatherData.registerObserver(this);
		}

		public void update() {
			float temp = weatherData.getTemperature();
			tempSum += temp;
			numReadings++;

			if (temp > maxTemp) {
				maxTemp = temp;
			}
	 
			if (temp < minTemp) {
				minTemp = temp;
			}

			display();
		}

		public void display() {
			System.out.println("Avg/Max/Min 온도 = " + (tempSum / numReadings)
				+ "/" + maxTemp + "/" + minTemp);
		}
	}
	static class ForecastDisplay implements Observer, DisplayElement {
		private float currentPressure = 29.92f;  
		private float lastPressure;
		private WeatherData weatherData;

		public ForecastDisplay(WeatherData weatherData) {
			this.weatherData = weatherData;
			weatherData.registerObserver(this);
		}

		public void update() {
	        lastPressure = currentPressure;
			currentPressure = weatherData.getPressure();

			display();
		}

		public void display() {
			System.out.print("기상 예보: ");
			if (currentPressure > lastPressure) {
				System.out.println("날씨가 좋아지고 있습니다.");
			} else if (currentPressure == lastPressure) {
				System.out.println("지금과 비슷합니다.");
			} else if (currentPressure < lastPressure) {
				System.out.println("쌀쌀하며 비가 올 것 같습니다.");
			}
		}
	}
	
	public static void main(String[] args) {
		WeatherData weatherData = new WeatherData();
		
		CurrentConditionsDisplay conditionsDisplay = new CurrentConditionsDisplay(weatherData);
		StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
		ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
		
		weatherData.setMeasurements(80, 65, 30.4f);
		weatherData.setMeasurements(82, 70, 29.2f);
		weatherData.setMeasurements(78, 90, 29.2f);
	}
}

 

현재 상태: 온도 80.0F, 습도 65.0%
Avg/Max/Min 온도 = 80.0/80.0/80.0
기상 예보: 날씨가 좋아지고 있습니다.
현재 상태: 온도 82.0F, 습도 70.0%
Avg/Max/Min 온도 = 81.0/82.0/80.0
기상 예보: 쌀쌀하며 비가 올 것 같습니다.
현재 상태: 온도 78.0F, 습도 90.0%
Avg/Max/Min 온도 = 80.0/82.0/78.0
기상 예보: 지금과 비슷합니다.

 

 

 

 

핵심 정리

옵저버 패턴에는 Push 방식과 Pull 방식이 있다. 일반적으로 Pull방식이 더 좋다고 여겨진다.

옵저버 인터페이스만 구현하면 어떤 클래스도 옵저버 패턴에 참여할 수 있다.

 

+ Recent posts