State Pattern

스테이트 패턴을 정리한다.

FSM (Finite State Machine)

지하철 개찰구가 동작하는 방식에서 간단한 유한 상태 기계 (FSM: Finite State Machine) 의 예를 보자.
아래 다이어그램을 STD (State Transition Diagram) 이라고 한다.

  • 기계가 Locked 상태에서 coin 이벤트를 받으면, Unlocked 상태로 전이가 되고 unlock 행동을 호출
  • 기계가 Unlocked 상태에서 pass 이벤트를 받으면, Locked 상태로 전이가 되고 lock 행동을 호출

같은 의미로 STT (State Transition Table) 로도 표현할 수 있다.

1
2
Locked      coin    Unlocked    unlock
Unlocked pass Lokced lock

이 도구의 장점은, 설계자가 이상한 조건 또는 그 조건을 다룰 행위의 정의되지 않은 조건을 찾기 쉽다.
에를 들어,

  • Unlocked 상태에서 coin 이벤트를 다루는 전이가 없고,
  • Locked 상태에서 pass 이벤트를 다루는 전이도 없다.

추가해보자.

Read more

Abstract Server, Adapter Pattern

추상 서버 패턴과 어뎁터 패턴을 정리한다.

탁상 스탠드 설계

탁상 스탠드 내부의 소프트웨어를 설계해보자.
단순하게 다음과 같이 설계할 수 있다.

이 설계의 문제는,

  1. DIP 위반: Light 로 향하는 의존 관계가 구체 클래스에 대한 의존 관계이다.
  2. Switch 는 Light 이외의 객체를 제어할 수 있도록 확장이 힘들다.

추상 서버 패턴

Switch 와 Light 사이에 인터페이스를 도입해서, Switch 가 무엇이든 제어할 수 있게 만들 수 있다.

여기서 주목할 것은, 인터페이스의 이름이 ILight 가 아니라 Switchable 이다.
즉, 인터페이스의 클라이언트인 Switch 를 위한 쪽으로 인터페이스 이름을 지었다.
인터페이스는 파생 클래스나 파생 인터페이스에 속하는 것이 아니라, 클라이언트에 속하기 때문이다.

Read more

Composite Pattern

Composite Pattern 을 정리한다.

Sensor-Command 예제

Sensor 는 무엇을 감지하면 Command 의 do() 를 호출한다.
Command 를 하나 이상 실행해야하는 경우도 있다.
하나 이상 실행해야할 때는, Sensor 가 목록을 순회하면서 각 Command 의 do() 를 호출한다.

Composite Pattern 적용

Command 에 복수성 개념을 추가해서 다음과 같이 Composite 패턴을 사용할 수 있다.

다수성

composite 패턴을 사용하면 일대다 관계 없이도 일대다의 행위를 할 수 있다.
클래스의 클라이언트마다 목록 관리와 순환 코드를 중복해서 사용하는 대신, 그 코드가 컴포지트 클래스에서만 사용하게 된다.


Read more

Factory Pattern

Factory Pattern 을 정리한다.

new Circle 예시

DIP 에 따르면, 구체 클래스에 의존하지 말고 추상 클래스에 의존해야한다.
다음 코드는 DIP 를 위반한다.

1
Circle c = new Circle(origin, 1)

Circle 은 구체 클래스이기 때문에, Circle 인스턴스를 생성하는 모듈은 DIP 를 어긴다.

다만, DIP 위반이 해롭지 않은 경우가 있다.
구체 클래스가 쉽게 변경되는 종류의 클래스가 아닌 경우이다. ex) String

팩토리 패턴 적용

팩토리 패턴을 사용하면 추상 인터페이스에만 의존하면서 구체적인 인스턴스를 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface Shape

public interface ShapeFactory {
public Shape makeCircle();

public Shape makeSquare();
}

public class ShapeFactoryImplementation implements ShapeFactory {

public Shape makeCircle() {
return new Circle();
}

public Shape makeSquare() {
return new Square();
}
}

trade-off

Read more

Principles of Package Design

큰 애플리케이션을 조직화하기 위해서는, 클래스보다 더 큰 package 가 필요하다.
패키지의 응딥도에 대한 원칙과 결합도에 대한 원칙을 정리한다.

  1. 패키지 응집도에 대한 원칙: 클래스를 패키지에 할당하는 것을 도와준다.
  2. 패키지 결합도에 대한 원칙: 패키지 간 관계를 결정하는 것을 도와준다.

패키지 응집도에 대한 원칙

패키지 응집도에 대한 원칙은 아래 세 가지가 있다.

  1. 재사용 릴리즈 등가 원칙 (REP: Reuse-Release Equivalent Principle)
  2. 공통 재사용 원칙 (CRP: Common-Reuse Principle)
  3. 공통 폐쇄 원칙 (CCP: Common-Closure Principle)

재사용 릴리즈 등가 원칙

재사용의 단위는 릴리즈 단위다.

재사용하는 모든 것은 반드시 릴리즈 된 다음에 추적 가능해야한다.
사용자들에게 필요한 통보, 안정성, 지원에 대한 보장을 제공하는 추적 시스템이 먼저 있어야 재사용성이라는 말을 할 수 있다.
재사용성은 패키지에 기반을 두어야하기 때문에, 재사용 가능한 패키지는 재사용 가능한 클래스를 포함해야한다.

또한, 재사용성 뿐만 아니라 재사용자가 누구인지도 고려해야한다.
컨테이너 클래스 라이브러리는 사용하고 싶지만, 금융 관련 프레임워크에는 관심 없는 사람이 있을 수 있다.
패지지 안의 모든 클래스는 동일한 재사용자를 대상으로 해야한다.

공통 재사용 원칙

Read more

Null Object Pattern

널 오브젝트 패턴을 정리한다.

기존의 not null check

1
2
3
4
Employee e = DB.getEmployee("Bob");
if (e != null && e.isTimeToPay(today)) {
e.pay();
}

not null 확인은 관용적인 표현이지만 보기 싫고, 에러가 발생하기 쉽다.
DB.getEmployee 가 null 대신 예외를 발생시키면 에러가 발생할 위험을 감소시킬 수 있다.
하지만, try/catch 블록이 추가되어야한다.

Null Object Pattern 적용

널 오브젝트 패턴을 사용하면 null 검사 코드가 제거되고 코드를 단순화시킨다.

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
public class DB {
public static Employee getEmployee(String name) {
return Employee.NULL;
}
}

public interface Employee {
public boolean isTimeToPay(Date payDae);

public void pay();

public static final Employee NULL = new Employee() {

public boolean isTimeToPay(Date payDae) {
return false;
}

public void pay() {

}
}
}

Employee e = DB.getEmployee("Bob");
if (e.isTimeToPay(today)) {
e.pay();
}

없는 직원을 익명 내부 클래스로 만들어서, 인스턴스가 오직 하나임을 보장한다.
없는 직원 내부에서는, isTimeToPay 는 false 를 반환하고 아무 임금도 지급하지 않는다.


클린 소프트웨어 <로버트 C.마틴>

Read more

Singleton && MonoState Pattern

싱글톤 패턴과 모노스테이트 패턴을 정리한다.

Singleton Pattern

단 하나의 인스턴스를 가져야하는 클래스가 있을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
private static Singleton theInstance = null;

private Singleton() {

}

public static Singleton Instance() {
if (theInstance == null) {
theInstance = new Singleton();
}

return theInstance;
}
}

Monostate Pattern

단일성을 이루기 위한 또 다른 방법이다.

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
public class Monostate {
private static int itsX = 0;

public Monostate() {

}

public void setX(int x) {
itsX = x;
}

public int getX() {
return itsX;
}
}

public class Test {

public void testInstanceBehavesAsOne() {
Monostate m1 = new Monostate();
Monostate m2 = new Monostate();

for (int x = 0; x < 10; x++) {
m1.setX(x);
assetEquals(x, m2.getX());
}
}
}

싱글톤 패턴은 단일성 구조를 강제하여 둘 이상의 인스턴스가 생성되는 것을 막는다.
모노스테이트 패턴은 구조적인 제약은 없지만, 단일성이 있는 행위를 강제한다.


클린 소프트웨어 <로버트 C.마틴>

Read more

Facade && Mediator Pattern

퍼사드 패턴과 미디에이터 패턴을 정리한다.

Facade Pattern

복잡하고 일반적인 인터페이스를 가진 객체 그룹에 간단하고 구체적인 인터페이스를 제공할 때 사용된다.

DB 클래스가 Application 이 java.sql 의 구체적인 내용을 알 필요가 없게 보호하고 있다.
즉, java.sql 의 일반성과 복잡성을 간단하고 구체적인 인터페이스 뒤에 숨긴다.

퍼사드 패턴의 사용은 개발자가 모든 DB 호출이 DB 클래스를 통과해야한다 라는 규정을 의미힌다.
Application 코드의 어떤 부분이 직접 java.sql 을 사용한다면 규정을 위반하는 것이다.
이 처럼, 퍼사드는 자신의 정책을 Application 에 적용하고 있다.

Mediator Pattern

미디에이터 패턴도 정책을 적용한다.
퍼사드가 자신의 정책을 가시적이고 강제적인 방식으로 적용했다면, 미디에이터 패턴은 자신의 정책을 은밀하고 강제적이지 않은 방식으로 적용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class QuickEntryMediator {

private JTextField itsTextField;
private JList itsList;

public QuickEntryMediator(JTextField t, JList l){
itsTextField = t;
itsList = l;

itsTextField.getDocument().addDocumentListerner(new DocumentListener() {
public void changeUpdate(DocumentEvent e){
textFieldChanged();
}
})
// ...
}

private void textFieldChanged() {
// ...
}
}

QuickEntryMediator 는 익명 DocumentListener 를 JTextField 에 등록한다.
이 리스너는 텍스트에 변화가 있을 때마다, textFieldChanged 를 호출한다.

Read more

Template Method && Strategy Pattern

템플릿 메서드 패턴과 스트레티지 패턴을 정리한다.

Template Method Pattern

버블 정렬을 예로 들어보쟈.

1
2
3
4
5
6
7
8
9
10
11
public abstract class BubbleSorter {

protected int doSort() {
// ...
if(outOfOrder(index)) swap(index);
// ...
}

protected abstract void swap(int index);
protected abstract boolean outOfOrder(int index);
}

BubbleSorter 로 다른 어떤 종류의 객체든 정렬할 수 있는 간단한 파생 클래스를 만들 수 있다.
ex) IntBubbleSorter, DoubleBubbleSorter …

템플릿 메서드 패턴은 고전적인 재사용 형태의 하나이다.
일반적인 알고리즘은 기반 클래스에 있고, 다른 구체적인 내용에서 상속된다.
하지만, 상속은 아주 강한 관계여서 파생 클래스에서 필연적으로 기반 클래스에 묶이게 된다.

Strategy Pattern

버블정렬에 적용해보자.

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
public abstract class BubbleSorter {

private SortHandle sortHandle = null;

public BubbleSorter(SortHandle handle){
sortHandle = handle;
}

public int sort(Object array){
// ...
if (sortHandle.outOfOrder(index)) sortHandle.swap(index)
// ...
}
}

public interface SortHandle {
public void swap(int index);
public boolean outOfOrder(int index);
public int length();
public void setArray(Object array);
}

public class IntSortHandle implements SortHandle {
private int[] array = null;

// ...
}

IntSortHandle 의 swap 과 outOfOrder 메서드는 버블 정렬 알고리즘에 직접 의존하고 있지 않다.
그래서, BubbleSorter 가 아니라 다른 Sorter 의 구현과도 사용할 수 있다.

Read more

Command Pattern && Active Object Pattern

커멘드 패턴과 액티비 오브젝트 패턴을 정리한다.

Command Pattern

1
2
3
4
public interface Command
{
public void do();
}

대부분의 클래스는 한 벌의 메서드와 변수의 집합을 결합시키는데, 커멘드 패턴은 그렇지 않다.
오히려 함수를 캡슐화해서 변수에서 해방시킨다. 즉, 함수의 역할을 클래스 수준으로 격상시킨다.

AddEmployeeTransaction

직원들의 DB 를 관리하는 시스템을 작성하는 예를 보자.
command 객체는 검증되지 않은 데이터를 위한 저장소 역할을 하고, 검증 메서드를 구현하고 마지막으로 트랜잭션을 실행하는 메서드를 구현한다.

예를 들어, AddEmployeeTransaction 은 Employee 가 포함하는 데이터를 모두 가지고 있다.
validate 메서드는 모든 데이터를 살펴보고 문법적으로나 의미적으로 오류가 없는지 확인한다.
또한, 트랜잭션의 데이터가 기존의 데이터베이스 상태와 일치하는지 확인할 수도 있다.
execute 메서드는 검증된 데이터를 사용해 데이터베이스를 갱신한다.

물리적, 시간적 분리

이 방식의 장점은, 사용자에게 데이터를 받는 코드와 데이터 검증 및 작업 코드를 분리할 수 있다는 것이다.
검증과 실행 코드를 따로 떼어 AddEmployeeTransaction 클래스에 넣으면, 이 코드를 입력 인터페이스에서 물리적으로 분리한 것이 된다.

Read more