열거형 데이터의 처리

Java SE 5.0부터 소개된 enum 을 사용하기 이전의 코드는 열거형 데이터를 구현하기 위해 다음과 같은 방법을 써왔다.

// 정수 타입의 열거형 패턴 이용
package exam.enums;

// 자바 1.5 이전 형식의 코드를 사용한 요일 표현
public class Aweekday {
public static final int SUNDAY = 0;
public static final int MUNDAY = 1;
public static final int TUESDAY = 2;
public static final int WEDNESDAY = 3;
public static final int THURSDAY = 4;
public static final int FRIDAY = 5;
public static final int SATURDAY = 6;
}
// 4계절을 표현
public class Season {
public static final int SPRING = 0;
public static final int SUMMER = 1;
public static final int FALL = 2;
public static final int WINTER = 3;
}

통상적으로 열거형은 정수타입의 순서값을 가지고 있는 것으로 취급된다. 하지만 위의 두 클래스를 이용하여 코딩을 하면 다음과 같은 문제를 만난다.

//
...
if (season == Aweekday.SUNDAY) {
...
} else if (season == Season.SPRING) {
...
}

계절을 나타나내는 데이터와 요일을 나타내는 데이터는 결국 모두 int 타입의 정수값이기 때문에 다른 정수값과 구별이 되지 않는다. (Not typesafe) 그리고 또다른 계절이나 요일을 나타내는 열거형이 존재할 경우 서로 구별할 방법이 없다. (No namespace)

// 두 나라의 계절 표시
...
public static final int KR_SPRING = 0;
public static final int KR_SUMMER = 1;
public static final int KR_FALL = 2;
public static final int KR_WINTER = 3;
public static final int JP_SPRING = 4;
public static final int JP_SUMMER = 5;
...

그리고 위의 코드로 작성된 열거형 데이터들은 컴파일시에 값이 결정되기 때문에 두 값 사이에 새로운 값을 추가해야 하는 경우나 순서를 바꿔야 하는 경우 다시 컴파일을 해야한다. (Brittleness) 마지막으로 열거 데이터는 모두 정수값이므로 정보를 표현하거나 데이터 타입을 별도로 표시하는 것도 불가능하다. (Printed values are uninformative)
이런 문제점을 해결하기 위해 다음과 같이 typesafe enum pattern을 이용한다.

// Typesafe enum pattern을 사용한 계절 표현
public class Season {
private final String name;

public static final Season SPRING = new Season("SPRING");
public static final Season SUMMER = new Season("SUMMER");
public static final Season AUTUMN = new Season("AUTUMN");
public static final Season WINTER = new Season("WINTER");

private Season(String name) {
this.name = name;
}

public String toString() {
return name;
}
}

여기서 눈여겨 볼 점은 생성자가 private로 선언되었다는 점이다. 생성자가 private로 선언되었다는 것은 하위 클래스에서 영향을 줄 수 없다는 얘기이다. 상수들은 접근하기 쉽도록 static으로 선언되었다.
이렇게 선언된 열거형 객체인 Season의 인스턴스는 다음과 같이 사용할 수 있다.


...
System.out.println(Season.SUMMER);
...
if (season == Season.SPRING) {
...
} else if (season == Season.SUMMER) {
...
}
...

이제 이 소스를 확장하여 순서의 개념을 추가해보록 하겠다.
// 순서 개념이 들어간 계절 표현
public class Season implements Comparable {
private final String name;

public static final Season SPRING = new Season("SPRING");
public static final Season SUMMER = new Season("SUMMER");
public static final Season AUTUMN = new Season("AUTUMN");
public static final Season WINTER = new Season("WINTER");

// 생성될 다음 Season의 순서
private static int nextOrdinal = 0;

// 현재 Season의 순서 할당
private final int ordinal = nextOrdinal++;

private Season(String name) {
this.name = name;
}

public String toString() {
return name;
}

public int compareTo(Object o) {
return ordinal - ((Season)o).ordinal;
}
}

크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 정샘

2008/07/30 16:09 2008/07/30 16:09
, ,
Response
No Trackback , No Comment
RSS :
http://jeongsam.net/rss/response/8

팩토리 패턴의 소개 (2)

Head First Design Patterns - 10점
에릭 프리먼 외 지음, 서환수 옮김/한빛미디어

지난 번 설계한 "그냥피자" 클래스는 단점이 있었습니다. 주문할 피자의 종류가 많아질 수록 "그냥피자" 클래스가 복잡해지고 매번 코딩을 추가해야한다는 점입니다. 피자 종류를 추가하거나 제거할 때마다 코드를 고쳐야하므로 재활용성은 당연히 떨어집니다.
이러한 단점을 개선하기위해 변경이 일어나는 부분을 분리하고 피자의 종류 대로 피자 인스턴스가 만들어지도록 하겠습니다.

사용자 삽입 이미지
피자의 종류를 지정하는 부분을 '간단한피자팩토리' 클래스로 분리하였습니다. 이번 설계에서는 피자의 종류가 늘어나더라도 종류에 맞는 피자 클래스를 추가하고 '간단한피자팩토리' 클래스만을 변경하는 것으로 작업이 끝납니다.
이런 설계를 간단한 팩토리(simple factory)라고 하며 객체지향 설계 방법에 충실히 따른 모습이라고 할 수 있습니다. 하지만 아직은 패턴이라고 얘기할 수있는 정도는 아닙니다.
그럼 시퀀스 다이어그램으로 각 인스턴스가 어떤 순서로 메세지를 주고 받으면서 실행하는지 살펴보겠습니다.
사용자 삽입 이미지
고객이 '피자가게'에 '불고기피자'를 주문합니다. 피자가게에서는 주방('간단한피자팩토리')에 '불고기피자'를 만들도록 메세지를 전달하고 주방에서는 '불고기피자' 인스턴스를 만들어서 '피자가게' 인스턴스에게 돌려줍니다. 피자가게 인스턴스는 불고기피자 인스턴스를 사용하여 피자를 내놓습니다. 이때 수퍼 클래스인 '피자'를 이용하여 어떤 종류의 피자도 다룰 수 있게 됩니다.(다형성)
결국 어떤 종류의 피자를 선택할지는 간단한 피자 팩토리에서 결정을 하게 되는거죠.
실제 코드로 구현했습니다.
/* 피자와 그로부터 파생되어 구성된 피자들 */
// 각각의 피자들은 굽는 방법과 같은 것들이 다를 수 있기 때문에
// 피자는 추상 클래스로 선언합니다.
package exam.pattern.factoryMethod;

import java.util.ArrayList;
import java.util.Iterator;

public abstract class 피자 {
String 이름;
String 반죽방법;
String 소스;
@SuppressWarnings("unchecked")
ArrayList 토핑 = new ArrayList();

@SuppressWarnings("unchecked")
public void 재료준비() {
System.out.print("토핑재료 : ");
Iterator 토핑재료 = 토핑.iterator();

while (토핑재료.hasNext()) {
System.out.print(토핑재료.next() + ((토핑재료.hasNext()) ? ", " : ""));
}
System.out.println();
}

public void 빵굽기() {
System.out.println("25분간 350도에서 굽는다...");
}

public void 자르기() {
System.out.println("8조각으로 자른다...");
}

public void 포장하기() {
System.out.println("배달용 박스로 포장한다...");
System.out.println("'" + 이름 + "'의 포장이 끝났습니다.");
}
}


/* 피자가 실제로 구현된 구상 클래스 */
package exam.pattern.factoryMethod;

public class 불고기피자 extends 피자 {
@SuppressWarnings("unchecked")
public 불고기피자() {
이름 = "불고기피자";
반죽방법 = "얇은 반죽";
소스 = "토마토 소스";
토핑.add("불고기");
}
}


/* 피자가게와 간단한피자팩토리 */
// 피자의 선택은 간단한피자팩토리에서 실행시간에 결정됩니다.
package exam.pattern.factoryMethod;

public class 피자가게 {
private 간단한피자팩토리 factory;

public 피자가게(간단한피자팩토리 factory) {
this.factory = factory;
}

public void 주문하기(String 종류) {
피자 pizza;

pizza = factory.피자만들기(종류);
pizza.재료준비();
pizza.빵굽기();
pizza.자르기();
pizza.포장하기();
}
}


/* Simple Factory */
package exam.pattern.factoryMethod;

public class 간단한피자팩토리 {
public 피자 피자만들기(String 종류) {
피자 pizza;

if (종류.equals("치즈피자"))
pizza = new 치즈피자();
else if (종류.equals("불고기피자"))
pizza = new 불고기피자();
else
pizza = new 보통피자();

return pizza;
}
}


/* 테스트 코드 */
package exam.pattern.factoryMethod;

public class 간단한피자팩토리테스트 {

/**
* @param args
*/
public static void main(String[] args) {
new 피자가게(new 간단한피자팩토리()).주문하기("보통피자");;
}
}


이상으로 simple factory를 살펴보았습니다. 이 정도로도 상당히 객체지향적이면서 재활용성이 높은 유연한 코드가 만들어졌습니다.
다음은 본격적으로 '팩토리 메서드 패턴'을 소개하도록 하겠습니다.

크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 정샘

2008/07/23 16:53 2008/07/23 16:53
, ,
Response
No Trackback , No Comment
RSS :
http://jeongsam.net/rss/response/7

Assoiation (관련)

클래스 다이어그램을 그릴 때 클래스간의 관계와 실제 코드의 구현을 구별해 내는 것이 가장 어려운 것 같다. 여기에 각 관계와 실제 코딩을 구현해 보았다.
1. Association (관련)

사용자 삽입 이미지
/* Car 클래스 */
package exam.assocation;

public class Car {
public static void print() {
System.out.println("Car의 인스턴스");
}
}

/* Foo 클래스는 Car 클래스와 association */
package exam.assocation;

package exam.assocation;

public class Foo {

/**
* @param args
*/
public static void main(String[] args) {
Car.print();
// Car 인스턴스 호출 - Foo는 Car와 association
}

public void callCar(Car car) {
// Car 타입 매개변수 사용 - Foo는 Car와 association
// 자바 뉴스그룹을 살펴보니 dependency(의존)로 보는 경향이 더 많았습니다.
// Car 클래스의 인스턴스를 사용한다면 명백한 의존관계로 볼 수 있다더군요.
System.out.println(car.toString());
}
}


association은 Foo 인스턴스가 Car 인스턴스를 호출하는 경우나 Car 타입의 매개 변수를 사용할 경우에 사용한다.

크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 정샘

2008/07/22 17:21 2008/07/22 17:21
, ,
Response
No Trackback , No Comment
RSS :
http://jeongsam.net/rss/response/4

팩토리 패턴의 소개 (1)

Head First Design Patterns - 10점
에릭 프리먼 외 지음, 서환수 옮김/한빛미디어
오늘 소개할 패턴은 팩토리  메서드 패턴입니다. 팩토리 메서드 패턴은 객체의 생성을 서브클래스에게 위임하여 객체 생성을 캡슐화하는 것입니다. 객체 생성을 캡슐화함으로써 얻는 이익은 객체의 생성을 실행시에 결정함으로써 고정된 코드를 지양합니다.
컴파일시 객체의 생성을 결정하면 다른 객체를 사용하려고 할 때마다 소스를 고치고 컴파일을 해야함으로 객체의 재활용에 재약을 받게 됩니다.
예를 들어 데이터베이스와 연동하는 웹 어플리케이션의 경우 데이터베이스마다 JDBC 드라이버를 달리 선택하여야 하는데 팩토리 메서드 패턴을 활용하면 데이터베이스에 알맞은 JDBC 드라이버를 실행시에 선택할 수 있게 되고 지원해야할 데이터베이스가 추가되어도 기존에 데이터베이스에 접속해서 작업하는 코드는 거의 손댈 필요가 없기 때문에 융통성있는 어플리케이션을 설계할 수 있습니다.
그럼, 우선 교재에 나오는 예를 보도록 하겠습니다. 여러분은 피자가게를 운영하는 고객의 요청으로 피자주문을 관리하는 어플리케이션을 제작하기로 하였습니다.
피자주문은 다음과 같은 순서에 따라 진행됩니다.
① 고객이 피자를 주문한다.
② 피자를 만들기 위한 재료를 준비한다.
③ 재료를 얹고 빵을 굽는다.
④ 조각내어 자른다.
⑤ 박스에 넣어 포장한다.
이 과정을 클래스 다이어그램으로 다음과 같이 설계하였습니다.
사용자 삽입 이미지

시퀀스 다이어그램으로 실행 순서를 설계하였습니다.
사용자 삽입 이미지

위의 설계를 자바로 다음과 같이 코딩하였습니다.
/* 피자 클래스 */
package exam.pattern.factoryMethod;
import java.util.ArrayList;
public class 그냥피자 {
String 이름;
String 반죽;
String 소스;
ArrayList 토핑 = new ArrayList();

public void 재료준비() {
System.out.println("피자이름은 " + 이름);
}

public void 빵굽기() {
System.out.println("25분간 350도에서 굽는다...");
}

public void 자르기() {
System.out.println("8조각으로 자른다...");
}

public void 포장하기() {
System.out.println("배달용 박스로 포장한다...");
}
}
/* 피자가게 클래스 */
package exam.pattern.factoryMethod;

public class 패턴없는피자가게 {
그냥피자 피자;
public void 피자주문() {
피자 = new 그냥피자();
피자.재료준비();
피자.빵굽기();
피자.자르기();
피자.포장하기();
}
}

피자를 주문해 보겠습니다.

/* 피자주문 테스트 코드 */
package exam.pattern.factoryMethod;
public class 피자주문테스트 {

/**
* @param args
*/
public static void main(String[] args) {
패턴없는피자가게 pizzaStore1 = new 패턴없는피자가게();

pizzaStore1.피자주문();
}
}


일반적으로 피자가게에는 여러가지 종류의 피자를 판매합니다. 위의 설계를 변경하여 피자이름을 지정하여 이름에 맞는 토핑 재료가 들어가도록 고쳐봅시다.
/* 변경된 피자 클래스 */
package exam.pattern.factoryMethod;

import java.util.ArrayList;
import java.util.Iterator;

public class 그냥피자 {
String 이름;
String 반죽;
String 소스;
ArrayList 토핑 = new ArrayList();

public 그냥피자(String name) {
이름 = name;
}

public void 재료준비() {
System.out.println("주문한 피자는 '" + 이름 + "'입니다...");
if (이름.equals("치즈피자")) {
토핑.add("치즈");
토핑.add("쏘세지");
} else if (이름.equals("불고기피자")) {
토핑.add("불고기");
}

System.out.print("추가된 토핑 재료 : ");
Iterator 토핑재료 = 토핑.iterator();
// 추가된 토핑재료를 출력하기 위해 Iterator 타입으로 변환
while (토핑재료.hasNext()) {
System.out.print(토핑재료.next() + ((토핑재료.hasNext()) ? ", " : ""));
// 출력할 내용이 있으면 ', '를 계속 출력
}
System.out.println();
}

public void 빵굽기() {
System.out.println("25분간 350도에서 굽는다...");
}

public void 자르기() {
System.out.println("8조각으로 자른다...");
}

public void 포장하기() {
System.out.println("배달용 박스로 포장한다...");
}
}

/* 변경된 피자가게 클래스 */
package exam.pattern.factoryMethod;

public class 패턴없는피자가게 {
그냥피자 피자;
String 피자이름;

public void 피자주문(String 피자이름) {
피자 = new 그냥피자(피자이름);
피자.재료준비();
피자.빵굽기();
피자.자르기();
피자.포장하기();
}
}

/* 변경된 테스트 클래스 */
package exam.pattern.factoryMethod;

public class 피자주문테스트 {

/**
* @param args
*/
public static void main(String[] args) {
패턴없는피자가게 피자가게 = new 패턴없는피자가게();

피자가게.피자주문("치즈피자");
}
}

크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 정샘

2008/07/22 14:58 2008/07/22 14:58
, ,
Response
No Trackback , No Comment
RSS :
http://jeongsam.net/rss/response/3