[SE] 7장 상세 설계

7장 상세 설계

 - 아키텍처 설계에서 주요 요소(컴포넌트, 모듈, 서브시스템)을 나열하고
   요소들 간의 상호작용 관계를 파악한 다음, 

 - 상세 설계에서 주요 요소 내부를 설계

 - [그림 7.1]

    서스시스템 설계 => 7.1절 디자인 패턴
    클래스/메소드 상세 설계 => 7.2절 클래스 설계

 - 이 외에도 필요한

    사용자 인터페이스 설계 => 7.3절
    데이터베이스 설계 => 7.4절


7.1 디자인 패턴

디자인 패턴

 - 소프트웨어 설계에서 자주 반복되어 나타나는 문제와 
   이 문제에 대한 일반적인 설계 해결 방법

GoF (Gang of Four, 4인방)이 정리한 23가지 디자인 패턴 목록

 - [책] Design Patterns: Elements of Reusable Object-Oriented Software
   : E. Gamma, R. Helm, R. Johnson, J. Vlissides

 - 3가지 디자인 패턴 유형

   : Creational patterns - 객체 생성과 조합에 대한 패턴들

   : Structural patterns - 클래스와 객체의 구성에 대한 패턴들

   : Behavioral patterns - 객체들 역할 분담과 상호작용 방법에 대한 패턴들

Creational Patterns

 - Abstract Factory (*)
 - Factory method
 - Builder
 - Prototype
 - Singleton

Structural Patterns

 - Adapter (*)
 - Bridge
 - Composite (*)
 - Decorator
 - Facade (*)
 - Fligyweight
 - Proxy

Behavioral Patterns

 - Chain of Responsibility
 - Command
 - Interpreter
 - Iterator (*)
 - Mediator
 - Memento
 - Observer (*)
 - State (*)
 - Strategy
 - Template Method
 - Visitor

 => 각 디자인 패턴에 대한 간략한 설명 (Wikipedia) 또는 슬라이드 참고


7.1.1 팩토리 메소드 패턴

 - 클래스를 직접 지정하지 않고 객체를 생성

 - 그림 7.3

 - new Hyundai()나 new Kia()를 하는 대신

   Automobile.createAutomobile()로 객체 생성

   (즉, 객체 생성시 특정 클래스 이름을 지칭하지 않음)

 - 특정 클래스에 대한 의존도를 낮춤

 - 예) Java의 XML 문서에 대한 DOM 트리를 만드는 클래스

    : abstract class DocumentBuilderFactory
           abstract DocumentBuilder newDocumentBuilder()

7.1.2 추상 팩토리 패턴

 - 논리적으로 같은 그룹에 속하는 객체들을 생성하는
   팩토리 메소드를 모아놓은 클래스

 - 추상 팩토리 (abstract factory)
   구상 팩토리 (concrete factory)
    : 팩토리 메소드1
    : 팩토리 메소드2
    : ...
     

7.1.3 어댑터 패턴

 - 서로 호환성이 없는 클래스들을 공통 인터페이스를 통해 호환되도록 만드는 방법
 
 - 슬라이드의 클래스 다이어그램

    TextShape : adapter class
    TextView : adaptee class


7.1.4 싱글톤 패턴

 - 클래스에서 생성할 수 있는 객체를 하나로 제한하는 방법

 - 싱글톤 패턴의 전형적인 코드로 설명 (슬라이드 참고)

 - Calendar 클래스 (Java)

7.1.5 콤포지트 패턴

 - 여러 유사한 객체들을 공통된 인터페이스를 통해 조합하는 방법

 - P.373 (Before) 

   public class Computer {
     private Body body;
     private Keyboard keyboard;
     private Monitor monitor;

     public void addBody(Body body) { this.body = body; }
     public void addKeyboard(Keyboard keyboard) { this.keyboard = keyboard; }
     public void addMonitor(Monitor monitor) { this.monitor = monitor; }

     public int getPrice() {
      return body.getPrice() + keyboard.getPrice() + monitor.getPrice(); 
     }

     public int getPower() {
      return body.getPower() + keyboard.getPower() + monitor.getPower(); 
     }
   }

   public class Client {
     public static void main(String[] args) {
      Body body = new Body(...);
      Keyboard keyboard = new Keyboard(...);
      Monitor monitor = new Monitor(...);

      Computer computer = new Computer();
      computer.addBody(body);
      computer.addKeyboard(keyboard);
      computer.addMonitor(monitor);

      System.out.println( computer.getPrice() );
      System.out.println( computer.getPower() );
     }
   }

   문제점: 위 클래스에 새로운 타입의 장치를 추가한다면 
           Client 클래스와 Computer 클래스 둘 다 수정해야 함

 - 해결책: 

   public abstract class ComputerDevice {
     public abstract int getPrice();
     public abstract int getPower();
   }

   public class Keyboard extends ComputerDevice {
     public int getPrice() { ... }
     public int getPower() { ... }
   }

   public class Body extends ComputerDevice {
     public int getPrice() { ... }
     public int getPower() { ... }
   }

   public class Monitor extends ComputerDevice {
     public int getPrice() { ... }
     public int getPower() { ... }
   }

   public class Computer extends ComputerDevice {
     private List components = new ArrayList();

     public void addComponent(ComputerDevice component) { 
      components.add(component);
     }
     public void removeComponent(ComputerDevice component) { 
      components.remove(component);
     }

     public int getPrice() {
      int price = 0;
      for (ComputerDevice component : components)
         price += component.getPrice();
      return price; 
     }

     public int getPower() {
      int power = 0;
      for (ComputerDevice component : components)
         power += component.getPower();
      return power; 
     }
   }

   public class Client {
     public static void main(String[] args) {
      Body body = new Body(...);
      Keyboard keyboard = new Keyboard(...);
      Monitor monitor = new Monitor(...);

      Computer computer = new Computer();
      computer.addComponent(body);
      computer.addComponent(keyboard);
      computer.addComponent(monitor);

      System.out.println( computer.getprice() );
      System.out.println( computer.getPower() );
     }
   }

 - 스피커를 새로 추가할 때 (P.384) Client 클래스만 수정
   Computer 클래스를 변경할 필요가 없음


7.1.6 반복자 패턴

 - 객체의 원소들을 순차적으로 따라가는 방법 (원소들의 배치나 표현과 독립적)

 - Java의 Iterator, Iterable (cf. for each 문장)

   interface Iterator {
     boolean hasNext();
     E next();
     void remove();
   }

   interface Iterable {
     Iterator iterator();
   }

   class MyCollection implements Iterable {
     Integer x1, x2, x3;
     int i;

     Iterator iterator() {
       return new MyIterator();
     }
   }

   class MyIterator implements Iterator {
     MyCollection c;
     public MyIterator(MyCollection c) {
       this.c = c;
       c.i = 1;
       c.x1 = 123; c.x2 = 456; c.x3 = 789;
     }
     public boolean hasNext() {
       return c.i == 1 || c.i == 2 || c.i == 3;
     }
     public Integer next() {
       if (c.i == 1) { c.i++; return c.x1; }
       else if (c.i == 2) { c.i++; return c.x2; }
       else if (c.i == 3) { c.i++; return c.x3; }
       else return null;
     }
     public void remove() {
       // do nothing
     }
   }
   
   public class Client {
    public static void main(String[] args) {

      // Iterator 패턴을 직접 사용하면
      MyCollection c = new MyCollection();
      Iterator iter = c.iterator();

      while (iter.hasNext()) {
       System.out.println( iter.next() )
      }

      // Java의 for each 문을 사용하면
      MyCollection c = new MyCollection();

      for (Integer i : c) {
       System.out.println( i );
      }

    }
   }

 - 객체 내부의 자료 구조를 모르더라도 또는 변경되더라도
   개별 자료를 모두 확인할 수 있음
   
   

7.1.7 옵서버 패턴

 - 하나의 객체에 여러 옵서버 객체를 연결하여(subscribe) 
   그 객체에서 특정 이벤트가 발생할 때 옵서버 객체들에게 알리는(publish) 방법

   cf. publish/subscribe 패턴

 - Subject는 Observer에게 통지할 때 어떤 객체가 받는지 모르더라도 가능하다.
   따라서, 둘 사이의 결합도를 낮출 수("loosely-coupling") 있다.


7.1.8 상태 패턴

 - 내부 상태가 변하면 그에 따라 객체의 행동을 변경하는 방법
 - 상태를 객체로 표현하는 방법

 - 예제: Light 클래스

   class Light {
     private static final int ON  = 0;
     private static final int OFF = 1;
     private int state;

     public Light() { state = OFF; }

     public void on_button() {
       if (state == ON) { /* do nothing */ }
       else { state = ON; }
     }

     public void off_button() {
       if (state == ON) { state = OFF; }
       else { /* do nothing */ }
     }
   }   

   상태 패턴으로 변경한 코드

   interface State {
     public void on_button(Light light);
     public void off_button(Light light);
   }

   class ON implements State {
     public void on_button(Light light) {
     }

     public void off_button(Light light) {
       light.setState( new OFF() );
     }
   }

   class OFF implements State {
     public void on_button(Light light) {
       light.setState( new ON() );
     }

     public void off_button(Light light) {
     }
   }

   class Light {
     private State state;

     public Light() {
      state = new OFF();
     }

     pubilc void setState(State state) { this.state = state; }

     public void on_button() {
      state.on_button(this);
     }

     public void off_button() {
      state.off_button(this);
     }
   }

   스테이트 패턴으로 변경한 결과 각 상태에서 ON/OFF 버튼을 누를 때
   if문이 필요없다. ON/OFF 이외에 새로운 상태를 추가할 때 이 장점이
   더욱 부각된다.


   class SLEEP implements State {  // 새로 추가
     public void on_button(Light light) {
       light.setState( new ON() );
     }

     public void off_button(Light light) {
       light.setState( new OFF() );
     }
   }


   class ON implements State {
     public void on_button(Light light) {
       light.setState( new SLEEP() );   // 추가 변경
     }

     ...
   }

   원래 Light 클래스에 SLEEP 상태를 추가하면 다음과 같이 작성할 수 있다.

   class Light {
     ...
     private static final int SLEEP = 2;  // 새로운 상태값 추가
     ...

     public void on_button() {
       if (state == ON) { state = SLEEP; }
       else if (state == SLEEP) { state = ON; } // 추가 변경
       else { state = ON; }
     }

     public void off_button() {
       if (state == ON) { state = OFF; }
       else if (state == SLEEP) { state = OFF; } // 추가 변경
       else { /* do nothing */ }
     }
   } 


  - 장점:

    1) 관리해야 할 상태의 수가 늘어나면 하나의 길고 복잡한
       if/swich문을 작성해야 하는데, 
       스테이트 패턴으로 작성하면 각 상태에 대한 Behavior를 구현한 코드를
       State의 서브 클래스에 따로 따로 작성하여 분산시킬 수 있다.

       길고 복잡한 코드는 수정하거나 유지 보수하기 어렵다. 
       
    2) 상태 전이를 State 서브 클래스의 메소드로 명확하게 표현. 
       원래 버전에서는 state 변수에 특정 상태 값을 대입함으로써 상태 전이를 표현

    3) State 객체 자체는 별도의 attribute가 없어서 공유 가능


    

7.1.9 퍼싸드 패턴

 - 서브 시스템의 세부 클래스들을 직접 사용하지 못하도록 막고
   이 클래스들을 대표하는 클래스들을 통해서 간적접으로 사용하게 하는 방법

 - 예제) 컴파일러 서브 시스템, 가상 메모리 프레임워크

 - 장점 : 
   
   1) 사용자와 서브 시스템 간의 결합도를 줄인다.

   2) 서브 시스템 내부의 특정 클래스들만 공개하고 나머지는 숨길 수 있다.