[무료] 자바스크립트로 알아보는 함수형 프로그래밍 (ES5) - 인프런 | 강의

마플(http://www.marpple.com)의 CTO 유인동님이 알려주는 함수형 프로그래밍에 대한 강좌 입니다. 함수형 프로그래밍으로 라이브러리를 직접 만들어가며 함수형 프로그래밍의 패러다임과 코딩의 즐거

www.inflearn.com

map, filter에 curryr 적용

map, filter의 평가 시점을 조절

const _ = (()=>{
    return {
        filter:curryr(filter) , //커링
        map:curryr(map) ,      //커링
        each: each,
        curry:curry,
        curryr:curryr,
        reduce: reduce,
    };
    //.... 생략

map도 동일하다.

console.log(_.filter);
console.log(_.map);

const upperAge30 = _.filter(u=>u.age>=30);
const userName = _.map(u=>u.name);
console.log(upperAge30);
console.log(userName);

디버거로 확인

console.log(upperAge30(users));
console.log(userName(users));
console.log(userName(upperAge30(users)));

 

reduce

요소를 하나로 접어가는 함수

function reduce(list, iter, memo){
    each(list, val =>{
        memo = iter(memo, val);
    });
    return memo;
}
console.log(_.reduce([1,2,3,4,5],(sum,val)=>sum+val,0));
console.log(_.reduce([1,2,3,4,5],(sum,val)=>sum+val,10));
console.log(_.reduce([1,2,3,4,5],(sum,val)=>sum+val,-10));

reduce 개선

현재 아쉬운 점은 반드시 3번째 인자 memo가 있어야 한다.

memo가 없을 시 list[0] 인자를 memo로 사용하도록 수정

/*요소를 하나로 접어가는 함수
list 요소, iter 보조함수, memo 초기값*/
function reduce(list, iter, memo){
    if(arguments.length===2){
        memo = list[0];
        list = rest(list);
    }
    each(list, val =>{
        //memo에 계속 누적하여 적용한다.
        memo = iter(memo, val);
    });
    return memo;
}
/* 배열 첫 번째 요소를 잘라 나머지 배열을 리턴*/
function rest(list, num){
    return Array.prototype.slice.call(list,num||1);
}
console.log(_.reduce([1,2,3,4,5],(sum,val)=>sum+val ));
//30세 이상 사용자 age 합
console.log(_.reduce(_.map(upperAge30(users),u=>u.age),(sum,val)=>sum+val ));

memo 인자 없이도 문제없이 동작

function.html
0.01MB

'개발 > 함수형 프로그래밍' 카테고리의 다른 글

07 - 컬렉션 중심 프로그래밍 - map (수집하기)  (0) 2023.06.17
06 - 다형성 높이기  (0) 2023.06.16
05 - 파이프라인, pipe, go  (1) 2023.06.14
03 - 커링, curry, curryr  (0) 2023.06.11
02 - map, filter, each  (0) 2023.06.09
 

[무료] 자바스크립트로 알아보는 함수형 프로그래밍 (ES5) - 인프런 | 강의

마플(http://www.marpple.com)의 CTO 유인동님이 알려주는 함수형 프로그래밍에 대한 강좌 입니다. 함수형 프로그래밍으로 라이브러리를 직접 만들어가며 함수형 프로그래밍의 패러다임과 코딩의 즐거

www.inflearn.com

커링, curry, curryr, 클로저

함수의 평가 시점을 조정하는 기법

/* 커링 함수 오직 함수만을 인자로 받아 함수를 리턴함, 평가 시점을 조율*/
function curry(fn){
    return function(a){
        return function (b){
            return fn(a,b);
        }
    }
}
/* 단순히 인자 순서만 변경, 화살표 함수로만 표현 */
function curryr(fn){
    return a => b => fn(b,a);
}
//중복이름으로 구분자 _
const _add = _.curry((a,b)=>a+b);
const _add10 = _add(10);
console.log(_.curry);
console.log(_add);
console.log(_add10);
console.log(_add10(20));

함수를 인자로 받아 함수를 리턴하는 방식으로 평가 시점을 조율한 것을 볼 수 있다. 이 과정에서 curry가 적용된 함수는 모두 클로저 상태가 된다.

함수가 함수를 계속 리턴하면서 최종적으로 맨 처음 (a,b)=>a+b 함수가 실행된 것을 볼 수 있다.

클로저

const _add = _.curryr((a,b)=>a+b); //curryr
const _add10 = _add(10);
console.log(_.curry);
console.log(_add);
console.log(_add10);
console.log(_add10(20));

인자가 두 개일 때는 그 즉시 평가하도록 수정

/* 커링 함수 오직 함수만을 인자로 받아 함수를 리턴함, 평가 시점을 조율*/
function curry(fn){
    return function(a,b){
        return arguments.length === 2 ? fn(a,b) : function (b){
            return fn(a,b);
        }
    }
}
/* 단순히 인자 순서만 변경, 화살표 함수로만 표현 */
function curryr(fn){
    return (a,b) => arguments.length === 2 ? fn(a,b) : b => fn(b,a);
}
})();
const _add = _.curry((a,b)=>a+b);
const _add10 = _add(10);
console.log(_add);
console.log(_add10);
console.log(_add10(20));
console.log(_add(10,20));

curry 정상동작 확인

const _add = _.curryr((a,b)=>a+b);
const _add10 = _add(10);
console.log(_add);
console.log(_add10);
console.log(_add10(20));
console.log(_add(10,20));

curryr 비정상 동작

화살표 함수는 this 바인딩이 없어 그 상위 문맥 curryr 함수로 잡혀 arguments.length가 1이 나온다.

/* 단순히 인자 순서만 변경, 화살표 함수로만 표현 */
function curryr(fn){
    return function(a,b){
        return arguments.length === 2 ? fn(a,b) : b => fn(b,a)
    };
}

익명 함수로 변경 후 정상 동작 확인

 

curry, curryr 효용

const _sub = _.curry((a,b)=>a-b); //curry
const _sub10 = _sub(10);
console.log( _sub10(20) ); // 인지적으로 20 - 10 = 10 일 것 같지만  - 10

const _sub = _.curryr((a,b)=>a-b);
const _sub10 = _sub(10);
console.log( _sub10(20) );

인자 순서만 변경했을 뿐이지만, 더 명확한 표현력을 가진다.

 

[무료] 자바스크립트로 알아보는 함수형 프로그래밍 (ES5) - 인프런 | 강의

마플(http://www.marpple.com)의 CTO 유인동님이 알려주는 함수형 프로그래밍에 대한 강좌 입니다. 함수형 프로그래밍으로 라이브러리를 직접 만들어가며 함수형 프로그래밍의 패러다임과 코딩의 즐거

www.inflearn.com

 

//테스트 데이터
var users = [
  { id: 10, name: 'ID', age: 36 },
  { id: 20, name: 'BJ', age: 32 },
  { id: 30, name: 'JM', age: 32 },
  { id: 40, name: 'PJ', age: 27 },
  { id: 50, name: 'HA', age: 25 },
  { id: 60, name: 'JE', age: 26 },
  { id: 70, name: 'JI', age: 31 },
  { id: 80, name: 'MP', age: 23 }
];
//앞으로 함수를 담을 obj
const _ = (()=>{
    return {
        
    };
})();

 

filter

요소를 걸러내는 함수

인자로 받은 요소 수와 같거나 적은 요소를 리턴한다.

//함수를 담은 obj
const _ = (()=>{
    return {
        filter:filter ,
    };

/* 요소를 걸러내는 함수, predi라는 보조함수를 인자로 받는다. */
function filter(list, predi){
    //함수형 스타일은 기존 요소를 손상시키지 않고 항상 새로운 값을 리턴한다.
    const result = [];
    for(const val of list){
        //보조함수를 인자로 받아 
        if(predi(val)) result.push(val);
    }
    return result;
}
})();
//나이 25세 이상 사용자 걸러내기
//절차지향 코드
const tmp = [];
for(const user of users){
    if(user.age>=25) tmp.push(user);
}
console.log(tmp);
//함수형 코드
console.log(
    _.filter(users, u=>u.age>=25)
);

console.log(
    _.filter(users, u=>u.age-(u.age%10) === 20), //20대만
    _.filter(users, u=>u.name.startsWith("J")) //J로 시작하는 user
);

map

요소를 변환시키는 함수

들어온 요소와 같은 수의 변환된 요소를 리턴한다.

/* 요소를 변환시키는 함수*/
function map(list , mapper){
    const result = [];
    for(const val of list)
        result.push(mapper(val));
    return result;
}
//map 사용
console.log(
    _.map(users, u=>u.age) ,
    _.map(users, u=>u.name) ,
    _.map(users, u=>u.id) ,
)

중간 점검

//함수를 담은 obj
const _ = (()=>{
    return {
        filter:filter ,
        map:map ,
    };

/* 요소를 걸러내는 함수, predi라는 보조함수를 인자로 받는다. */
function filter(list, predi){
    //함수형 스타일은 기존 요소를 손상시키지 않고 항상 새로운 값을 리턴한다.
    const result = [];
    for(const val of list){
        //보조함수를 인자로 받아 
        if(predi(val)) result.push(val);
    }
    return result;
}
/* 요소를 변환시키는 함수*/
function map(list , mapper){
    const result = [];
    for(const val of list)
        result.push(mapper(val));
    return result;
}

})();

향상된 for문을 사용하고 있어 편리하지만, 분명히 반복문에서 코드 중복이 발생하고있다.

 

each

들어온 요소 리스트에서 값을 하나씩 꺼내어 주는 함수

function each(list, iter){
    for(const val of list) iter(val);
    return list;
}
_.each(users,u=>console.log(u));

filter, map 에 적용 후 동작 확인

//함수를 담은 obj
const _ = (()=>{
    return {
        filter:filter ,
        map:map ,
        each: each,
    };

/* 요소를 걸러내는 함수, predi라는 보조함수를 인자로 받는다. */
function filter(list, predi){
    //함수형 스타일은 기존 요소를 손상시키지 않고 항상 새로운 값을 리턴한다.
    const result = [];
    //보조함수를 인자로 받아 
    each(list, val =>{
        if(predi(val)) result.push(val);
    });
    return result;
}
/* 요소를 변환시키는 함수*/
function map(list , mapper){
    const result = [];
    each(list, val => result.push(mapper(val)));
    return result;
}

function each(list, iter){
    for(const val of list) iter(val);
    return list;
}
})();

함수가 함수를 받아서 처리하는 것을 고차함수 혹은 응용형 함수라 한다.

함수 내부를 보면, 기존 값에 대한 변형 없이 항상 새로운 값을 리턴하고 있다. 그래서 코딩을 계속 진행하는 과정에도 원본 데이터에는 손상이 가지 않을 것을 알 수 있다.

섞어써보기

//섞어쓰기, 30세 이상, user 이름
console.log(
    _.map(_.filter(users, u=> u.age >=30), u=>u.name)
);

function.html
0.00MB

 

[무료] 자바스크립트로 알아보는 함수형 프로그래밍 (ES5) - 인프런 | 강의

마플(http://www.marpple.com)의 CTO 유인동님이 알려주는 함수형 프로그래밍에 대한 강좌 입니다. 함수형 프로그래밍으로 라이브러리를 직접 만들어가며 함수형 프로그래밍의 패러다임과 코딩의 즐거

www.inflearn.com

함수형 프로그래밍은 부수효과(side effect)를 배제하고 조합성을 강조하는 프로그래밍 패러다임

 

순수함수

//순수함수 예시
function add(a,b){
    return a+b;
}
console.log(add(12,34));
console.log(add(12,34));

순수 함수는 항상 동일한 결과를 리턴한다. 

//부수효과 예시
let sideEffect1 = 10;
function add2(a,b){
    return add(a,b)+ sideEffect1;
}
console.log(add2(10,20));
sideEffect1 = 20;
console.log(add2(10,20));

외부 인자에 영향을 받는다.

let sideEffect2 = 10;
function add3(a,b){
    sideEffect1 = a; // 외부에 영향을 준다
    sideEffect2 = b;
    return add(a,b);
}
console.log(add3(10,20));
console.log(add3(10,20));

항상 같은 결과를 리턴하지만, 함수가 외부에 영향을 미치고 있다.

함수형 프로그래밍에선 원래 값을 유지하며, 새로운 값을 만들어 가공해 리턴한다.

//새로운 값을 만든다는 것은 Call By Reference가 대상이다.
const arr = [1,2,3,4,5];
function multiplyArr(arr){
    //새로운 값
    const result = arr.slice();
    for(const idx in result){
        result[idx] = result[idx] * 2;
    }
    return result;
}
console.log(multiplyArr(arr));
console.log(arr);

순수함수는 평가 시점이 중요하지 않다. 아무때나 호출해도 항상 같은 결과를 리턴한다.

 

일급함수

함수를 변수에 담는 것

즉, 함수를 값처럼 다룰 수 있다는 의미로, 이는 변수로 다른 함수의 인자로, 혹은 함수의 반환 값으로 함수가 올 수도 있다는 의미다.

//일급함수
const get10 = () => 10; //함수를 변수에 담는다.
//함수를 인자로 받는 함수, 함수를 변수에 담았음
const add30 = function(fn){
    return 30 + fn();
}
//인자로 함수를 전달했다.
console.log(add30(get10));

함수와 메서드 차이

//함수는 단독으로 존재할 수 있다.
function hello(name){
    console.log("hello "+name);
}
//메서드는 클래스 속에 존재한다.
class Hello{
    hello(name){
        console.log("hello "+name);
    }
}
hello("홍길동");
new Hello().hello("홍길동");

 

function.html
0.00MB

 

 

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

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

ebook-product.kyobobook.co.kr

패턴 개를 결합한다고 해서 무조건 복합 패턴이 아니다.

복합 패턴이라면 여러 가지 문제의 일반적인 해결법을 제시해야 한다.

 

기본형

public class Exam001Basic {
	//인터페이스
	static interface Quackable{
		void quack();
	}
	static class MallardDuck implements Quackable{
		public void quack() {
			System.out.println("꽥꽥");
		}
	}
	static class RedheadDuck implements Quackable{
		public void quack() {
			System.out.println("꽥꽥");
		}
	}
	static class DuckCall implements Quackable{
		public void quack() {
			System.out.println("꽉꽉");			
		}
	}
	static class RubberDuck implements Quackable{
		public void quack() {
			System.out.println("삑삑");			
		}
	}
	
	static class DuckSimulator{
		void simulate() {
			//인터페이스로 변수 다루기
			Quackable mallardDuck = new MallardDuck();
			Quackable redheadDuck = new RedheadDuck();
			Quackable duckCall = new DuckCall();
			Quackable rubberDuck = new RubberDuck();
			
			System.out.println("\n오리 시뮬레이션 게임");
			
			simulate(mallardDuck);
			simulate(redheadDuck);
			simulate(duckCall);
			simulate(rubberDuck);
		}
		
		//인터페이스로 메서드 인자로 다루기
		private void simulate(Quackable duck) {
			duck.quack();
		}
	}
	
	public static void main(String[] args) {
		DuckSimulator duckSimulator = new DuckSimulator();
		duckSimulator.simulate();
		duckSimulator.simulate(new DuckCall());
	}
}
오리 시뮬레이션 게임
꽥꽥
꽥꽥
꽉꽉
삑삑
꽉꽉

어댑터 추가

public class Exam002Adapter {
	//인터페이스
	static interface Quackable{
		void quack();
	}
	static class MallardDuck implements Quackable{
		public void quack() {
			System.out.println("꽥꽥");
		}
	}
	static class RedheadDuck implements Quackable{
		public void quack() {
			System.out.println("꽥꽥");
		}
	}
	static class DuckCall implements Quackable{
		public void quack() {
			System.out.println("꽉꽉");			
		}
	}
	static class RubberDuck implements Quackable{
		public void quack() {
			System.out.println("삑삑");			
		}
	}
	
	//어댑터 패턴
	static class Goose{
		void honk() {
			System.out.println("끾끾");
		}
	}
	static class GooseAdapter implements Quackable{
		Goose goose;
		public GooseAdapter(Goose goose) {
			this.goose = goose;
		}
		public void quack() {
			goose.honk();
		}
	}
	
	static class DuckSimulator{
		void simulate() {
			//인터페이스로 변수 다루기
			Quackable mallardDuck = new MallardDuck();
			Quackable redheadDuck = new RedheadDuck();
			Quackable duckCall = new DuckCall();
			Quackable rubberDuck = new RubberDuck();
			Quackable gooseAdapter = new GooseAdapter(new Goose());
			
			System.out.println("\n오리 시뮬레이션 게임");
			
			simulate(mallardDuck);
			simulate(redheadDuck);
			simulate(duckCall);
			simulate(rubberDuck);
			simulate(gooseAdapter);
		}
		
		//인터페이스로 메서드 인자로 다루기
		private void simulate(Quackable duck) {
			duck.quack();
		}
	}
	
	public static void main(String[] args) {
		DuckSimulator duckSimulator = new DuckSimulator();
		duckSimulator.simulate();
		duckSimulator.simulate(new DuckCall());
	}
}
오리 시뮬레이션 게임
꽥꽥
꽥꽥
꽉꽉
삑삑
끾끾
꽉꽉

데코레이터 패턴 적용하기

public class Exam003Decorator {
	//인터페이스
	static interface Quackable{
		void quack();
	}
	static class MallardDuck implements Quackable{
		public void quack() {
			System.out.println("꽥꽥");
		}
	}
	static class RedheadDuck implements Quackable{
		public void quack() {
			System.out.println("꽥꽥");
		}
	}
	static class DuckCall implements Quackable{
		public void quack() {
			System.out.println("꽉꽉");			
		}
	}
	static class RubberDuck implements Quackable{
		public void quack() {
			System.out.println("삑삑");			
		}
	}
	
	//어댑터 패턴
	static class Goose{
		void honk() {
			System.out.println("끾끾");
		}
	}
	static class GooseAdapter implements Quackable{
		Goose goose;
		public GooseAdapter(Goose goose) {
			this.goose = goose;
		}
		public void quack() {
			goose.honk();
		}
	}
	
	//데코레이터
	static class QuackCounter implements Quackable{
		Quackable duck;
		//이 클래스의 모든 인스턴스가 공유하는 변수
		static int count = 0;

		public QuackCounter(Quackable duck) {
			this.duck = duck;
		}

		public void quack() {
			++count;
			duck.quack();
		}
		
		static int getQuacks() {
			return count;
		}
		
	}
	
	static class DuckSimulator{
		void simulate() {
			//인터페이스로 변수 다루기
			Quackable mallardDuck = new QuackCounter(new MallardDuck());
			Quackable redheadDuck = new QuackCounter(new RedheadDuck());
			Quackable duckCall = new QuackCounter(new DuckCall());
			Quackable rubberDuck = new QuackCounter(new RubberDuck());
			Quackable gooseAdapter = new GooseAdapter(new Goose());
			
			System.out.println("오리 시뮬레이션 게임");
			
			simulate(mallardDuck);
			simulate(redheadDuck);
			simulate(duckCall);
			simulate(rubberDuck);
			simulate(gooseAdapter);
			
			System.out.println("오리가 소리낸 횟수 : "+QuackCounter.getQuacks());;
			
		}
		
		//인터페이스로 메서드 인자로 다루기
		private void simulate(Quackable duck) {
			duck.quack();
		}
	}
	
	public static void main(String[] args) {
		DuckSimulator duckSimulator = new DuckSimulator();
		duckSimulator.simulate();
	}
}
오리 시뮬레이션 게임
꽥꽥
꽥꽥
꽉꽉
삑삑
끾끾
오리가 소리낸 횟수 : 4

추상 팩터리 추가

public class Exam004AbstractFactory {
	//인터페이스
	static interface Quackable{
		void quack();
	}
	static class MallardDuck implements Quackable{
		public void quack() {
			System.out.println("꽥꽥");
		}
	}
	static class RedheadDuck implements Quackable{
		public void quack() {
			System.out.println("꽥꽥");
		}
	}
	static class DuckCall implements Quackable{
		public void quack() {
			System.out.println("꽉꽉");			
		}
	}
	static class RubberDuck implements Quackable{
		public void quack() {
			System.out.println("삑삑");			
		}
	}
	
	//어댑터 패턴
	static class Goose{
		void honk() {
			System.out.println("끾끾");
		}
	}
	static class GooseAdapter implements Quackable{
		Goose goose;
		public GooseAdapter(Goose goose) {
			this.goose = goose;
		}
		public void quack() {
			goose.honk();
		}
	}
	
	//데코레이터
	static class QuackCounter implements Quackable{
		Quackable duck;
		//이 클래스의 모든 인스턴스가 공유하는 변수
		static int count = 0;

		public QuackCounter(Quackable duck) {
			this.duck = duck;
		}

		public void quack() {
			++count;
			duck.quack();
		}
		
		static int getQuacks() {
			return count;
		}
		
	}
	
	//추상 팩토리
	abstract static class AbstractDuckFactory{
		public abstract Quackable createMallardDuck();
		public abstract Quackable createRedheadDuck();
		public abstract Quackable createDuckCall();
		public abstract Quackable createRubberDuck();
	}
	
	//팩토리 군
	static class DuckFactory extends AbstractDuckFactory{
		public Quackable createMallardDuck() {
			return new MallardDuck();
		}
		public Quackable createRedheadDuck() {
			return new RedheadDuck();
		}
		public Quackable createDuckCall() {
			return new DuckCall();
		}
		public Quackable createRubberDuck() {
			return new RubberDuck();
		}
	}
	//와...추상 팩토리 패턴을 이런 식으로 사용하네
	static class CountingDuckFactory extends AbstractDuckFactory{
		public Quackable createMallardDuck() {
			return new QuackCounter(new MallardDuck());
		}
		public Quackable createRedheadDuck() {
			return new QuackCounter(new RedheadDuck());
		}
		public Quackable createDuckCall() {
			return new QuackCounter(new DuckCall());
		}
		public Quackable createRubberDuck() {
			return new QuackCounter(new RubberDuck());
		}
	}
	
	static class DuckSimulator{
		void simulate(AbstractDuckFactory duckFactory) {
			//인터페이스로 변수 다루기
			Quackable mallardDuck = duckFactory.createMallardDuck();
			Quackable redheadDuck = duckFactory.createRedheadDuck();
			Quackable duckCall = duckFactory.createDuckCall();
			Quackable rubberDuck = duckFactory.createRubberDuck();
			Quackable gooseAdapter = new GooseAdapter(new Goose());
			
			System.out.println("오리 시뮬레이션 게임");
			
			simulate(mallardDuck);
			simulate(redheadDuck);
			simulate(duckCall);
			simulate(rubberDuck);
			simulate(gooseAdapter);
			
			System.out.println("오리가 소리낸 횟수 : "+QuackCounter.getQuacks());;
			
		}
		
		//인터페이스로 메서드 인자로 다루기
		private void simulate(Quackable duck) {
			duck.quack();
		}
	}
	
	public static void main(String[] args) {
		DuckSimulator duckSimulator = new DuckSimulator();
		AbstractDuckFactory duckFactory = new CountingDuckFactory();
		duckSimulator.simulate(duckFactory);
	}
}
오리 시뮬레이션 게임
꽥꽥
꽥꽥
꽉꽉
삑삑
끾끾
오리가 소리낸 횟수 : 4

컴포지트 패턴 추가

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

public class Exam005Composite {
	//인터페이스
	static interface Quackable{
		void quack();
	}
	static class MallardDuck implements Quackable{
		public void quack() {
			System.out.println("꽥꽥");
		}
	}
	static class RedheadDuck implements Quackable{
		public void quack() {
			System.out.println("꽥꽥");
		}
	}
	static class DuckCall implements Quackable{
		public void quack() {
			System.out.println("꽉꽉");			
		}
	}
	static class RubberDuck implements Quackable{
		public void quack() {
			System.out.println("삑삑");			
		}
	}
	
	//어댑터 패턴
	static class Goose{
		void honk() {
			System.out.println("끾끾");
		}
	}
	static class GooseAdapter implements Quackable{
		Goose goose;
		public GooseAdapter(Goose goose) {
			this.goose = goose;
		}
		public void quack() {
			goose.honk();
		}
	}
	
	//데코레이터
	static class QuackCounter implements Quackable{
		Quackable duck;
		//이 클래스의 모든 인스턴스가 공유하는 변수
		static int count = 0;

		public QuackCounter(Quackable duck) {
			this.duck = duck;
		}

		public void quack() {
			++count;
			duck.quack();
		}
		
		static int getQuacks() {
			return count;
		}
		
	}
	
	//추상 팩토리
	abstract static class AbstractDuckFactory{
		public abstract Quackable createMallardDuck();
		public abstract Quackable createRedheadDuck();
		public abstract Quackable createDuckCall();
		public abstract Quackable createRubberDuck();
	}
	
	//팩토리 군
	static class DuckFactory extends AbstractDuckFactory{
		public Quackable createMallardDuck() {
			return new MallardDuck();
		}
		public Quackable createRedheadDuck() {
			return new RedheadDuck();
		}
		public Quackable createDuckCall() {
			return new DuckCall();
		}
		public Quackable createRubberDuck() {
			return new RubberDuck();
		}
	}
	//와...추상 팩토리 패턴을 이런 식으로 사용하네
	static class CountingDuckFactory extends AbstractDuckFactory{
		public Quackable createMallardDuck() {
			return new QuackCounter(new MallardDuck());
		}
		public Quackable createRedheadDuck() {
			return new QuackCounter(new RedheadDuck());
		}
		public Quackable createDuckCall() {
			return new QuackCounter(new DuckCall());
		}
		public Quackable createRubberDuck() {
			return new QuackCounter(new RubberDuck());
		}
	}
	
	//컴포지트 패턴
	static class Flock implements Quackable{
		private final List<Quackable> quackers = new ArrayList<>();
		
		public void add(Quackable quackable) {
			quackers.add(quackable);
		}
		@Override
		public void quack() {
			//반복자패턴
			for(Quackable quackable : quackers) {
				quackable.quack();
			}
		}
	}
	
	static class DuckSimulator{
		
		void simulate(AbstractDuckFactory duckFactory) {
			//인터페이스로 변수 다루기
			Quackable mallardDuck = duckFactory.createMallardDuck();
			Quackable redheadDuck = duckFactory.createRedheadDuck();
			Quackable duckCall = duckFactory.createDuckCall();
			Quackable rubberDuck = duckFactory.createRubberDuck();
			Quackable gooseAdapter = new GooseAdapter(new Goose());
			
			System.out.println("오리 시뮬레이션 게임: 무리 (컴포지트패턴)");
			
			Flock flockOfDucks = new Flock();
			//다양한 오리
			flockOfDucks.add(mallardDuck);
			flockOfDucks.add(redheadDuck);
			flockOfDucks.add(duckCall);
			flockOfDucks.add(rubberDuck);
			flockOfDucks.add(gooseAdapter);
			
			Flock flockOfMallards = new Flock();
			
			//물오리
			flockOfMallards.add(duckFactory.createMallardDuck());
			flockOfMallards.add(duckFactory.createMallardDuck());
			flockOfMallards.add(duckFactory.createMallardDuck());
			flockOfMallards.add(duckFactory.createMallardDuck());

			//오리군에 물오리군을 추가
			flockOfDucks.add(flockOfMallards);
			
			System.out.println("\n오리 시뮬레이션 게임 : 전체 무리");
			simulate(flockOfDucks);
			System.out.println("\n오리 시뮬레이션 게임 : 물오리 무리");
			simulate(flockOfMallards);
			
			System.out.println("\n오리가 소리 낸 횟수 : " + QuackCounter.getQuacks());
			
		}
		
		//인터페이스로 메서드 인자로 다루기
		private void simulate(Quackable duck) {
			duck.quack();
		}
	}
	
	public static void main(String[] args) {
		DuckSimulator duckSimulator = new DuckSimulator();
		//데코레이터 객체를 생산하는 추상팩토리
		AbstractDuckFactory duckFactory = new CountingDuckFactory();
		duckSimulator.simulate(duckFactory);
	}
}
오리 시뮬레이션 게임: 무리 (컴포지트패턴)

오리 시뮬레이션 게임 : 전체 무리
꽥꽥
꽥꽥
꽉꽉
삑삑
끾끾
꽥꽥
꽥꽥
꽥꽥
꽥꽥

오리 시뮬레이션 게임 : 물오리 무리
꽥꽥
꽥꽥
꽥꽥
꽥꽥

오리가 소리 낸 횟수 : 12

옵저버 패턴 추가

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

public class Exam006Observer {
	
	static class MallardDuck implements Quackable{
		Observable observable;
		
		public MallardDuck() {
			observable = new Observable(this);
		}
		
		public void quack() {
			System.out.println("꽥꽥");
			notifyObservers();
		}

		public void registerObserver(Observer observer) {
			observable.registerObserver(observer);
		}
		public void notifyObservers() {
			observable.notifyObservers();
		}
		public void removeObserver(Observer observer) {
			observable.removeObserver(observer);
		}
		public String toString() {
			return "물오리";
		}
	}
	static class RedheadDuck implements Quackable{
		Observable observable;
		
		public RedheadDuck() {
			observable = new Observable(this);
		}
		public void quack() {
			System.out.println("꽥꽥");
			notifyObservers();
		}

		@Override
		public void registerObserver(Observer observer) {
			observable.registerObserver(observer);
		}
		public void notifyObservers() {
			observable.notifyObservers();
		}
		public void removeObserver(Observer observer) {
			observable.removeObserver(observer);
		}
		public String toString() {
			return "붉은오리";
		}
	}
	static class DuckCall implements Quackable{
		Observable observable;
		public DuckCall() {
			observable = new Observable(this);
		}
		public void quack() {
			System.out.println("꽉꽉");	
			notifyObservers();
		}

		public void registerObserver(Observer observer) {
			observable.registerObserver(observer);
		}
		public void notifyObservers() {
			observable.notifyObservers();
		}
		public void removeObserver(Observer observer) {
			observable.removeObserver(observer);
		}
		public String toString() {
			return "오리호출";
		}
	}
	static class RubberDuck implements Quackable{
		Observable observable;
		
		public RubberDuck() {
			observable = new Observable(this);
		}
		
		public void quack() {
			System.out.println("삑삑");
			notifyObservers();
		}

		public void registerObserver(Observer observer) {
			observable.registerObserver(observer);
		}
		public void notifyObservers() {
			observable.notifyObservers();
		}
		public void removeObserver(Observer observer) {
			observable.removeObserver(observer);
		}
		public String toString() {
			return "고무오리";
		}
	}
	
	//어댑터 패턴
	static class Goose{
		void honk() {
			System.out.println("끾끾");
		}
		public String toString() {
			return "거위";
		}
	}
	static class GooseAdapter implements Quackable{
		Goose goose;
		Observable observable;

		public GooseAdapter(Goose goose) {
			this.goose = goose;
			observable = new Observable(this);
		}
		public void quack() {
			goose.honk();
			notifyObservers();
		}
		public void registerObserver(Observer observer) {
			observable.registerObserver(observer);
		}
		public void notifyObservers() {
			observable.notifyObservers();
		}
		public void removeObserver(Observer observer) {
			observable.removeObserver(observer);
		}
		public String toString() {
			return "오리인척하는 거위";
		}
	}
	
	//데코레이터
	static class QuackCounter implements Quackable{
		Quackable duck;
		//이 클래스의 모든 인스턴스가 공유하는 변수
		static int count = 0;
		
		public QuackCounter(Quackable duck) {
			this.duck = duck;
		}

		public void quack() {
			++count;
			duck.quack();
		}
		
		static int getQuacks() {
			return count;
		}

		public void registerObserver(Observer observer) {
			duck.registerObserver(observer);
		}
		public void notifyObservers() {
			duck.notifyObservers();
		}
		public void removeObserver(Observer observer) {
			duck.removeObserver(observer);
		}
		@Override
		public String toString() {
			return duck.toString();
		}
	}
	
	//추상 팩토리
	abstract static class AbstractDuckFactory{
		public abstract Quackable createMallardDuck();
		public abstract Quackable createRedheadDuck();
		public abstract Quackable createDuckCall();
		public abstract Quackable createRubberDuck();
	}
	
	//팩토리 군
	static class DuckFactory extends AbstractDuckFactory{
		public Quackable createMallardDuck() {
			return new MallardDuck();
		}
		public Quackable createRedheadDuck() {
			return new RedheadDuck();
		}
		public Quackable createDuckCall() {
			return new DuckCall();
		}
		public Quackable createRubberDuck() {
			return new RubberDuck();
		}
	}
	//와...추상 팩토리 패턴을 이런 식으로 사용하네
	static class CountingDuckFactory extends AbstractDuckFactory{
		public Quackable createMallardDuck() {
			return new QuackCounter(new MallardDuck());
		}
		public Quackable createRedheadDuck() {
			return new QuackCounter(new RedheadDuck());
		}
		public Quackable createDuckCall() {
			return new QuackCounter(new DuckCall());
		}
		public Quackable createRubberDuck() {
			return new QuackCounter(new RubberDuck());
		}
	}
	
	//컴포지트 패턴
	static class Flock implements Quackable{
		private final List<Quackable> ducks = new ArrayList<>();
		
		public void add(Quackable quackable) {
			ducks.add(quackable);
		}
		@Override
		public void quack() {
			//반복자패턴
			for(Quackable quackable : ducks) {
				quackable.quack();
			}
		}
		@Override
		public void registerObserver(Observer observer) {
			for(Quackable duck : ducks) {
				duck.registerObserver(observer);
			}
		}
		public void notifyObservers() {	}
		public void removeObserver(Observer observer) {
			for(Quackable duck : ducks) {
				duck.registerObserver(observer);
			}
		}
		public String toString() {
			return "오리무리";
		}
	}
	
	//옵저버 패턴
	static interface QuackObservable{//주제
		void registerObserver(Observer observer);
		void removeObserver(Observer observer);
		void notifyObservers();
	}
	//인터페이스 + 옵저버 기능 추가
	static interface Quackable extends QuackObservable{
		void quack();
	}

	static class Observable implements QuackObservable{
		List<Observer> observers = new ArrayList<>();
		QuackObservable duck;
		
		public Observable(QuackObservable duck) {
			this.duck = duck;
		}
		public void registerObserver(Observer observer) {
			observers.add(observer);
		}
		public void removeObserver(Observer observer) {
			observers.remove(observer);
		}
		public void notifyObservers() {
			for(Observer observer : observers) {
				observer.update(duck);
			}
		}
		public String toString() {
			return observers.toString();
		}
	}
	static interface Observer{
		void update(QuackObservable duck);
	}
	static class Quackologist implements Observer{
		public void update(QuackObservable duck) {
			System.out.println("꽥꽥학자 : "+duck+"가 방금 소리냈다.") ;
		}
		public String toString() {
			return "꽥꽥학자";
		}
	}
	static class MrKim implements Observer{
		public void update(QuackObservable duck) {
			System.out.println("경수가 관찰중인 : " + duck);
		}
	}
	
	
	static class DuckSimulator{
		
		void simulate(AbstractDuckFactory duckFactory) {
			//인터페이스로 변수 다루기
			Quackable redheadDuck = duckFactory.createRedheadDuck();
			Quackable duckCall = duckFactory.createDuckCall();
			Quackable rubberDuck = duckFactory.createRubberDuck();
			Quackable gooseAdapter = new GooseAdapter(new Goose());
			
			
			Flock flockOfDucks = new Flock();
			//다양한 오리
			flockOfDucks.add(redheadDuck);
			flockOfDucks.add(duckCall);
			flockOfDucks.add(rubberDuck);
			flockOfDucks.add(gooseAdapter);
			
			Flock flockOfMallards = new Flock();
			
			//물오리
			flockOfMallards.add(duckFactory.createMallardDuck());
			flockOfMallards.add(duckFactory.createMallardDuck());
			flockOfMallards.add(duckFactory.createMallardDuck());
			flockOfMallards.add(duckFactory.createMallardDuck());

			//오리군에 물오리군을 추가
			flockOfDucks.add(flockOfMallards);
			
			System.out.println("\n오리 시뮬레이션 게임(옵저버)");
			Quackologist quackologist = new Quackologist();
			MrKim mrKim = new MrKim();
			flockOfDucks.registerObserver(quackologist);
			flockOfDucks.registerObserver(mrKim);
			
			simulate(flockOfDucks);
			
			System.out.println("\n오리가 소리 낸 횟수 : " + QuackCounter.getQuacks());
			
		}
		
		//인터페이스로 메서드 인자로 다루기
		private void simulate(Quackable duck) {
			duck.quack();
		}
	}
	
	public static void main(String[] args) {
		DuckSimulator duckSimulator = new DuckSimulator();
		//데코레이터 객체를 생산하는 추상팩토리
		AbstractDuckFactory duckFactory = new CountingDuckFactory();
		duckSimulator.simulate(duckFactory);
	}
}
오리 시뮬레이션 게임(옵저버)
꽥꽥
꽥꽥학자 : 붉은오리가 방금 소리냈다.
경수가 관찰중인 : 붉은오리
꽉꽉
꽥꽥학자 : 오리호출가 방금 소리냈다.
경수가 관찰중인 : 오리호출
삑삑
꽥꽥학자 : 고무오리가 방금 소리냈다.
경수가 관찰중인 : 고무오리
끾끾
꽥꽥학자 : 오리인척하는 거위가 방금 소리냈다.
경수가 관찰중인 : 오리인척하는 거위
꽥꽥
꽥꽥학자 : 물오리가 방금 소리냈다.
경수가 관찰중인 : 물오리
꽥꽥
꽥꽥학자 : 물오리가 방금 소리냈다.
경수가 관찰중인 : 물오리
꽥꽥
꽥꽥학자 : 물오리가 방금 소리냈다.
경수가 관찰중인 : 물오리
꽥꽥
꽥꽥학자 : 물오리가 방금 소리냈다.
경수가 관찰중인 : 물오리

오리가 소리 낸 횟수 : 7

복합 패턴에서 가장 유명한 것은 MVC 패턴

  • Model (옵저버 패턴)
  • Controller(전략 패턴)
  • View(컴포지트 패턴)

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보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();
	}
	
}

핵심정리

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

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

 

+ Recent posts