24 May 2017
Implementation Patterns 01
Class
- 객체지향프로그래밍
- 클래스: 비슷한 성질을 가진 것을 총칭
- 객체: 클래스가 구체화된 것
- 클래스 관련 패턴
- 클래스: “특정 데이터들은 함께 사용하는데 그에 관련된 로직은 이 것이다.” 라고 이야기하고 싶을 때 사용
- 단순한 상위클래스 이름: 클래스 계층에서 상위에 위치하는 클래스 이름은 단순하게
- 한정적 하위클래스 이름: 상위클래스와의 유사점과 차이점이 분명히 드러나는 이름을 짓는다.
- 추상 인터페이스: 인터페이스와 구현을 분리
- 인터페이스: 자주 변하지 않는 추상 인터페이스에는 자바의 interface 를 사용
- 버전 인터페이스: 하위 인터페이스를 사용해 기존 인터페이스를 안전하게 확장
- 추상 클래스: 자주 바뀔 것 같은 추상 인터페이스에는 추상 클래스 사용
- 값 객체: 산술 값처럼 동작하는 객체를 사용
- 특화: 관련된 연산 사이의 유사점 및 차이점을 분명하게 나타낸다.
- 하위 클래스: 1차원적 변화는 하위클래스를 사용해서 표현
- 구현자: 연산 내용이 바뀌었다면 기존 메소드를 오버라이드해서 사용
- 내부 클래스: 클래스 내부에서 유용하게 사용할 수 있는 코드를 모아 전용 클래스로 사용
- 인스턴스별 행동: 인스턴스에 따라 로직에 변화를 줌
- 조건문: 명시적 조건에 따라 로직에 변화를 줌
- 위임: 여러 종류의 객체 중 하나에 위임해서 로직에 변화를 줌
- 플러그인 선택자: 리플렉션을 이용한 메소드 호출로 로직에 변화를 줌
- 익명 내부 클래스: 필요한 메소드에서 한두 개의 메소드만 오버라이드하는 객체를 만들어서 사용
- 라이브러리 클래스: 마땅히 들어갈 곳이 없는 기능들을 묶어 정적 메소드로 표현
Class 사용
- 클래스를 사용하는 기본 이유는 데이터가 로직에 비해 빈번하게 변화하기 때문이다.
- 로직과 데이터가 함께 사용되며, 로직은 데이터에 비해 변화율이 낮다.
- 내부의 데이터는 관련 로직에 의해 변화하며, 각 데이터들의 변화율은 비슷하다.
- 효과적인 프로그래밍을 위해, 로직을 클래스 단위로 어떻게 구성해야 하는지 또는 로직 사이의 차이점을 어떻게 효과적으로 표현하는지 알아야 한다.
단순한 상위클래스 이름
- 은유를 통해 단어 하나만으로 여러 연상작용을 통해 여러 관련 정보와 내포된 의미를 전달할 수 있도록 한다.
한정적 하위클래스 이름
- 하위클래스의 이름은 상위클래스와의 유사점과 차이점을 나타내야 한다.
- 어느 정도 간결성은 포기하더라도, 표현성을 택하는 것이 좋다.
- 이름은 가장 유사한 상위클래스의 이름을 바탕으로 짓는다.
하위클래스가 상위클래스의 매커니즘만 빌려 사용하고, 그 자체로 상위클래스와 같이 중요한 위치를 지니면 단순한 이름을 짓는 것이 좋다.
추상 인터페이스
- 인터페이스: 구현이 빠진 여러 연산의 집합
- 인터페이스 추가에는 비용이 발생한다.
- 인터페이스를 통해 유연성을 얻을 수 있을 때 사용
인터페이스
- 자바의 interface: 다중 상속의 유연성을 제공하면서도, 복잡성과 모호성을 갖고 있지 않은 균형 잡힌 메커니즘
- interface 자체를 바꾸는 것은 쉽지 않다.
- 인터페이스의 이름
- 인터페이스를 구현이 빠진 클래스로 보는 경우, 상위 / 하위클래스의 이름을 짓는 것처럼 짓는다.
- 구현클래스의 이름을 간결하게 짓는 것이 커뮤니케이션에 도움이 되는 경우, 인터페이스의 이름은 “I” 로 시작하게 지을 수 있다.
추상 클래스
버전 인터페이스
- 인터페이스를 바꾸고 싶은데 바꿀 수 없는 경우 버전 인터페이스 사용
- 새로운 인터페이스를 기존 인터페이스로부터 상속받아 새로운 연산 추가 가능
- 인터페이스 변형에는 별도의 로직이 필요
- 새로운 인터페이스가 많아진다는 것은 별도의 로직도 많이 필요 -> 설계를 수정할 때가 된 신호
interface Command {
void run();
}
interface ReversibleCommand extends Command {
void undo();
}
Command recentCommand = ...;
if (recent instanceof ReversibleCommand) {
ReversibleCommand downcasted = (ReversibleCommand) recent;
downcasted.undo();
}
값 객체
하위클래스
- 적절한 메소드를 오버라이드해서 사용할 경우 기존 연산과 다른 변형을 만들어낼 수 있다.
- 하위클래스 사용의 문제점
- 되돌리기가 쉽지 않음
- 하위클래스를 이해하기 위해 상위클래스도 이해해야 함
- 하위클래스가 상위클래스 세부 구현 특성에 의존할 수 있으므로 상위클래스 수정이 위험
- 상속 계층이 복잡해지면 모든 문제가 심화
- 동적으로 변화하는 로직을 나타낼 수 없다.
- 변화하는 로직을 나타낼 경우, 조건문이나 위임을 사용
- 하위클래스의 올바른 사용
- 상위클래스의 로직을 여러 개의 메소드로 잘게 분할
- 메소드가 너무 크면 하위클래스에 코드를 복사해서 수정해야 한다.
- 이럴 경우 2개 클래스 사이의 의존성이 생긴다.
내부클래스
- 어떤 연산을 표현하기 위해 클래스가 필요한데, 따로 만들지 않을 경우, 내부에 작은 전용클래스를 사용
- 자바의 이너클래스는 외부클래스에 대한 정보를 전달받을 수 있다.
- 외부클래스의 인스턴스와 분리된 내부클래스를 사용하려면 static으로 선언
인스턴스별 행위
- 모든 클래스의 인스턴스들은 보통 같은 로직을 공유한다.
- 인스턴스 별로 다른 행동을 수행하게 할 경우, 특정 인스턴스의 행동을 이해하기 위해 실례를 보거나 데이터의 흐름을 분석해야 한다.
- 코드를 쉽게 이해시키기 위해서는 인스턴스 별 행동을 지양
조건문
- 가장 단순한 인스턴스 별 행동의 형태는 if 문이나 switch 문을 사용
- 인스턴스 별 행동을 지원하면서도 모든 로직이 하나의 클래스에 들어있다.
- 수행 경로가 다양하면 결함 발생률이 높음.
- 조건문이 많으면 많을수록 프로그램의 안정성이 떨어진다.
public void display() {
switch (getType()) {
case RECTANGLE:
...
case OVAL:
...
case TEXT:
...
default:
...
}
}
위와 같은 로직에서 새로운 조건을 추가하면 기존 코드에 문제가 발생하는 위험을 감수하고 직접 수정해야 한다.
상위 클래스일 경우, 하위 클래스를 사용하는 사람들은 변경 내용을 모두 공유받아야 한다.
- 조건문을 지양하기 위해 하위클래스 사용이나 위임을 사용할 수 있다.
- 중복되는 조건부 로직이나 분기문의 결과에 따라 로직이 달라지는 경우
위임
- 각 인스턴스에서 다른 로직을 수행하는 방법으로 위임이 있다.
- 위임: 몇 가지 객체 중 하나를 선택하여 작업을 미루는 것
- 공통 사용 로직: 위임클래스를 참조하는 클래스에서 구현
- 변형 로직: 여러 위임클래스에서 각각 구현
- 인스턴스 별 행동을 지원할 뿐만 아니라, 코드 공유에도 사용할 수 있다.
public void mouseDown() {
switch(getTool()) {
case SELECTING:
...
case CREATEING:
...
default:
...
}
}
위 코드는 새로운 조건이 발생할 경우 코드를 수정해야하고, 비슷한 분기문이 다른 메소드에도 있을 경우 같이 변경해야 한다.
public void mouseDown() {
getTool().mouseDown();
}
위 코드와 같이 위임을 통해 코드에 유연성을 부여할 수 있다. 새로운 조건이 발생할 경우 기존 코드 수정없이, 위임 객체를 하나 더 생성하면 된다.
익명 내부클래스
- 한 곳에서만 사용되는 클래스를 생성해서 일부 메소드를 오버라이드한 후, 지역적으로만 사용
- 효율적으로 사용하려면, API가 매우 간단하거나 상위 클래스가 대부분의 구현을 담당하고 있어서 쉽게 익명 내부클래스를 구현할 수 있어야 한다.
- 별도로 테스트하기가 어려우므로, 복잡한 로직에 적합하지 않다.
라이브러리 클래스
- 어떤 객체에도 어울리지 않는 기능은 빈 클래스를 만들어서 정적 메소드로 구현하여 라이브러리 클래스로 생성
- 메소드가 많아질 경우, 객체 지향 프로그래밍의 장점을 잃으므로, 가능하다면 라이브러리 클래스를 객체로 변환하는 것이 좋다.