해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.

아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)

Combined Pattern


여러 패턴을 함께 사용하여 더욱 강력한 객체지향 디자인 패턴

  • 패턴 몇개를 결합한다고 무조건 복합 패턴이 되는 것이 아님.
  • 여러가지 문제의 일반적인 해결법을 제시해야함.
    • ex) MVC패턴
    • 여러 패턴에 대해 적용 가능한 시나리오 작성.
      • 같은 역할을 하는 클래스에 대해 기능 통일 → 상속
      • 다른 클래스에서 같은 인터페이스를 사용하도록 연결하는 역할 → 어댑터 패턴
      • 다양한 클래스를 한데 묶어 소리를 낸 횟수 세려면 → 데코레이터 패턴
      • 데코레이터에서 객체를 제대로 포장하지 않으면 원하는 행동 추가 불가능. 데코레이터로 감싸는 부분을 하나로 빼내서 캡슐화 → 팩토리 패턴
      • 다양한 클래스를 한데 관리 ⇒ 반복자 패턴, 구성패턴
      • 하나의 클래스에 대해 관리 ⇒ 옵저버 패턴

Model - View - Controller 알아보기

View

  • Model을 표현하는 방법을 제공
  • 화면에 표시할 때 상태와 데이터는 Model에서 가져오기

Controller

  • Controller에서 Model을 조작
  • 사용자가 인터페이스를 건드리면 그 행동이 Controller에게 전달
  • 사용자에게 입력을 받으며 입력받은 내용이 Model에게 의미 전달.

Model

  • View에게 상태 변화 통기
  • 필요한 상태, 데이터, 애플리케이션 로직은 Model에 들어있음.

동작

  • 사용자는 View에만 접촉 가능
  • Controller가 Model에게 상태를 변경하라고 요청
    • 사용자의 행동을 받아서 해석
    • 의미해석, 모델 조작 결정.
  • Controller가 View를 변경해달라고 요청
    • 결과로 View에게 바꿔달라고 요청할 수 있음.
  • 상태가 변경되면 Model이 View에게 사실 전달
    • 사용자가 한 행동으로 내부적인 변화 등으로 모델이 변경되면 뷰에게 상태가 변경되었다고 요청
  • View가 Model에게 상태 요청

모델-뷰-컨트롤러에 사용되는 패턴 알아보기

  • 컴포지트 패턴
    • 디스플레이는 여러 단계로 겹쳐있는 윈도우, 패널, 텍스트로 구성
    • 각 디스클레이는 복합 객체나 잎버튼이 될 수 있음.
    • 최상위 뷰 구성요소에게만 화면을 갱신
  • 전략 패턴
  • 옵저버 패턴
    • 상태가 변경되었을 때 그 모델과 연관된 객체들에게 연락.
    • 모델을 뷰와 컨트롤러로부터 분리

 

해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.

아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)

Proxy Pattern


접근을 제어하고 관리 => 기본적으로 프록시가 하는 역할.

  • 원격 객체의 프록시만 넘기면 다른 머신에서도 상태 확인 가능.

 Proxy

  • 진짜 객체를 대신하는 역할.
  • 네트워크와 떨어져있는 클래스의 정보를 주고 받아야 함.
  • 서비스 역할 필요.
  • 네트워크로 들어오는 요청 수용
  • 프록시 객체의 레퍼런스를 받아오는 기능 필요

원격 프록시의 역할


원격 프록시 diagram

  • 원격 객체의 로컬 대변자 역할. => 어떤 메소드를 호출하면 다른 원격 객체에서 그 메소드 호출을 전달하는 객체.
    • 원격 객체 => JVM의 힙에 존재하는 객체(다른 주소 공간에서 돌아가고있는 객체)
    • 네트워크 통신과 같은 low level 작업은 프록시 객체에서 관리.

모니터링 코드에 원격 프록시 추가하기.

  • 다른 JVM에 들어있는 객체의 메소드를 호출할 수 없음.
  • RMI(Remote Method Invocation)를 이용하여 프록시 만들기
💡 모니터링 코드란
이전 State Pattern에서 예시를 통해 든 GumballMachine에서의 상태를 확인하는 코드 

원격 메소드 기초

원격 메소드 기초

  • 로컬 객체의 메소드를 호출하면 그 요청을 원격 객체에 전달해주는 시스템을 디자인.
  • 통신을 처리해주는 보조 객체가 필요하고 보조 객체를 사용.
    • 클라이언트는 로컬 객체의 메소드만 알면 됨.
    • 클라이언트 보조 객체의 메소드를 호출 => 서비스 보조 객체 호출(1)
    • 진짜 원격 서비스인 척하는 객체 => 클라이언트 보조 객체
    • 클라이언트 보조 객체는 서버에 연락을 취하고(2) 메소드 호출에 관한 정보를 전달하고 서버로부터 리턴되는 정보를 기다림.
    • 서버 보조 객체는 소켓으로 연결된 보조 객체로부터 요청을 받아오고(2) 호출 정보를 해석해서 진짜 메소드 호출(3)
    • 이후 반환되는 과정은 3 -> 2 -> 1 순으로 전달.

RMI 개요


  • 클라이언트와 서비스 보조 객체를 만들어주는 JAVA API
  • 네트워킹 및 입출력 관련 코드를 작성하지 않아도 JVM 메소드를 호출하듯 원격 메소드 호출 가능
  • 클라이언트보조 객체 => 스텁(stub)
    서버 보조 객체 => 스켈레톤(skeleton)

원격 서비스 만들기

원격 인터페이스 만들기

import java.rmi.*;

public interface MyRemote extends Remote {
	public String sayHello() throws RemoteException;
}
  1. 클라이언트가 원격으로 호출할 메소드 정의.
  2. 인터페이스를 서비스의 클래스 형식으로 사용
  3. java.rmi.Remote 확장 및 Remote 클래스 상속
  4. 모든 메소드를 RemoteException을 던지도록 선언.
  5. 원격 메소드의 인자나 반환 값은 반드시 primitive 또는 Serializble 형식으로

서비스 구현 클래스 만들기.

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class GumballMachine extends UnicastRemoteObject implements GumballMachineRemote {

	State soldOutState;
	State noQuarterState;
	State hasQuarterState;
	State soldState;
	State winnerState;

	State state = soldOutState;
	int count = 0;
	String location;

	public GumballMachine(String location, int numberGumballs) throws RemoteException {

		soldOutState = new SoldOutState(this);
		noQuarterState = new NoQuarterState(this);
		hasQuarterState = new HasQuarterState(this);
		soldState = new SoldState(this);
		winnerState = new WinnerState(this);

		this.location = location;
		this.count = numberGumballs;
		if (numberGumballs > 0) {
			state = noQuarterState;
		}
	}
}

import java.rmi.Naming;

public class GumballMachineTestDrive {

	public static void main(String[] args) {
		GumballMachineRemote gumballMachine = null;
		int count;

		if (args.length < 2) {
			System.out.println("GumballMachine <name> <inventory>");
			System.exit(1);
		}

		try {
			count = Integer.parseInt(args[1]);

			gumballMachine =
					new GumballMachine(args[0], count);
			Naming.rebind("//" + args[0] + "/gumballmachine", gumballMachine);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
  1. 실제 작업 처리 클래스
  2. UnicastRemoteObject 확장.
  3. RemoteException을 선언하는 생성자 구현
  4. 서비스를 RMI 레지스트리에 등록

RMI 레지스트리 구성

  1. 클라이언트는 레지스트리로부터 프록시(스텁, 클라이언트 보조 객체)를 받아감.

원격 서비스 실행하기

  1. 서비스 객체 실행

Proxy Pattern


Proxy Pattern

특정 객체로의 접근을 제어하는 대리인 제공 => 프록시

프록시에 접근을 제어하는 방법

  1. 원격 프록시를 써서 원격 객체로의 접근을 제어
  2. 가상 프록시를 써서 생성하기 힘든 자원으로의 접근을 제어
  3. 보호 프록시를 써서 접근 권한이 필요한 자원으로의 접근을 제어
  • RealSubject와 Proxy 인터페이스를 제공하는 Subject 인터페이스 존재
  • 두 객체는 똑같은 인터페이스를 구현하기에 RealSubject가 들어가야할 자리에 Proxy를 대신 넣음
  • 진짜 작업은 RealSubject가 처리. 대변인 역할이자 접근 제어

원격 프록시와 가상 프록시 비교하기

원격 프록시

  • 다른 JVM에 들어있는 객체의 대리인에 해당하는 로컬 객체

가상 프록시

  • 생성하는데 많은 비용이 드는 RealSubject를 대신하는 프록시 객체

보호 프록시 만들기

  • java.lang.reflect 패키지 안에 프록시 기능이 내장
  • 즉석에서 하나 이상의 인터페이스를 구현하고 지정한 클래스에 메소드 호출을 전달하는 프록시 클래스 만들기 가능.
    • 진짜 프록시 클래스는 실행 중에 생성되므로 이러한 자바 기술을 동적 프록시라함.
  • 자바에서 Proxy 클래스를 생성해주므로 Proxy 클래스에게 무슨 일을 해야하는지 알려줄 방법이 필요함.
    • InvocationHandler에 넣어주기
    • 프록시에 호출되는 모든 메소드에 응답.
  • 클라이언트에서 아무 메소드나 마음대로 호출할 수도 있기에 보호 프록시가 필요
    • 접근 권한을 바탕으로 객체로의 접근을 제어하는 프록시
해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.

아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)

 

전략 패턴과 상태 패턴은 쌍둥이

  • 상태 다이어그램과 비슷
  • 다른 상태로 전환을 위해서는 어떤 동작이 필요
  • 예외 케이스에 대해서도 항상 잘 생각해야함.
  • 상태 값을 저장하는 인스턴스 변수를 만들고 메소드 내에서 조건문을 써서 다양한 상태를 처리

State Diagram

상태

  • 동전 없음
  • 동전 있음
  • 알맹이 없음
  • 알맹이 판매

상태를 저장하는 인스턴스 변수 정의

/*    
* 알맹이 상태 저장
*/   
final static int SOLD_OUT = 0; // 알맹이 없음
final static int NO_QUARTER = 1; // 동전 없음   
final static int HAS_QUARTER = 2; // 동전 있음   
final static int SOLD = 3; // 알맹이 내보내는 중

시스템에서 일어날 수 있는 모든 행동 정의

  • 동전투입
  • 동전 반환
  • 손잡이 돌림
  • 알맹이 내보냄

클래스 만들기

public 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 { // state == SOLD
            System.out.println("알맹이를 내보내는 중");
        }
    }

	// 이후 부터는 행동 정의 생략...
    
    /*
     * 동전 반환
     */
    public void ejectQuarter() {
    }

    /*
     * 손잡이를 돌리는 경우
     */
    public void turnCrank() {
    }

    /*
     * 알맹이 내보내기
     */
    public void dispense() {
    }

    public void refill(int numGumBalls) {
    }
}

뽑기 기계 수정 요청


  • 새로운 기능 추가 요구
  • 현재 코드 ⇒ 관리 및 수정의 어려움
  • 상태 별 행동을 별도의 클래스에 넣어두고 모든 상태에 대해 각각 자기가 할 일을 구현
    • 바뀌는 부분을 캡슐화
    • 뽑기 기계가 현재 상태를 나타내는 상태 객체에게 작업을 넘기도록 ⇒ 구성

새로운 디자인 구성하기

  • 기존 코드를 그대로 활용하는 대신 상태 객체들을 별도의 코드에 넣고 어떤 행동이 일어나면 현재 상태 객체에서 필요한 작업을 처리.
    • 뽑기 기계와 관련된 모든 행동에 관한 메소드가 들어있는 State 인터페이스 정의
    • 기계의 모든 상태를 대상으로 상태 클래스 구현.
      • 상태에 대해서는 상태 클래스가 모든 책임을 갖도록
    • 조건문 코드 없애고 상태 클래스에 작업 위임.
  • 상태 패턴 적용

State Pattern

구조 살펴보기

  • 해당 결과 도출
    • 각 상태의 행동을 별개의 클래스로 국지화
    • 관리하기 힘든 분기문 삭제
    • 상태를 변경에는 닫혀있고 새로운 상태를 추가하는 확장에는 열려있도록. (OCP적용)
    • 이해하기 좋은 코드 베이스와 클래스 구조 적용
public class GumballMachine {

    /*
     * 알맹이 상태 저장
     */
    State soldOutState; // 알맹이 없음
    State noQuarterState; // 동전 없음
    State hasQuarterState; // 동전 있음
    State soldState; // 알맹이 내보내는 중

    State state;
    int count = 0;

    public GumballMachine(int count) {
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        soldState = new SoldState(this);

        this.count = count;
        if (count > 0) {
            state = noQuarterState;
        } else {
            state = soldOutState;
        }
    }

    /*
     * 동전이 투입된 경우
     */
    public void insertQuarter() {
        state.insertQuarter();
    }

    /*
     * 동전 반환
     */
    public void ejectQuarter() {
        state.ejectQuarter();
    }

    /*
     * 손잡이를 돌리는 경우
     */
    public void turnCrank() {
        state.turnCrank();
        state.dispense();
    }

	// State 생성
    void setState(State state) {
        this.state = state;
    }
}

// State 예시
public interface State {
    // 동전을 넣었을 때의 상태 정의
    public void insertQuarter();

    // 동전을 반환했을 때의 상태 정의
    public void ejectQuarter();

    // 손잡이를 돌렸을 때의 상태 정의
    public void turnCrank();

    // 알맹이가 나올 때의 상태 정의
    public void dispense();

    public void refill();
}

public class HasQuarterState implements State {
    GumballMachine gumballMachine;

    public HasQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("동전을 한개만 넣어주세요");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("동전을 반환합니다");
        gumballMachine.setState(gumballMachine.getNoQuarterState());
    }

    @Override
    public void turnCrank() {
        System.out.println("손잡이를 돌리셨습니다.");
        gumballMachine.setState(gumballMachine.getSoldState());
    }

    @Override
    public void dispense() {
        System.out.println("동전을 넣어주세요");
    }

    public void refill() {}
}

State Pattern

State Pattern

💡 상태 패턴
객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있음. 클래스가 바뀌는 것과 같은 결과
  • 상태를 별도의 클래스로 캡슐화
  • 상태 패턴과 전략 패턴은 비슷!!
    • 하지만 용도가 다름
    • 상태 패턴
      • 상태 객체에 일련의 행동이 캡슐화
      • 여러 상태 객체 중 하나의 상태에 모든 행동을 맡기게 됨.
      • 그에 따라 객체가 바뀌게 될 수도 있음
    • 전략 패턴
      • 클라이언트가 context 객체에게 어떤 전략을 사용할 지를 지정
      • 실행 시에 전략 객체를 변경할 수 있는 유연성을 제공하는 용도로 사용.
      • 서브클래스를 만드는 방법을 대신해 유연성 극대화 ⇒ 구성
해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.

아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)

문제점


각자 다른 자료구조 및 구현 방법이 달라 코드를 통일시킬 수가 없음.

PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu(); 
ArrayList<MenuItem> breakfastItems = pancakeHouseMenu.getMenuItems();

DinerMenu dinerMenu = new DinerMenu(); 
MenuItem[] lunchItems = dinerMenu.getMenuItems();

for (int i = 0; i < breakfastItems.size(); i++) { 
	MenuItem menuItem = breakfastItems.get(i); 
	System.out.print(menuItem.getName() + " "); 
	System.out.println(menuItem.getPrice() + " "); 
	System.out.println(menuItem.getDescription());
}

for (int i = 0; i < lunchItems.length; i++) {
	MenuItem menuItem = lunchItems[i];
    System.out.print(menuItem.getName() + " ");
    System.out.println(menuItem.getPrice() + " ");
    System.out.println(menuItem.getDescription());
}
  • PancakeHouse는 메뉴를 List로 관리.
  • Diner는 메뉴를 array로 관리

반복을 캡슐화하기


디자인 패턴의 기본: 바뀌는 부분을 캡슐화하라!

class diagram

Iterator iterator = breakfastMenu.createIterator();
while (iterator.hasNext()) {
	MenuItem menuItem = iterator.next();
}

Iterator iterator = lunchMenu.createIterator();
while (iterator.hasNext()) {
	MenuItem menuItem = iterator.next();
}

public class PancakeHouseMenuIterator implements Iterator {
	List<MenuItem> items;
	int position = 0;
 
	public PancakeHouseMenuIterator(List<MenuItem> items) {
		this.items = items;
	}
 
	public MenuItem next() {
		return items.get(position++);
	}
 
	public boolean hasNext() {
		return items.size() > position;
	}
}


public class DinerMenuIterator implements Iterator {
	MenuItem[] items;
	int position = 0;
 
	public DinerMenuIterator(MenuItem[] items) {
		this.items = items;
	}
 
	public MenuItem next() {
		return items[position++];
	}
 
	public boolean hasNext() {
		return items.length > position;
    }
}

Iterator Pattern

Iterator Pattern

  • 반복 작업을 캡슐화
  • Iteraotr 인터페이스의 메소드. hasNext(), next()
  • 메뉴 구현이 캡슐화되어 있음
    • 종업원은 메뉴에서 컬렉션을 어떻게 저장하고 있는지 알 필요가 없음.
    • 반복자만 구현한다면 다형성을 활용하여 하나의 반복문으로 처리 가능.
    • 종업원은 인터페이스만 알면 됨.
💡 Iterator Pattern
컬렉션의 구현방법을 노출하지 않으면서 집합체 내의 모든 항목에 접근하는 방법 제공.
  • 인터페이스와 구현이 간단.
  • 각자의 비즈니스 로직만 처리 가능
  • 확장 가능성이 좋음

구조

  • 공통된 인터페이스를 클라이언트가 참조할 수 있도록 개발.
  • 서브 클래스는 인터페이스에 따라 개발.

단일 역할 원칙

  • 어떤 클래스가 바뀌는 이유는 하나뿐 이여야 한다.

Iterable 인터페이스

  • 어떤 클래스던 Iterable을 구현한다면 그 클래스는 iterator() 메소드를 구현
    • Iterator 인터페이스를 구현하는 반복자 리턴
  • forEach() 메소드 제공

새로운 문제점


public void printMenu() {
	Iterator<MenuItem> pancakeIterator = pancakeHouseMenu.createIterator();
    Iterator<MenuItem> dinerIterator = dinerMenu.createIterator();
    Iterator<MenuItem> cafeIterator = cafeMenu.createIterator();
    
    System.out.println("메뉴\n----\n아침 메뉴"); printMenu(pancakeIterator);
    System.out.println("\n점심 메뉴"); printMenu(dinerIterator);
    System.out.println("\n저녁 메뉴");
    
    printMenu(cafeIterator);
}
  • 메뉴들이 추가가 되면 OCP(Open-Closed Principle)에 위배됨.
  • 메뉴를 한꺼번에 관리할 방법이 필요함.
public class Waitress {
    List<Menu> menus;
    
    public Waitress(List<Menu> menus) {
    	this.menus = menus;
    }
    
    public void printMenu() {
    	Iterator<Menu> menuIterator = menus.iterator();
        while(menuIterator.hasNext()) {
        	Menu menu = menuIterator.next();
            printMenu(menu.createIterator()); 
        }
    }
    
	void printMenu(Iterator<MenuItem> iterator) { 
    	while (iterator.hasNext()) {
			MenuItem menuItem = iterator.next();
            
            System.out.print(menuItem.getName() + ", ");
            System.out.print(menuItem.getPrice() + " -- ");
            System.out.println(menuItem.getDescription());
		} 		
	}
}

Composite Pattern


💡 컴포지트 패턴
객체를 트리구조로 구성해서 부분-전체 계층 구조를 구현. 컴포지트 패턴을 사용하면 클라이언트에서 개별 객체와 복합 객체를 똑같은 방법으로 다룰 수 있음.
  • 단일 역할 원칙을 깨는 대신 투명성을 확보하는 패턴
    • Component 인터페이스에 자식들을 관리하는 기능과 잎으로써의 기능을 전부 넣어서 클라이언트가 복합 객체와 잎을 똑같은 방식으로 처리할 수 있도록 만들었음.
  • 상황에 따라 원칙을 적절하게 사용해야 함.

'기타 > 디자인패턴' 카테고리의 다른 글

[Design Pattern] Proxy Pattern  (2) 2024.06.10
[Design Pattern] State Pattern  (0) 2024.06.09
[Design Pattern] Template Method Pattern  (0) 2024.06.05
[Design Pattern] Adapter Pattern  (0) 2024.06.04
[Design Pattern] Command Pattern  (2) 2024.06.03
해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.

아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)

Coffee 클래스와 Tea 클래스 만들기


  • 커피와 홍차 만드는 방법은 비슷.
  • 비슷한 클래스는 공통된 부분을 추상화해서 베이스 클래스로 만들면 좋은 방법
  • 추상화 클래스 적용.

  • Coffee 클래스에서도 Tea 클래스에서도 추상화 가능한 메소드를 추가
    • brewCoffeeGrinds(), steepTeaBag() 의 역할이 비슷
    • addSugarAndMilk(), addLemon()의 역할이 비슷
    • 각각 brew()addCondiments()로 추상화
  • 일반화를 최대한 적용. ⇒ CaffeinBeverage 클래스에 반영
  • 일부 메서드를 서브 클래스에 의존 ⇒ 각각의 brew()addCondiments()를 추상화

Template Method Pattern


  • prepareRecipe()는 템플릿 메소드
    • 메소드이자 카페인 음료수를 만드는 알고리즘의 템플릿
  • 이전 코드
    • 음료 코드 각자가 알고리즘 수행.
    • 중복, 분산 코드가 많음.
    • 알고리즘이 바뀌면 수정해야 할 부분이 여러군데임.
  • 템플릿 메소드 적용 코드
    • 알고리즘을 CaffeineBeverage가 독점
    • 서브 클래스에서 코드를 재사용 가능.
    • 다른 음료도 쉽게 추가할 수 있는 알고리즘 제공. => 알고리즘의 템플릿 만들기
💡 템플릿 메소드
알고리즘의 골격을 정의. 템플릿 메소드를 사용하면 알고리즘의 일부 단계를 서브 클래스에서 구현할 수 있으며, 알고리즘의 구조는 그대로 유지하면서 알고리즘의 특정 단계를 서브 클래스에서 재정의.

Example

public abstract class CaffeineBeverage {
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    abstract void brew();
    abstract void addCondiments();

    public void pourInCup() {
        System.out.println("컵에 따르는 중");
    }
    public void boilWater() {
        System.out.println("물 끓이는 중");
    }
}

public class Coffee extends CaffeineBeverage{

    @Override
    public void brew() {
        System.out.println("필터로 커피 우려내는 중");
    }

    @Override
    public void addCondiments() {
        System.out.println("설탕과 우유를 추가하는 중");
    }
}

public class Tea extends CaffeineBeverage{

    @Override
    public void brew() {
        System.out.println("찻잎을 우려내는 중");
    }

    @Override
    public void addCondiments() {
        System.out.println("레몬을 추가하는 중");
    }
}

hook

  • 기본적으로 아무것도 하지 않는 구상 메소드를 정의.
  • 서브 클래스에서 오버라이드할 수도 있고 아닐 수도 있음.
    • 서브 클래스에서 메소드를 커스텀해서 사용할 수 있음.
public class CoffeeWithHook extends CaffeineBeverageWithHook {
 
	public void brew() {
		System.out.println("Dripping Coffee through filter");
	}
 
	public void addCondiments() {
		System.out.println("Adding Sugar and Milk");
	}
 
	public boolean customerWantsCondiments() {

		String answer = getUserInput();

		if (answer.toLowerCase().startsWith("y")) {
			return true;
		} else {
			return false;
		}
	}
 
	private String getUserInput() {
		String answer = null;

		System.out.print("Would you like milk and sugar with your coffee (y/n)? ");

		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		try {
			answer = in.readLine();
		} catch (IOException ioe) {
			System.err.println("IO error trying to read your answer");
		}
		if (answer == null) {
			return "no";
		}
		return answer;
	}
}

고찰

  • 제공해야만 하는 기능 ⇒ 추상 메소드
  • 서브 클래스 전용 커스텀후크
  • 추상 메소드가 너무 많아지는 문제.
    • 템플릿 메소드를 만들 때에 이 점을 염두.
    • 모든 단계가 필수가 아니므로 후크 사용

할리우드 원칙


먼저 연락 X. 연락 주겠어

  • 할리우드 원칙을 사용하면 dependency rot를 방지.
    • 의존성 부패 ⇒ 고수준 구성요소와 저수준 구성요소가 서로서로 의존하는 형태.

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

  • CaffeineBeverage 클래스는 고수준 구성요소
  • Tea, Coffee(구상 클래스)가 고수준 구성요소를 호출하지 않음.

프레임 워크를 만드는데 적절한 패턴

  • Arrays.sort() 에서 사용되는 패턴
    • 서브 클래스를 구현하기 위해서 Comparable 인터페이스 제공을 통해 compareTo() 구현하도록 유도
  • JFrame 클래스에서 사용
    • paint() 함수가 후크
해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.

아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)

디자인 패턴의 설계

느슨한 결합으로 객체지향 디자인

  • 객체의 인스턴스는 항상 공개 되어야 하는 것이 아니며 모든 것을 공개 했다가는 문제가 생길 수도 있음
  • new 연산자가 구상을 생각!!
    • new를 사용하면 구상 클래스의 인스턴스가 만들어짐
    • 이는 구상 클래스를 바탕으로 코드를 수정해야 할 가능성이 커지고 유연성이 떨어짐.
      Duck duck = new MallardDuck()

new의 문제?

  • 구상 클래스 간의 변화되는 부분이 문제!
  • 인터페이스에 맞춰 코딩하면 변화에 대응이 가능함. ⇒ 다형성
  • 변경에는 닫혀있고 확장에는 열려있어야한다.

피자 코드 설계


public class Pizza {
    public Pizza() {
    }

    Pizza orderPizza(String type) {
        Pizza pizza;

        if (type.equals("cheese")) {
            pizza = new ChesePizza();
        } else if (type.equals("greek")) {
            pizza = new GreekPizza();
        } else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        }

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}
  • pizza는 type에 따라 구상 클래스의 인스턴스를 생성.
  • 사라지는 피자와 새로 생기는 피자가 생기면 코드 지우고 코드 새로 추가해야됨.

객체 생성 부분 캡슐화(변화가 생기는 부분)

  • 객체 생성을 처리하는 클래스를 팩토리라고 함.
  • SimplePizzaFactory가 OrderPizza()메소드가 새로 만든 객체의 클라이언트 생성해줌.
public class SimplePizzaFactory {
    Pizza create(String type) {
        Pizza pizza = null;
        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (type.equals("greek")) {
            pizza = new GreekPizza();
        } else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        }

        return pizza;
    }
}
  • 이러한 방법은 static factory라고 부름
    • 객체 생성 메소드를 실행하려고 객체의 인스턴스를 만들지 않아도 되기 때문.

Client Code 수정하기

객체의 팩토리 인스턴스 저장하기

public class PizzaStore {
    SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory factory) { 
        this.factory = factory;
    }

    public Pizza orderPizza(String type) {
        Pizza pizza;

        pizza = factory.createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}

public class SimplePizzaFactory {

    public SimplePizzaFactory() {
    }

    Pizza create(String type) {
        Pizza pizza = null;

        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (type.equals("greek")) {
            pizza = new GreekPizza();
        } else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        }

        return pizza;
    }
}
  • 각각의 지점들은 굽는 방식이 달라지거나 피자의 토핑이 달라질 수 있음.
  • PizzaStore를 만들어 각각 스타일대로 구현

다양한 팩토리 만들기


다양한 스타일의 피자 가게가 있음.

public abstract class PizzaStore { 
	abstract Pizza createPizza(String item);
 
	public Pizza orderPizza(String type) {
		Pizza pizza = createPizza(type);
		System.out.println("--- Making a " + pizza.getName() + " ---");
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}
}

public class NYPizzaStore extends PizzaStore {
	Pizza createPizza(String item) {
		if (item.equals("cheese")) {
			return new NYStyleCheesePizza();
		} else if (item.equals("veggie")) {
			return new NYStyleVeggiePizza();
		} else if (item.equals("clam")) {
			return new NYStyleClamPizza();
		} else if (item.equals("pepperoni")) {
			return new NYStylePepperoniPizza();
		} else return null;
	}
}

public class ChicagoPizzaStore extends PizzaStore {
	Pizza createPizza(String item) {
		if (item.equals("cheese")) {
			return new ChicagoStyleCheesePizza();
		} else if (item.equals("veggie")) {
			return new ChicagoStyleVeggiePizza();
		} else if (item.equals("clam")) {
			return new ChicagoStyleClamPizza();
		} else if (item.equals("pepperoni")) {
			return new ChicagoStylePepperoniPizza();
		} else return null;
	}
}

// 뉴욕식 치즈피자
PizzaStore nyStore = new NYPizzaStore();
nyStore.orderPizza("cheese");

// 시카고식 치즈피자
PizzaStore chicagoStore = new ChicagoPizzaStore();
chicagoStore.orderPizza("cheese");

팩토리 메소드 패턴

Factory Method Pattern

  • 팩토리 패턴객체 생성(Pizza)을 캡슐화
  • 팩토리 메서드 패턴은 서브클래스(NYPizzaStore, ChicagoPizzaStore)에서 어떤 클래스를 만들지 정하므로, 객체 생성을 캡슐화
  • 제품 클래스(Pizza) 와 생산자 클래스(PizzaStore)를 분리함으로써 구체적인 구현은 구상 클래스가 책임 지도록.
  • 특징
    • 캡슐화: 객체 생성 로직을 팩토리 클래스나 메서드로 분리하여 외부에서 직접 객체를 생성하지 않도록 함.
    • 유연성: 객체 생성 방식을 변경할 때 클라이언트 코드를 수정할 필요 없이 팩토리 클래스만 수정하면 됨.
    • 확장성: 새로운 종류의 객체를 추가할 때도 기존 클라이언트 코드에 영향을 주지 않고 팩토리 클래스만 확장하면 됨.
    • OCP: 하나의 클래스에 추가적인 기능을 넣어도 다른클래스의 변경을 하지 않아도된다.
💡 팩토리 메서드 패턴
객체를 생성할 때 필요한 인터페이스(PizzaStore)를 만들고 어떤 클래스의 인터페이스를 만들지는
서브 클래스(NYPizzaStore, ChicagoPizzaStore)에서 결정하도록 함.
💡 Simple Factory VS Factory Method 
팩토리 메소드 패턴간단한 팩토리와 상당히 비슷하지만 간단한 팩토리는 일회용 처방에 불과한 반면, 팩토리 메소드 패턴을 사용하면 여러번 재사용이 가능한 프레임워크를 만들 수 있습니다. 예를 들어, 팩토리 메소드 패턴의 orderPizza( ) 메소드는 피자를 만드는 일반적인 프레임워크를 제공합니다. 그 프레임워크는 팩토리 메소드 피자 생성 구상 클래스를 만들었죠. PizzaStore 클래스의 서브 클래스를 만들 때, 어떤 구상 제품 클래스에서 리턴할 피자를 만들지를 결정합니다. 이 프레임워크를 간단한 팩토리와 한번 비교해 보세요. 간단한 팩토리는 객체 생성을 캡슐화하는 방법을 사용하긴 하지만 팩토리 메소드만큼 유연하지는 않습니다. 생성하는 제품을 마음대로 변경할 수 없기 때문입니당. => (SimpleFactory라는 인스턴스를 가지고 있기 때문)

 

의존성 뒤집기 원칙(Dependency Inversion)

  • 구상 클래스의 의존성을 떨어뜨리기
  • 즉, 고수준 구성요소는 저수준 구성요소에 의해 정의되는 행동이 들어있는 구성요소
    • PizzaStore는 고수준 구성요소
    • Pizza 클래스는 저수준 구성요소.
💡 추상화된 것에 의존하게 만들고, 구상 클래스에 의존하지 않게 만든다.
  • DI를 지키는 방법
    • 변수에 구상 클래스의 레퍼런스를 저장하지 않는다.
    • 구상 클래스에서 유도된 클래스를 만들지 말자.
    • 베이스 클래스에 이미 구현되어 있는 메소드를 오버라이드하지 말자.

추상 팩토리 패턴

Abstract Factory Pattern

💡 구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스 제공.

팩토리 메소드 패턴 VS 추상 팩토리 패턴

비교\패턴 팩토리 메소드 패턴 추상 팩토리 패턴
공통점 애플리케이션을 특정 구현으로부터 분리.
제품 생성 클래스를 사용 객체를 사용
제품 생성 방식 상속 구성
제품 생성 방법 객체를 생성하고 클래스를 확장할 때에는 팩토리 메소드를 오버라이드하여 서브 클래스에서 객체를 생성 제품군을 만드는 추상 형식을 제공. 인스턴스를 만든 다음 추상 형식을 써서 만든 코드에 전달.

 

'기타 > 디자인패턴' 카테고리의 다른 글

[Design Pattern] Command Pattern  (2) 2024.06.03
[Design Pattern] Singleton Pattern  (0) 2024.05.31
[Design Pattern] Decorator Pattern  (0) 2024.05.28
[Design Pattern] Observer Pattern  (0) 2024.05.27
[Design Pattern] Strategy Pattern  (0) 2024.05.27
해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.

아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)

카페 프로그램 개발


  • 음료수가 많으니 추상 클래스에서 Beverage 클래스 정의
  • 더 자세한 음료는 서브 클래스에서 구현

문제점

  • 인기가 증가하면서 메뉴가 너무 많이 증가하고, 모든 메뉴가 Beverage를 상속함
  • 첨가물도 있으니 첨가물이나 가격변동에 따른 대비가 어려움

해결책

  • 인스턴스 변수와 슈퍼 클래스 상속으로 관리

문제점

  1. 첨가물의 가격이 바뀔때마다 코드 수정
  2. 새로운 음료가 출시될 경우, 특정 첨가물이 들어가면 안되는 음료가 있을 수 있음.
  3. 같은 첨가물을 두번 주문하는 경우

고찰


  • 상속만으로는 무조건 유연하거나 관리하기 좋은 디자인은 아님
  • 구성과 위임으로 실행 중에 행동을 상속하는 방법이 있음
    • 서브 클래스로 행동을 상속받으면 컴파일 타임에 결정됨.
    • 구성으로 객체의 행동을 확장하면 실행 중에 동적으로 할당가능.
💡 OCP(Open-Closed Principle)
클래스 확장에는 열려있지만 변경에는 닫혀있다.

 

Decorator Pattern


이전까지는 클래스가 너무 많아지거나 적합하지 않은 기능을 추가해야했음.

첨가물 == 음료의 장식(decorate)으로 생각

Decorator Pattern

방법

  1. DarkRoast 객체 생성
  2. Mocha 객체로 장식
  3. Whip 객체로 장식
  4. cost() 메서드 호출. 첨가물의 가격을 계산하는 일은 객체에게 위임.

정리

  1. 데코레이터의 슈퍼 클래스는 자신이 장식하고 있는 객체의 슈퍼클래스와 동일(Beverage)
  2. 한 객체(음료)를 여러개의 데코레이터(샷, 우유, 모카...)로 감싸기
  3. 데코레이터는 행동을 위임하는것 말고도 다른 추가 메서드 가능
  4. 실행중에 데코레이터 적용 가능
💡 Decorator Pattern
객체에 추가 요소를 동적으로 더할 수 있음.
서브 클래스를 만들때보다 유연하게 기능확장 가능.

 

고찰

  • CondimentDecorator에서 Beverage 클래스를 확장 ⇒ 상속아닌가?
    • 상속맞지만 행동을 물려받는게 아님
  • 이 구조로 실행 중에 동적으로 결정 가능

Java IO 클래스

데코레이터 패턴을 적용한 예

예시

public abstract class Beverage {
    enum SIZE {
        TALL, GRANDE, VENTI
    }

    String description = "아무것도 없음";
    SIZE size = SIZE.TALL;

    public String getDescription() {
        return description;
    }

    public SIZE getSize() {
        return size;
    }

    public void setSize(SIZE size) {
        this.size = size;
    }

    abstract double cost();
}

public abstract class CondimentDecorator extends Beverage {
    Beverage beverage;

    public abstract String getDescription();
}


public class DarkRoast extends Beverage {
    private double cost;

    public DarkRoast() {
        description = "다크 로스트 커피";
    }

    double cost() {
        if (getSize() == SIZE.TALL) {
            cost += 0.99;
        } else if (getSize() == SIZE.GRANDE) {
            cost += 1.49;
        } else {
            cost += 1.99;
        }

        return cost;
    }
}

public class Mocha extends CondimentDecorator {
    public Mocha(Beverage b) {
        this.beverage = b;
    }

    public String getDescription() {
        return beverage.getDescription() + "모카";
    }

    public double cost() {
        return beverage.cost() + 0.2;
    }
}

public class Whip extends CondimentDecorator {
    public Whip(Beverage b) {
        description = "휘핑";
        this.beverage = b;
    }

    public String getDescription() {
        return beverage.getDescription() + "휘핑";
    }

    public double cost() {
        return beverage.cost() + 0.4;
    }
}

public class StarbuzzCoffee {
    public static void main(String[] args) {
        Beverage espresso = new Espresso();
        System.out.println("espresso: " + espresso.getDescription() + " cost: " + espresso.cost());

        Beverage darkRoast = new DarkRoast();
        darkRoast = new Mocha(darkRoast);
        darkRoast = new Mocha(darkRoast);
        darkRoast = new Whip(darkRoast);
        System.out.println("Dark double Mocha whipping: " + darkRoast.getDescription() + " cost: " + darkRoast.cost());

        Beverage sameB = new Whip(new Mocha(new Mocha(new DarkRoast())));
        System.out.println("Dark double Mocha whipping: " + sameB.getDescription() + " cost: " + sameB.cost() + "\t" + sameB.getClass());

        // SIZE = GRANDE;
        Beverage darkRoastGrande = new DarkRoast();
        darkRoastGrande.setSize(Beverage.SIZE.GRANDE);
        darkRoastGrande = new Mocha(darkRoastGrande);
        darkRoastGrande = new Mocha(darkRoastGrande);
        darkRoastGrande = new Whip(darkRoastGrande);
        System.out.println("Dark Grande double Mocha whipping: " + darkRoastGrande.getDescription() + " cost: " + darkRoastGrande.cost());
    }
}

'기타 > 디자인패턴' 카테고리의 다른 글

[Design Pattern] Command Pattern  (2) 2024.06.03
[Design Pattern] Singleton Pattern  (0) 2024.05.31
[Design Pattern] Factory Pattern  (0) 2024.05.29
[Design Pattern] Observer Pattern  (0) 2024.05.27
[Design Pattern] Strategy Pattern  (0) 2024.05.27
해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.

아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)

SimUduck


  • 모든 오리가 할 수 있는 동작 Duck 클래스에서 정의
    • quack()
    • swim()
    • display()
      • 모든 오리의 동작이 다르므로 추상 메소드에서 정의
  • MallardDuck 클래스와 RedHeadDuck 클래스가 Duck 클래스를 상속받아서 display override
  • 이후에 모든 오리에게 날 수 있는 동작을 정의하고 싶음
    • Duck 클래스에서 fly() 메소드 추가로 정의로 해결 가능

문제발생

  • fly() 메소드는 일부 Duck 클래스를 상속한 서브 클래스에만 되어야했는데 모든 서브 클래스에 적용이 다 되어버림

해결책


1. 인터페이스 설계

  • 모든 오리가 날지 못함 + 모든 오리가 꽥꽥거리면서 울지 않음
    • Flyable, Quackable 이라는 인터페이스 설계

2. 캡슐화

💡 애플리케이션에서 달라지는 부분을 찾아내고 달라지지 않는 부분과 분리.

 

  • 달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 캡슐화
    • 코드를 변경하는 과정에서 의도치 않게 발생하는 일을 줄이면서 시스템의 유연성을 향상.
  • 구현보다는 인터페이스에 맞춰 프로그래밍한다.
    • 각 행동은 인터페이스로 표현(FlyBehavior, QuackBehavior…)
    • 메소드는 Duck 클래스에서 구현X. 인터페이스에서 먼저 정의
💡 구현보다는 인터페이스에 맞춰 프로그래밍한다.

오리행동 통합하기


나는 행동과 꽥꽥거리는 행동을 Duck 클래스에서 정의한 메소드를 써서 구현하지 않고 다른 클래스로 위임하는 행동.

  • Duck 클래스에 FlyBehaviorQuackBehavior을 인스턴스 변수로 저장.
    • Duck 클래스는 인터페이스에서 구현한 fly()quack() 메소드를 실행하기 위한 메소드 정의(performFly(), performQuack())
public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    // 모든 오리의 동작을 여기서 정의

    void performQuack() {
        quackBehavior.quack();
    }

    void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }

    void performFly() {
        flyBehavior.fly();
    }

    void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    void swim() {
        System.out.println("헤엄!");

    }

    // 오리에 따른 description
    abstract void display();
}

/*
 * 오리 날기 정의
 * 인터페이스로 정의 후 각각의 날개의 방법대로 정의.
 */
public interface FlyBehavior {
    void fly();
}

public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("i can't fly!");
    }
}

public class FlyWithWings implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("i can fly!");
    }
}

/*
 * 오리 울음소리 정의
 * 인터페이스로 정의 후 각각의 소리마다 class로 정의
 */
public interface QuackBehavior {
    void quack();
}

public class Quack implements QuackBehavior{
    @Override
    public void quack() {
        System.out.println("꽥꽥!");
    }
}

public class Squeak implements QuackBehavior{
    @Override
    public void quack() {
        System.out.println("삑삑!");
    }
}

 

Example

public class RedheadDuck extends Duck {

    public RedheadDuck() {
        flyBehavior = new FlyWithWings();
        quackBehavior = new Squeak();
    }

    @Override
    void display() {
        System.out.println("RedheadDuck!");
    }
}
  • RedheadDuck 클래스는 Duck 클래스를 상속.
  • 상속자에서 날기 동작과 울음소리를 정의.
    • 날기 동작과 울음소리는 인터페이스로 정의되어 있는 것에 클래스로 할당.
  • display()는 Duck 클래스에서 추상메소드로 정의하였기 때문에 클래스에서 정의.
💡 특정 구현에 맞춰서 프로그래밍하면 안됨.
하지만 new FlyWithWings() 처럼, 특정 객체에 구현되어 있는 구상 인스턴스를 만들었음. ⇒ 잘못되었지만 1장이라 이처럼 표현

동적으로 행동 지정하기

  • Duck 클래스에 Setter method 정의(setQuackBehavior(), setFlyBehavior())

두 클래스를 합치는 방법

💡 상속(as-is)보다는 구성(has-is)을 활용
  • A에는 B가 있다. ⇒ Duck 클래스에 FlyBehavior와 QuackBehavior가 있어서 Duck 클래스에 나는 행동과 꽥꽥거리는 행동을 위임. ⇒ 구성

전략패턴


알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해주는 패턴

클라이언트로부터 알고리즘을 분리해서 독립적으로 분리.

요약 :

변화가 생길 기능(행동:behavior)을 예상하여 이를 캡슐화(및 추상화) 한다.

캡슐화 후 각 기능별로 구현해두고(ex: fly 중 noWay, WithWing, Rocket 등)

각 객체별로 사용할 기능을 (쉽게) 교환 받는다.

각 기능들이 추상화되어 있어 재사용성이 좋고, 수정이 용이하다.

 

기억 추적 :

서브클래스는 단순히 super 클래스를 상속받는식의 구현이 아닌,

interface, abstract 등을 이용하여 기능의 동작만 받음.

각 기능은 인터페이스를 구현하여 미리 정의해두고 모델은 이를 조합하여 기능을 만들어가는 방식.

'기타 > 디자인패턴' 카테고리의 다른 글

[Design Pattern] Command Pattern  (2) 2024.06.03
[Design Pattern] Singleton Pattern  (0) 2024.05.31
[Design Pattern] Factory Pattern  (0) 2024.05.29
[Design Pattern] Decorator Pattern  (0) 2024.05.28
[Design Pattern] Observer Pattern  (0) 2024.05.27

+ Recent posts