반응형
추상 클래스
- 상속 관계에 있는 클래스 중에서 상위 클래스에서는 특별한 구현 없이 사용하고자 하는 메소드만 기술하고,
   구체적인 구현은 하위 클래스에서 하도록 함. 구체적인 내용없이 모양만 갖춘 클래스

> 추상 메소드를 가진 클래스로, new 연산자로 객체 생성할 수 없습니다.

> 따라서, 추상 클래스의 추상 메소드는 자신이 직접 이용하지 못하고, 반드시 하위 클래스에서 이 메소드를 상속받아 구현해야 합니다.

> 추상클래스의 추상 메소드는 반드시 Overriding 되어야 하기 때문에,

   하위 클래스들이 특정 메소드를 반드시 구현하도록 강제할 수 있습니다.

   그리고, 추상 메소드를 오버라이딩 하지 않으면 상속받는 클래스는 자동으로 추상 클래스가 됩니다.

 > 추상 클래스는 일반 메소드와 추상 메소드를 모두 포함 할 수 있습니다.



인터페이스

> Java는 다중상속을 지원하지 않는대신, 다중상속과 유사한 기능이 있는 Interface를 제공합니다.

> Interface는 모든 메소드가 추상메소드이며, 데이터는 final static 변수만을 가지는 특별한 형태이다.



차이점

1) 인터페이스는 다중 상속을 지원하나 추상 클래스는 단일 상속만 가능하다.
2) 추상 클래스에서는 메소드의 부분적인 구현이 가능하지만 인터페이스에서는 오직 메소드 선언만 있을 수 있다.



출처  http://dante2k.egloos.com/199169  와 1개의 주소는 ...기억이 안남 곳곳의 정보를 참조


반응형

'Java기초' 카테고리의 다른 글

replace replaceAll 차이  (0) 2014.08.01
java stack trace  (0) 2014.05.09
JVM 메모리  (0) 2014.04.30
정규식 특수문자  (0) 2013.07.15
추상클래스 인터페이스 차이2  (0) 2013.07.11
반응형

아는 것이 힘이다.

강해지자!!!

반응형

'감동적인 삶으로의 한걸음...' 카테고리의 다른 글

삶은...  (0) 2013.07.09
반응형

http://cafe.naver.com/sqler.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=830



java Generics 1 - 소개

이 글은 http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf 에 대한 손을 좀 본 번역본입니다. 손을 봤다는 게 직역하지 않고, 뺄 거는 빼고 더할 거는 더하고 풀어 쓸 거는 풀어 쓰고 했단 소립니다. 사실 원본이 말이 쓸데없이 많아서 많이 짤라냈습니다.


자바 1.5에서는 자바 언어에 대한 몇가지 확장 기능이 추가되었다. 그 중 하나가 Generics이다.
이 글은 generics를 소개하기 위한 글이다. C++의 템플릿과 같은 다른 언어와 비슷하다.
Generics는 타입에 대한 추상성을 제공한다. Collection에서 쓰이는 컨테이너 타입이 가장 일반적인 예가 될 것이다.
예전에 작성하던 일반적인 코드는 다음과 같다.

List myIntList = new LinkedList(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = (Integer) myIntList.iterator().next(); // 3



3 번째 줄에서 캐스팅을 하는 것은 다소 번거롭다. 대개의 경우 프로그래머는 어떤 리스트에 어떤 타입의 데이터가 들어갈 것인지 알고있지만, 캐스팅은 필수적인 작업이다. 컴파일러는 iterator에 의해 Object 가 리턴될 것이라는 것까지 밖에 보장하지못한다. Integer 타입이란 것을 명확히 하기 위해서는 캐스팅을 할 수 밖에 없다. 물론, 캐스팅 작업은 프로그램을난잡하게할 뿐만 아니라, 런타임 에러의 가능성을 발생시킨다.
만약 프로그래머가 만들고자 하던 의도대로 리스트에 특정한 타입만 들어갈 수 있도록 강제할 수 있다면 얼마나 좋겠는가! 이것이 바로 generics의 핵심 아이디어다. generics를 이용한 프로그램의 예는 다음과 같다.

List<Integer> myIntList = new LinkedList<Integer>(); // 1’
myIntList.add(new Integer(0)); //2’
Integer x = myIntList.iterator().next(); // 3’



myIniList 변수에 타입에 대한 정의를 했다는 것이 중요하다. List<Integer>라고 명확하게 적어줌으로써 이 인스턴스가아무 타입이나 들어갈 수 있는 리스트가 아니라 Integer 리스트라는 것을 명확하게 한다. 이런 경우 List는타입인자(예제의 경우는 Integer)를 받는 generic 인터페이스라한다. list 객체를 생성하는 시점에 타입인자를 명확히해준다.
또 한가지 주의할 점은 3'에서 보는 것과 같이 타입 캐스팅이 사라졌다는 것이다.
3 번째 줄에서 타입캐스팅을 하는 대신 1'에서 타입 파라미터로 Integer를 설정해 줌으로 인해 프로그램이 한결 간결해졌다. 매우 중요한 차이는컴파일러가 이제 타입을 체크할 수 있기 때문에 인자의 정확성을 컴파일 타임에 알 수 있게 되었다는 것이다. myIntList가List<Integer>로 정의되었을 경우 그게 언제 어디서 쓰이던 타입 안정성에 대해 컴파일러로 부터 보장받을 수있다. 즉, 타입이 명확하지 않으면, 컴파일이 되지 않는다. 또한 이렇게 코딩함으로 개발자는 그 인스턴스에 들어가는 타입을 더직관적으로 알 수 있다.


java Generics 2 - 간단한 Generics 정의하기

다음은 java.util 패키지에 있는 List와 Iterator의 선언부를 발췌한 것이다.

public interface List<E> { 
    void add(E x);
    Iterator<E> iterator();
}
public interface Iterator<E> { 
    E next();
    boolean hasNext();
}

<> 안에 들어간 요소를 제외하면, 위의 코드는 유사하다. 이게 List와 Iterator에서 타입인자를 정의하는 방법이다.
타입인자는 정해진 타입을 사용하는 generic의 선언부를 통해 정의할 수 있다.(여기에는 몇 가지 제한 사항이 있으며, 7장을 참고하라.)
List<Integer> 와 같은 List의 generic 방식의 선언을 이미 살펴보았다. parameterized type 이라고 불리는 이런 방식의사용법은 일반적인 타입인자(이 경우는 E라고 정의된 부분) 에서 모두 사용되며, 사용시에 특정한 타입(이 경우에는Integer)으로 바뀔 수 있다.
다시 말해 List<Integer>는 위의 코드에서 E라고 정의된 부분이 Integer로 바뀐 것이라고 생각하면 된다.

public interface IntegerList { 
    void add(Integer x)
    Iterator<Integer> iterator();
}



이렇게 쓰는 것은 매우 직관적이지만, 오해의 소지가 좀 있다.
parameterized type인 List<Integer>는 이런 식의 확장의 경우 굉장히 명확해 보인다. 그러나, 이런 식으로 인터페이스가 선언되어 있다면, 확장을 할 수가 없다.
generic type의 선언은 한번만 컴파일 되면, 아무데서나 쓸 수 있다. 즉, 다른 일반적인 클래스나 인터페이스와 같이 하나의 클래스 파일만 생기게 된다.

타 입인자는 일반적인 메쏘드나 생성자에서 사용하는 보통 인자들과 비슷하다. 메쏘드에서 사용하는 일반적인 인자와 마찬가지로generic은 선언부에 타입인자를 정의한다. 메쏘드가 호출되면, 실제값으로 그 인자가 치환되어 메쏘드의 내부가 실행되는 것과마찬가지로 generics의 선언이 실행되면 실제 인자는 타입 파라미터 값을 대체하게 된다.

이름을 짓는 데는 몇가지 약정이 있다. 타입인자는 어떤 의미를 내포하는(가능하면 한 글자로) 하기를 권고한다. ( Map의 경우 key, value시스템이기 때문에 <K, V> 라고 표현하고 있다.) 또 소문자를  타입인자로 사용하지 말아야 일반적인 클래스나인터페이스에서 쓰이는 인자와 명확하게 구분할 수 있다. element라는 의미로 많은 경우 E를 사용하는 게 좋은 예이다.



java Generics 3 - Generics과 하위 타입

아래 코드를 보자.

List<String> ls = new ArrayList<String>(); //1
List<Object> lo = ls; //2



첫 번째 줄은 당연히 맞다. 두 번째 줄이 문제다. 다시 정리하면 다음과 같은 문제가 된다. String의 리스트는 Object의 리스트가 될 수 있는가? 보통은 "당근 빠따다!"라고 생각할 거다.
그럼 추가적으로 다음과 같은 코드가 있을 때는?

lo.add(new Object()); // 3
String s = ls.get(0); // 4: Object를 String에 대입하려고 한다!



Stirng 의 리스트인 ls를 Object 리스트인 lo에 대입하고, lo에 Object를 넣었다. 그래서 lo에는 이제 String만들어있는 게 아니다. 위와 같은 문제를 방지하기 위해 컴파일러는 두 번째 줄에서 에러를 발생시킨다.

일반적으로 Foo가 Bar의 하위 타입이고 G는 어떤 generic 타입일 경우 G<Foo>는 G<Bar>의 하위 타입이 아니다.



java Generics 4 - Wildcards

collection의 요소들을 루프를 돌면서 찍는 코드를 생각해보자. 이전 버전에서는 다음과 같은 코드가 될 것이다.

void printCollection(Collection c) { 
    Iterator i = c.iterator();
    for (k = 0; k < c.size(); k++) { 
        System.out.println(i.next());
    }
}


다음은 generics을 이용한 새로운 방법이다.

void printCollection(Collection<Object> c) { 
    for (Object e : c) { 
        System.out.println(e);
    }
}



문 제는 이런 새로운 방식이 이전 방식에 비해 별로 쓸모가 없다는 것이다. 예전 방식의 코드는 모든 종류의 collection을 쓸수 있었지만, 새로운 방식은 Collection<Object>만 쓸 수 있다. chap 3에서 본 것과 같이Collection<Object>는 모든 Collection의 상위 타입이 아니다.

다시 잠시 정리를 하고 넘어가면.

void foo(Collection<Object> arg){

}
와 같이 선언된 메쏘드에 대해

foo(Collection<String>타입의 머시기)

와 같은 방법으로 호출할 수 없다는 것이다.

그러면, 모든 타입의 collection에 대한 상위타입은 무엇일까? Collection<?>라고 쓰면 된다. 이를 wildcard 타입이라 부른다. wildcard 타입을 이용한 메쏘드는 다음과 같이 쓰면 된다.

void printCollection(Collection<?> c) { 
    for (Object e : c) { 
        System.out.println(e);
    }
}


이 렇게하면 모든 타입의 collection에 대해서 이 메쏘드를 사용할 수 있다. printCollection() 메쏘드 안을 잘보면 여전히 인자를 Object 타입으로 사용하는 것을 볼 수 있다. 이렇게 쓰는 것은 항상 안전하다.
그러나 다음과 같은 코드는 문제가 있다.

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 컴파일 에러!


c 가 가지는 요소의 타입이 무엇인지 불확실하기 때문에 Object 인스턴스를 추가할 수가 없다. add() 메쏘드는 인자로 E라는인자 타입을 가지는데, 이 경우 ?로 정의되어 있기 때문에 여기서는 여전히 알 수 없는 타입이기 때문이다. 이런 경우add()에 들어갈 수 있는 유일한 값은 null 뿐이다.
반대로 List<?>에서 get()을 생각해보자. 리스트의 타입이 무엇인지 알 수 없지만, Object의 하위 타입이란 건 알고 있다. 그래서 이런 경우 get()은 Object 타입을 리턴한다.

제한된 wildcards

사각형과 원과 같은 어떤 모양을 그리는 간단한 애플리케이션을 생각해보자. 프로그램에서 이런 모양을 표현하기 위해서는 다음과 같은 상속관계가 정의될 것이다.

public abstract class Shape { 
    public abstract void draw(Canvas c);
}
public class Circle extends Shape { 
    private int x, y, radius;
    public void draw(Canvas c) { ... }
}
public class Rectangle extends Shape { 
    private int x, y, width, height;
    public void draw(Canvas c) { ... }
}



이들을 그리기 위한 캔버스는 다음과 같다.

public class Canvas { 
    public void draw(Shape s) { 
        s.draw(this);
    }
}


Shape들을 한번에 그리기 위해서는 다음과 같은 메쏘드가 있을 법하다.

public void drawAll(List<Shape> shapes) { 
    for (Shape s: shapes) { 
        s.draw(this);
    }
}


drawAll() 은 정확히 Shape의 리스트에 의해 호출된다. 따라서 List<Circle>과 같은 것은 인자로 넣을 수 없다.즉, 인자로 들어가는 것은 Shape의 하위객체까지 들어갈 수 있어야 한다. 이런 경우 메쏘드는 다음과 같이 정의되면 된다.

public void drawAll(List<? extends Shape> shapes) { ... }



List<Shape>가 List<? extends Shape>로 바뀐 것이 포인트다. 이렇게 하면 List<Circle> 타입도 인자로 사용할 수 있다.
List<?extends Shape> 이 제한된 wildcard의 예제다. ?는 불분명한 타입을 표현하는 것인데, ?가 Shape의하위타입으로 제한을 하는 것이다. 이런 경우 Shape는 wildcard의 upper bound라 부른다.
wildcard의 유연함을 사용하는 데는 대가가 따른다. 메쏘드 안에서 shapes라는 인자에 추가하는 것은 안 된다. 예를 들면 다음과 같은 코드는 안 된다.

public void addRectangle(List<? extends Shape> shapes) { 
    shapes.add(0, new Rectangle()); // compile-time error!
}



이 메쏘드를 호출하는 시점을 생각해보자. 인자로 List<Circle>이 들어올 수도 있다. 그렇기 때문에List<? extends Shape>로 선언된 변수에 Shape의 하위 클래스인 Rectangle을 넣을 수 없다.


다음은 허용된다. (예제에서 Driver는 Person의 하위타입이다.)

public class Census { 
    
public static void
        addRegistry(Map<String, ? extends Person> registry) { ...}
}...


Map<String, Driver> allDrivers = ...;
Census.addRegistry(allDrivers);






java Generics 5 - Generic 메쏘드

어떤 array의 모든 요소를 Collection에 추가하는 메쏘드를 만들고자 한다.
첫번째 생각은 이러하다.

static void fromArrayToCollection(Object[] a, Collection<?> c) { 
    for (Object o : a) { 
        c.add(o); // 컴파일 에러!
    }
}



자, 이제 초보자들이 흔히 하는 실수인 Collection<Object>를 메쏘드에 인자로 쓰는 것과 같은 실수를 피하는법을 알아보자. 역시 Collection<?>도 이 경우 통하지 않는다. 이런 경우에 사용하는 것이 바로generic 메쏘드다. 타입 정의와 마찬가지로 메쏘드 정의에서도 generic을 사용할 수 있다.

static <T> void fromArrayToCollection(T[] a, Collection<T> c) { 
    for (T o : a) { 
        c.add(o); // 옳다
    }
}



이제 array 요소의 상위타입으로 구성된 collection을 이용하여 이 메쏘드를 쓸 수 있다.

Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<Object>();
fromArrayToCollection(oa, co);// T는 Object
String[] sa = new String[100];
Collection<String> cs = new ArrayList<String>();
fromArrayToCollection(sa, cs);// T는  String
fromArrayToCollection(sa, co);// T는 Object
Integer[] ia = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection<Number> cn = new ArrayList<Number>();
fromArrayToCollection(ia, cn);// T는 Number
fromArrayToCollection(fa, cn);// T는 Number
fromArrayToCollection(na, cn);// T는 Number
fromArrayToCollection(na, co);// T는 Object
fromArrayToCollection(na, cs);// 컴파일 에러


실제 인자가 어떤 타입인지 메쏘드를 호출할 때 넘겨주지 않는다는 것에 유의하자. 컴파일러가 넘어오는 인자에 따라 알아서 다 처리해준다. 
여기서 한 가지 의문점이 생긴다. 어떤 때 generic 메쏘드를 사용할 것이며, 어떤 때 wildcard 타입을 사용할 것인가? 다음의 Collection 라이브러리를 살펴보자.

interface Collection<E> { 
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);
}



이것을 generic 메쏘드를 사용하면 다음과 같이 된다.

interface Collection<E> { 
    public <T> boolean containsAll(Collection<T> c);
    public <T extends E> boolean addAll(Collection<T> c);
    //타입 변수도 정해져야 한다구!
}



그 러나 conatainsAll과 addAll 모두 타입인자 T는 한번만 사용된다. 리턴 타입은 타입인자에 무관하며, 메쏘드의인자와도 상관없다. 즉 여기서 인자의 타입은 다형성을 위해 사용된 것이다. 다시 정리하면, generic 메쏘드는 두 개 이상의인자 사이의 타입 연관성을 위해 정의된 것이다. 또, wildcard는 인자의 하위유연성을 위해 만들어졌다.
물론, generic 메쏘드와 wildcard를 동시에 사용하는 케이스도 있을 수 있다. 다음은 Collections.copy() 메쏘드 이다.

class Collections { 
    public static <T> void copy(List<T> dest, List<? extends T> src){...}
}



두개의 파라미터 간의 연관성을 살펴보자. 원본 array인 src로부터 복사된 객체 dest는 반드시 T로 할당될 수 있어야 한다. 그러므로, src의 타입은 반드시 T의 하위 타입이어야 한다.
위의 선언부를 wildcard를 쓰지 않고 정의하는 방법도 있다.

class Collections { 
    public static <T, S extends T>
        void copy(List<T> dest, List<S> src){...}
}



첫 번째 타입인자 T는 첫 번째 메쏘드 인자의 선언부와 두 번째 타입인자 S를 정의하기 위한 부분에 쓰였다. S는 src의 타입을정의하기 위해 한 번만 쓰였다. 즉 S는 두 번째 타입인자를 정의하기 위한 곳 외에는 쓰이지 않았다. 다른 곳에 쓰이지 않았기때문에 S는 wildcard로 변환될 수 있다. wildcard를 쓰는 것이 훨씬 더 명확하다. wildcard를 썼다는 것은이것이 다른 용도로 사용되지 않는다는 것을 명확히 해주기 때문이다.
wildcard는 메쏘드 선언부 밖에서 멤버 변수나 array 등의 타입을 정의할 수 있다는 이점도 있다.

section 4 에서 보았던 Shape를 그리는 문제로 돌아가보자. 그리기 전에 인자로 넘어온 Shape의 리스트의 리스트를(List가 두 겹이다.) static 멤버변수에 저장하는 상황을 가정하자.

static List<List<? extends Shape>> history =
new ArrayList<List<? extends Shape>>();
public void drawAll(List<? extends Shape> shapes) { 
    history.addLast(shapes);
    for (Shape s: shapes) { 
        s.draw(this);
    }
}



이 런 경우 T와 같이 쓰지 않고, wildcard를 사용하는 것이 바람직하다. T를 재사용할 필요가 없기 때문이다. 또 위와 같이여러 개의 ?들끼리 굳이 구별을 할 필요가 있다면 ?대신 T,S와 같은 파라미터 변수를 사용하면 된다. 위와 같이 중첩된 경우도마찬가지다.



java Generics 6 - 기존 코드와의 호환

지금까지는 generic을 지원하는 버젼에 대해서만 알아보았다. 하지만 이미 기존에 만들어진 소스가 있을 것이다. 기존에 짜여진 모든 소스 코드를 수정하는 것은 있을 수 없는 일이다.
10 장에서 기존코드를 generic을 사용하는 새로운 코드로 바꾸는 것에 대해 알아보도록 하겠다. 이번 장에서는 훨씬 간단한 문제인기존 소스 코드와의 호환성에 대해서만 알아볼 것이다. 이것은 두 부분으로 나뉘어 진다. 기존 코드를 generic이 사용된코드에서 사용하는 것과 generic이 들어간 코드를 기존 코드에서 사용하는 것이다.

기존 코드를 generic 코드에서 사용하기.

generic의 장점을 충분히 살리면서 기존 코드를 사용하는 법을 알아보겠다.
예제로 com.Fooblibar.widgets 패키지를 사용하고자 한다 치자.(Fooblibar가 뭐 하는 건지는 알 필요 없으므로 생략!)

package com.Fooblibar.widgets;
public interface Part { ...}
public class Inventory { /**
* Adds a new Assembly to the inventory database.
* The assembly is given the name name, and consists of a set
* parts specified by parts. All elements of the collection parts
* must support the Part interface.
**/
    public static void addAssembly(String name, Collection parts) {...} 
    public static Assembly getAssembly(String name) {...}
}
public interface Assembly { Collection getParts(); // Returns a collection of Parts
}



이 제 위에서 소개한 API를 사용하는 코드를 짠다고 가정하자. addAssembly() 메쏘드를 호출할 때 적절한 인자로 호출하는게 바람직할 것이다. 그러니까 Collection<Part>라고 정의된 generic type이 명시적인 인자로호출을 하고자 한다.

package com.mycompany.inventory;
import com.Fooblibar.widgets.*;
public class Blade implements Part {
...
}
public class Guillotine implements Part {
}
public class Main { 
    public static void main(String[] args) { 
        Collection<Part> c = new ArrayList<Part>();
        c.add(new Guillotine()) ;
        c.add(new Blade());
        Inventory.addAssembly(”thingee”, c);
        
Collection<Part> k = Inventory.getAssembly(”thingee”).getParts();
    }
}


addAssembly 를 호출할 때 두 번째 인자는 Collection 타입이다. 그리고 실제로 넘기는 인자는Collection<Part> 타입이다. 어떻게 작동할까? 컴파일러는 Collection이 어떤 타입으로 지정될지모른다.
Collection과 같은 generic type이 타입인자 없이 호출 될 때는 row-type으로 호출된다.
이미 살펴봤던 것처럼 Collection은 Collection<?>의 의미이지 Collection<Object>를 의미하는 게 아니다라고 생각할 것이다.
그러나 위의 코드를 보면 별로 그런 것 같지도 않다. 

Collection<Part> k = Inventory.getAssembly(”thingee”).getParts();

가 정상적으로 작동하고 있기 때문이다. Collection<?>은 Collection<Part>로 명시적인캐스팅없이 할당이 불가능하다. 즉, Collection<?>로 호출이 되었다면 에러가 났어야 마땅하다.
이 러한것은 정상적으로 작동한다. 다만 "unchecked warning"(@로 시작하는 annotation 중 하나) 이 없다면,warning이 생긴다. 컴파일러는 정합성을 보장해 줄 수 없기 때문이다. getAssembly()라는 기존에 이미 존재하던메쏘드가 Collection<Part>를 리턴할 지를 체크할 방법이 없다. 위에서 사용한 코드에서는 단지Collection이라고만 되어 있고, 그런 경우 모든 object를 사용할 수 있게 하는 방법뿐이다.
이론적으로만 봤을 때 이건 별로 합당치 못하지만, 기존에 쓰던 코드를 버리라고 할 수는 없다. 이건 개발자의 몫이다.
raw type이란 결국 wildcard와 매우 흡사하지만, raw type은 타입체크를 하지 않는 것이다. 이것은 기존 코드와의 호환성을 고려한 결정이다.
generic 코드에서 기존의 코드를 호출하는 것은 위험하다. 타입에 대한 안정성을 어떻게도 보장할 수 없기 때문이다.


Erasure 와 Translation

public String loophole(Integer x) { 
    List<String> ys = new LinkedList<String>();
    List xs = ys;
    xs.add(x); // 컴파일 시점 unchecked warning
    return ys.iterator().next();
}



String 리스트를 옛날 방식의 리스트에다가 집어 넣으려 한다. 컴파일 시점에 warning이 뜬다.
이번에는 Integer를 리스트에 넣고 String으로 뽑아 쓰려는 경우를 보자. 물론, 잘못된 시도다. Warning을 무시하고 일단 실행시켜보면 잘못된 타입으로 뽑아 쓸 때 에러가 난다.

public String loophole(Integer x) { 
    List ys = new LinkedList();
    List xs = ys;
    xs.add(x);
    return (String) ys.iterator().next(); // 런 타임 에러.
}



리스트로 부터 요소를 뽑아와 String으로 캐스팅을 하려고 하면 ClassCastException이 발생하게 된다.
컴파일러는 erasure라는 generics에 대한 첫 단계 변환을 실행한다. 소스 코드 차원에서의 변환이라고 생각하면 대략 맞다. 이는 generic을 제거하는 작업이라고 볼 수 있다.
erasure는 generic type 정보를 전부 제거해버리는 작업이다. List<String>을 List로 바꿔버리는 것처럼 <> 사이에 감싸진 모든 정보를 날려버린다.

기존 코드에서 generic 코드 사용하기


반대의 경우를 생각해보자. Fooblibar.com의 API를 generics를 사용하도록 바꿔보자. 그러나 그 API를 쓰는 코드는 아직 generics를 사용하지 않는다고 하면 아래와 같은 코드가 나올 것이다.

package com.Fooblibar.widgets;
public interface Part { ...} 
public class Inventory { 
    /**
    * Adds a new Assembly to the inventory database.
    * The assembly is given the name name, and consists of a set
    * parts specified by parts. All elements of the collection parts
    * must support the Part interface.
    **/    
    public static void addAssembly(String name, Collection<Part> parts) {...} 
    public static Assembly getAssembly(String name) {...}
}
public interface Assembly { 
    Collection<Part> getParts(); // Returns a collection of Parts
}



그걸 사용하는 코드는 다음과 같을 것이다.

package com.mycompany.inventory;
import com.Fooblibar.widgets.*;
public class Blade implements Part {
}
public class Guillotine implements Part {
}
public class Main { 
    public static void main(String[] args) { 
        Collection c = new ArrayList();
        c.add(new Guillotine()) ;
        c.add(new Blade());
        Inventory.addAssembly(”thingee”, c); // 1: unchecked warning
        Collection k = Inventory.getAssembly(”thingee”).getParts();
    }
}



위 의 코드는 generics가 나오기 전에 짜여졌으며, com.Fooblibar.widgets와 collection 라이브러리(둘다 generics를 쓴다) 를 사용하고 있다. 위의 코드에서 generics가 사용되어야 할 부분은 전부 raw type으로정의되어 있다.

첫번째 줄에서 Collection<Part>를 사용해야하는데 raw type의Collection이 호출되므로 unchecked warning이 발생한다. 컴파일러 입장에서는 이 Collection이Collection<Part>라고 확인할 방법이 없다.




java Generics 7 - Fine Print

Generic 클래스는 그 클래스의 모든 객체에 공유된다.

다음 코드는 무엇을 출력할까?

List <String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());



false가 출력될 거라 생각하겠지만 틀렸다. true를 출력한다. 모든 generic 클래스의 인스턴스는 실제 타입이 무엇이건 간에 같은 run-time 클래스를 가지게 된다.
generic이냐 아니냐를 결정하는 것은 타입 인자에 의한 것인데, 어떤 타입인자를 쓰건 그 클래스가 다른 것이 되진 않는다.
static 변수나 메쏘드는 그 클래스의 모든 인스턴스에 공유된다. 따라서 static 메쏘드나 static initializer에서타입인자를 지정하는 것은 안 되며, static 변수의 선언부나 initializer에 사용할 수도 없다. 다음 코드는 에러투성이다.

public class Test<E> {
    public static List<E> a = null; 
    static{
        List<E> b = null;
    }
    public static List<E> c = null;
    public static List d = new ArrayList<E>();
}



Cast 와 instanceOf

특정 타입인자로 구성된 인스턴스가 타입인자를 포함한 generics의 클래스의 인스턴스인지 체크하는 것은 허용되지 않는다.

Collection cs = new ArrayList<String>();
if (cs instanceof Collection<String>) { ...} // 안된다!



마찬가지로 다음 코드도 문제가 있다.

Collection<String> cstr = (Collection<String>) cs; // unchecked warning



여기서는 unchecked warning이 발생하는데, 이건 런타임 시스템에서 체크할 수 있는 게 아니기 때문이다.
다음 코드도 마찬가지다.

<T> T badCast(T t, Object o) {
    return (T) o; // unchecked warning
}



run time에 타입 변수라는 것은 없다. 이는 시간적으로나 공간적으로 과부하를 일으키지 않는다는 것이다. 이런 점은 바람직하지만, 믿을만한 캐스팅을 할 수는 없다.

Arrays

wildcard 타입을 제외하고는 generics의 array를 만들 수 없다. 아래 코드를 보자.

List<String>[] lsa = new List<String>[10]; // 이건 안 되지!
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // 말도 안 되지만, run time에는 무사통과
String s = lsa[1].get(0); // run-time error - ClassCastException



만 약에 parameterized type이 허용된다면 위와 같은 문제가 생길 수 있고, unchecked warning도 없이컴파일은 성공하고, run time에 에러가 발생할 것이다. generics를 사용하면 uncheck warning 없이컴파일이 잘 되었다는 것은 모든 타입이 다 정상적이라는 것을 보장한다.

그러나 여전히 wildcard는 쓸 수 있다. 위에서 사용한 코드를 조금 바꿔보자.

List<?>[] lsa = new List<?>[10]; // ok, wildcard 타입은 괜찮아.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // 옳타꾸나~
String s = (String) lsa[1].get(0); // run time error 그러나 waning은 떠준다.



마찬가지로 타입변수로 된 element를 가지는 array를 만들려고 하면 컴파일 에러가 발생한다.

<T> T[] makeArray(T t) { 
    return new T[100]; // error
}



타입변수로 된 것은 run time에는 존재하지 않기 때문에, 실제로 array의 요소의 타입이 뭔지 알 방법이 없다.




java Generics 8 - Class Literals as Run-time Type Tokens

JDK 1.5에서의 변화 중 하나는 java.lang.Class가 generic이 되었다는 것이다. 다른 클래스들이 generic을 사용한다는 것보다 훨씬 재미있는 예이다.
Class는 타입 변수 T를 가지고 있는데, 왜 있는 것일까? Class가 어떤 타입을 나타내는 지를 표현하는 것이다.
예 를 들어, String.class는 Class<String>이 되고, Serializable.class는Class<Serialize>가 되는 것이다. 이렇게 하면, reflection을 이용한 코드에서 안정성을 높일 수있다.
특히 Class<T>.newInstance()는 T를 리턴한다.
예를 하나 들어보자. db query를 수행하는 유틸리티 메쏘드를 작성하는데, 그 메쏘드는 Collection의 인스턴스를 반환한다고 가정하자.
 Factory 인스턴스를 인자로 받는 코드는 대략 다음과 같을 것이다.

interface Factory<T> { T make();}
public <T> Collection<T> select(Factory<T> factory, String statement) { 
    Collection<T> result = new ArrayList<T>();
    /* run sql query using jdbc */
    for (/* iterate over jdbc results */ ) { 
        T item = factory.make();
        /* use reflection and set all of item’s fields from sql results */
        result.add(item);
    }
    return result;
}



이는 다음과 같이 호출할 수 있다.

select(new Factory<EmpInfo>(){ 
    public EmpInfo make() { 
        return new EmpInfo();
    }} , ”selection string”);



또는 다음과 같이 Factory 인터페이스의 구현체인 EmpInfoFactory 클래스를 정의할 수도 있다.

class EmpInfoFactory implements Factory<EmpInfo> {
    ...
    public EmpInfo make() { return new EmpInfo();}
}



EmpInfoFactory를 정의했을 경우 호출하는 코드는 다음과 같다.

select(getMyEmpInfoFactory(), ”selection string”);


아래의 두 가지 방법으로 정리가 된다.

-호출하는 쪽에서 factory 클래스를 익명으로 만든다.

-또는, 호출할 때 적합한 Factory 클래스들을 만들어 놓고, 각각의 인스턴스를 생성해서 호출한다.(매우 바람직하지 않다!)

reflection을 쓰면, 이런 식으로 복잡하게 factory 클래스들을 만들어 내지 않을 수 있다.

Collection emps = sqlUtility.select(EmpInfo.class, ”select * from emps”);
...
public static Collection select(Class c, String sqlStatement) { 
    Collection result = new ArrayList();
    /* jdbc를 이용한 sql 수행 */
    for ( /* sql 수행 결과를 반복 */ ) { 
        Object item = c.newInstance();
        /* 수행된 결과로 부터 만들어진 필드를 reflection을 이용해서 세팅. */
        result.add(item);
    }
    return result;
}



그러나, 이런 식으로 하면 우리가 정말 원하는 타입의 collection을 받아올 수 없다. 그래서 Class가 generic으로 선언된 것이며, 이를 이용하여 다음과 같이 코드를 수정할 수 있다.

Collection<EmpInfo> emps = sqlUtility.select(EmpInfo.class, ”select * from emps”);
...
public static <T> Collection<T> select(Class<T>c, String sqlStatement) { 
    Collection<T> result = new ArrayList<T>();
    /* jdbc를 이용한 sql 수행 */
    for ( /* sql 수행 결과를 반복 */ ) { 
        T item = c.newInstance();
        /* 수행된 결과로부터 만들어진 필드를 reflection을 이용해서 세팅. */
        result.add(item);
    } 
    return result;
}


이렇게 하면, 처음의 의도대로 원하는 타입의 collection을 뽑아올 수 있다.



java Generics 9 - wildcards 심화학습

이번 장에서는 wildcard를 좀더 심도 있게 다뤄볼 생각이다. 읽기용으로 제한된 wildcard를 쓰는 것은 이미 몇 번 봐왔다. 이번에는 쓰기 전용을 생각해 보자.
Sink라는 예제 인터페이스를 생각해보자.

interface Sink<T> {
    flush(T t);
}



위 인터페이스를 사용하는 아래와 같은 코드를 생각해보자. writeAll 이라는 메쏘드는 coll이라는 변수로 정의된 Collection의 모든 원소를 Sink의 flush()에 대입시키고, 마지막 원소를 반환하는 메쏘드다.

public static <T> T writeAll(Collection<T> coll, Sink<T> snk){ 
    T last;
    for (T t : coll) { 
        last = t;
        snk.flush(last);
    }
    return last;
}
...
Sink<Object> s;
Collection<String> cs;
String str = writeAll(cs, s); // 잘못된 호출


위 에서 나타난 바와 같이 writeAll은 잘못 호출되고 있다. 왜냐하면 메쏘드 선언부에서는 T로 동일한 generic으로사용하였지만,  호출하는 부분에서는 cs(Collection의 인스턴스)와 s(String의 인스턴스)가 각각 다른generic을 사용하기 때문이다.
writeAll() 메쏘드를 다음과 같이 고쳐보겠다.

public static <T> T writeAll(Collection<? extends T>, Sink<T>){...}
...
String str = writeAll(cs, s); // 호출은 잘 되겠지만, return type이 안 맞음.



호출까지는 잘 되겠지만 잘못된 코드다. 왜냐하면 return type이 Object로 넘어오기 때문이다. 위와 같이 호출할 경우 s는 Sink<Object> 이므로, T는 Object를 의미하게 된다.

이런 경우 해답은 lower bound다. <? super T>으로 쓰면 된다.  ? extends T 가 T의 하위객체라는 뜻이라면, ? super T는 T의 상위 객체라는 의미이다. 이를 사용한 코드는 아래와 같다.

public static <T> T writeAll(Collection<T> coll, Sink<? super T> snk){...}
...
String str = writeAll(cs, s); // 아싸~~



이렇게 쓰면, 호출도 정상적으로 되고 return type도 우리가 원했던 대로 된다.
좀 더 현실적인 예제를 들어보자. java.util.TreeSet<E>는 E라는 원소 타입을 가지며 정렬된 Set을 만들수 있다. 이는 몇 개의 생성자를 가지는데, 그 중에 Comparator를 인자로 받는 생성자가 있다. 이 인자는 정렬할 방법을정의해준다. 생성자를 정의하는 부분은 다음과 같이 생겼을 것 같다.

TreeSet(Comparator<E> c)



Comparator 인터페이스의 정의는 다음과 같다.

interface Comparator<T> { 
    int compare(T fst, T snd);
}



TreeSet<String> 을 적절한 Comparator를 통해 호출하려고 하면 String 끼리 비교할 수 있는 Comparator를 만들어서 생성자에전달해주어야 한다. 즉, Comparator<String>을 만들어 주어야 한다. 그러나Comparator<Object>도 그 역할을 해낼 수 있다. 그러나,TreeSet(Comparator<E> c) 와 같은 생성자로 할 경우 E는 String을 의미하기 때문에Comparator<Object>는 받을 수가 없다. 이럴 경우 유연성을 위해 lower bound를 사용하면 된다.즉, Comparator를 인자로 받는 생성자는 다음과 같이 정의되면 된다.

TreeSet(Comparator<? super E> c)



이렇게 하면 이 생성자에 적용할 수 있는 모든 Comparator를 받을 수 있다.
lower bound의 마지막 예를 들어보자. Collections.max() 메쏘드를 살펴보자. 이 메쏘드는 인자로 받은 collection의 최대 값을 반환한다.
 최대값은 정렬 방법에 따라 달라질 수 있다. 때문에 인자로 받은 collection의 모든 원소들은 Comparable을 구현해야 한다.(Comparator가 아니다.) 게다가 그 원소들끼리는 서로 비교가 가능해야 한다.
이를 코드로 정리해 보면 다음과 같이 될 것이다.

public static <T extends Comparable<T>>
T max(Collection<T> coll)



Collection<T> 란 T를 원소로 가지는 어떤 Collection을 의미한다. 리턴 타입이 T라는 것은 인자로 받은 Collection의 원소의타입으로 리턴된다는 것이다. 또 T는 Comparable<T>를 구현해야 서로 비교가 가능하다. 그러나 이것은 너무제약이 심하다. 다음의 코드를 보면 그 이유를 알 것이다.

class Foo implements Comparable<Object> {...}
...
Collection<Foo> cf = ...;
Collections.max(cf); // 쫌 돌아가면 좋겠는걸...



cf 의 모든 원소는 cf의 다른 원소들과 비교가 가능하다. cf의 원소는 Foo의 타입이고, Foo는 Comparable을 구현하고있기 때문이다. 그러나 위에서 정의한 것처럼 Collections.max()가 정의되어 있다면, 위의 코드는 작동하지 않는다.Foo가 Comparable<Foo>를 구현한 것이 아니라 Comparable<Object>를 구현했기때문이다. 사실 정확히 Comparable<Foo>를 구현해야만 Foo의 인스턴스들이 서로 비교 가능한 것은 아니다.Foo의 상위 타입으로 비교를 해도 된다. 그래서 Collections.max()를 다음과 같이 정의하면 된다.

public static <T extends Comparable<? super T>>
T max(Collection<T> coll)



만 약에 만들고자하는 API에서 타입 인자 T를 사용만 한다면, lower bound( ? super T) 가 나을 것이고,API가 T의 타입을 리턴한다면 유연성을 위해 upper bound( ? extends T) 를 사용하는 게 나을 것이다.

Wildcard Capture


/** Set s에 어떤 element를 추가해주는 메쏘드 */
public static <T> void addToSet(Set<T> s, T t) {...}



위와 같은 코드가 있을 때, 아래와 같은 코드로 호출한다고 치자.

Set<?> unknownSet = new HashSet<String>();
addToSet(unknownSet, “abc”); // 아니되옵니다~



unknownSet은 실제로는 HashSet<String>이지만 변수를 정의할 때는 generic 타입이 무엇인지 알 수 없기 때문에 이는 정상적으로 작동하지 않는다.

다음 코드를 살펴보자.

class Collections {
    ...
    <T> public static Set<T> unmodifiableSet(Set<T> set) { ... }
}
...
Set<?> s = Collections.unmodifiableSet(unknownSet); // 이건 정상적으로 작동한다. 왜??



제대로 작동하면 안 될 것 같다. 그러나 Set<T>는 Set<?>로 캐스팅이 가능하기 때문에 정상적으로작동한다. 결국 unmodifiableSet은 요소의 타입이 무엇이든 간에 상관없이 정상적으로 작동한다.
 이 런 상황은상대적으로 많이 일어날 수 있기 때문에 안전하다는 게 보장된 코드를 받아들일 수 있도록 해야 하는 규칙이 있다. 이 규칙을wildcard capture라고 부르는데, 이는 컴파일러가 wildcard로 표현된 알 수 없는 타입을 타입 인자로 처리할 수있게 해준다.




java Generics 10 - 기존 코드를 Generic을 사용하도록 변경하기

지금까지 기존에 generic을 사용하지 않는 코드와 새로 짠 generic을 사용하는 코드 간의 상호작용에 대해서 알아보았다. 이제 기존 코드를 generic을 사용하도록 바꾸는 좀 더 어려운 작업에 들어가 보자.
generic을 사용하도록 코드를 바꾸기로 결정하면, API를 어떻게 바꿀 것인지 잘 생각해보아야 한다.
기존에 이 API를 사용하던 코드들도 여전히 정상적으로 작동해야 하므로, 지나치게 제약을 주면 안 된다. java.util.Collection를 다시 살펴보자. 아래는 generic을 사용하기 전의 코드다.

interface Collection { 
    public boolean containsAll(Collection c);
    public boolean addAll(Collection c);
}


이를 generic을 사용해서 대충 바꿔보면 아래와 같이 된다.

interface Collection<E> { 
    public boolean containsAll(Collection<E> c);
    public boolean addAll(Collection<E> c);
}



타 입이 안전하게 된 것 같아 보이지만, 사실 containsAll()은 어떤 타입의 Collection이든 다 받을 수 있어야한다. ("인자로 받을 수 있다"와 "결과가 false다"는 다른 얘기다.) 이 메쏘드가 true를 리턴하기 위해서는 인자로받은 Collection의 E 타입의 값들을 가지고 있어야 할 것이다. 그러나 인자로 넘어오는 Collection이 정확히 E의타입이란 보장은 없다. E의 서브 타입일 수도 있다.

addAll()에 있어서도 E의 서브 타입이 추가 가능하도록 만들어야 한다. section 5 에서 이 방법에 대해 다루었다.
이런 식으로 generic을 추가할 때는 기존에 이 API를 사용하는 코드들도 정상적으로 작동하도록 해야 한다. 그러니까, generic이 적용되지 않은 코드에서도 정상적으로 작동하도록 해야 한다는 것이다.

이번에는 section 9에서 보았던 Collections.max()를 다시 살펴 보자. 마지막으로 우리가 생각했던 max()의 선언부는 다음과 같다.

public static <T extends Comparable<? super T>>
T max(Collection<T> coll)



괜찮아 보인다. 하지만 generic을 제거하고 생각해 보면 아래와 같이 된다.

public static Comparable max(Collection coll)



하지만 실제로는 아래와 같이 되어야 정상이다.

public static Object max(Collection coll)



즉, 리턴 타입은 Object가 되어야 한다.
이런 경우 타입 변수 T를 이용하여 다시 정의를 하면 다음과 같이 된다.

public static <T extends Object & Comparable<? super T>>
T max(Collection<T> coll)



이 것은 multiple bound라는 것의 예다. &를 이용하는 문법이다. T1 & T2 & .. &Tn과 같이 정의된 경우 T는 T1, T2...Tn 의 서브 타입이 되며, 첫번째 T1의 타입으로 erasure가 작동한다.

마지막으로 고려해야 할 사항은 인자로 받는 Collection은 읽기가 가능해야 한다는 것이다. 그래서 JDK에서는 max()가 다음과 같이 정의되어 있다.

public static <T extends Object & Comparable<? super T>>
T max(Collection<? extends T> coll)




맨 마지막 한 페이지 정도는 generic과 직접 관련이 없는 부분이 있어서 뺐습니다. 이것으로 generic 공부 끝~~~


반응형
반응형

참고 및 출처 ::

 - Head First Design Pattern

 - http://www.gurubee.net/pages/viewpage.action?pageId=1507401

 - http://www.hoons.kr/Lecture/LectureMain.aspx?BoardIdx=50097&kind=62&view=0

 - http://donxu.tistory.com/89

 - http://goo.gl/zX5yO



* Abstract Factory Pattern (추상 팩토리 패턴)

 - 팩토리 : 객체 생성을 처리하는 클래스. 

 - 추상 팩토리 패턴의 본질은 "관련된 객체들의 집단(family)을 생성하는 인터페이스를 제공하되, 생성되는 객체의 구체적인

    클래스를 알 필요 없다는 것"이다.

 - Factory를 사용하는 Client가 많을 경우 여러 클래스에서 사용할 수 있다.

 - 생성할 클래스가 변하면 인터페이스 메서드 전체를 수정해야 하는 단점이 있으나 다수의 클래스가 존재하고 상황에 맞게

   적당한 클래스를 골라서 생성할 때 유용하다.

 - 객체 생성을 위한 메소드 실행을 위해 객체 인스턴스를 만들지 않아도 된다.


Abstract Factory Pattern(이하 추상 팩토리 패턴)은 공통의 테마를 가진 팩토리의 그룹을 캡슐화하는 방법을 제공하는 디자인

패턴이다. 일반적으로 클라이언트 소프트웨어는 추상 팩토리의 구체적인 구현체를 생성하고 그 구현체의 인터페이스를 사용한다.

클라이언트는 생성된 객체의 인터페이스만 사용하기 때문에 각각의 내부 팩토리로부터 얻는 구체적인 객체에 대해 알지 못한다.

이 패턴은 어떤 객체들의 그룹에 대한 구체적인 구현을 일반적인 사용법(인터페이스)으로부터 분리시킨다.




* 간단한 팩토리 정의

 간단한 피자 팩토리를 만들어 보자.


1. 일반 피자 팩토리

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//==============================================
//피자 가게 운영을 위한 객체생성
//==============================================
Pizza orderPizza() {
 
    Pizza pizza = new Pizza();
 
    Pizza.prepare();
    Pizza.bake();
    Pizza.cut();
    Pizza.box();
 
    return pizza;
}
 
//==============================================
//여러 종류 피자를 만들기 위한 수정
//==============================================
Pizza orderPizza(String type){
    Pizza pizza;
   
    //피자 종류가 바뀔 때 마다 코드를 고쳐야함.
    if(type.equals("cheese")){
        pizza = new CheesePizza();
    }
    else if(type.equals("pepperoni")){
        pizza = new PepperoniPizza();
    }
    else if(type.equals("clam")){
        pizza = new ClamPizza();
    }
    else if(type.equals("veggie")){
        pizza = new VeggiePizza();
    }
 
    //피자 종류에 상관없이 바뀌지 않는 부분.
    Pizza.prepare();
    Pizza.bake();
    Pizza.cut();
    Pizza.box();
 
    return pizza;
}


2. 간단한 피자 피자 팩토리 (Simple Factory)

상황에 따라 코드가 변경되기 때문에 바뀌는 부분과 바뀌지 않는 부분을 분리한 후 캡슐화를 적용한다.
Simple Factory를 적용해서 위와 같은 문제점을 수정하자.
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
29
30
31
32
33
34
35
36
37
38
39
40
//==============================================
//피자 가게 객체 생성에 캡슐화 적용
//==============================================
Pizza orderPizza(String type){
    Pizza pizza;
   
    Pizza.prepare();
    Pizza.bake();
    Pizza.cut();
    Pizza.box();
 
    return pizza;
}
 
//==============================================
//피자를 만들기 위한 객체생성
//==============================================
public class SimplePizzaFactory{
 
    //클라이언트에서 새로운 객체의 인스턴스를 만들때 호출
    public Pizza createPizza (String type){
        Pizza pizza = null;
 
        //orderPizza() 메소드에서 뽑아낸 코드(피자의 종류를 결정)
        if(type.equals("cheese")){
            pizza = new CheesePizza();
        }
        else if(type.equals("pepperoni")){
            pizza = new PepperoniPizza();
        }
        else if(type.equals("clam")){
            pizza = new ClamPizza();
        }
        else if(type.equals("veggie")){
            pizza = new VeggiePizza();
        }
     
        return pizza;
    }
}


간단한 피자 팩토리의 장점은 아래와 같다.
 - Factory를 사용하는 클라이언트가 많을 경우, 여러 클래스에서 사용할 수 있다.
   (예를 들어, 피자를 생성하는 작업을 한 클래스에 캡슐화시켜 놓으면 구현을 변경해야하는 경우, 해당 팩토리 클래스 하나만 수정하면 된다.)

정적 팩토리(Static Factory)

 - 장점 : 객체 생성을 위한 메소드 실행을 위해 객체 인스턴스를 만들지 않아도 된다.

 - 단점 : 서브 클래스를 만들어 객체 생성 메소드의 행동을 변경 시킬 수 없다.

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 PizzaStore{
    //Simplepizzafactory에 대한 레퍼런스를 저장
    SimplePizzaFactory factory;
   
    //PizzaStore의 생성자에 팩토리 객체 전달
    public Pizza createPizza (SimplePizzaFactory factory){
        this.factory = factory;
    }
   
    public Pizza orderPizza (String type){
        Pizza pizza;
 
        /*orderPizza() 매소드에서는 팩토리를 써서 피자 객체 생성.
            new 연산자 대신 팩토리 객체에 있는 create 메소드를 사용.
        구상 클래스의 인스턴스를 만들 필요가 없다.*/
        pizza = faactory.createPizza(type);
 
        Pizza.prepare();
        Pizza.bake();
        Pizza.cut();
        Pizza.box();
     
        return pizza;
    }
}
 


피자 가게의 클래스 다이어그램은 아래와 같다.




* 피자가게 프레임워크

 문제점이 생겼다. 각 분점마다 독자적인 방법을 사용하기 시작했기 때문이다. 그래서 피자 가게와 피자 제작 과정을 하나로 묶어주는 프레임워크를 만들어야 겠다는 생각에 도달했다. 피자를 만드는 활동 자체는 전부 PizzaStore 클래스에 국한 시키면서도 분점마다 고유의 스타일을 살릴 수 있도록 하는 방법을 적용해보자.



1. Factory Method (팩토리 메소드) 선언
 - 객체 생성을 처리한다.
 - 객체 생성 작업을 서브 클래스에 캡슐화한다.
 - 슈퍼 클래스의 클라이언트 코드와 서브 클래스의 객체 생성 코드를 분리한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//==============================================
//추상클래스 (PizzaStore) 생성
//==============================================
public abstract class PizzaStore {
 
    public Pizza orderPizza(String type) {
        Pizza pizza;
     
        //PizzaStore의 createPizza를 호출
        pizza = createPizza(type);
     
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
     
        return pizza;
    }
   
    //팩토리 객체대신 사용
    abstract createPizza(String type);
 
}




* Factory Method Pattern (팩토리 메서드 패턴)

정의

 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하게 만드는 것.

특징

 - 모든 팩토리 패턴에서는 객체 생성을 캡슐화한다.

 - 객체 생성 코드를 전부 한 객체 또는 메소드에 집어 넣으면 코드에서 중복되는 내용을 제거할 수 있고, 나중에 관리할 때도 한 군데에만 신경 쓰면 된다.

 - 구현이 아닌 인터페이스를 바탕으로 프로그래밍을 할 수 있고, 그 결과 유연성과 확장성이 뛰어난 코드를 만들 수 있게 된다.




* 피자 팩토리 메소드를 이용한 피자 주문

 1. PizzaStore 인스턴스 확보

 2. orderPizza() 호출

 3. createPizza() 호출. (Pizza 객체가 OderPizza() 메소드로 리턴됨)

 4. orderPizza() 피자를 준비하고, 굽고, 자르고, 포장하는 작업 완료함.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public abstract class Pizza {
    String name;
    String dough;
    String sauce;
    ArrayList toppings = new ArrayList();
   
    void prepare() {
        System.out.println("Preparing " + name);
        System.out.println("Tossing dough..");
        System.out.println("Adding sauce... ");
        System.out.println("Adding toppings: ");
     
        for(int i=0; i< toppings.size(); i++) {
            System.out.println("  "+ toppings.get(i));
        }
    }
   
    void bake() {
        System.out.println("Bake for 25 miutes at 350");
    }
   
    void cut() {
            System.out.println("Cutting the pizza into diagonal slices");
    }
   
    void box() {
            System.out.println("Place pizza in official PizzaStore box");
    }
   
    public String getName() {
        return name;
    }
}
 
 
public class NYStyleCheesePizza extends Pizza {
    public NYStyleCheesePizza() {
        name = "NY Style Sauce and Cheese Pizza";
        dough = "Thin Crust Dough";
        sauce = "Marinara Sauce";
     
        toppings.add("Grated Reggiano Cheese");
    }
}
 
 
public class ChicagoStyleCheesePizza extends Pizza {
    public ChicagoStyleCheesePizza() {
        name = "Chicago Style Sauce and Cheese Pizza";
        dough = "Extra Thick Crust Dough";
        sauce = "Plum Tomato Sauce";
     
        toppings.add("Shredded Mozzarella Cheese");
    }
   
    void cut() {
        System.out.println("Cutting the pizza into square slices");
    }
}
 
 
public class PizzaTestDrive {
    public static void main(String[] args) {
        PizzaStore nyStore = new NYPizzaStore();
        PizzaStore chicagoStore = new ChicagoPizzaStore();
     
        Pizza pizza = nyStore.orderPizza("cheese");
        System.out.println("Ethan ordered a " + pizza.getName() + "\n");
     
        Pizza pizza = chicagoStore.orderPizza("cheese");
        System.out.println("Joel ordered a " + pizza.getName() + "\n");
    }
}



* 객체 의존성 살피기

객체 인스턴스를 직접 만들면 구상 클래스에 의존해야 한다.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class DependentPizzaStore {
  
    public Pizza createPizza(String style, String type) {
        Pizza pizza = null;
     
        //뉴욕풍 피자를 처리하는 부분
        if (style.equals("NY")) {
            if (type.equals("cheese")) {
                pizza = new NYStyleCheesePizza();
            }
            else if (type.equals("veggie")) {
                pizza = new NYStyleVeggiePizza();
            }
            else if (type.equals("clam")) {
                pizza = new NYStyleClamPizza();
                        }
                        else if (type.equals("pepperoni")) {
                              pizza = new NYStylePepperoniPizza();
                        }
 
                  //시카고풍 피자를 처리하는 부분
                  } else if (style.equals("Chicago")) {
                        if (type.equals("cheese")) {
                              pizza = new ChicagoStyleCheesePizza();
                        }
                        else if (type.equals("veggie")) {
                              pizza = new ChicagoStyleVeggiePizza();
                        }
                        else if (type.equals("clam")) {
                              pizza = new ChicagoStyleClamPizza();
                        }
                        else if (type.equals("pepperoni")) {
                              pizza = new ChicagoStylePepperoniPizza();
                        }
            } else {
                  System.out.println("Error: invalid type of pizza");
                  return null;
            }
 
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
 
            return pizza;
      }
}



* 의존성 뒤집기 원칙 (Dependency Inversion Priciple)


 - 추상화 된 것에 의존하도록 만들어라.

 - 구상 클래스에 의존하도록 만들지 말라.

심하게 의존적인 PizzaStore() 의 가장 큰 문제점은 모든 종류의 피자에 의존한다는 점이다.


- 팩토리 메소드 패턴을 적용하고 나면 고수준 구성요소인 PizzaStore 와 저수준 구성요소인 피자 객체들이 모두 추상 클래스인 Pizza에 의존한다.

 - 팩토리 메소드 패턴은 의존성 뒤집기 원칙을 준수하기 위해 쓸 수 있는 가장 적합한 방법 가운데 하나이다.




* 개발자가 지향해야 하는 규칙

  
 - 어떤 변수에도 구상 클래스에 대한 레퍼런스를 저장하지 않는다.

 - 구상 클래스에서 유도된 클래스를 만들지 않는다.

 - 베이스 클래스에 이미 구현되어 있던 메소드를 오버라이드 하지 않는다.




* 원재료 공장 만들기

원재료를 생산할 팩토리를 위한 인터페이스 정의.

모든 팩토리 인스턴스에서 공통적으로 사용하는 부분이 있다면 인터페이스가 아닌 추상클래스로 만들어도 된다.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public interface PizzaIngredientFactory {
     
      //각 재료별로 생성 메소드 정의.
      public Dough createDough();
      public Sauce createSauce();
      public Cheese createCheese();
      public Veggies[] createVeggies();
      public Pepperoni createPepperoni();
      public Clams createClam();
}
 
 
//========================================================================
//모든 재료 공장에서 구현해야 하는 인터페이스를 구현
//========================================================================
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
  
      public Dough createDough() {
            return new ThinCrustDough();
      }       
            
      public Sauce createSauce() {
            return new MarinaraSauce();
      }       
            
      public Cheese createCheese() {
            return new ReggianoCheese();
      }       
            
      public Veggies[] createVeggies() {
            Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
            return veggies;
      }       
            
      public Pepperoni createPepperoni() {
            return new SlicedPepperoni();
      }       
            
      public Clams createClam() {
            return new FreshClams();
      }       
}


반응형

'Design Pattern' 카테고리의 다른 글

Strategy Pattern  (0) 2013.08.12
facade패턴  (0) 2013.07.05

반응형

MyBatis ResultHandler sample

dao 와 sqlmap.xml 를 변경하지 않고 service 에만 ResultHandler 처리를 해주면 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Autowired
private SqlSessionFactory sqlSessionFactory;
  
public String getCSVList() {
 
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        class MyResultHandler implements ResultHandler {
 
            @Override
            public void handleResult(ResultContext context) {
                BookInfoBean bean = (BookInfoBean) context.getResultObject();
            }
        };
 
        MyResultHandler handler = new MyResultHandler();
        bookInfoDao.getCSVList(handler);
 
        return null;
    } finally {
        sqlSession.close();
    }
}

< service.java >


1
2
3
public interface BookInfoDao {
    public void getCSVList(ResultHandler handler);
}

< dao.java >


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<resultMap id="bookInfoResult" type="kr.lancerme.webapp.dao.bookinfo.BookInfoBean">
    <id property="bookId" column="I_BOOK_ID" javaType="int"/>
    <result property="bookNm" column="S_BOOK_NM" javaType="java.lang.String"/>
    <result property="isbn" column="S_ISBN" javaType="java.lang.String"/>
    <result property="author" column="S_AUTHOR" javaType="java.lang.String"/>
    <result property="factoryId" column="S_FACTORY_ID" javaType="java.lang.String"/>
</resultMap>
  
<select id="getCSVList" resultMap="bookInfoResult">
<!-- book_info 의 목록 -->
    SELECT I_BOOK_ID,
           S_BOOK_NM,
           S_ISBN,
           S_AUTHOR,
           S_FACTORY_ID
      FROM book_info
</select>

< sqlmap.xml >

 출처 : lancerme.tistory.com/33



RowHandler 



이전에


List result = sqlMapClient.queryForList("selectList*****", parameter);


이렇게 사용했다면


 


아래처럼 사용하기


ResultRowHandler resultHandler = new ResultRowHandler() ;

sqlMapClient.queryWithRowHandler("selectList*****", parameter, resultHandler);

List result = resultHandler.getReturnList() ;


 


이렇게 사용하는 잇점은 검색해봐요 (^_^)


 


추가한 Class내용은


--------------------------------------------


import java.sql.ResultSet;

import java.util.ArrayList;

import java.util.List;


import com.ibatis.sqlmap.client.event.RowHandler;

import com.lg.g4c.capp.lib.entity.Entity;


public class ResultRowHandler implements RowHandler{


 private List<Entity> returnList = new ArrayList<Entity>() ;


 /**

  * Constuctor

  */

 public ObjectRowHandler(){


 }


 /**

  * 결과조회에 대해 하나의 Row를 어떻게 담을지에 대한 로직구현

  * @param rowObject

  */

 public void handleRow(Object rowObject){

  Entity e_result = new Entity() ;

  try{

   e_result.parseResultSet((ResultSet)rowObject) ;

   returnList.add(e_result) ;


  }catch(Exception e){

   e.printStackTrace();

  }

 }


 public List<Entity> getReturnList(){

  return returnList ;

 }


}

[출처] iBatis RowHandler 사용하기 Sample|작성자 여우아저씨

반응형
반응형

자신이 생각하기에 따라 인생이 달라진다.



(마르쿠스 아우렐리우스)

반응형

'감동적인 삶으로의 한걸음...' 카테고리의 다른 글

아는 것이 힘  (0) 2013.07.10
반응형

volatile이란 단어의 뜻은 "변덕스러운"이다. 다시 말하자면 "자주 변할 수 있다"로 생각할 수 있다. 프로그래밍 언어에서는 정의는 언어와 버전마다 다르지만, 대충은 "자주 변할 수 있는 놈이니 있는 그대로 잘 가져다써"정도로 생각을 하면 되겠다. 조금 더 엄밀히 정의를 하자면, (1) 특정 최적화에 주의해라, (2) 멀티 쓰레드 환경에서 주의해라, 정도의 의미를 준다고 보면 된다.

Java에서는 어떤 의미를 가질까? volatile을 사용한 것과 하지 않은것의 차이는 뭘까? volatile의 버전마다의 차이는 뭘까? synchronization과 volatile의 차이는 뭘까? 이 의문들에 대해서 정리한 것은 다음과 같다.

  • volatile을 사용하지 않은 변수: 마구 최적화가 될 수 있다. 재배치(reordering)이 될 수있고, 실행중 값이 캐쉬에 있을 수 있다.
  • volatile을 사용한 변수 (1.5미만): 그 변수 자체에 대해서는 최신의 값이 읽히거나 쓰여진다.
  • volatile을 사용한 변수 (1.5이상): 변수 접근까지에 대해 모든 변수들의 상황이 업데이트 되고, 변수가 업데이트된다.
  • synchronziation을 사용한 연산: synch블락 전까지의 모든 연산이 업데이트 되고, synch안의 연산이 업데이트된다.


무슨 말인지 전혀 모를 수 있다. 앞으로 예제를 들면서 이해를 시켜보도록 노력하겠다.

첫 예제는 Jeremy의 블로그에서 가져온다. 나는 위의 4가지의 경우를 완전히 정립하지 못한 상태에서 봐서 이 예제의 설명이 모호했다고 느꼈다. 블로그의 설명을 보고 내 설명을 보면 이해가 더 될지도 모르겠다.

Thread 1
1: answer = 42;
2: ready = true;

Thread 2
3: if (ready)
4: print (answer);

예제1. 1 -> 2 -> 3 -> 4 순서로 프로그램이 진행된다. ready는 애초에 false다.


첫번째로 ready를 volatile을 걸지 않았다고 해보자. 그럼, answer와 ready가 마구 최적화가 된다. 또한, 그들이 실행시간에 캐쉬된 값들이 바로바로 메인 메모리에 업데이트 되지 않을 수 있다. 만약, 2번 문장의 ready값이 실행이 된 후에 캐쉬만 업데이트를 한 후, 3번이 실행되었다면, 3에서는 ready를 false로 읽었을 수가 있다. --> 에러

두번째로 ready에 volatile을 걸었다고 하자 (버전 1.5 미만). 그럼, ready의 값은 읽혀지거나 쓰여질 때마다 바로 업데이트 된다. 즉, 2번 문장이 실행된 후에 메인 메모리의 ready는 true라고 쓰여진다. 따라서, 3번 문장이 실행될때에 ready는 메인 메모리에서 값을 읽어와서 4번을 안정적으로 실행을 한다. 하지만, answer는 volatile이 정의되지 않았다면 값이 정확히 전해지는 것을 보장할 수가 없다. 4번 문장이 42말고 그 전의 값을 "읽을수도 있다". ---> 에러

세번째로 ready에 volatile을 걸었다고 하자 (버전 1.5 이상). 그럼, ready의 값이 읽혀지거나 쓰여질 때마다 그 때까지의 쓰레드의 모든 상태가 없데이트 된다. 즉, 2에서 ready값이 메인 메모리로 업데이트 되면서, 같은 쓰레드에 있는 answer도 메인 메모리에 업데이트가 된다! 그래서, 3번의 if문은 당연히 참이 되고, 4번에서 answer값도 42를 읽게 된다. --> 성공

이제 대충 감이 잡히는가? 그럼 예제를 하나 더 보자. 그 유명한 Double-Checked Locking 문제이다.

class Foo { 
  private Helper helper = null;
    public Helper getHelper() {
1:    if (helper == null) 
2:      helper = new Helper();
3:    return helper;
    }
}

코드 1. Single-thread 버전의 singleton pattern (Multi에서 안돌아).


이 글을 읽는 사람들이 singleton 디자인 패턴은 다 안다고 가정을 하고 설명을 하겠다. 위의 코드는 singleton 패턴을 사용한 코드다. 쓰레드가 하나일 때에는 잘 동작을 한다. 하지만, 쓰레드가 여럿일 때에는 문제가 생긴다. 예를들어, 다음과 같은 순서를 생각해봐라.


  1. Thread 1이 Statement 1접근 (if --> true)
  2. Thread 2가 Statement 1접근 (if --> true)
  3. Thread 1이 Statement 2접근하여 할당
  4. Thread 2가 Statement 2접근하여 할당 ---> 에러!

위의 에러를 피하기 위해서 간단한 방법을 생각해보면 아예 함수자체를 동기화 시키는 방법이 있다. 아래처럼 말이다.

class Foo { 
  private Helper helper = null;
  public synchronized Helper getHelper() {
    if (helper == null) 
      helper = new Helper();
    return helper;
  }
}

코드 2. Multi-thread 버전의 singleton pattern (너무 비쌈).


코드 2는 완벽히 잘 동작한다. 하지만. 문제는 synchronization이 너무 비싸다는 데에 있다. 우리는 저렇게 비싼걸 접근시 매번 불러주기는 싫다. 그래서, 아래처럼 double checked locking이라는 요상한 방법을 고안해낸다.

class Foo { 
  private Helper helper = null;
    public Helper getHelper() {
      if (helper == null) 
        synchronized(this) {
          if (helper == null) 
            helper = new Helper();
        } 
        return helper;
      }
    }

코드 3. Double Checked Locking (문제있음).


우아, 똑똑하다. 왠지 잘 동작할 것 같은 코드다. 만약 할당 안된 두 개의 쓰레드가 접근을 하면 멈춰서 하나만 할당을 해주고 넘겨준다. 당연히 잘 되야 하지 않는가? 근데, 이것도 잘 안된다. 문제는 아래처럼 컴파일 될 때이다.

class Foo { 
  private Helper helper = null;
    public Helper getHelper() {
1.    if (helper == null) 
2.      synchronized(this) {
3.        if (helper == null) {
4.          some_space = allocate space for Helper object;
5.          helper = some_space;
6.          create a real object in some_space;
          } 
          return helper;
        }
    }

예제 2. Double Checked Locking (상세하게).


머신 코드단에서는 최적화에 의해 저렇게 재배치(reordering)이 될 수 있다. 그러면 이제 어떤 시나리오가 문제가 되냐?

1. Thread1이 1~5까지 실행. 즉, helper는 null은 아니지만, 완전한 객체는 아님.
2. Thread2가 1을 실행후에 helper가 생성되었다고 인지.
3. Thread2가 getHelper()함수를 탈출하고, 외부에서 helper를 이용해서 무언가를 하려함 --> 에러!

진짜 생각지도 못한 low-level버그가 생기는 것이다. 이 버그는 volatile을 안쓰면 당연히 생기고, helper를 volatile로 선언해도 version에 따라 차이가 있다. 왜 그런가?

버전 1.5 미만일 경우에는 접근에서 그 변수 자체에만 업데이트를 해주도록 되어있다. 즉, some_space는 상관없이 5번 문장을 실행한 후에 helper가 가진 값이 some_space라고 메인 메모리에 써주기만 하면 되는 것이다. 즉, 위의 시나리오가 그냥 그대로 진행될 수가 있다.

버전 1.5 이상일 경우에는 그 변수를 포함한 모든 값이 업데이트가 된다고 했다. 즉, 코드 3에서 new Helper() 가 다 만들어지고 그게 업데이트가 되고 helper에 그 값이 들어가야 하는 것이다. 다시 말하면, 애초에 예제 2처럼 컴파일이 되지도 않는 다는 거다! 재배치 없이 컴파일이 되고, Helper()가 업데이트가 되고, 그게 helper에 써지고, helper가 메인 메모리에 업데이트가 되어 문제가 생길 소지가 없게 된다. 

이렇게, 두 예제를 살펴봤다. 대충 volatile이 쓰면 어떻게 변하는지, 버전에 따른 변화가 어떤지 감이 잡힐꺼라고 생각을 한다. 

마지막으로 volatile과 synchronization을 살펴보자. 아래의 코드가 이해를 도와줄 거라고 생각한다. i와 j를 보고 연산에 어떤 차이가 있을지 생각해봐라. 어느 변수가 멀티쓰레드 환경에서 문제가 될까?

1. volatile int i;
2. i++;
3. int j;
4. synchronized { j++; }

코드 4. volatile vs synchronized


대략 감이 잡힌다면 정말 센스 만점인 사람이다. 답은 i가 문제가 될 수 있고, j는 괜찮다는 거다. 왜냐면 i++ 이란 문장은 read i to temp; add temp 1 ; save temp to i; 라는 세개의 문장으로 나뉘어지기 때문이다. 따라서, read나 write하나만 완벽히 실행되도록 도와주는 volatile은 2번 문장이 3개로 나뉘어 질 경우에 다른 쓰레드가 접근하면 문제가 생길 수가 있다. 하지만, synchronized는 그 블럭안에 모든 연산이 방해받지 않도록 보장해주기에 j는 제대로 업데이트가 된다.

이제 대략 감이 잡혔으면 한다. 다른 자료들에 나온 설명이 어려운 용어들을 써서 이해가 잘 안될수가 있는데, 내 글이 이해에 도움이 되길 바란다. 만약 이 글도 너무 어렵다면 리플을 남기면 최대한 노력해서 답변하겠다.


참고자료.
1. Volatile in wikipedia: 1.5 전후의 설명을 아래처럼 해놨다. 어려워 보이지만 내가 위에 써놓은 것과 같은 뜻이다. 
  • Java (모든 버전): volatile로 선언한 변수의 read, write에는 global ordering이 주어진다.
  • Java 1.5 이후: volatile로 선언한 변수의 read, write마다 happens-before relationship이 성립이 된다.
2. The volatile keyword in Java: volatile, synchronized를 테이블로 깔끔하게 비교해 놓음
3. What Volatile Means in Java: 예제 1이 나온 블로그. 아마 헷갈릴 수도 있으니 내 글과 비교해서 보길...
4. The "Double-Checked Locking is Broken" Declaration: volatile보다는 double-checked locking에 대해서 제대로 나와있다. synchronized를 사용한 비싼 방법이나, volatile을 사용하는 방법 이외에도 재미있는 해결책이 많다.

출처 http://valley.egloos.com/viewer/?url=http://tomowind.egloos.com/4571673


반응형
반응형

VO, DTO, DAO 클래스 설명

[출처] http://choijaehyuk.com/128



VO

개념

Value Object DTO와 동일한 개념이나 차이 점은 read only 속성을 갖습니다.

Value Object는 관계데이터베이스의 레코드에 대응되는 자바클래스입니다형태는 db레코드를 구성하는 필드들을 Value Object Attribute로 하고 해당 변수에 접근 할 수 있는 Getter Setter 메소드의 조합으로 클래스를 형성되어진 클래스입니다특성은 대체로 불변성이고equals()로 비교할 때 객체의 모든 값을 비교해야 합니다.

필요성

Network traffic을 줄임으로 인해서 효과적입니다.

기대효과

Network traffic이 줄어듭니다.

 

장 단점

장점으로는 비 서버 측 클라이언트도 네트워크 오버헤드 없이 영속성 데이터에 액세스 할 수 있다는 점입니다데이터 전달을 위해 가장 효율적인 방법이지만클래스의 선언을 위해 많은 코드가 필요합니다즉 파일수가 많아지게 되고 관리도 힘들어지게 됩니다.

예제 소스코드

DTO

개념

데이터가 포함된 객체를 한 시스템에서 다른 시스템으로 전달하는 작업을 처리하는 객체입니다. Vo dto의 차이점은 vo는 특정한 비즈니스 값을 담는 객체를 vo라 하고 dto는 레이어간의 통신용도로 오가는 객체를 dto라고 합니다.

Vo DTO의 비교

DTO의 나머지 속성은 vo와 똑같다고 생각하여서 차라리 차이점을 비교하려고 합니다.

Core J2EE Patterns 라는 책에서는... Value Object Transfer Object를 동일한 뜻으로 사용합니다만 반대로 Martin Fowler는 저서 Patterns of Enterprise Application Architecture에서 약간 다른 의미로 이야기 합니다. DTO는 메소드 호출 횟수를 줄이기 위해 데이터를 담고 있는 녀석으로, VO는 값이 같으면 동일 오브젝트라고 볼 수 있는 녀석으로 표현을 하고 있습니다.

 

쉽게

DTO a = new DTO(1);

DTO b = new DTO(1);

이라고 했을 때 a != b 이지만,

 

VO a = VO(1);

VO b = VO(1); 이라고 했을때는 a == b라고 정의하는 형태입니다.

사실 이러한 내용도 헷갈리는 부분이 있는게 대부분의 검색에서 사람들은 vo dto를 같은 개념으로 이야기 하고 있어서 아직도 vo dto가 이런거다라기 보다 거의 똑 같은 개념으로 생각하고 있습니다.

 

DAO

개념

데이터 접근을 목적하는 객체입니다커넥션 같은 것을 하나만 두고 여러 사용자가 DAO의 인터페이스를 사용하여 필요한 자료에 접근 하도록 하는 것이 DAO의 개념입니다.

필요성

모든 데이터베이스에 공통적으로 접속 할 수 있는 ODBC가 나왔지만 완벽하진 못했습니다여전히 로우 레벨의 API를 포함하고 있었기 때문에 개발 장벽이 여전히 높았습니다이런 이유 때문에 개발자들은 정작 데이터베이스에 들어 있는 데이터를 어떻게 이용할지에 초점을 맞추기 보다어떻게 데이터베이스에 접속해서 데이터베이스와 교류하는지에 더 초점을 기울였습니다즉 데이터를 활용하는 논리적 고민보다 기술적 고민에 더 많은 신경을 썻었습니다이런 이유로 DAO란 대안이 나왔습니다.

기대효과

사용자는 자신이 필요한 Interface DAO에게 던지고 DAO는 이 인터페이스를 구현한 객체를 사용자에게 편리하게 사용 할수 있도록 반환해줍니다.

장 단점

DB에 대한 접근을 DAO가 담당하도록 하여 데이터베이스 엑세스를 DAO에서만 하게 되면 다수의 원격호출을 통한 오버헤드를 VO DTO를 통해 줄일수 있고 다수의 DB 호출문제를 해결할 수 있습니다또한 단순히 읽기만 하는 연산이므로 트랜잭션 간의 오버헤드를 감소할 수 있습니다.

 그러나 Persistent Storage를 너무 밀접하게 결합해서 작성을 하게 되면 Persistent Stroage를 다시 작성할 경우가 생기는데 이러한 경우 유지 보수의 문제가 생길수도 있습니다.

반응형

'Spring' 카테고리의 다른 글

Spring Transaction #2  (0) 2014.10.01
Spring Transaction #1  (0) 2014.10.01
domain object에 대한...  (0) 2013.07.08
Spring Java Mail  (0) 2013.07.05
Spring MultipartResolver  (0) 2013.07.02
반응형

먼저 도메인 객체에 대해 잘 모르는 사람들을 위해 이 오브젝트를 조금 자세히 설명할 필요가 있다.과거 자바가 웹의 영역에 드러서기 시작하면서 다른 플랫폼들과 소통하기 위한 장치가 필요했는데 그것이 바로 JSR-000220, 자바빈즈였다. 자바빈은 본래 다양한 목적으로 설계되었으나 지금은 대부분이 외부 리소스를 담는 그릇같은 용도로 사용되고 있으며, 빈 생성자를 가지며 내부에 private로 설정된 프로퍼티에 get…, set…과 같은 명명규칙을 갖고 있는 클래스를 일컫는다.


이렇듯 도메인 오브젝트의 첫 시작이었던 자바빈즈는 클래스 내부에 연산이나 다양한 기능을 넣기엔 기술적 한계가 많았으므로 대부분 해당 데이터를 처리하기 전에 임시로 담아두는 정도로 밖에 사용하지 않았다. 임시적으로 발생하는 데이터를 처리하기 전까지 담아두는 도메인 객체를 일컫어 빈약한 도메인 오브젝트 방식이라도 한다.

빈약한 도메인 오브젝트를 보다 잘 이해할 수 있도록 한가지 예를 들어보자. @Controller에서 파라미터로 넘어온 값을 DB에 넣기 위해 다음과 비슷한 도메인 오브젝트를 만들어 본 기억이 있을 것이다.

public class User {
private String id;
private String password;
private String name;

public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}

…중략…

}

이런 단순한 도메인 오브젝트는 테이블의 컬럼 또는 뷰에서 넘어오는 파라미터를 기본적으로 완벽하게 이해하게끔 설계하고 있으므로 해당 요청을 처리하는 계층에서 로직이 시작되기 전에 미리 User란 그릇에 테이블의 컬럼값과 파라미터 값을 저장시켜놓고 사용할 수 있었다.

근데 이런 도메인 오브젝트를 계속 사용하다보니 조금 헷갈리기 시작한다. 왜냐하면 따지고 보면 도메인 오브젝트가 가장 객체의 본질을 잘 표현해주는 주된 형태인데 이걸 User 오브젝트 자신보다 연관성이 떨어지는 UserService나 UserDao, UserController같은 곳에서 대신 컨트롤 하고 있으니 말이다.

혹시나 이해가 잘 안된 사람을 위해서 좀 더 자세히 설명하고자 한다. 객체지향적인 관점에서 말해보자면 도메인 오브젝트는 객체 자신에 그 무엇보다 가깝다. 현재의 자바기술로 도메인 오브젝트는 바로 객체 자신이라고까지 할 수 있으며 비록 잠시 태어나 소멸할지언정 가장 명확하게 객체 자신을 표현하고 있다는 오브젝트였다는 말이다.

너무 중요한 단락이기에 더욱 쉽게 설명해보겠다. 예를 들어 나는 초밥을 좋아하고 키가 178cm에 단정한 머리 스타일이고 …블라블라… 여하튼 나 자신을 너무 잘 알고 있다. 근데 나를 만든 개발자 때문에 나는 어디든 내 마음대로 움직일 수 없다. 이 개발자는 내가 스스로 못 움직이게끔 바닥에 나를 고정시켜놓고는 대신 나와 비슷한 녀석들을 대량으로 처리하는 이상한 기계에 맡겨 나를 조종하고 있다. 나는 저 기계보다 내 자신이 누군지 잘 이해하고 행동할 자신이 있는데 개발자 때문에 내가 누군지도 잘 모르는 기계가 나를 다스리도록 하고 있는 셈이다.

이제 이해가 조금 됬는지 모르겠다. 우리가 객체지향 프로그래밍이라 부르는 것은 바로 사물을 객체지향적인 관점으로 바라보고 그대로 해석할 수 있는 언어이기 때문에 객체지향적이라고 일컫는 것이다. 근데 빈약한 오브젝트 방식은 이런 객체지향적인 관점을 어느 정도 훼손하고 있다. 물론 보다 능률적인 대량처리를 위해 빈약한 오브젝트 방식이 결코 나쁘다고는 할 수는 없지만 한편으로는 결코 좋은 방식이라고도 할 수 없는 것이다.

그렇다면 이 빈약한 오브젝트를 조금 풍성하게 해보면 어떨까? 다음과 같은 소스처럼 말이다.

public class User {
private String id;
private String password;
private String name;

public int getToken() {
return getId() + getPassword();
}

… 중략…
}

다시 말하지만 도메인 오브젝트는 자신을 가장 잘 이해하고 있으므로 getToken() 메서드를 효율적으로 처리할 수 있는 로직을 가장 직관적으로 구현할 수 있다. 바로 아래와 같이 말이다.

public class User {
private String id;
private String password;
private String name;

private UserDao userDao;

@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public int getToken() {
return getId() + getPassword();
}
public String getId() {
Assert.hasText(id);
return id;
}
public String getPassword() {
if(password==null) return userDao.getPassword(id);
else return password;
}

… 중략 …
}

위의 코드는 여러모로 부족한 점이 많지만 중요한 것은 getToken()의 무결성을 보장해준다는 것이다. 만약 이 로직을 도메인 오브젝트 외의 Service 계층이나 Dao계층에서 처리 해야했다면 굉장히 이해가 불편함과 동시에 무결성을 보장하기가 까다로웠을 것이다. 하지만 우리는 위와 같이 도메인 오브젝트가 자기 자신과 관련한 비지니스 로직은 스스로 처리하게 하고 그 무결성을 스스로 보장하게끔 한 덕분에 매우 직관적이고 깔끔한 코드를 얻을 수 있게 되었다.

결론적으로 DDD는 도메인 자체에 더 많은 권한과 기능을 부여함으로써 도메인이 가질 수 있는 능력을 극대화 시키고 주도해 나가는 개발방식이며 더 나아가 도메인을 하나의 계층으로 인정하고 다른 영역과 분리해내는 4Tier 아키텍쳐인 것이다.

물론 위의 코드는 여러모로 문제점이 많다. 우선 도메인 객체에 적용될 수 없는 @Autowired와 같은 DI기술이 적용됬다는 점이며 지금은 보이지 않지만 해당 도메인이 DAO 계층을 흡수한 만큼 DB에 접근할 수 있게된 권한으로 자칫 엄청난 불상사를 초래할 수 있다는 점이다.

하지만 이런 문제점은 스프링의 @Configurable과 AOP 기술, 그리고 DTO 모델을 도입함으로써 해결할 수 있는 문제점들이기 때문에 DDD 주도의 개발을 막지는 못한다. 되려 DDD는 개발을 보다 쉽고 빠르게 도와주며 코드 자체를 직관적으로 바꿔주어 이용자의 부담과 불필요한 이해를 막아준다.


글을 마치며...

도메인 오브젝트는 이렇듯 단순한 자바빈에서 시작하여 조금씩 기능을 더해오더니 이제 개발의 주도적인 역할까지 수행하는 단계에 이르게 되었다. 이렇듯 도메인 오브젝트를 적극 활용하여 개발을 하게 되면 소스의 테스트가 더욱 쉬워지고 활용도 또한 극대화 된다. 그렇다고 무작정 도메인 오브젝트에 모든 기능을 씌우려고 해서는 절대로 안된다. 왜냐하면 DDD를 적용하는 것은 어디까지나 직관적이고 객체지향적인 설계를 위한 것이지 무분별한 남용을 위한 것은 아니기 때문이다.

만약 도메인 오브젝트에 존재하지 않는 외부값의 첨가가 필요하다면 이런 로직은 필히 해당 도메인의 Service 계층, 또는 Support 계층에서 전담하여 처리하고 도메인 오브젝트는 오로지 본인 내부의 리소스만을 조합하여 처리할 수 있는 로직만을 담당하여야 한다.

이렇게 도메인 오브젝트의 발전사를 마쳤다. 필자는 멋도 모르고 코딩을 해대던 과거 시절, 이미 자바빈 객체를 내멋대로 확장하여 사용한 적이 있었는데 그 당시 MVC를 알게 되고 부터는 자바빈에 기능을 부여하는 것이 잘못된 것으로만 알고 있었다. 헌데 이젠 DDD란 이름으로 예전의 나의 코딩방식이 또다시 새로운 패러다임이라고 조명받고 있으니 조금 우습기도 했다.

사람이란게 말을 함부로 바꿔서는 안되는 모양인가 보다.



반응형

'Spring' 카테고리의 다른 글

Spring Transaction #2  (0) 2014.10.01
Spring Transaction #1  (0) 2014.10.01
VO, DTO, DAO  (0) 2013.07.08
Spring Java Mail  (0) 2013.07.05
Spring MultipartResolver  (0) 2013.07.02

+ Recent posts