24 May 2017

Implementation Patterns 01

Class

  • 객체지향프로그래밍
    • 클래스: 비슷한 성질을 가진 것을 총칭
    • 객체: 클래스가 구체화된 것
  • 클래스 관련 패턴
    • 클래스: “특정 데이터들은 함께 사용하는데 그에 관련된 로직은 이 것이다.” 라고 이야기하고 싶을 때 사용
    • 단순한 상위클래스 이름: 클래스 계층에서 상위에 위치하는 클래스 이름은 단순하게
    • 한정적 하위클래스 이름: 상위클래스와의 유사점과 차이점이 분명히 드러나는 이름을 짓는다.
    • 추상 인터페이스: 인터페이스와 구현을 분리
    • 인터페이스: 자주 변하지 않는 추상 인터페이스에는 자바의 interface 를 사용
    • 버전 인터페이스: 하위 인터페이스를 사용해 기존 인터페이스를 안전하게 확장
    • 추상 클래스: 자주 바뀔 것 같은 추상 인터페이스에는 추상 클래스 사용
    • 값 객체: 산술 값처럼 동작하는 객체를 사용
    • 특화: 관련된 연산 사이의 유사점 및 차이점을 분명하게 나타낸다.
    • 하위 클래스: 1차원적 변화는 하위클래스를 사용해서 표현
    • 구현자: 연산 내용이 바뀌었다면 기존 메소드를 오버라이드해서 사용
    • 내부 클래스: 클래스 내부에서 유용하게 사용할 수 있는 코드를 모아 전용 클래스로 사용
    • 인스턴스별 행동: 인스턴스에 따라 로직에 변화를 줌
    • 조건문: 명시적 조건에 따라 로직에 변화를 줌
    • 위임: 여러 종류의 객체 중 하나에 위임해서 로직에 변화를 줌
    • 플러그인 선택자: 리플렉션을 이용한 메소드 호출로 로직에 변화를 줌
    • 익명 내부 클래스: 필요한 메소드에서 한두 개의 메소드만 오버라이드하는 객체를 만들어서 사용
    • 라이브러리 클래스: 마땅히 들어갈 곳이 없는 기능들을 묶어 정적 메소드로 표현


Class 사용

  • 클래스를 사용하는 기본 이유는 데이터가 로직에 비해 빈번하게 변화하기 때문이다.
    • 로직과 데이터가 함께 사용되며, 로직은 데이터에 비해 변화율이 낮다.
    • 내부의 데이터는 관련 로직에 의해 변화하며, 각 데이터들의 변화율은 비슷하다.
  • 효과적인 프로그래밍을 위해, 로직을 클래스 단위로 어떻게 구성해야 하는지 또는 로직 사이의 차이점을 어떻게 효과적으로 표현하는지 알아야 한다.


단순한 상위클래스 이름

  • 은유를 통해 단어 하나만으로 여러 연상작용을 통해 여러 관련 정보와 내포된 의미를 전달할 수 있도록 한다.


한정적 하위클래스 이름

  • 하위클래스의 이름은 상위클래스와의 유사점과 차이점을 나타내야 한다.
    • 어느 정도 간결성은 포기하더라도, 표현성을 택하는 것이 좋다.
    • 이름은 가장 유사한 상위클래스의 이름을 바탕으로 짓는다.

하위클래스가 상위클래스의 매커니즘만 빌려 사용하고, 그 자체로 상위클래스와 같이 중요한 위치를 지니면 단순한 이름을 짓는 것이 좋다.


추상 인터페이스

  • 인터페이스: 구현이 빠진 여러 연산의 집합
    • 인터페이스 추가에는 비용이 발생한다.
    • 인터페이스를 통해 유연성을 얻을 수 있을 때 사용


인터페이스

  • 자바의 interface: 다중 상속의 유연성을 제공하면서도, 복잡성과 모호성을 갖고 있지 않은 균형 잡힌 메커니즘
    • interface 자체를 바꾸는 것은 쉽지 않다.
  • 인터페이스의 이름
    • 인터페이스를 구현이 빠진 클래스로 보는 경우, 상위 / 하위클래스의 이름을 짓는 것처럼 짓는다.
    • 구현클래스의 이름을 간결하게 짓는 것이 커뮤니케이션에 도움이 되는 경우, 인터페이스의 이름은 “I” 로 시작하게 지을 수 있다.


추상 클래스

  • 추상 인터페이스와 실제 구현의 차이를 나타내는 다른 방법은 추상 클래스를 사용

  • 추상 클래스와 자바의 interface 사용의 장단점

    • 인터페이스 수정의 용이성과 단일 클래스가 여러 인터페이스 지원 여부
    • 자바 interface 수정 시, 그 것을 구현한 모든 클래스도 따라 고쳐야 한다.
      • 추상 클래스는 이런 문제가 없이, 기존 설계에 대한 변경 없이 새로운 연산을 얼마든지 추가 가능하다.
    • 추상 클래스의 단점은 각 클래스가 단 1개의 상위클래스만 지정 가능하다.


버전 인터페이스

  • 인터페이스를 바꾸고 싶은데 바꿀 수 없는 경우 버전 인터페이스 사용
    • 새로운 인터페이스를 기존 인터페이스로부터 상속받아 새로운 연산 추가 가능
    • 인터페이스 변형에는 별도의 로직이 필요
      • 새로운 인터페이스가 많아진다는 것은 별도의 로직도 많이 필요 -> 설계를 수정할 때가 된 신호
interface Command {
  void run();
}

interface ReversibleCommand extends Command {
  void undo();
}

Command recentCommand = ...;

if (recent instanceof ReversibleCommand) {
  ReversibleCommand downcasted = (ReversibleCommand) recent;
  downcasted.undo();
}


값 객체

  • 함수형 스타일의 연산: 상태를 변화시키지 않으며 새로운 값을 생성
    • 일시적이더라도 고정적인 상황을 표현하고자 할 때 함수형 스타일, 값 객체 사용
  • 상황이 변하는 경우라면 state를 두는 것이 좋다.

  • 값 객체 구현
    • 생성자에서만 모든 필드를 설정할 뿐, 다른 경로를 통해서는 필드 값을 변경해서는 안된다.
    • 값 객체를 다루는 연산은 언제나 새로운 객체를 생성한다.


하위클래스

  • 적절한 메소드를 오버라이드해서 사용할 경우 기존 연산과 다른 변형을 만들어낼 수 있다.
  • 하위클래스 사용의 문제점
    • 되돌리기가 쉽지 않음
    • 하위클래스를 이해하기 위해 상위클래스도 이해해야 함
    • 하위클래스가 상위클래스 세부 구현 특성에 의존할 수 있으므로 상위클래스 수정이 위험
    • 상속 계층이 복잡해지면 모든 문제가 심화
    • 동적으로 변화하는 로직을 나타낼 수 없다.
      • 변화하는 로직을 나타낼 경우, 조건문이나 위임을 사용
  • 하위클래스의 올바른 사용
    • 상위클래스의 로직을 여러 개의 메소드로 잘게 분할
    • 메소드가 너무 크면 하위클래스에 코드를 복사해서 수정해야 한다.
      • 이럴 경우 2개 클래스 사이의 의존성이 생긴다.


내부클래스

  • 어떤 연산을 표현하기 위해 클래스가 필요한데, 따로 만들지 않을 경우, 내부에 작은 전용클래스를 사용
  • 자바의 이너클래스는 외부클래스에 대한 정보를 전달받을 수 있다.
    • 외부클래스의 인스턴스와 분리된 내부클래스를 사용하려면 static으로 선언


인스턴스별 행위

  • 모든 클래스의 인스턴스들은 보통 같은 로직을 공유한다.
    • 인스턴스 별로 다른 행동을 수행하게 할 경우, 특정 인스턴스의 행동을 이해하기 위해 실례를 보거나 데이터의 흐름을 분석해야 한다.
    • 코드를 쉽게 이해시키기 위해서는 인스턴스 별 행동을 지양


조건문

  • 가장 단순한 인스턴스 별 행동의 형태는 if 문이나 switch 문을 사용
    • 인스턴스 별 행동을 지원하면서도 모든 로직이 하나의 클래스에 들어있다.
    • 수행 경로가 다양하면 결함 발생률이 높음.
      • 조건문이 많으면 많을수록 프로그램의 안정성이 떨어진다.
public void display() {
  switch (getType()) {
    case RECTANGLE:
      ...
    case OVAL:
      ...
    case TEXT:
      ...
    default:
      ...
  }
}

위와 같은 로직에서 새로운 조건을 추가하면 기존 코드에 문제가 발생하는 위험을 감수하고 직접 수정해야 한다. 상위 클래스일 경우, 하위 클래스를 사용하는 사람들은 변경 내용을 모두 공유받아야 한다.

  • 조건문을 지양하기 위해 하위클래스 사용이나 위임을 사용할 수 있다.
    • 중복되는 조건부 로직이나 분기문의 결과에 따라 로직이 달라지는 경우


02.png


위임

  • 각 인스턴스에서 다른 로직을 수행하는 방법으로 위임이 있다.
  • 위임: 몇 가지 객체 중 하나를 선택하여 작업을 미루는 것
    • 공통 사용 로직: 위임클래스를 참조하는 클래스에서 구현
    • 변형 로직: 여러 위임클래스에서 각각 구현
  • 인스턴스 별 행동을 지원할 뿐만 아니라, 코드 공유에도 사용할 수 있다.
public void mouseDown() {
  switch(getTool()) {
    case SELECTING:
      ...
    case CREATEING:
      ...
    default:
      ...
  }
}

위 코드는 새로운 조건이 발생할 경우 코드를 수정해야하고, 비슷한 분기문이 다른 메소드에도 있을 경우 같이 변경해야 한다.

public void mouseDown() {
  getTool().mouseDown();
}

위 코드와 같이 위임을 통해 코드에 유연성을 부여할 수 있다. 새로운 조건이 발생할 경우 기존 코드 수정없이, 위임 객체를 하나 더 생성하면 된다.


익명 내부클래스

  • 한 곳에서만 사용되는 클래스를 생성해서 일부 메소드를 오버라이드한 후, 지역적으로만 사용
  • 효율적으로 사용하려면, API가 매우 간단하거나 상위 클래스가 대부분의 구현을 담당하고 있어서 쉽게 익명 내부클래스를 구현할 수 있어야 한다.
    • 별도로 테스트하기가 어려우므로, 복잡한 로직에 적합하지 않다.


라이브러리 클래스

  • 어떤 객체에도 어울리지 않는 기능은 빈 클래스를 만들어서 정적 메소드로 구현하여 라이브러리 클래스로 생성
    • 메소드가 많아질 경우, 객체 지향 프로그래밍의 장점을 잃으므로, 가능하다면 라이브러리 클래스를 객체로 변환하는 것이 좋다.

Tags:
Stats:
0 comments