/*요소를 하나로 접어가는 함수
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 ));
/* 커링 함수 오직 함수만을 인자로 받아 함수를 리턴함, 평가 시점을 조율*/
function curry(fn){
return function(a){
return function (b){
return fn(a,b);
}
}
}
/* 단순히 인자 순서만 변경, 화살표 함수로만 표현 */
function curryr(fn){
return a => b => fn(b,a);
}
/* 커링 함수 오직 함수만을 인자로 받아 함수를 리턴함, 평가 시점을 조율*/
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);
}
})();
//함수를 담은 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;
}
//함수를 담은 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)
);
함수형 프로그래밍은 부수효과(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("홍길동");
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에서 해도, 상태 클래스에서 해도 상관없다. 다만, 상태 전환 코드를 상태 클래스에 넣으면 상태 클래스 간 의존성이 생긴다. 이 문제로 위 예제에선 겜블머신에 게터 메소드를 사용했다.
물 끊이는 중
찻잎을 우려내는 중
컵에 따르는 중
레몬을 추가하는 중
물 끊이는 중
필터로 커피를 우려내는 중
컵에 따르는 중
설탕과 우유를 추가하는 중
홍차 준비중...
물 끊이는 중
찻잎을 우려내는 중
컵에 따르는 중
차에 레몬을 넣을까요? (y/n)
n
커피 준비중...
물 끊이는 중
필터로 커피를 우려내는 중
컵에 따르는 중
커피에 우유와 설탕을 넣을까요?(y/n)y
설탕과 우유를 추가하는 중
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 을 구현했는지 컴파일러에서 체크를 할 수 없어 런타임 에러를 유발한다…
핵심 정리
템플릿 메소드는 알고리즘의 단계를 정의하며 일부 단계를 서브 클래스에서 구현하도록 할 수 있다.
템플릿 메소드 패턴은 코드 재사용에 도움된다.
후크 메서드는 서브 클래스에서 선택적으로 재정의한다.
할리우드 원칙에 의하면, 저수준 모듈은 언제 어떻게 호출할지는 고수준 모듈에서 결정해야 한다.
기존 시스템과 업체에서 제공한 코드가 있다고 치자. 이 사이를 어댑터 코드로 연결지어주면, 두 코드는 코드 변경이 없다.
어댑터사용방법알아보기
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();
}
}