헤드퍼스트 디자인 패턴 1장 | 디자인 패턴 소개와 전략 패턴
(에릭 프리먼 외 4인, 서환수 옮김, 한빛미디어)
누군가가 이미 여러분의 문제를 해결해 놓았습니다.
- 37p
모든 서브클래스에 날거나 꽥꽥거리는 기능이 있어야 하는 것은 아니므로 상속이 올바른 방법은 아닙니다. 서브클래스에서 Flyable, Quackable을 구현해서 (고무 오리가 날아다니는 것과 같은) 일부 문제점은 해결할 수 있지만, 코드를 재사용하지 않으므로 코드 관리에 커다란 문제가 생깁니다. 물론 날 수 있는 오리 중에서도 날아다니는 방식이 서로 다를 수 있다는 문제도 포함해서 말이죠.
- 43p
소프트웨어 개발 불변의 진리
=> 변화
- 44p
디자인 원칙 : 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다.
- 45p
바뀌는 부분은 따로 뽑아서 캡슐화한다. 그러면 나중에 바귀지 않는 부분에는 영향을 미치지 않고 그 부분만 고치거나 확장할 수 있다.
- 45p
디자인 원칙 : 구현보다는 인터페이스에 맞춰서 프로그래밍한다.
- 47p
각 행동은 인터페이스(예: FlyBehavior, QuackBehavior)로 표현하고 이런 인터페이스를 사용해서 행동을 구현하겠습니다. 나는 행동과 꽥꽥거리는 행동은 이제 Duck 클래스에서 구현하지 않습니다. 대신 특정 행동("삑삑 소리 내기"와 같은 행동)만을 목적으로 하는 클래스의 집합을 만들겠습니다.
- 47p
핵심은 실제 실행시에 쓰이는 객체가 코드에 고정되지 않도록 상위 형식(supertype)에 맞춰 프로그래밍해서 다형성을 활용해야 한다는 점에 있습니다. 그리고 "상위 형식에 맞춰서 프로그래밍하라"는 원칙은 "변수를 선언할 때 보통 추상 클래스나 인터페이스 같은 상위 형식으로 선언해야 한다. 객체를 변수에 대입할 때 상위 형식을 구체적으로 구현한 형식이라면 어떤 객체든 넣을 수 있기 때문이다. 그러면 변수를 선언하는 클래스에서 실제 객체의 형식을 몰라도 된다"라는 뜻으로 생각하면 됩니다.
- 48p
Dog d = new Dog();
d.bark();
변수 d를 dog 형식(Animal을 확장한 구상 클래스)으로 선언하면 구체적인 구현에 맞춰서 코딩해야 합니다.
하지만 인터페이스와 상위 형식에 맞춰서 프로그래밍한다면 다음과 같이 할 수 있습니다.
Animal animal = new Dog();
animal.makeSound();
Dog라는 걸 알고 있긴 하지만 다형성을 활용해서 Animal 레퍼런스를 써도 됩니다.
더 바람직한 방법은 상위 형식의 인스턴스를 만드는 과정을 (new Dog()) 같은 식으로) 직접 코드를 만드는 대신 구체적으로 구현된 객체를 실행시에 대입하는 것입니다.
a = getAnimal();
a.makeSound();
Animal의 하위 형식 가운데 어떤 형식인지는 모릅니다. 단지 makeSound()에 올바른 반응만 할 수 있으면 됩니다.
- 48p
이런 식으로 디자인하면 다른 형식의 객체에서도 나는 행동과 꽥꽥거리는 행동을 재사용할 수 있습니다. 그런 행동이 더 이상 Duck 클래스 안에 숨겨져 있지 않으니까요.
그리고 기존의 행동 클래스를 수정하거나 날아다니는 행동을 사용하는 Duck 클래스를 전혀 건드리지 않고도 새로운 행동을 추가할 수 있습니다.
- 49p
가장 중요한 점은 나는 행동과 꽥꽥거리는 행동을 Duck 클래스(또는 그 서브클래스)에서 정의한 메소드를 써서 구현하지 않고 다른 클래스에 위임한다는 것입니다.
- 51p
public abstract class Duck{
QuackBehavior quackBehavior;
// 기타코드
public void performQuack(){
quackBehavior.quack();
}
}
모든 Duck에는 QuackBehavior 인터페이스를 구현하는 것의 레퍼런스가 있습니다.
꽥꽥거리는 행동을 직접처리하는 대신, quackBehavior로 참조되는 객체에 그 행동을 위임합니다.
- 51p
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
public void display(){}
}
}
MallardDuck이 꽥꽥거리는 행동을 처리할 때는 Quack 클래스를 사용하므로 performQuack()이 호출되면 꽥꽥거리는 행동은 Quack 객체에게 위임됩니다. 결과적으로 진짜 꽥꽥 소리를 들을 수 있겠죠.
그리고 FlyBehavior 형식으로는 FlyWithWings를 사용합니다.
MallardDuck은 Duck 클래스에서 quackBehavior와 flyBehavior 인스턴스 변수를 상속받는다는 사실을 잊지 마세요.
- 52p
public abstract class Duck{
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() { }
public abstract void display();
public void performFly(){
flyBehavior.fly();
}
public void performQuack(){
quackBehavior.quack();
}
public void swim() {
System.out.println("All ducks float, even decoys!");
}
}
package headfirst.designpatterns.strategy;
public interface FlyBehavior {
public void fly();
}
package headfirst.designpatterns.strategy;
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("I'm flying!!");
}
}
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performQuack();
mallard.performFly();
}
}
mallardDuck에서 상속받은 performQuack() 메소드가 호출됩니다.
이 메소드에서는 객체의 QuackBehavior에게 할 일을 위임하죠.
(즉 quackBehavior 레퍼런스의 quack() 메소드가 호출됩니다.)
performFly() 메소드도 호출합니다.
- 55p
동적으로 행동 지정하기
Duck 클래스에 메소드 2개를 새로 추가합니다.
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
이 두 메소드를 호출하면 언제든지 오리의 행동을 즉석에서 바꿀 수 있습니다.
Duck model = new ModelDuck();
model.performFly(); => 를 처음 호출하면 ModelDuck 생성자에서 설정되었던 flyBehavior, 즉 flynoway 인스턴스가 fly()메서드가 호출
model.setFlyBehavior(new FlyRocketPowered()); => 이러면 상속받은 행동 세터 메서드가 호출됩니다. 이제 모형 오리에 로켓 추진력으로 날 수 있는 능력이 생겼어요.
model.performFly();
실행 중에 오리의 행동을 바꾸고 싶으면 원하는 행동에 해당하는 Duck의 세터 메소드를 호출합니다.
- 56 ~ 57p
"A는 B이다"보다 "A에는 B가 있다"가 나을 수 있습니다.
- 59p
이런 식으로 두 클래스를 합치는 것을 '구성(composition)'을 이용한다'라고 부릅니다. 여기에 나와 있는 오리 클래스에서는 행동을 상속받는 대신, 올바른 행동 객체로 구성되어 행동을 부여받습니다.
- 59p
디자인 원칙 : 상속보다는 구성을 활용한다.
- 59p
전략 패턴은 알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해 줍니다. 전략 패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있습니다.
- 60p
패턴의 밑바탕에는 객체지향 패턴이 있어요. 그러한 원칙을 알고 있으면 문제에 딱 맞는 패턴을 찾을 수 없을 때 도움이 될 거예요.
원칙이라고요? 그럼 추상화나 캡슐화...
그렇죠. 관리가 용이한 객체지향 시스템을 만드는 비결 가운데 하나가 바로 "나중에 어떻게 바귈 것인지" 생각해 보는 거죠. 지금까지 배운 원칙에 바로 그런 내용이 담겨 있어요.
- 67p
핵심 정리
객체지향 기초
- 추상화
- 캡슐화
- 다형성
- 상속
객체지향 원칙
- 바뀌는 부분은 캡슐화한다.
- 상속보다는 구성을 활용한다.
- 구현보다는 인터페이스에 맞춰서 프로그래밍한다.
- 패턴은 검증받은 객체지향 경험의 산물입니다.
- 패턴은 발명되는 것이 아니라 발견되는 것입니다.
- 대부분의 패턴과 원칙은 소프트웨어의 변경 문제와 연관되어 있습니다.
- 대부분의 패턴은 시스템의 일부분을 나머지 부분과 무관하게 변경하는 방법을 제공합니다.
- 많은 경우에 시스템에서 바뀌는 부분을 골라내서 캡슐화해야 합니다.
- 패턴은 다른 개발자와의 의사소통을 극대화하는 전문 용어 역할을 합니다.
- 68p
'Book' 카테고리의 다른 글
[독서 기록] 헤드퍼스트 디자인 패턴 3장 | 객체 꾸미기 - 데코레이터 패턴 (0) | 2022.11.14 |
---|---|
[독서 기록] 헤드퍼스트 디자인 패턴 2장 | 객체들에게 연락 돌리기 - 옵저버 패턴 (0) | 2022.11.14 |
[독서 기록] 클린 코드 11장, 12장 , 13장 외 / 시스템, 창발성, 동시성 (1) | 2022.11.13 |
[독서 기록] 클린 코드 9장, 10장 / 단위 테스트, 클래스 (0) | 2022.11.09 |
[독서 기록] 클린 코드 7장, 8장 / 오류 처리, 경계 (0) | 2022.11.09 |