일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- pypy3
- level1
- Python
- baekjoon
- HAVING 절
- 기본
- java
- 파이썬
- Codeforces Round #802 (Div. 2)
- SELECT 절
- SQLD / SQLP
- 응용
- 자바
- 공공데이터
- 헤드퍼스트 디자인패턴
- 기초
- Java11
- 이론
- Codeup
- 개념
- 백준
- 명품 자바 프로그래밍
- Python 3
- 기초100제
- programmers
- 단계별로 풀어보기
- GROUP BY 절
- JAVA 11
- 코딩테스트
- BOJ
- Today
- Total
Development Project
[ 헤드퍼스트 디자인패턴 - 11/06 ] Chap1. 디자인 패턴 소개와 전략패턴 본문
헤드퍼스트 디자인패턴 개정판을 읽은 후 터득한 내용을 정리해 올린 포스팅임을 밝힙니다.
▶ 오리 시뮬레이션 게임, SimUduck
해당 시뮬레이션 게임의 초기 디자인은, Duck이라는 슈퍼클래스를 상속받는 여러 종류의 오리클래스로 구성됨
ex) MallardDuck(청둥오리), RedheadDuck(머리가 빨간 오리), RubberDuck(러버덕 - 고무오리) ...
▶ 오리에게 날 수 있도록 하려면?
1st 접근) 슈퍼클래스인 Duck에 fly() 메소드를 추가
모든 오리들에게 fly라는 기능이 추가되어버리기 때문에, 날지못하는 오리도 날수 있게됨.
몇 오리들은 날지못하게 바꾸기 위해서는 오버라이드가 필수적임.
2nd 접근) Duck에 fly() 메소드를 추가하고, 오버라이드를 통해 다른 행동을 하도록 변경
1. 서브클래스에서 코드가 중복된다.
날수있는 오리도 여러마리, 날지못하는 오리도 여러마리가 될 수 있음
즉, 날수있는 메소드로 Duck을 설정해두면, 날수없는오리들은 오버라이드를 받아서 날지못하도록 바꿔주어야하므로 비효율적임
2. 실행 시에 특징을 바꾸기 힘들다.
날 수 있었던 오리가 실행도중 날 수 없도록, 날 수 없었던 오리가 실행도중 날 수 있도록 바꾸기 위해서는 코드를 또다시 작성해주어야 하므로 좋지않다.
3. 모든 오리의 행동을 알기 힘들다.
오리마다 행동을 각기 다르게 오버라이드 할 수 있어서, 한눈에 파악하기 쉽지않다.
4. 코드를 변경했을 때 다른 오리들에게 원치 않은 영향을 끼칠 수 있다.
Duck의 fly()메소드를 날수있는 코드로 설정해두고 수많은 오리들을 그에 맞춰서 바꾸었는데, Duck의 fly()를 날수없는 코드로 바꾸어버린다면 굉장히 많은 시간을 써서 수정해야 할 수 있다.
3rd 접근) Flyable이라는 인터페이스를 설계하여, 해당 인터페이스에 fly() 메소드를 정의
날 수 있는 오리에게만 인터페이스를 구현하여 사용하면되므로 효율적이라 생각할 수 있지만, 날아가는 동작을 바꿔야한다면 여전히 수많은 코드를 수정해야하므로 비효율적.
즉, 한가지 행동을 바꿀 때마다 그 행동이 정의된 서브클래스를 모두 찾아 고치는 것은 굉장히 비효율적이다.
↓
Design Principle 1. 변경가능 부분과 변경불가 부분을 분리한다.
< 오리의 날아가는 행동들은 여러가지이고 변경가능한 부분이므로, 날아가는 행동들을 모은 클래스 집합을 별도로 생성 >
클래스 집합은 슈퍼클래스의 인스턴스에 행동을 할당할 수 있을텐데, 동적으로 행동을 바꾸려면?
↓
Design Principle 2. 인터페이스에 맞춰 코드를 작성한다.
< 오리의 날아가는 행동들은 Duck클래스가 아닌 특정행동만을 목적으로 하는 클래스의 집합으로 생성하고 여기서 구현 >
// 인터페이스로 구현할때의 이점
/* 1. 기본형태
d를 Dog 형식으로 선언하면,
Dog의 구체적 표현에 맞게 코딩해야함.*/
Dog d = new Dog();
d.bark();
/* 2. 다형성의 활용
Animal이라는 인터페이스와 상위형식에 맞춰 코딩한다면,
다형성을 활용할 수 있음.*/
Animal animal = new Dog();
animal.makeSound();
/* 3. 바람직한 방법
new Dog()처럼 상위형식의 인스턴스를 만드는 과정을
직접 코드로 만들지 않고, 구체적으로 구현된 객체를 실행해 대입*/
a = getAnimal();
a.makeSound();
4th 접근) 디자인 원칙 1,2 적용
1. 다른 형식의 객체에서도 날아가거나 울음소리를 내는 행동들을 재사용할 수 있다.
☞ 이미 FlyBehavior나 QuackBehavior이라는 인터페이스의 각각 fly(), quack()메소드로 관련된 구체적 행동들이 구현되어있는 클래스들이 있기때문!
2. 슈퍼클래스인 Duck을 건드리지 않고도, 새로운 행동을 추가할 수 있다.
☞ FlyBehavior나 QuackBehavior이라는 인터페이스와 이를 상속받은 클래스들은 슈퍼클래스와 별개의 클래스이므로, 독립적이다!
즉, 결과는 아래와 같게된다.
※ 오리의 큰 행동을 나타낸 인터페이스와 구체적인 행동을 담은 클래스
FlyBehavior [인터페이스] fly() |
QuackBehavior [인터페이스] quack() |
|||
FlyWithWings [클래스] fly() { // 날아가는 방법 } |
FlyNoWay [클래스] fly() { } // 날수없도록 |
Quack [클래스] fly() { // 꽥꽥 소리 } |
Squeak [클래스] fly() { // 삑삑 소리 } |
MuteQuack [클래스] fly() { } // 울수 없도록 |
=> 위와 같이 날아가는 방법을 담은 FlyBehavior 인터페이스를 상속받은, FlyWithWings 클래스, FlyNoWay 클래스를 생성할 수 있다. 울음소리의 경우도 마찬가지.
※ 행동을 담은 슈퍼클래스 Duck
Duck [슈퍼 클래스] // 행동 인터페이스 형식으로 미리 선언 FlyBehavior flyBehavior QuackBehavior quackBehavior // 오리 메소드 performFly() performQuack() swim() . . . |
=> Duck 슈퍼클래스에 행동 인터페이스 형식의 인스턴스를 추가함으로써, 각 오리객체에서는 실행시 이 변수에 특정행동형식의 클래스를 다형적으로 설정이 가능함! |
실제 코드로 표현하면 아래와 같다. FlyBehavior에 관한 것만 적어뒀는데, QuackBehavior이나 다른 행동인터페이스가 와도 같은 방식으로 활용 가능하다.
- Duck.java
행동인터페이스 형식의 레퍼런스 변수로 선언하여 가져온 뒤, 날거나 우는 등의 행동은 행동클래스에게로 위임한다.
실행중에 동적으로 행동을 지정하기 위해, 세터 메소드를 선언한다. - FlyBehavior.java , FlyWithWings.java , FlyNoWay.java
행동인터페이스인 FlyBehavior는 fly()를 가지고, 이를 행동구현클래스에서 실제 정의를 내린다. - MallardDuck.java - 청둥오리
구체적인 나는 동작을 이미 정의된 행동묶음(행동 인터페이스를 implements받은 클래스들)에서 가져온다. - DuckSimulator.java
위에서 선언한 아이들을 실행하기 위한 예시코드
RubberDuck은 따로 선언하지 않았지만, MallardDuck과 같은 형식의 클래스이고, FlyNoWay로 정의되어있다 가정함
// Duck.java - 오리 (슈퍼클래스)
public abstract class Duck{
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
// 기타 코드. . .
// 날기- 행동클래스에 위임
public void performFly(){
flyBehavior.fly();
}
// 날아가는 방식을 바꾸기 위한 세터메소드
public void setFlyBehavior(FlyBehavior fb){
flyBehavior = fb;
}
}
//---------------------------------------------------------------
//FlyBehavior.java - 날아가는 행동 인터페이스
public interface FlyBehavior{
public void fly();
}
// FlyWithWings.java - 날개로 날아가는 구체적 행동을 담은 클래스
public class FlyWithWings implements FlyBehavior{
public void fly(){
System.out.println("날개로 날아요!");
}
}
// FlyNoWay.java - 날 수 없다는 행동을 담은 클래스
public class FlyNoWay implements FlyBehavior{
public void fly(){
System.out.println("못날아요..");
}
}
//---------------------------------------------------------------
// MallardDuck.java - 청둥오리 (오리를 상속받음)
public class MallardDuck extends Duck{
// 청둥오리를 생성할때 (해당 생성자로 들어올때)
public MallardDuck() {
// 날아가는 동작은 날개로 날아가록 하겠다는 의미
flyBehavior = new FlyWithWings();
}
// 기타 코드. . .
}
//---------------------------------------------------------------
// DuckSimulator.java
public class DuckSimulator{
public static void main(String[] args){
Duck mallard = new MallardDuck();
mallard.performFly();
Duck rubber = new RubberDuck();
rubber.performFly(); // 못날아요.. 를 출력
rubber.setFlyBehavior(new FlyWithWings());
rubber.performFly(); // 날개로 날아요! 를 출력
}
}
▶ 캡슐화된 행동 살펴보기
클라이언트( 슈퍼클래스 Duck을 포함한 여러 오리들 )에서는 날아가는 행동이나 울음소리를 내는 행동들 등등을 캡슐화된 알고리즘으로 구현하게 된다.
즉, 각 오리에는 FlyBehavior, QuackBehavior를 가지므로 오리에는 FlyBehavior, QuackBehavior가 있다 라고 표현가능함
이렇게 두 클래스를 합치는 것을 구성(composition)이라 한다.
"A는 B이다" 보다 "A에는 B가 있다"가 효율적일 수 있다.
↓
Design Principle 3. 상속보다는 구성을 활용한다.
< 구성을 활용하면 유연성을 크게 향상시킬 수 있다! >
▶ 첫 번째 디자인 패턴 : 전략 패턴
위에서 언급한 총 3가지의 디자인 원칙은 전략패턴이다.
알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해준다.
전략 패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경 할 수 있다,
'Web Tech > Design Pattern' 카테고리의 다른 글
[ 헤드퍼스트 디자인패턴 - 11/08 ] Chap2. 옵저버 패턴 (0) | 2022.11.08 |
---|