'개발'에 해당되는 글 90건

  1. 2014.03.12 인텐트 필터 (1)
  2. 2014.03.12 안드로이드 리소으와 에셋
  3. 2014.03.12 logback 로그 프레임워크
  4. 2014.03.12 Spring fro Android
  5. 2014.03.12 Fragment와 Activity통신
  6. 2014.03.12 이미지 캐싱
  7. 2014.03.12 큰 이미지 로딩
  8. 2011.10.06 N-Screen 이란?
  9. 2011.09.26 Visitor
  10. 2011.09.26 Mediator
  11. 2011.09.26 Builder
  12. 2011.09.26 FlightWeight
  13. 2011.09.26 ProtoType
  14. 2011.09.26 Observer
  15. 2011.09.26 Facade
  16. 2011.09.26 Chain of responsibility
  17. 2011.09.26 Decorate
  18. 2011.09.26 Composite
  19. 2011.09.26 Strategy
  20. 2011.09.26 Singleton
posted by 동건이 2014. 3. 12. 14:51

인텐트 필터

안드로이드 인텐트 필터


인텐트 필터

- intent-filter 는 적어도 하나의 action 을 포함하여야 한다.

샘플 AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.notepad">

<application android:icon="@drawable/app_notes" android:label="@string/app_name">

<provider android:name="NotePadProvider" android:authorities="com.google.provider.NotePad"/>

<activity android:name="NotesList" android:label="@string/title_notes_list">

<intent-filter>

<action android:name="android.intent.action.MAIN"/> // 이 어플리케이션에 대한 메인 시작 점을 선언한다.

<category android:name="android.intent.category.LAUNCHER"/> // 이 시작점이 어플리케이션 런처안에 나열되어야 한다는 것을 말한다.

</intent-filter>

<intent-filter>          // 이 필터를 통해 액티비티가 할 수 잇는 것들을 선언한다.

<action android:name="android.intent.action.VIEW"/> // 디렉토리 조회

<action android:name="android.intent.action.EDIT"/> // 디렉토리 수정

<action android:name="android.intent.action.PICK"/> // 디렉토리 내의 특정 노트를 가져올 수 있음

<category android:name="android.intent.category.DEFAULT/>

<data android:mimeType="vnd.android.cursor.dir/vnd.google.note"/> // 위의 액션들이 디렉토리를 대상으로 실행되는것을 지정

</intent-filter>

<intent-filter>

<action android:name="android.intent.action.GET_CONTENT"/> // 사용자에 의해 선택된 노트를 반환

<category android:name="android.intent.category.DEFAULT"/>

<data android:mimeType="vnd.android.cursor.item/vnd.google.note"/>  // 위의 액션들이 하나의 노트를 대상으로 실행되는것을 지정

</intent-filter>

</activity>

<activity android:name="NoteEditor"

android:theme="@android:style/Theme.Light"

android:label="@string/title_note" >

<intent-filter android:label="@string/resolve_edit">

<action android:name="android.intent.action.VIEW" /> // 노트 조회

<action android:name="android.intent.action.EDIT" /> // 노트 수정

<action android:name="com.android.notepad.action.EDIT_NOTE" />  // EDIT_NOTE 는 EDIT 와 동의어이다.

<category android:name="android.intent.category.DEFAULT"/>

<data android:mimeType="vnd.android.cursor.item/vnd.google.note" />   // 위의 액션들이 하나의 노트를 대상으로 실행되는것을 지정

</intent-filter>

<intent-filter>

<action android:name="android.intent.action.INSERT"/> // 신규 노트 생성

<category android:name="android.intent.category.DEFAULT"/>

<data android:mimeType="vnd.android.cursor.dir/vnd.google.note"/> // 위의 액션들이 디렉토리를 대상으로 실행되는것을 지정

</intent-filter>

</activity>

<activity android:name="TitleEditor"

android:label="@string/title_edit_title"

android:theme="@android:style/Theme.Dialog">

<intent-filter android:label="@string/resolve_title">

<action android:name="com.android.notepad.action.EDIT_TITLE" /> // 노트의 제목을 수정하는 커스텀 액션

<category android:name="android.intent.category.DEFAULT" />

<category android:name="android.intent.category.ALTERNATIVE" />

<category android:name="android.intent.category.SELECTED_ALTERNATIVE" />

<data android:mimeType="vnd.android.cursor.item/vnd.google.note"/>   // 위의 액션들이 하나의 노트를 대상으로 실행되는것을 지정

</intent-filter>

</activity>

</application>

</manifest>


'개발' 카테고리의 다른 글

인텐트 필터  (1) 2014.03.12
안드로이드 리소으와 에셋  (0) 2014.03.12
logback 로그 프레임워크  (0) 2014.03.12
Spring fro Android  (0) 2014.03.12
Fragment와 Activity통신  (0) 2014.03.12
이미지 캐싱  (0) 2014.03.12

댓글을 달아 주세요

  1. Favicon of https://tood-re.tistory.com BlogIcon 먹튀 검증 2018.07.30 11:52 신고  Addr  Edit/Del  Reply

    잘보고갑니다

posted by 동건이 2014. 3. 12. 14:49

안드로이드 리소으와 에셋

안드로이드 리소스와 에셋


리소스와 에셋

- 리소스와 에셋의 차이는 그리 크지 않다. 읽어 들이는 방법에 차이가 존재한다.

- 리소스는 컴파일된 R 클래스에 의해 쉽게 접근 가능하며 에셋은 AssetManager 를 사용해야 한다.


리소스와 국제화

res/anim/  애니매이션 프레임 또는 트윈드(tweened) 애니매이션 오브젝트

res/drawable/ .png, .9.png, .jpg 

※ 여기에 위치한 이미지는 aapt 도구에 의해 자동으로 최적화 된다. 예를 들어 256 컬러 이상이 요구되지 않는 

트루컬러 PNG는 컬러 팔레트를 가진 8-비트 PNG로 변환된다.

따라서 빌드 하는동안 변경된다. 변경되지 않도록 하기 위해서는 res/raw/ 폴더에 위치시키자

res/layout/ 스크린 레이아웃

res/values/ 기타 값을 정의하는 xml 파일들

array.xml 배열을 정의함, colors.xml 컬러 drawable 과 컬러 문자열 값을 정의함 Resources.getColor()

dimens.xml 크기 값을 정의함 Resources.getDimension()

string.xml 문자열 값을 정의함 Resources.getText()

styles.xml

res/xml/ Resources.getXML() 로 읽어들여질 수 있는 임의의 xml 파일들

res/raw/ Resources.openRawResource()


코드에서 리소스 사용

R.resource_type.resource_name

android.R.resource_type.resource_name



xml 파일에서 리소스 사용

android:textColor=”@color/opaque_red”

android:text=”@string/hello_world” />

android:textColor=”@android:color/opaque_red” // 시스템 리소스를 참조하기 위해서는 @android: 를 붙여준다.



리소스 선택

- 디바이스 상의 언어 설정, 하드웨어 설정 에 따라 사용할 리소스를 제공할 수 있다.

MyApp/

res/

values-en/

strings.xml

values-fr/

string.xml



각각의 설정에 따라 여러 타입의 수식어를 지원한다. 수식어(qualifier)는 -(대시) 로 구분되어진다.

MyApp/

res/

drawable-en-rUS-finger/

drawable-port/

drawable-port-mdpi/

drawable-qwerty/



수식어(Qualifier) 종류

MCC 와 MNC    디바이스의 SIM에 잇는 모바일 국가 코드(MCC : Mobile Coutry Code, MNC : Mobile Network Code)

언어와 지역 en-rUS, fr-fFR, es-rES , 대소문자 구분이 있다. 언어코드는 소문자, 국가코드는 대문자

스크린 크기 small, normal, large, xlarge

더 넓은 / 더 긴 스크린 long, notlong

스크린 오리엔테이션 port(세로, portrait), land(가로), square(정사각형)

스크린 픽셀 밀도 ldpi, mdpi, hdpi, xhdpi, nodpi

터치스크린 타입 notouch, stylus, finger

키보드 제공 여부 keysexposed(사용가능), keyshidden(없음), keyssoft( 소프트 키보드 사용가능)

텍스트 입력방법 nokeys, qwerty, 12key

네비게이션키 제공 여부 navexposed( 사용가능), navhidden( 없음)

터치스크린외 네비게이션 방법 nonav(없음), dpad(방향키), trackball(트랙볼), wheel(휠)

SDK 버젼



수식어는 위 표에 나와 있는 순서대로 -(대시) 를 사용해서 구성된다.

ex) drawable-en-rUS-large-long-port-mdpi-finger-keysexposed-qwerty-dpad



- 리소스 컴파일러는 대소문자를 구분하지 않는다


코드에서 리소스 레퍼런스

MyApp/res/drawable-port-mdpi/myimage.png


R.drawable.myimage (JAVA)

@drawable/myimage (XML)


'개발' 카테고리의 다른 글

인텐트 필터  (1) 2014.03.12
안드로이드 리소으와 에셋  (0) 2014.03.12
logback 로그 프레임워크  (0) 2014.03.12
Spring fro Android  (0) 2014.03.12
Fragment와 Activity통신  (0) 2014.03.12
이미지 캐싱  (0) 2014.03.12

댓글을 달아 주세요

posted by 동건이 2014. 3. 12. 11:03

logback 로그 프레임워크

안드로이드 로그 프레임웤 logback 


안드로이드 프로그래밍시 Log 관련해서는 자체 제공하고 있는 Log 클래스를 사용하고 있었습니다.

그런데...

로그 레벨 변경하는게 꽤 번잡합니다. properties 에서 설정을 변경하라고 하는데

그동안 자바 프로그래밍을 주로 하던 저에게는 귀차니즘을 동반하더군요...

그래서 찾아봤더니 역시나 저와 같은 고민을 하시던 분께서 아예 log4j 같은 로그 프레임웤을 안드로이드용으로 개발해 놓으셨더라구요


이름하여 logback 

사이트는 아래 URL

http://tony19.github.io/logback-android/


사용방법은 그동안 log4j 를 사용해 오셨으면 간단하게 적응하실 수 있습니다.

우선 간단하게 ADT 에서 제공하는 LogCat 에 출력하길 위해서는 관련 library와 설정파일이 필요합니다.

현시점에서 logback 을 사용하기위해서는 아래 두개의 jar 파일이 필요합니다. 메이븐 사용시는 홈페이지에서 참조 바랍니다.

(사이트에서 다운로드 받은 파일을 잘 살펴보면 아래 두개의 jar 파일이 있습니다.)

slf4j-api-1.7.5.jar

logback-android-1.0.10-2.jar


그리고 설정파일은 프로젝트내 /assets 디렉토리에 logback.xml 파일을 생성하시면 됩니다.



LogCat 에 출력을 하기 위해 아래 설정을 사용합시다. (아래 내용을 logback.xml 에 복사합니다.)

<configuration>

  <!-- Create a logcat appender -->

  <appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">

    <encoder>

      <pattern>%msg</pattern>

    </encoder>

  </appender>


  <!-- Write INFO (and higher-level) messages to logcat -->

  <root level="DEBUG">

    <appender-ref ref="logcat" />

  </root>

</configuration>


자바에서 사용하는 방법은 log4j 의 사용방법과 거의 동일합니다.

// Logger 생성

org.slf4j.Logger Log = LoggerFactory.getLogger(Method1Activity.class);


// Logging

if( Log.isDebugEnabled()){

Log.debug( "checkbox " + i + " : ischecked." );

...

}


log4j 를 사용해 봤다면 쉽게 적응하시리라 생각됩니다.


홈페이지에는 다양한 방법이 나와 있으니 고급 사용을 위해서는 홈페이지를 꼭 방문하시기 바랍니다.

'개발' 카테고리의 다른 글

인텐트 필터  (1) 2014.03.12
안드로이드 리소으와 에셋  (0) 2014.03.12
logback 로그 프레임워크  (0) 2014.03.12
Spring fro Android  (0) 2014.03.12
Fragment와 Activity통신  (0) 2014.03.12
이미지 캐싱  (0) 2014.03.12

댓글을 달아 주세요

posted by 동건이 2014. 3. 12. 11:00

Spring fro Android

Spring For Android, Rest Client 를 사용한 서버와의 통신



Spring For Android 는 안드로이드 애프리케이션 개발을 단순화하기 위한 Spring 프레임워크의 확장이다. 라고 정의가 되어 있네요

주요 지원하는 기능을 살펴보면 아래와 같습니다.


첫째. 안드로이드용 Rest Client 

- Rest Client 를 통해서 서버와의 통신을 쉽게 처리할 수 있습니다.

둘째. Auth support for accessing secure APIs

- 인증 관련 지원 입니다.


Rest Client 를 사용해서 서버와의 통신을 지원하게 되면서 서버와 데이터를 손쉽게 주고 받을 수 있습니다.

서버 통신 예제


public class HttpGetActivity extends Activity {


@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_http_get);

}


@Override

protected void onStart() {

super.onStart();

new HttpGetTask().execute( 10);

}

@Override

public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the menu; this adds items to the action bar if it is present.

getMenuInflater().inflate(R.menu.http_get, menu);

return true;

}

public void showResult( UserModel model){

TextView view = (TextView)findViewById( R.id.textview);

if( model != null){

view.setText( "id : " + model.getId() + ", name : " + model.getName());

}

}

class HttpGetTask extends AsyncTask<Integer, Void, UserModel>{

@Override

protected UserModel doInBackground(Integer... params) {

final String url = "http://192.168.56.1/zadu/user/{id}";

// Accept header "application/json" 을 명시하여 JSON 데이터를 리턴받길 원한다고 설정해 줍니다.

                        HttpHeaders requestHeaders = new HttpHeaders();

            List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();

            acceptableMediaTypes.add(MediaType.APPLICATION_JSON);

            requestHeaders.setAccept(acceptableMediaTypes);

            

            // Rest Template 객체에서 사용할 reqeustEntity 를 위에서 정의한 request Header 를 사용해서 생성합니다.

            HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);


            // 서버와의 통신을 담당할 RestTemplate 객체를 생성합니다.

            RestTemplate restTemplate = new RestTemplate();

    // MappingJacksonHttpMessageConverter 를 통해서 결과값인 JSON 데이터를 UserModel 데이터로 변환할 수 있습니다.

            MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();

            restTemplate.getMessageConverters().add(converter);

            

            // HTTP GET 요청을 수행합니다.

            ResponseEntity<UserModel> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, UserModel.class, params[0]);

            

// 결과값을 받아옵니다.

                        return responseEntity.getBody();

}

@Override

        protected void onPostExecute(UserModel result) {


            // 결과값을 찍어줍니다.

            showResult(result);

        }

}

}



'개발' 카테고리의 다른 글

안드로이드 리소으와 에셋  (0) 2014.03.12
logback 로그 프레임워크  (0) 2014.03.12
Spring fro Android  (0) 2014.03.12
Fragment와 Activity통신  (0) 2014.03.12
이미지 캐싱  (0) 2014.03.12
큰 이미지 로딩  (0) 2014.03.12

댓글을 달아 주세요

posted by 동건이 2014. 3. 12. 10:52

Fragment와 Activity통신

안드로이드 Fragment에서 Activity 와 통신하기


Fragment 는 여러 Activity 에서 사용될 수 있으므로 Activity 에 독립적으로 구현되어야 합니다.

Fragment 는 getActivity() 메소드로 Attach 되어 있는 Activity 를 가져올 수 있습니다.


Activity 로 이벤트 콜백 메소드 만들기

Fragment 내에서 발생하는 이벤트를 Activity 와 공유하기 위해서는 Fragment 에서 이벤트 콜백 인터페이스를 정의하고 Activity 에서 그 인터페이스를 구현해야 합니다.


예를 들면

Activity 내에 두개의 Fragment 가 있고, 하나는  최신기사의 제목 목록을 보여주는 ListFragment 이고  다른 Fragment 는 선택된 기사의 상세내용을 보여주는 DetailFragment 입니다.

사용자가 ListFragment에서 최신기사의 제목을 선택했을 때 선택한 선택된 기사의 상세 내용을 보여주기 위해서는 선택됬다는 이벤트를 DetailFragment에서 알아채야 합니다.


이때 가장 쉽게 생각할 수 있는 방법은 

ListFragment 에서 사용자가 선택 했을때 DetailFragment 를 바로 호출하는 방법입니다.

DetailFragment detailFrag = (DetailFragment)getActivity().getFragmentManager().findFragmentById( ... );

detailFrag.viewArticle(...)


Fragment 에서는 Activity 를 가져올 수 있는 getActivity() 메소드가 존재하기 때문에 위처럼 작성하는 것도 가능합니다.

그럼 위 코드의 문제점은 뭘까요? ListFragment가 기사를 보여주는 DetailFragment 와 연관되어 있기 때문에 ListFragment 는 재사용이 가능하도록 독립적으로 설계되지 않았습니다.


그럼 ListFragment 가 홀로 설수 있는 방법은 무엇을 까요?

1. ListFragment 내에 이벤트 인터페이스를 정의합니다.

public interface OnArticleSelectedListener {

        public void onArticleSelected(Uri articleUri);

}



2. Activity 에서 Fragment 의 이벤트 인터페이스를 구현합니다.

... Activity implements ListFragment.OnArticleSelectedListener{

...

public void onArticleSelected(Uri articleUri){

// DetailFragment 를 가져와서 이벤트를 처리합니다.

DetailFragment detailFrag = (DetailFragment)getFragmentManager().findFragmentById( ... );

detailFrag.viewArticle(...)

}

}



3. ListFragment 에서 Activity 에 대한 참조를 얻습니다.

Fragment는 onAttach lifecycle 콜백함수에서 Activity 에 대한 참조를 얻을 수 있습니다.

    OnArticleSelectedListener mListener;

    @Override

    public void onAttach(Activity activity) {

        super.onAttach(activity);

        try {

            mListener = (OnArticleSelectedListener) activity;

        } catch (ClassCastException e) {

            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");

        }

    }



4. ListFragment 의 이벤트 콜백 함수에서 Activity 에서 인터페이스를 구현한 이벤트 콜백 함수를 호출합니다.

    @Override

    public void onListItemClick(ListView l, View v, int position, long id) {

        // Send the event and Uri to the host activity

        mListener.onArticleSelected(noteUri);

    }



복잡하지만 Fragment 와 Activity 중간에 인터페이스를 두어 Fragment 를 독립적으로 구현할 수 있습니다.

'개발' 카테고리의 다른 글

안드로이드 리소으와 에셋  (0) 2014.03.12
logback 로그 프레임워크  (0) 2014.03.12
Spring fro Android  (0) 2014.03.12
Fragment와 Activity통신  (0) 2014.03.12
이미지 캐싱  (0) 2014.03.12
큰 이미지 로딩  (0) 2014.03.12

댓글을 달아 주세요

posted by 동건이 2014. 3. 12. 10:46

이미지 캐싱

안드로이드 이미지 캐쉬하기


이미지 캐쉬하는 것은 그렇게 어렵지 않습니다. 기본적으로 Cache 클래스를 제공해주기 때문에 사용자는 HashMap 처럼 넣고 빼고 하면서 자연스럽게 사용하면 됩니다.

제공되는 Cache 클래스는 메모리 캐쉬는 LruCache, 디스크 캐쉬는 DiskLruCache 입니다.


메모리 캐쉬를 사용하여 이미지를 캐쉬해보자


// 캐쉬 변수 설정

private LruCache<String, Bitmap> mMemoryCache;


@Override

protected void onCreate( Bundle savedInstanceState){

// 캐쉬의 기본 단위는 킬로바이트 입니다. 

final int maxMemory = (int)(Runtime.getRuntime().maxMemory() / 1024);

final int cacheSize = maxMemory / 8;

        // 캐쉬를 생성합니다. cacheSize 는 이 메모리 캐쉬의 사이즈입니다.(단위 킬로바이트)

        // sizeOf 함수는 구현해야 합니다. 캐쉬하려는 객체의 사이즈를 리턴하는 함수. 캐쉬 남은용량과 캐쉬하려는 객체의 용량을 비교하기 위해서 사용되는것 같습니다.

mMemoryCache = new LruCache<String, Bitmap>( cacheSize){

@Override

protected int sizeOf( String key, Bitmap bitmap){

return bitmap.getByteCount() / 1024;

}

};

}


// 이미지 캐쉬 하기

public void addBitmapToMemoryCache( String key, Bitmap bitmap){

if( getBitmapFromMemCache( key) == null){

mMemoryCache.put( key, bitmap);

}

}


// 캐쉬된 이미지 가져오기

public Bitmap getBitmapFromMemCache( String key){

return mMemoryCache.get( key);

}


'개발' 카테고리의 다른 글

안드로이드 리소으와 에셋  (0) 2014.03.12
logback 로그 프레임워크  (0) 2014.03.12
Spring fro Android  (0) 2014.03.12
Fragment와 Activity통신  (0) 2014.03.12
이미지 캐싱  (0) 2014.03.12
큰 이미지 로딩  (0) 2014.03.12

댓글을 달아 주세요

posted by 동건이 2014. 3. 12. 10:45

큰 이미지 로딩

안드로이드 큰 이미지를 효율적으로 로드하는 방법


- 사이즈가 큰 이미지를 로드할때는 메모리에 부담이 발생합니다. 따라서 화면크기보다 큰 사이즈의 이미지는 아래와 같이 로드하는게 메모리에 부담이 적게됩니다.


로드하려는 비트맵 이미지의 정보 읽기

BitmapFactory.Options options = new BitmapFactory.Options();

// 이미지는 로드 하지 않고 out~~(outHeight, outWidth) 으로 시작되는 속성 정보만 읽는다., 메모리에 부담이 적게 된다.

// true 로 설정되어 있을 경우 decode 시에 null 을 리턴한다. 로드된 이미지가 없기 때문에

options.inJustDecodeBounds = true; 

BitmapFactory.decodeResource( getResources(), R.id.myimage, options);

int imageHeight = options.outHeight;

int imageWidth = options.outWidth;

String imageType = options.outMimeType;



로드하려는 이미지가 실제 필요한 사이즈보다 큰지 체크하는 메소드

실제 필요한 사이즈로 이미지를 조절하기 위해 체크하는 메소드

public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight){

final int height = options.outHeight;

final int width = options.outWidth;

int inSampleSize = 1;

if( height > reqHeight || width > reqWidth){

final int heightRatio = Math.round((float)height / (float)reqHeight);

final int widthRatio = Math.round((float)width / (float)reqWidth);

inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;

}

return inSampleSize;

}


위의 calculateInSampleSize 메소드를 사용하여 이미지 로드하기

public static Bitmap decodeSampleBitmapFromResource( Resources res, int resId, int reqWidth, int reqHeight){

final BitmapFactory.Options options = new BitmapFactory.Options();

options.inJustDecodeBounds = true;

BitmapFactory.decodeResource( res, resId, options);

options.inSampleSize = calculateInSampleSize( options, reqWidth, reqHeight);

// 로드하기 위해서는 위에서 true 로 설정했던 inJustDecodeBounds 의 값을 false 로 설정합니다. 

options.inJustDecodeBounds = false;

return BitmapFactory.decodeResource( res, resId, options);

}


'개발' 카테고리의 다른 글

안드로이드 리소으와 에셋  (0) 2014.03.12
logback 로그 프레임워크  (0) 2014.03.12
Spring fro Android  (0) 2014.03.12
Fragment와 Activity통신  (0) 2014.03.12
이미지 캐싱  (0) 2014.03.12
큰 이미지 로딩  (0) 2014.03.12

댓글을 달아 주세요

posted by 동건이 2011. 10. 6. 17:06

N-Screen 이란?

모바일업계에서 오랫동안 일해오면서 가장 싫어했던 단어들이 Seamless, Ubiqutous, 3-Screen 등이었다. 이러한 단어들은 실제 정의와 무관하게 '서로 다른 Screen Device에서 하나의 서비스를 동일한 사용성으로 제공한다'는 의미로 통용되었다. 개인적으로는 크기, 플랫폼, H/W 성능, 목적에 따라 Device의 고유한 사용성이 있는데 그것을 무시한다는 것을 받아드리기 힘들었다. 이러한 Multi Screen Device들에 대한 해석은 오랫동안 모바일 산업을 지배하였고 지금까지 사용되고 있다.

사용자 삽입 이미지

최근에는 'N-Screen 전략' 이라는 단어가 많이 사용된다. 혹자는 기존 3-Screen에서 Screen이 좀 더 다양해진 개념으로 사용하기도 하고, 일부 통신사나 언론기관에서는 Cloud와 혼용하여 사용하기도 한다. 하지만, 'N-Screen 전략'은 기존 Seamless한 3-Screen 전략과는 아주 큰 차이가 있다.

사용자 삽입 이미지

N-Screen 전략은 Online Activity가 더 이상 Single Task가 아니라는 것에서 시작한다. H/W와 Network 성능이 좋아지고 사용자들의 인터넷 사용이 익숙해지면서 다양한 인터넷 서비스들을 동시에 사용하는 것이 일반화되고 있다. 특히, 인터넷 서비스를 사용하면서 TV를 보거나 음악을 듣는 비율은 매우 높은 것으로 조사되고 있다.

사용자 삽입 이미지

이러한 Multi Tasking 현상은 모바일 Device의 발전과 함께 더욱 심화되고 있다. Yahoo의 최근 자료에 의하면 86%의 사용자가 TV를 시청하면서 Mobile Internet에 접속한다고 한다. n-Screen은 이렇게 서로 다른 Screen Device를 통해 하나의 서비스에 새로운 가치를 만들어 내는데에 목적이 있다.



위 영상(광고가 끝난 후에 플레이 된다)은 삼성전자가 2009년 9월에 발표한 LED TV와 세트인 Tablet 이다. 802.11n Wi-Fi를 통해 TV를 제어할 수 있고 TV 영상을 Preview 하거나 Widget을 통해 일부 Internet 서비스를 사용할 수도 있다. Apple iPad가 2010년 1월 28일에 발표되었으니 약 4개월이나 앞선 Device이다. 하지만, 이 Tablet의 아쉬움은 TV와 서로 연동되어 편리함은 전해주었지만 새로운 가치를 만들어내지는 못했다.

참고로 위 삼성 LED TV과 Tablet 세트는 $3,399로 판매될 예정이었으나 실제 시장에 나오지는 못한 것으로 보인다. TV과 Tablet으로 구성된 2-Screen 상품은 2003년에 소니가 WEGA PDP에서 최초로 시도하였다.



n-Screen은 서로 다른 Screen Device들이 각자의 특성에 맞는 기능(Feature)를 제공하면서 새로운 가치를 만들어 내야 한다. n-Screen 전략에서 Xfinity가 가장 많은 예로 등장하는 것이 바로 이 때문이다. Xfinity는 ‘2010 케이블쇼’에서 Comcast가 발표한 iPad App 인데, 기본적인 기능은 삼성전자 Tablet과 매우 유사하다. 하지만 Xfinity는 단순히 고도화된 리모콘에 멈춘 것이 아니라 친구를 초대하여 같은 TV 프로그램을 볼 수 있고 친구들과 채팅까지 할 수 있는 새로운 Communication 서비스를 만들어 냈다.

사용자 삽입 이미지

Yahoo의 최근 자료에 의하면 TV 시청 중에 40%는 SNS를, 37%는 인터넷 브라우저를, 33%를 모바일앱을 사용할만큼 Mobile Device를 많이 사용한다고 한다. 현재 이러한 사용자들의 Multi Tasking은 TV 내용과는 무관한 경우가 많다. 만일 Smart Phone이나 Smart Pad를 통해 TV내용과 관련있는 컨텐츠를 자연스럽게 노출할 수 있다면 새로운 서비스가 만들어지지 않을까?

과거 3-Screen이나 Ubiqutous는 Serial한 사용자 활동에 Seamless를 강조하였다. N-Screen 전략은 Parallel한 사용자 활동에서 서로 다른 Screen Device간의 Interaction이 가장 중요하다. 물론, 그 Interaction을 통해 새로운 가치를 만들어내어야 한다. 개인적으로 가장 큰 고민거리이자 관심의 대상이다.

댓글을 달아 주세요

posted by 동건이 2011. 9. 26. 19:31

Visitor

자바 디자인 패턴 16 - Visitor

1. Visitor 패턴은..

복잡한 구조체 안을 돌아다니면서 어떤 일을 해야 할 경우가 있습니다. Visitor는 어떤 구조체에 대해 그 안을 돌아다니면서 어떤 일을 하는 것입니다. 이 때, 구조체 1개에 하는 일이 딱 1개라는 보장은 없습니다. 하나의 구조체에 대해 다양한 일들을 할 수 있습니다. 하고 싶은 일이 추가된다고 해서 구조체를 변경하는 것은 무리입니다. 이런 때는 Visitor를 추가하면 됩니다. 예제에서는 PC의 디렉토리-파일 구조에 대해 야동을 찾는 일을 하는 Visitor를 구현해보았습니다. 

2. 예제

--------- Component, Composite, Leaf 등은 Composite 패턴 설명에 썼던 것을 거의 그대로 사용했습니다. 바뀐 부분은 색깔 처리했습니다. 처리한 부분만 보시면 됩니다.


package ch16_Visitor;
import java.util.ArrayList;
import java.util.List;

public abstract class Component implements Acceptor{
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    private String componentName;
    protected List<Component> children = new ArrayList<Component>();
    public Component(String componentName) {
        this.componentName = componentName;
    }
    public String getComponentName() {
        return componentName;
    }
    public abstract void add(Component c);
    public List<Component> getChildren(){
        return children;
    }
}

package ch16_Visitor;
public class Composite extends Component {
    public Composite(String componentName) {
        super(componentName);
    }
    @Override
    public void add(Component c) {
        children.add(c);
    }
}

package ch16_Visitor;
public class Leaf extends Component{
    public Leaf(String componentName) {
        super(componentName);
    }
    @Override
    public void add(Component c) {
        throw new UnsupportedOperationException();
    }
}
------------------ Visitor를 받아들일 수 있는 구조체 ---------------- 
package ch16_Visitor;

public interface Acceptor {
    void accept(Visitor visitor);
}
------------------ Acceptor를 방문하는 Visitor---------------- 
package ch16_Visitor;

public interface Visitor {
    void visit(Acceptor acceptor);
}
------------------ 야동 찾는 YadongFinder ---------------- 
package ch16_Visitor;
import java.util.ArrayList;
import java.util.List;
public class YadongFinder implements Visitor {
    private List<String> yadongList = new ArrayList<String>();
    private List<String> currentList = new ArrayList<String>();
    public void visit(Acceptor acceptor) {
        if (acceptor instanceof Composite) {
            Composite composite = (Composite) acceptor;
            currentList.add(composite.getComponentName());
            List<Component> children = composite.getChildren();
            for (Component component : children) {
                component.accept(this);
            }
            currentList.remove(currentList.size()-1);
        }else  if (acceptor instanceof Leaf) {
            Leaf leaf = (Leaf) acceptor;
            doSomething(leaf);
        }
    }
    protected void doSomething(Leaf leaf){
        if (isYadong(leaf)) {
                String fullPath = getFullPath(leaf);
                yadongList.add(fullPath);
            }
    }
    protected String getFullPath(Leaf leaf) {
        StringBuilder fullPath = new StringBuilder();
        for (String element : currentList) {
            fullPath.append(element).append("\\");
        }
        return fullPath.append(leaf.getComponentName()).toString();
    }
    private boolean isYadong(Leaf leaf) {
        return leaf.getComponentName().endsWith(".avi");
    }

    public List<String> getYadongList() {
        return yadongList;
    }
}
------------------ 테스트 클래스 ----------------  
package ch16_Visitor;

public class Test {
    public static void main(String[] args) {
        Composite main = createComposite();
        YadongFinder visitor = new YadongFinder();
        visitor.visit(main);
        for (String string : visitor.getYadongList()) {
            System.out.println(string);
        }

    }

    private static Composite createComposite() {
        Composite main = new Composite("C:");
        Composite sub1 = new Composite("Program Files");
        Composite sub2 = new Composite("WINDOWS");
        Composite sub11 = new Composite("Pruna");
        Composite sub21 = new Composite("system32");
        Composite sub111= new Composite("Incoming");

        Leaf leaf1111 = new Leaf("강호동 닮은여자-짱이쁨.avi");
        Leaf leaf1112 = new Leaf("EBS야동특강.avi");
        Leaf leaf211 = new Leaf("야메떼-다이조부.avi");
        Leaf leaf212 = new Leaf("이건 야동아님.jpg");
        
        main.add(sub1);
        main.add(sub2);
        sub1.add(sub11);
        sub2.add(sub21);
        sub11.add(sub111);

        sub111.add(leaf1111);
        sub111.add(leaf1112);
        sub21.add(leaf211);
        sub21.add(leaf212);
        return main;
    }
}
---------------- 테스트 결과 -------------
C:\Program Files\Pruna\Incoming\강호동 닮은여자-짱이쁨.avi
C:\Program Files\Pruna\Incoming\EBS야동특강.avi
C:\WINDOWS\system32\야메떼-다이조부.avi

위의 예제에서 중요한 것은 Visitor의 visit(Acceptor) 와  Acceptor의 accept(Visitor)  입니다.

Visitor는 visit(Acceptor) 를 가지고 있고, Acceptor는 accept(Visitor) 를 가지고 있습니다. 둘의 차이가 헤깔립니다. 게다가 accept(Visitor) 를 구현해 놓은 것을 보면 아래와 같이 그냥 Visitor한테 자기 자신을 던져버리는 게 하는 일의 전붑니다.

public void accept(Visitor visitor) {
    visitor.visit(this);
}

이렇게 해 놓은 이유는 구조체를 돌아다닐 수 있게 하기 위한 것입니다. 실제로 구조체를 돌아다니는 일은 Visitor에서 담당하게 됩니다. 만약 이렇게 해놓지 않았다면, Visitor 안에서 구조체를 돌기 위해 재귀적인 호출을 해야만 복잡한 구조를 다 돌 수 있습니다. YadongFinder의 visit(Acceptor) 를 보면, 재귀적인 호출은 없습니다. 일반적으로 accept(Visitor)의 구현은 위와 같으며 달라질 일이 거의 없습니다.

Visitor의 visit(Acceptor)나 Acceptor의 accept(Visitor) 중 하나는 구조체를 도는 역할을 해야합니다. 구조체를 도는 역할은 Visitor의 visit(Accept)에 맡기는 것이 좋습니다. 구조체를 돌면서 하는 일 뿐만 아니라 "구조체를 도는 방법"도 다른 게 할 수도 있기 때문입니다. 위의 예제에서 YadongFinder는 아무래도 엄마가 짠 것 같습니다. 만약에 아들이 짰다면 구조체를 돌 때 "C:\Program Files\Pruna\Incoming\" 과 같은 비밀스러운 디렉토리는 슬쩍 뛰어넘는 로직을 짤 수 있었겠지요.

그러나 일반적으로 순화하는 로직은 거의 바뀌지 않습니다. 위의 예제에서는 YadongFinder를 상속 받아 doSomething(Leaf) 만 override 하면 뭔가 새로운 Visitor를 만들 수 있습니다.

3. 기타

visit(Acceptor) 안 쪽에서 instance of 로 어떤 클래스인지를 찾아냅니다. 이것은 visit(Composite) 와 visit(Leaf)로 분리시켰더라면 굳이 instance of를 쓸 필요가 없었을 것입니다. 하지만, 그렇게되면 Acceptor라는 인터페이스가 무의미해집니다.

Visitor와 Acceptor는 매우 밀접하게 묶여있습니다. 하지만 사실 Visitor의 구현체와 구조체 사이도 꽤 끈끈하게 묶여있습니다. YadongFinder 안에서 instance of로 체크한 것은 전부 구조체에서 정의된 클래스들(Composite, Leaf) 입니다.

테스트 코드의 YadongFinder를 선언하는 부분을 보면,
YadongFinder visitor = new YadongFinder();  와 같이 되어있습니다. 왜
Visitor visitor = new YadoingFinder(); 라고 인터페이스로 정의를 하지 않았을까요?

YadongFinder의 getYadongList() 부분이 포인트입니다. visitor는 구조체를 돌아다니면서직접 일을 할 수도 있습니다.( 예를들어, YadongFinder를 조금만 수정하면 YadongRemover 로 만들 수도 있습니다.) 하지만 돌아다니면서 뭔가를 수집하는 것과 같이 직접 구조체를 수정하지 않고 단지 정보만 수집하는 경우가 있는데, 그런 경우는 수집한 정보를 다시 뽑아서 사용할 수 있는 방법을 제공해야 합니다. Test 클래스에서 야동 리스트를 찍는 부분을 보시면 됩니다.

'개발 > 디자인패턴(외부글)' 카테고리의 다른 글

Visitor  (0) 2011.09.26
Mediator  (0) 2011.09.26
Builder  (0) 2011.09.26
FlightWeight  (0) 2011.09.26
ProtoType  (0) 2011.09.26
Observer  (0) 2011.09.26

댓글을 달아 주세요

posted by 동건이 2011. 9. 26. 19:24

Mediator

자바 디자인 패턴 15 - Mediator

1. Mediator 패턴은..

비행기가 이착륙하다가 충돌하는 일은 좀체로 일어나지 않습니다. 비행기들끼리 서로 통신하지 않는데도 말이죠. 각각의 비행기는 관제탑하고만 통신을 하고, 관제탑이 각각의 비행기에게 착륙해도 된다 또는 안 된다 식으로 메시지를 보내줍니다. 비행기들끼리 서로서로 직접 통신을 한다면 통신할 경우의 수가 무진장 많아져서 혼란스럽게 됩니다. Mediator 패턴은 관제탑과 같이 통신을 집중시킴으로써 통신의 경로를 줄이고 단순화시키는 역할을 합니다.

2. 예제

------------------ 관제탑 역할을 하는 ControlTower (활주로 역할도 함) ---------------- 
package ch15_Mediator;

public class ControlTower {
    private volatile  boolean inUse;
    
    public synchronized boolean getPermission(){
        if (inUse) {
            return false;
        }else{
            inUse = true;
            return true;
        }
    }
    
    public void land(Airplane airplane) throws InterruptedException{
        int seq = airplane.getSeq();
        System.out.println(seq +"번 비행기 착륙 시작");
        Thread.sleep(50L);
        System.out.println(seq + "번 비행기 착륙 끝");
        inUse = false;
    }
}
------------------ 착륙허가를 받아야하는 Airplane ---------------- 
package ch15_Mediator;

public class Airplane extends Thread {
    private final ControlTower tower;
    private final int seq;

    public Airplane(ControlTower tower, int seq) {
        this.tower = tower;
        this.seq = seq;
    }

    public int getSeq() {
        return seq;
    }

    @Override
    public void run() {
        try {
            while (!tower.getPermission()) {
                // System.out.println(seq +"번 째 비행기 대기 중.");
                Thread.sleep(10L);
            }
            tower.land(this);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

------------------ 테스트 클래스 ---------------- 
package ch15_Mediator;

public class Test {
    public static void main(String[] args) {
        ControlTower tower = new ControlTower();
        Airplane[] airplanes = new Airplane[10];
        for (int i = 0; i < airplanes.length; i++) {
            airplanes[i] = new Airplane(tower, i);
        }
        for (Airplane airplane : airplanes) {
            airplane.start();
        }
    }
}

위 프로그램을 실행시키면, 비행기가 동시에 활주로를 사용하는 일은 발생하지 않습니다. 10대의 비행기는 모두 ControlTower를 가지고 있는데, 이는 동일한 ControlTower입니다. 즉, 비행기들끼리는 통신하지 않고 관제탑하고만 통신해서 충돌을 방지하게 되는 것입니다. 
위의 예제에서 ControlTower는 모든 통신의 중계 역할을 합니다. 이를 Mediator(중계자)라 합니다. 그리고, Airplane은 Mediator와 통신을 하는 역할을 합니다. 이를 Colleague(사전적인 의미는 동료이지만, Mediator와 통신하는 객체들이라고 생각하시면 됩니다.) 라고 합니다.

위의 예제를 좀 더 확장해서 생각해 봅시다.

첫번째로 Airplane 은 ControlTower가 한 번 세팅이 되면 바꿀 방법이 없습니다. 위의 예제만으로는 비행기는 일회용이겠죠. 다른 공항에 착륙하고자할 때는 비행기가 통신할 관제탑이 바뀔 수도 있습니다. 그래서 보통은 Colleague 클래스는 setMediator 와 같은 메쏘드를 가지고 있습니다.

두번째로는 callback입니다. 예제에서는 비행기들이 관제탑에 "나 착륙해도 되?" 라고 계속 물어봅니다. "된다"라는 대답을 들을 때까지! 관제탑에서는 정신없습니다. (녹색부분의 주석을 풀어보면 얼마나 정신없는 지 바로 보입니다.)  
질문 방식을 바꾸면 어떨까요? 비행기가 "나 착륙할라고 하는데 활주로 비면 연락줘. 기둥기께" 라고 관제탑에 메시지를 보내고 관제탑은 그 비행기를 착륙 대기 리스트에 추가시켜 놓고, 리스트 앞에서 부터 각각의 비행기에게 "너 인제 착륙해도 된다"는 메시지를 보내면 됩니다. 이렇게 하면 통신 횟수를 확 줄일 수 있습니다.
callback은 명령을 내리고 임무를 완료하면 다시 연락하라는 겁니다. 짜장면 주문을 예로 들어보겠습니다. 짜장면집에 전화를 걸고, 주문을 합니다. 우리는 짜장면이 올 때까지 딴짓을 합니다. 짜장면이 배달오면 배달원이 초인종을 누릅니다. "주문" 이 명령이고, "배달"이 임무 완료입니다. "내"가 "짜장면집"에 명령을 하고 "짜장면집"은 그 명령을 수행합니다. "짜장면집"이 임무를 완수하면 "나"한테 "다시 연락"을 줍니다. 임무가 수행되는 동안 "나"는 "짜장면집"에서 짜장면을 잘 만들고 있는 지 신경을 쓰지 않습니다.

 세번째는 활주로가 1개 뿐이라는 겁니다. 활주로가 여러개라면 Runway 라는 활주로 클래스를 만들어야 합니다. 이 활주로는 당연히 ControlTower와 통신을 해야 합니다.  ControlTower가 어떤 비행기가 착륙 요청했다는 정보를 활주로에 알려주고, 비행기한테는 어떤 활주로에 착륙해라 라고 비행기와 활주로 사이에 관계를 맺어줍니다. 실제 착륙 작업에서는 더 이상 관제탑이 관여할 게 없습니다. 다만, 착륙이 완료 되면 활주로한테 이제 활주로가 다시 사용가능하다는 callback은 받아야겠죠. 
이 경우 Runwary도 Colleague가 되어야 합니다. Colleague는 한 가지 특정타입(예제의 경우 Airplane)으로 국한될 필요는 없습니다.
Airplane의 getPermission은 getAvailableRunway로 바뀌고 리턴 타입역시 Runway로 바뀌어야겠습니다. 사용가능한 활주로가 없을 때는 null을 리턴한다거나 하면 되겠습니다.
그리고 land 메쏘드도 ControlTower가 아닌 Runway로 옮겨가야겠지요. 그리고 land 안에는 ControlTower에게 착륙이 끝났다는 정보를 전달할 수 있는 로직이 필요합니다.

3. 수 많은 Adapter가 필요한 경우

A,B,C 3개 회사가 합병을 했다 칩시다. 각각의 회원 정보를 다음과 같은 인터페이스로 정의해서 사용합니다.

interface ACompanyUser{     String getName();  }
interface BCompanyUser{     String getName();  }
interface CCompanyUser{     String getName();  }

이제 하나가 된 만큼 회원 정보를 서로 공유해야 하는데, 각 회사의 시스템은 예전 자기 회사의 인터페이스만을 받아들이도록 되어있습니다. 서로 캐스팅이 불가능한 객체를 캐스팅하고 싶을 때 Adapter 패턴을 씁니다. 다음과 같은 6개의 Adapter가 필요합니다.

AToBUser
AToCuser
BToAUser
BToCUser
CToAUser
CToBUser

아... 복잡해지기 시작합니다. 중계 객체를 하나 두면 어떨까요? 중계 객체를 M이라 합시다. 즉, A에서 B로 바꾸려면 위에서는 AToBUser 를 통해서 바꾸면 되었지만, 이제 AToM , MToB와 같이 두 단계를 거쳐서 만들면 됩니다. 단계가 늘어났지만 뭔가 좋은 게 있을 겁니다.

AToM
BToM
CToM
MToA
MToB
MToC

얼레? 똑같이 6갭니다. 그러나 갯수가 많아지면 얘기가 달라집니다. N개의 회사가 있다고 하면, 직접 변환을 할 경우 N*(N-1) 개의 인터페이스가 필요합니다. 그러나, 중계 객체를 만들면 2*N개만 있으면 됩니다. 4개의 회사의 경우 12개 - 8개 , 5개 회사는 20개-10개 와 같이 갯수가 많아 질수록 중계 객체를 두는 게 유리해집니다.

'개발 > 디자인패턴(외부글)' 카테고리의 다른 글

Visitor  (0) 2011.09.26
Mediator  (0) 2011.09.26
Builder  (0) 2011.09.26
FlightWeight  (0) 2011.09.26
ProtoType  (0) 2011.09.26
Observer  (0) 2011.09.26

댓글을 달아 주세요

posted by 동건이 2011. 9. 26. 19:16

Builder

자바 디자인 패턴 14 - Builder

1. Builder 패턴은..

뭔가가 만들어 지는 과정은 꽤나 복잡할 수가 있습니다. 게다가 그 복잡한 과정이 순서대로 실행되어야 할 때도 있습니다. 객체의 생성에 있어서 이런 복잡한 과정들을 분리해 내는 것이 Builder 패턴입니다. 

2. 예제

---------------- 복잡한 과정을 거쳐서 만들어 지는 객체가 될 Hero 클래스 ---------------- 

package ch14_builder;

public class Hero {
    private String armSource;
    private String legSource;
    private String name;
    
    public Hero(String name) {
        super();
        this.name = name;
    }
    public void setArmSource(String armSource) {
        this.armSource = armSource;
    }
    public void setLegSource(String legSource) {
        this.legSource = legSource;
    }
    public void showResult(){
        System.out.println(armSource +"로 만든 팔과 " + legSource +"로 만든 다리를 가진 " + name);
    }
}

---------------- 복잡한 Hero 객체를 만들어내기 위한 객체 생성과정을 관리하는 Builder 인터페이스 ---------------- 

package ch14_builder;

public interface Builder {
    void makeArm();
    void makeLeg();
    Hero getResult();
}

---------------- 복잡한 Hero 객체를 실제로 만들어내는 Builder의 구현체인 배트맨 찍어내는 클래스 ------------------

package ch14_builder;

public class BatmanBuilder implements Builder {
    private Hero batman;
    BatmanBuilder(){
        batman = new Hero("배트맨");
    }
    public void makeArm() {
        batman.setArmSource("돈지랄");
    }
    public void makeLeg() {
        batman.setLegSource("돈지랄");
    }
    public Hero getResult() {
        return batman;
    }
}

---------------- Builder를 관리해 주는 Director ---------------- 

package ch14_builder;

public class Director {
    private Builder builder;
    public Director(Builder builder) {
        this.builder = builder;
    }
    public void build(){
        builder.makeArm();
        builder.makeLeg();
    }
    public Hero getHero(){
        return builder.getResult();
    }
}

---------------- Director를 이용해 Hero를 찍어내는 Test클래스 -------------

package ch14_builder;

public class Test {
    public static void main(String[] args) {
        Builder builder = new BatmanBuilder();
        Director director = new Director(builder);
        director.build();
        Hero hero = director.getHero();
        hero.showResult();
    }
}

---------------- 테스트 결과 -------------
돈지랄로 만든 팔과 돈지랄로 만든 다리를 가진 배트맨

Hero라는 클래스가 있습니다. 이 클래스는 그냥 생성자만 호출해서는 아무 쓸모없는 클래스입니다. 이런 저런 정보들이 많이 쎄팅이 되어야 비로소 쓸만한 객체가 됩니다.(예제에서는 setArmSource()과 setLegSource()와 같은 게 그런 복잡한 세팅 과정에 관여하는 메쏘듭니다.) 따라서 이 클래스의 객체를 생성하는 과정은 매우 번거롭습니다.
이런 번거로운 과정을 Director에서 간단하게 build()라는 메쏘드로 해결을 하려고 합니다. build()라는 메쏘드는 참 간단한 것 같은데, 번거로운 과정을 어떻게 다 커버하느냐는 결국 Builder에 위임해서 해결합니다.
Builder는 비교적 세부적인 사항들을 다룹니다. 이에 비해 Director는 좀 더 포괄적인 과정은 다룹니다. 위의 예제의 경우는 Builder는 팔은 어떻게 만들어 지고 다리는 어떻게 만들어지는 지 등과 같은 것을 다루며(세부적인 사항인 makeArm(),makeLeg()와 같은 메쏘드), Director는 팔을 만들고 다리를 만들면 대략 Hero 하나 완성시킬 수 있다는 전체적인 로직(포괄적인 과정인 build() 메쏘드)을 다룹니다. 즉, Director는 다른 Hero를 만드는데도 활용할 수 있지만, Builder는 각각의 Hero에 국한됩니다.

위의 예제는 예제인 만큼 간단하게 만들었습니다만, Hero를 만들기 위해서 수십수백 가지의 정보가 세팅되어야 한다고 칩시다. 이럴 때, Hero의 생성자에 그런 정보들을 다 세팅해줄 수는 없습니다.

3. UML을 벗어나서...

위에서는 등장인물이 Builder(interface)와 그 구현체들, 그를 관리하는 Director 그리고 만들어지는 생산품(Hero) 등이 있었습니다. 그러나, 이는 그냥 전형적이 UML 모냥새를 나타내는 것 뿐이고, Builder 패턴에 있어서는 저런 UML을 벗어나는 경우들이 허다합니다.
Builder에서의 포인트는 "뭔가 복잡한 과정을 거쳐 만들어지는 객체가 있는데, 이때 이 복잡한 과정을 Builder에게 담당시키겠다"는 것입니다. 따라서 Builder와 Product 두 개만으로도 구성될 수 있습니다. 즉, Builder 자체가 Abstract Class나 인터페이스가 아니라 그냥 클래스 일수도 있습니다.
StringBuilder와 같은 것이 대표적인 예입니다. StringBuilder는 이름 그대로 String을 만들어냅니다. 코드를 봅시다.

String ab = new StringBuilder("a").append("b").toString();

여기서 StringBuilder는 결국 "ab"라는 String을 만들기 위한 것입니다. StringBuilder 자체가 의미를 가지는 것이 아니라, 만들어 내는 String이 의미를 가지는 것입니다.

4. Builder를 쓰면 좋은 경우

같은 클래스의 인자를 여러 개 받은 Constructor를 생각해봅시다.

public class Person{
    private final String name;
    private final String city;
    public Person(Stirng name, String city){ //인자가 둘 다 String
        this.name = name;
        this.city = city;
    }
    //getter는 생략.
}

이 코드는 다음과 같이 호출되어야 합니다.
Person p = new Person("홍길동", "서울");

그런데, 인자가 둘 다 String이라 다음과 같은 실수가 있을 수도 있죠.
Person p = new Person("서울", "홍길동"); // 앞 뒤가 바뀜!

이러면 "서울"에 사는 "홍길동"씨가 아니라, "홍길동"에 사는 "서울"씨가 만들어집니다. 

Person p = new PersonBuilder().setName("홍길동").setCity("서울").build();

와 같이 호출된다면, 앞 뒤가 바뀔 가능성이 별로 없겠지요.

(PersonBuilder는 생략합니다.)

이번에는 다음과 같은 복잡한 클래스를 생각해 봅시다.

public class Complex(){
    private Object member1;
    등등등 수많은 member 변수..
    public void set1(Object arg) { member1 = arg;}
    등등등 수많은 setter..
    public Object get1() { return member1;}
    등등등 수많은 getter..
}

------ Complex를 만들어 내는 클라이언트 ---- 
Complex complex = new Complex();
complex.set1(...);
여러 개의 setter 호출....

------ Complex를 가져다 쓰는 클라이언트 ---- 
complex.get1();
여러 개의 getter 호출

이 클래스의 사용 코드를 보면, 복잡하게 만들어내는 부분과 가져다가 사용하는 부분이 명확하게 분리가 되어있습니다. 따라서 다음과 같은 요구가 있을 수 있습니다.

1. 한번 세팅된 값은 변하면 안 된다.
2. getter는 모든 member 변수가 세팅이 된 후에만 사용할 수 있다.

모든 setter는 수정하려는 변수가 세팅이 되어있는지 확인해야 하고, 모든 getter에서는 모든 member 변수가 세팅이 되었는지 확인해야 합니다. 이런 경우 코드가 다음과 같이 좀 지저분하게 수정되어야 합니다.

public class Complex(){
    private Object member1;
    등등등 수많은 member 변수..
    private boolean completed(){
        //member 들이 세팅이 전부 완료되었는 지를 체크해서 리턴.
    }


    public void set1(Object arg) { if(member1 != null) throw new IllegalStateException("세팅 중복") member1 = arg;}
    등등등 수많은 setter..
    public Object get1() { if(!completed()) throw new IllegalStateException("세팅 미완료") return member1;}
    등등등 수많은 getter..
}

모든 getter 들에서 세팅이 완료되었는지 체크하는 로직이 들어가야 합니다. completed() 안의 부분을 효율적으로 바꿀 수는 있지만, getter에서 IllegalStateException이 발생하는 것은 근본적으로 막을 수는 없습니다.

여기서의 문제는 Complex 클래스를 한 번에 만들어내지 못한다는 것이었습니다. 이럴 경우 만들어내는 복잡한 과정을 ComplexBuilder를 만들어서 해결하면 됩니다. 또 ComplexBuilder는 오로지 Complex 클래스를 만들어내기 위한 클래스입니다. 따라서 ComplexBuilder를 분리하면 됩니다.

public class ComplexBuilder{
    private Object member1;
    등등등 수많은 member 변수..
    public void set1(Object arg) { member1 = arg;}    
    등등등 수많은 setter..
    //여기는 getter가 있을 지 없을 지 생각 좀 해보고 필요에 따라 넣던지 빼던지...
    public Complex build(){
        //Complex 객체를 만드는 과정을 전부 넣어둠.
    }
}

public class Complex(){
    Complex(){} //public constructor를 제공하지 않음.
    
    private Object member1;
    등등등 수많은 member 변수..
    void set1(Object arg) { member1 = arg;} // public이 아님!!
    등등등 수많은 setter..
    public Object get1() { return member1;}
    등등등 수많은 getter..
}

위와 같이 2개의 클래스를 같은 패키지에 넣어두고 쓰면 됩니다.

이를 사용하는 코드는 아래와 같이 됩니다.

------ Complex를 만들어 내는 클라이언트 ---- 
ComplexBuilder cb = new ComplexBuilder();
cb.set1(...);
여러 개의 setter 호출....
Complex complex = cb.build();

------ Complex를 가져다 쓰는 클라이언트 ---- 
complex.get1();
여러 개의 getter 호출

일단 Complex는 public constructor를 제공하지 않도록 했습니다. 잘못된 객체 생성을 막고, 오로지 ComplexBuilder를 통해서만 만들어지게 합니다. 또 Complex의 setter 들도 public이 아닙니다. 즉, 외부에서 변수를 세팅하지 못하도록 막았습니다. 따라서 멤버 변수가 변경될 수 있는 가능성을 근본적으로 막아버렸습니다. 그리고 getter에서 member 변수가 세팅이 되었는지를 일일이 확인할 필요도 없어졌습니다.

간략히 정리하면, 복잡하게 만들어지는 Immutable 클래스는 Builder를 통해 만들면 편하다는 얘기죠.

5. Factory 패턴과의 차이점

Factory와 Builder는 모두 객체 생성에 관련된 패턴이고 둘이 좀 비슷해보이기도 합니다. 그러나 용도가 좀 다릅니다.

Factory는 일반적으로 다음의 두 가지 경우에 해당하는 경우에 사용합니다.

1. 리턴 타입이 일정하지 않은 경우. 예를들어, create("소") 라고 하면 Cow 클래스의 인스턴스를 리턴하고, create("개")라고 하면 Dog 클래스의 인스턴스를 리턴하는 것처럼 인자에 따라 리턴 타입이 바뀌는 경우.
2. 대략 비슷하지만 멤버가 살짝 다른 경우. Boolean.valueOf(String arg) 와 같은 경우 리턴 타입은 모두 Boolean 이지만, 속의 내용이 조금 다름. 이 경우는 대부분 Singleton 패턴으로 처리됨.

그러나 Builder는 객체 생성과정의 복잡성을 떠넘기는 게 포인트입니다.

'개발 > 디자인패턴(외부글)' 카테고리의 다른 글

Visitor  (0) 2011.09.26
Mediator  (0) 2011.09.26
Builder  (0) 2011.09.26
FlightWeight  (0) 2011.09.26
ProtoType  (0) 2011.09.26
Observer  (0) 2011.09.26

댓글을 달아 주세요

posted by 동건이 2011. 9. 26. 19:03

FlightWeight

자바 디자인 패턴 13 - Flyweight

1. Flyweight 패턴은..

Flyweight 는 동일한 것을 공유해서 객체 생성을 줄여 가볍게 만드는 것입니다. 클래스 별로 factory를 씁니다. 그리고 그 factory에서는 자신이 찍어내는 객체들을 관리합니다. 이미 가지고 있는 객체에 대한 요청이 들어왔을 때는 관리하고 있던 객체를 던져주고, 가지고 있지 않은 것을 요청하면 새로 객체를 만들어 관리 리스트에 추가시키고 던져줍니다.

2. 예제

---------------- Person class 및 Person을 Flyweight로 관리하는 Factory -------------

package ch13_Flyweight;
import java.util.HashMap;
import java.util.Map;
public class PersonFactory {
    private static Map<String, Person> map = new HashMap<String, Person>();
    public synchronized static Person getPerson(String name){
        if (!map.containsKey(name)) {
            Person tmp = new Person(name);
            map.put(name, tmp);
        }

        return map.get(name);
    }
    public static class Person {
        private final String name;
        private Person(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
    }
}

---------------- Flyweight가 적용된 factory를 사용하는 Test클래스 -------------

package ch13_Flyweight;
import ch13_Flyweight.PersonFactory.Person;
public class Test {
    public static void main(String[] args) {
        Person p1 = PersonFactory.getPerson("홍길동");
        Person p2 = PersonFactory.getPerson("김말자");
        Person p3 = PersonFactory.getPerson("홍길동");
        
        System.out.println(p1 == p2);
        System.out.println(p1 == p3);
    }
}

---------------- 테스트 결과 -------------
false
true

테스트 코드에서 p1과 p3는 둘다 "홍길동"이란 String을 이용해서 객체를 주문합니다. 같은 String으로 객체를 주문했으므로 같은 객체가 리턴됩니다. 두번째 요청에서는 새로 생성하지 않습니다.

위에서 관리하려는 클래스는 Person입니다. Person은 PersonFactory에 의해 관리됩니다. 다시 말해 Person이 Flyweight 를 담당하고, PersonFactory가 Flyweight Factory를 담당합니다. 

예제에서는 Map을 이용해서 Person의 인스턴스를 관리합니다.(일반적인 Flyweight는 다 Map으로 관리합니다.) Map의 key, value가 어떤 클래스로 결정되는지 살펴보죠. value는 당연히 리턴 타입과 같아야 하지만, key를 무엇으로 쓸 것인지는 깊이 생각해봐야 합니다. 역시 HashMap을 사용하고 있으므로, key로 사용할 클래스에는 hashCode()와 equals()가 잘 구현되어있어야 합니다.

PersonFactory에 getPerson() 메쏘드는 sysnchronized로 처리가 되어있습니다. HashMap이 아니라 Hashtable을 쓰면, 알아서 synchonized가 걸리지만, 그건 Hashtable의 메쏘드 안에서만 걸린다는 뜻입니다. 예제에서 녹색처리된 if 블럭이 하나의 synchronized 블럭에 들어가 있어야 하기 때문에 메쏘드에 synchronized가 걸려 있어야 합니다. Hashtable로 해결해서는 안 됩니다. 다시 정리를 하면, if 블럭 안에서 하는 일은 두 단계로 되어있습니다. 이미 있는 놈인지 체크를 하고(1단계) 없으면 새로 만듭니다.(2단계) 1,2 단계가 합쳐져서 하나의 synchronized 안에 들어가야 한다는 것입니다. Hashtable을 쓰면, 각각의 단계별로 synchronized가 걸립니다.

3. Flyweight  추가 사항

Flyweight 클래스(여기서는 Person)은 가능하면 생성자를 외부로 공개하지 않는 것이 좋습니다. 몇 가지 방법이 있습니다.
첫째는 위에서 사용한 것처럼 Flyweight Factory에서 내부 클래스로 가지고 있으면서 생성자를 외부로 공개하지 않는 방법입니다.
두번째는 Flyweight의 클래스 선언은 public으로 생성자는 default(package-privae 또는 friendly 라고도 합니다. 접근자를 선언하지 않는 거죠) 로 선언하고, Flyweight Factory 클래스를 같은 패키지 안에 넣는 방법입니다.

4. JAVA API에 있는 Flyweight 

일반적으로 immutable 타입만을 멤버 변수로 가지고 어떤 메쏘드를 제공하는 경우에도 flyweight를 사용합니다. Servlet 이 그 대표적인 예입니다. 하나의 servlet에 여러 쓰레드가 동시에 접근할 수 있습니다. 하지만, 이 요청들을 처리하는 것은 하나의 인스턴스 입니다. javax.servlet.Servlet 인터페이스를 기준으로 보면, service 메쏘드는 여러 쓰레드에서 동시에 실행시킬 수 있습니다.
따라서 보통 Servlet의 경우는 멤버 변수에 의존하는 로직을 만들면 안 됩니다. 아주 미세한 시간 차이로 꼬일 수 있습니다. 사용자 정보를 출력하는 Servlet의 경우 사용자 정보를 Servlet에 저장하게 되면, 다른 사용자가 그 Servlet에 접근했을 때 보여질 수도 있습니다.

'개발 > 디자인패턴(외부글)' 카테고리의 다른 글

Mediator  (0) 2011.09.26
Builder  (0) 2011.09.26
FlightWeight  (0) 2011.09.26
ProtoType  (0) 2011.09.26
Observer  (0) 2011.09.26
Facade  (0) 2011.09.26

댓글을 달아 주세요

posted by 동건이 2011. 9. 26. 18:56

ProtoType

자바 디자인 패턴 12 - Prototype

1. Prototype 패턴은..

기존에 만들어진 복잡다난한 인스턴스의 내용이 일부만 살짝 변경된 비스무레한 객체가 필요한 경우에 쓰입니다. 일반적으로 객체를 새로 생성할 때는 new Object()와 같은 방법으로 생성을 합니다. 그러나 그렇게 생성할 경우 기존에 만들어진 것과 유사하다고 해도 결국 모든 정보를 다시 세팅해주어야 합니다. 그러나, clone()을 이용할 경우에는 기존에 만들어진 것을 복사해서 바뀐 부분만 대체해 주면 인스턴스를 생성하기가 쉽습니다. 아주 일반적인 "원형"을 만들어서 그것을 복사한 후 적당히 커스터마이징을 하면 new로 객체를 생성하는 것보다 쉽게 됩니다. 

2. 예제

---------------- 복잡한 정보를 가지고 있는 Complex -------------

package ch12_prototype;
import java.util.Date;
public class Complex implements Cloneable{
    private String complexInfo;

    private Date date;

    public Complex(String complexInfo) {
        this.complexInfo = complexInfo;
    }
    public String getComplexInfo() {
        return complexInfo;
    }
    public void setDate(Date date){
        this.date = new Date(date.getTime());
    }
    public Date getDate() {
        return date;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        Complex tmp = (Complex) super.clone();
        return tmp;
    }
}



---------------- 복사를 실행하는 테스트 클래스 -------------

package ch12_prototype;
import java.util.Date;
public class Test {
    public static void main(String[] args) {
        Complex com = new Complex("매우 복잡한 정보");
        try {
            Complex cloned1 = (Complex)com.clone();
            cloned1.setDate(new Date(2008,0,1));

            Complex cloned2 = (Complex)com.clone();
            cloned2.setDate(new Date(2008,2,1));
            
            System.out.println(cloned1.getDate());
            System.out.println(cloned2.getDate());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

---------------- 테스트 결과 -------------
Wed Jan 01 00:00:00 KST 3908
Sun Mar 01 00:00:00 KST 3908


테스트 클래스에는 변수가 3개 등장합니다. com과 cloned1, clone2 입니다. 셋다 Complex 클래스의 인스턴스 들입니다.
com은 소스코드에서는 매우 쉽게 생성해냈지만, 생성하는 과정이 매우 복잡하며, 멤버 변수로 매우 복잡한 것들을 가지고 있다고 가정합니다. 그래서 다시 한 번 똑같은 것을 만들어 내기가 매우 어렵다고 가정합시다.
그렇게 어렵게 만들어진 com과 다 똑같은 데 멤버 변수 한 두개만 다른 값을 가지는 객체를 만들고 싶습니다. cloned1과 cloned2가 바로 그런 인스턴스들입니다. com이 바로 원형이 되는 것이고, cloned1과 cloned2는 그 원형에서 복사해서 만들어진 객체들이며, 실제로 뭔가를 하는 것은 cloned1, cloned2 입니다. com은 cloned1,cloned2를 찍어내기 위한 원형일 뿐입니다.

clone 메쏘드에 대한 구현은 http://iilii.egloos.com/4022941 를 참조하세요. 위의 예제에서는 코드가 너무 길어져서 hashCode()나 equals는 별도로 구현하지 않았습니다. 또 Date의 Constructor로 Date(int, int, int)는 Deprecated 된 거지만 걍 예제니까 씁니다.

3. Prototype 용도 및 일반적인 구현.

스타크래프트 같은 걸 생각을 해봅시다. 건물은 Building 이라는 클래스 하나로 정의된다고 칩시다.(물론, 안 그렇겠지만 그렇다 칩시다.-_-;) 한 명의 플레이어는 커맨드 센터를 여러 개 지을 수 있습니다. 각각의 커맨드 센터끼리는 위치 등 일부 정보만 다르고 생김새, 기본 에너지 등등 다른 건 다 비스무레할 겁니다. 그럴 때 Building이라는 클래스의 속성으로 모든 값들을 다시 세팅하는 것은 굉장히 번거로운 작업이 될 겁니다. 그래서 대략적인 모양새를 갖춘 객체를 하나 생성하고 그 객체를 복사해서 필요한 필드만 다시 세팅하면 쓸만한 커맨드 센터가 만들어집니다.

또 위의 예제에서는 그렇게 구현하지 않았지만 일반적으로 Prototype은 외부로 드러내지 않습니다. 팩토리 패턴과 조합해서 쓰는 게 일반적입니다. Factory 클래스에서 원형을 관리하고, 그 Factory의 create 메쏘드가 호출되면, 원형으로부터 복사해서 외부로 던져주는 겁니다. Prototype은 Factory에서만 관리되고 그 외부로 드러나지 않습니다.

clone() 메쏘드가 호출되어 새로운 객체가 생성되는 시점에 원형이 어찌 생겼는지 크게 신경쓰지 않습니다. 그냥 다짜고짜 복사할 뿐입니다.

'개발 > 디자인패턴(외부글)' 카테고리의 다른 글

Builder  (0) 2011.09.26
FlightWeight  (0) 2011.09.26
ProtoType  (0) 2011.09.26
Observer  (0) 2011.09.26
Facade  (0) 2011.09.26
Chain of responsibility  (0) 2011.09.26

댓글을 달아 주세요

posted by 동건이 2011. 9. 26. 13:30

Observer

자바 디자인 패턴 11 - Observer

1. Observer 패턴은..

어떤 클래스에 변화가 일어났을 때, 다른 클래스에 통보해 주는 패턴입니다. 통보를 하는 "어떤 클래스"가 Observable 이고, 통보를 받는 "다른 클래스"는 Observer입니다. Observable은 여러개의 Observer를 가질 수 있습니다. Observable이 "담임 떴다"를 외치면, Observer은 알아서 그에 걸맞는 행동을 합니다. 어떤 Observer는 만화책을 덮고 교과서를 꺼내고 어떤 Observer는 흘린 침 닦고 일어나서 공부하는 척하고 또 어떤 Observer는 먹던 도시락 치우고 창문 열어 환기를 시킵니다. Observable은 "담임 떴다"까지만 알려주지 "담임 떴으니깐, 누구는 어찌하고 누구는 저찌해라~" 까지 상세한 지시는 하지 않습니다.

2. 예제

---------------- 변화를 통보하는 Observable -------------

package ch11_Observer;
import java.util.Observable;
public class Watcher extends Observable {
    public void action(String string) {
        System.out.println("======="+string+"========");
        setChanged();
        notifyObservers(string);
    }
}

---------------- 변화를 통보받는 직원 -------------

package ch11_Observer;
import java.util.Observable;
import java.util.Observer;
public class Employee implements Observer {
    private String desc;
    public Employee(String desc) {
        this.desc = desc;
    }
    public void update(Observable o, Object arg) {
        if (o instanceof Watcher) {
            System.out.println(desc + "이 일하는 척");
        }
    }
    public String getDesc() {
        return desc;
    }
}

---------------- 변화를 통보받는 사장 끄나풀 -------------

package ch11_Observer;
import java.util.Observable;
import java.util.Observer;
public class Spy implements Observer {
    private Employee employee;
    public Spy(Employee employee) {
        this.employee = employee;
    }
    public void update(Observable o, Object arg) {
        if (o instanceof Watcher) {
            System.out.println("고자질쟁이가 "+employee.getDesc() +"이 놀고 있었다고 고자질.");
        }
    }
}

---------------- 테스트 클래스 -------------

package ch11_Observer;
public class Test {
    public static void main(String[] args) {
        Watcher watcher = new Watcher();
        Employee pc1 = new Employee("만화책보는 놈");
        Employee pc2 = new Employee("퍼질러 자는 놈");
        Employee pc3 = new Employee("포카치는 놈");
        //spy는 pc3을 보고 있음.
        //요놈은 꼰질르기의 대가
        Spy spy = new Spy(pc3);
        
        watcher.addObserver(pc1);
        watcher.addObserver(pc2);
        watcher.addObserver(pc3);
        watcher.addObserver(spy);
        
        watcher.action("사장 뜸.");
        watcher.deleteObserver(pc3);
        watcher.deleteObserver(spy);
        
        watcher.action("사장 뜸.");
    }
}

---------------- 테스트 결과 -------------

=======사장 뜸.========
고자질쟁이가 포카치는 놈이 놀고 있었다고 고자질.
포카치는 놈이 일하는 척
퍼질러 자는 놈이 일하는 척
만화책보는 놈이 일하는 척
=======사장 뜸.========
퍼질러 자는 놈이 일하는 척
만화책보는 놈이 일하는 척

일단 테스트 클래스만 보세요.
사장이 오는 지 감시하는 Watcher가 하나 있습니다. 그리고 3명의 Employee와 사장 끄나풀인 1명의 Spy가 있습니다. 얘네들은 전부 Watcher한테 사장 뜨면 알려달라고 얘기해 놓습니다. addObserver() 가 Observer로 등록하는 부분입니다.
각각의 Employee 들은 사장이 뜨면, 하던 일 멈추고 일하는 척을 합니다. Spy는 사장이 뜨면, 누가 놀고 있었다고 꼰지릅니다. Employee와 Spy는 같은 통보(사장이 떴다는 것)을 받지만 하는 일은 전혀 다릅니다. 이렇게 같은 통보에 전혀 다른 Observer 들을  붙여 버릴 수도 있습니다.
테스트 코드와 테스트 결과를 보면, observer를 등록한 순서와 통보를 받는 순서가 일치하지 않는 것을 알 수 있습니다. 통보를 받는 순서는 등록 순서와 무관합니다. 
사장이 처음 떴을 때, "포카치는 놈"은 Spy한테 고자질 당해서 짤렸습니다. 짤렸기 때문에 더 이상 사장이 뜨던 말던 관심 없습니다. 그래서 Watcher에 Observer로 더 이상 등록되어 있을 필요가 없어졌습니다. 또, Spy는 잘했다고 포상휴가 갑니다. 얘도 더 이상 사장이 뜨는 지 안 뜨는지 통보 받을 필요가 없어졌습니다. 그래서 얘네 둘은 Watcher에 등록된 Observer 리스트에서 제거합니다. 색칠된 deleteObserver() 가 제거하는 부분입니다.
그래서 두 번째로 사장이 떴을 때는 "만화책 보는 놈"과 "퍼질러 자는 놈"만 통보를 받습니다.

다음은 Employee 클래스를 봅시다.
Observer 인터페이스를 구현하고 있습니다. Observer 인터페이스에는 update(Observable , Object) 메쏘드가 정의되어 있습니다.
첫번째 인자 Observable은 update를 호출해준 Observable 을 말합니다. 여기서는 Watcher 클래스가 되겠습니다. 하나의 Observer는 여러 개의 Observable에 등록될 수가 있습니다. "만화책 보는 놈"의 경우는 사장이 떠도 통보를 받아야겠지만, 신간 만화책이 나왔을 때도 통보를 받아야 합니다. 각기 다른 통보인 만큼 할 일이 달라지겠지요. 그러나, update 메쏘드를 각각의 Observable에 대응하도록 여러 개를 만들 수 없기 때문에 어떤 일인지를 Observable을 받아서 파악하고, 그에 걸맞는 행동을 합니다. 예제에서는 그 Observable이 단지 Watcher 클래스의 인스턴스인지 체크하는 방식으로 구현했습니다.
두번째 인자 Object를 통해서는 구체적인 정보를 받을 수 있습니다.  

이번에는 Watcher 클래스를 봅시다.
일단 Observable이란는 클래스를 상속받습니다. Observable 에는 몇 가지 메쏘드가 있습니다만 여기서는 두가지만 썼습니다. setChanged()와 notifyObservers() 입니다. setChanged() 는 변화가 일어났다는 것을 알리는 겁니다. 변화가 일어나지 않으면, 굳이 Observer 들에게 알릴 필요가 없습니다. 예를들어, Watcher는 무조건 감시하고 있는 클래스이기 때문에 잡상인이 뜨던가 사장이 뜨던가 다 압니다. 하지만, 잡상인이 떴을 경우는 Observer들에게 알릴 필요가 없습니다. 사장이 떴을 때만 setChanged()를 호출하고, Observer들에게 알립니다. (사장인지 아닌 지를 판단하는 로직은 코드가 길어져서 뺐습니다.) setChanged()가 호출되지 않고 notifyObservers()가 호출되면, 아무일도 일어나지 않습니다.
다음에 notifyObservers() 메쏘드가 있는데, 이 메쏘는 오버로드되어있습니다. notifyObservers()와 notifyObservers(Object) 두 가지가 있습니다. Object에는 어떤 일이 일어났는지 상세 정보를 담을 수 있습니다. 사장이 떴는지, 부장이 떴는 지 정도의 정보를 담을 수 있겠지요. notifyObservers()는 notifyObservers(null) 과 같습니다. 
어찌되었건 notifyObservers(Object)가 호출이 되면, Observer들에게 전부 알립니다. 그러면, Observer들은 각각 자기가 가진 update() 메쏘드가 호출됩니다.

3. Observer 의 특징

말이 Observer이지 단지 Observerable에게 통보를 받는 입장입니다. 
Observer 들은 Observable에 추가 삭제가 자유롭습니다. Observable 입장에서는 어떤 Observer인지 신경쓰지 않습니다. 단지 어떤 일이 일어났다는 통보만을 합니다. 통보에 대한 반응은 전적으로 Observer가 합니다. update() 메쏘드가 Observer에 있고, notifyObservers() 메쏘드가 Observable에 있는 것이 바로 그런 얘깁니다.

4. JAVA API에 있는 Observer

또, GUI 쪽 얘기를 꺼내게 되는군요. GUI에 있는(awt, swing, swt 등등 전부 비슷합니다.) 각종 버튼, 텍스트 등에 add머시기Listener 메쏘드 들이 잔뜩 있습니다. 이벤트가 일어나면, 뭔가를 호출하라는 것이지요. 예제에서 설명한 것과는 인터페이스가 다소 상이하긴 합니다.
GUI의 event는 Observer 패턴으로 구현되어 있어 이벤트 추가 삭제가 매우 자유롭습니다. 

'개발 > 디자인패턴(외부글)' 카테고리의 다른 글

FlightWeight  (0) 2011.09.26
ProtoType  (0) 2011.09.26
Observer  (0) 2011.09.26
Facade  (0) 2011.09.26
Chain of responsibility  (0) 2011.09.26
Decorate  (0) 2011.09.26

댓글을 달아 주세요

posted by 동건이 2011. 9. 26. 12:07

Facade

자바 디자인 패턴 10 - Facade

1. Facade 패턴은..

여러가지 복잡한 것들을 하나로 간주해서 편하게 다루는 방법입니다.
예를 들어, 우리가 흔히 자동차라고 하는 물건은 바퀴, 엔진, 오디오, 사이드 미러 등등 으로 이루어져있지만 우리는 그냥 다 자동차라고 생각하는 게 편합니다. 차안에서 음악을 듣고 싶으면, 그냥 오디오를 켜면 됩니다. "차량에 내장된 오디오"라는 것을 굳이 명확히 하기는 귀찮죠. 

2. 예제

----------------- 내부구성품 1. TV ----------

package ch10_Facade;
public class TV {
    private boolean turnedOn = false;
    public void turnOn(){
        turnedOn = true;
        System.out.println("TV를 켬.");
    }
    public void turnOff(){
        turnedOn = false;
        System.out.println("TV를 끔.");
    }
    public boolean isTurnedOn(){
        return turnedOn;
    }
}

----------------- 내부구성품 2. 오디오 ----------

package ch10_Facade;
public class Audio {
    private boolean playing = false;
    public void play(){
        playing = true;
        System.out.println("음악을 연주.");
    }
    public void stop(){
        playing = false;
        System.out.println("음악을 멈춤");
    }
    public boolean isPlaying() {
        return playing;
    }
}

----------------- 내부구성품 3. 전등 ----------

package ch10_Facade;
public class Light {
    private int lightness = 0;
    public int getLightness() {
        return lightness;
    }
    public void setLightness(int lightness) {
        System.out.println("밝기를 "+ lightness + "로 변경.");
        this.lightness = lightness;
    }
}

----------------- Facade ----------

package ch10_Facade;
public class Home {
    private Audio audio;
    private Light light;
    private TV tv;
    public Home(Audio audio, Light light, TV tv) {
        this.audio = audio;
        this.light = light;
        this.tv = tv;
    }
    public void enjoyTv(){
        System.out.println("==불을 밝게하고 TV보기.");
        light.setLightness(2);
        tv.turnOn();

    }
    public void enjoyMusic(){
        System.out.println("==불을 약간 어둡게하고 음악듣기.");
        light.setLightness(1);
        audio.play();

    }
    public void goOut(){
        System.out.println("==TV끄고, 음악도 끄고, 불도 끄고 외출하기.");
        if (tv.isTurnedOn()) {
            tv.turnOff();
        }
        if (audio.isPlaying()) {
            audio.stop();
        }
        light.setLightness(0);

    }
}

----------------- 테스트 클래스 ----------

package ch10_Facade;
public class Test {
    public static void main(String[] args) {
        TV tv = new TV();
        Audio audio = new Audio();
        Light light = new Light();
        
        Home home = new Home(audio, light, tv);
        
        home.enjoyTv();
        home.enjoyMusic();
        home.goOut();
    }
}

----------------- 테스트 결과 ----------

==불을 밝게하고 TV보기.
밝기를 2로 변경.
TV를 켬.
==불을 약간 어둡게하고 음악듣기.
밝기를 1로 변경.
음악을 연주.
==TV끄고, 음악도 끄고, 불도 끄고 외출하기.
TV를 끔.
음악을 멈춤
밝기를 0로 변경.

Test 클래스에서는 TV, Audio, Light 등의 클래스의 인스턴스를 가지고 있긴하지만, Home을 생성하는 용도 외에는 사용하지 않습니다.
TV나 Audio 등이 저차원 클래스라면, Home은 저차원 클래스들을 감싸고 있는 고차원 클래스 입니다. 실질적인 저차원 클래스들의 조합은 바로 고차원 클래스인 Home클래스가 담당합니다.(코드의 황토색 부분이 조합시키는 부분입니다.) 클라이언트인 Test클래스는 고차원 클래스만 신경씁니다. 

3. Facade 의 특징

예제에서 굳이 TV, Light, Audio라는 클래스들을 만들지 않고, Home 클래스 안에다가 기능을 다 넣을 수도 있었습니다. 그런데, TV가 꼭 Home에 종속되어야 할까요? 요즘은 핸드폰으로도 TV보는 세상인데요. 
Facade 패턴을 사용하면 최소단위로 클래스를 설계할 수 있습니다. 물론, 지나치게 잘게 쪼개는 것도 그다지 바람직하진 않습니다 . 코드를 다른 데에 복사해서 사용하지 않는 선 정도에서 클래스를 분리시키면 될 것 같습니다.
예제코드의 경우 TV를 분리시키지 않는다면 핸드폰 클래스가 나왔을 때, 핸드폰 클래스의 TV기능을 다시 구현해야하는 사태가 발생합니다.

'개발 > 디자인패턴(외부글)' 카테고리의 다른 글

ProtoType  (0) 2011.09.26
Observer  (0) 2011.09.26
Facade  (0) 2011.09.26
Chain of responsibility  (0) 2011.09.26
Decorate  (0) 2011.09.26
Composite  (0) 2011.09.26

댓글을 달아 주세요

posted by 동건이 2011. 9. 26. 12:00

Chain of responsibility

자바 디자인 패턴 9 - Chain of Responsibility

1. Chain of Responsibility 패턴은..

오션스 일레븐과 같은 류의 영화를 보신 적이 있죠? 전문가들이 몇 명 있습니다. 그러나, 그 전문가들은 할 수 있는 일이 극히 제한되어 있죠. 예를 들어, 해커가 격투에 능하진 않습니다. 해커는 단지 해킹에만 능합니다. 그들은 각각은 할 수 있는 일들이 제한적이지만, 모여있으면 세상만사 다 해결합니다. 각각의 전문가들이 자기가 할 수 있는 일만 하면 되거든요. 만약에 그들 모두가 해결할 수 없는 문제가 발생하면... 오션스 투엘브가 되고, 오션스 써틴이 되고 하면 됩니다. 또 영입하면 되죠 멀..
Chain of Responsiblity 패턴에서는 문제 해결사들이 한줄로 쫙 서있다가 문제가 들어오면, 자기가 해결할 수 있으면 해결하고, 안 되면 다음 해결사에게 문제를 넘겨버립니다.

2. 예제

------------------ 전문가: 상위 클래스 ---------------

package ch09_ChainOfResponsibility;
public abstract class Expert {
    private Expert next;
    protected String expertName;
    public final void support(Problem p){
        if (solve(p)) {
           System.out.println(expertName+ "이(가) " + p.getProblemName()  +"을(를) 해결해 버렸네.");
        }else{
            if (next != null) {
                next.support(p);
            }else{
                System.out.println(p.getProblemName() + "은(는) 해결할 넘이 없다.");
            }
        }
    }
    public Expert setNext(Expert next){
        this.next = next;
        return next;
    }
    protected abstract boolean solve(Problem p);
}

----------- 전문가들이 풀어야할 문제 클래스 -----------

package ch09_ChainOfResponsibility;
public class Problem {
    private String problemName;
    public Problem(String name) {
        this.problemName = name;
    }
    public String getProblemName() {
        return problemName;
    }
}

--------------- 첫번째 전문가 파이터! --------

package ch09_ChainOfResponsibility;
public class Fighter extends Expert {
    public Fighter(){
        this.expertName = "격투가";
    }
    @Override
    protected boolean solve(Problem p) {
        return p.getProblemName().contains("깡패");
    }
}

--------------- 두번째 전문가 해커! --------

package ch09_ChainOfResponsibility;
public class Hacker extends Expert {
    public Hacker(){
        this.expertName = "해커";        
    }
    @Override
    protected boolean solve(Problem p) {
        return p.getProblemName().contains("컴퓨터");
    }
}

--------------- 세번째 전문가 카사노바! --------

package ch09_ChainOfResponsibility;
public class Casanova extends Expert {
    public Casanova(){
        expertName = "카사노바";
    }
    @Override
    protected boolean solve(Problem p) {
        return p.getProblemName().contains("여자") || p.getProblemName().contains("여성");
    }
}

----------------- 테스트 클래스 ------------------

package ch09_ChainOfResponsibility;
public class Test {
    public static void main(String[] args) {
        Problem[] problems = new Problem[5];
        problems[0] = new Problem("덩치 큰 깡패");
        problems[1] = new Problem("컴퓨터 보안장치");
        problems[2] = new Problem("까칠한 여자");
        problems[3] = new Problem("날렵한 깡패");
        problems[4] = new Problem("폭탄");
        
        Expert fighter = new Fighter();
        Expert hacker = new Hacker();
        Expert casanova = new Casanova();
        
        fighter.setNext(hacker).setNext(casanova);
        
        for (Problem problem : problems) {
            fighter.support(problem);
        }
    }
}

--------------- 결과 ----------------

격투가이(가) 덩치 큰 깡패을(를) 해결해 버렸네.
해커이(가) 컴퓨터 보안장치을(를) 해결해 버렸네.
카사노바이(가) 까칠한 여자을(를) 해결해 버렸네.
격투가이(가) 날렵한 깡패을(를) 해결해 버렸네.
폭탄은(는) 해결할 넘이 없다.


등장인물들은 전부 테스트 클래스에 있습니다. 5개의 문제점들이 있고, 3명의 전문가들이 있죠. 테스트 클래스에서는 어떤 전문가가 어떤 문제를 해결하는 지는 관심 없습니다. "문제는 해결만 되면 된다!"가 클라이언트인 테스트 클래스의 입장입니다.

Expert 클래스는 마치 Decorator 패턴 처럼 Expert를 멤버 변수로 가지고 있습니다. 그러나 Decoarator와는 달리 그 값이 null일 수도 있습니다. 다음 전문가가 없을 수도 있는 거죠. 위의 코드에서는 casanova가 마지막 전문가입니다. 즉, casanova는 next라는 변수 값이 null입니다. 
일반적인 set머시기 하는 메쏘드들은 리턴 타입이 void인데, 여기서는 리턴 타입이 Expert입니다. 예제코드처럼 전문가 그룹을 연결시키는 코드를 한줄로 만들기 위해서입니다. 만약 리턴 타입이 void였다면, 황토색 부분은 아래와 같이 두 줄로 바뀔 것입니다.

fighter.setNext(hacker);
hacker.setNext(casanova);

Expert 클래스의 support() 가 하는 일은 자기가 해결할 수 있으면 하고, 못하면 다음 전문가한테 넘기고, 떠넘길 다음 전문가가 없으면, 못한다고 생떼를 쓰는 겁니다. 내부적으로 sovle()메쏘드를 호출합니다.
solve()는 각각의 개별 클래스별로 자기가 해결 가능한지 불가능한지를 판단하는 매쏘드입니다. 당연히 구체적으로 기술해야 하므로 하위 객체에 떠넘깁니다. 

3. Chain of Responsibility의 특징

위의 예제에서는 폭탄 앞에 전문가들이 좌절해야 했습니다. 기존에 있던 애들은 그대로 두고, 폭탄전문가를 한 명 영입하면 일이 해결될 것 같습니다. 폭탄전문가 클래스를 새로 만들어서 테스트 클래스에 넣어주면 일이 해결됩니다. 또 별로 쓸모 없는 전문가를 영입했을 때는 짜르기도 쉽습니다. 전체적인 로직이 바뀌지 않습니다.

템플릿 메쏘드가 숨어있습니다. 테스트 클래스에서는 각 전문가클래스의 solve() 메쏘드를 호출한 적이 없지만, 내부적으로 호출이 됩니다.

문제가 나타났을 때 어떤 전문가가 해결할 것인지 단번에 결정이 되지 않습니다. 일단 모든 문제는 fighter 객체를 거쳐갑니다. 맨 앞에 있으니까요. 따라서 전문가 객체의 순서가 전체적인 수행 속도에 영향을 끼칠 수 있습니다. 가능한한 일반적인 문제해결사들을 앞쪽에 세워두는 게 좋습니다.

4. JAVA API에 있는 Chain of Responsibility

JAVA 1.0의 GUI 에서는 Chain of Responsibility를 사용했었다고 하는군요. 이벤트가 발생했을 때 자기가 해결할 수 있는 지 보고 해결이 안되면 상위 컴포넌트로 던졌다네요. (확인한 적은 없습니다만, 대충 말은 되는 것 같군요.)

'개발 > 디자인패턴(외부글)' 카테고리의 다른 글

Observer  (0) 2011.09.26
Facade  (0) 2011.09.26
Chain of responsibility  (0) 2011.09.26
Decorate  (0) 2011.09.26
Composite  (0) 2011.09.26
Strategy  (0) 2011.09.26

댓글을 달아 주세요

posted by 동건이 2011. 9. 26. 11:55

Decorate

자바 디자인 패턴 8 - Decorator

1. Decorator 패턴은..

기존에 구현되어 있는 클래스에 기능을 추가하기 위한 패턴입니다. 기존에 있던 클래스를 상속하여 만들기 때문에 기존 클래스와 사용법이 크게 다르지는 않습니다.

2. 예제

-------------- 데코레이터 -------------
package ch08_Decorator;
public class Decorator {
    public String getMerong(){
        return "merong";
    }
}

-------------- 데코레이터를 상속 받은 넘 ----
package ch08_Decorator;
public class ChildDecorator extends Decorator{
    private Decorator decorator;
    public ChildDecorator(Decorator decorator){
        this.decorator = decorator;
    }
    @Override
    public String getMerong(){
        return "@" + decorator.getMerong() + "@";
    }
}

-------------- 테스트 클래스 --------------
package ch08_Decorator;
public class Test {
    public static void main(String[] args) {
        Decorator decorator = new Decorator();
        System.out.println(decorator.getMerong());
        Decorator child = new ChildDecorator(decorator);
        System.out.println(child.getMerong());
        Decorator child2 = new ChildDecorator(child);
        System.out.println(child2.getMerong());
    }
}
-------------- 결과 -----------------
merong
@merong@
@@merong@@

데코레이터 패턴에서 상위클래스(Decorator라 합니다.) 와 하위 클래스(ConcreteDecorator라 합니다.) 와의 관계를 알아봅시다.
첫째, 하위 클래스는 상위클래스의 형식을 멤버변수로 가집니다. ChildDecorator 는 Decorator를 멤버변수로 받습니다. 일반적으로 생성자의 인자로 받아서 멤버변수로 쎄팅을 합니다. 별도의 setter를 가지는 경우는 거의 없습니다.
둘째, 하위 클래스는 상위클래스를 상속 받아 상위클래스의 메쏘드를 이용합니다. 하위 클래스의 getMerong() 이라는 메쏘드는 상위 클래스의 getMerong()을 오버라이드하지만, 내부적으로 상위클래스의 getMerong()을 사용하고 있습니다.

3. Decorator가 일반적인 상속과 다른 점

Decorator는 메쏘드의 확장 개념입니다. 멤버 변수로 받은 객체의 메쏘드를 이용하여 그 메쏘드를 확장하는 것입니다.

4. JAVA API에 있는 Decorator

java.io에 있는 InputStream, Reader, OutputStream, Writer 등은 모두 Decorator 패턴으로 구성되어 있습니다.
파일을 Reader로 읽는 경우를 살펴보겠습니다.

Reader reader = new FileReader("파일명");

Reader reader = new BufferedReader(new FileReader("파일명");

파일은 위의 두가지 방법으로 모두 읽을 수 있습니다. 둘다 Reader의 형식으로 받습니다. BufferedReader의 생성자는 Reader를 받아 멤버 변수로 가지고 있으며, Reader를 상속 받습니다. 멤버 변수로 받은 Reader를 이용하여, 버퍼를 이용해서 읽습니다.

'개발 > 디자인패턴(외부글)' 카테고리의 다른 글

Facade  (0) 2011.09.26
Chain of responsibility  (0) 2011.09.26
Decorate  (0) 2011.09.26
Composite  (0) 2011.09.26
Strategy  (0) 2011.09.26
Singleton  (0) 2011.09.26

댓글을 달아 주세요

posted by 동건이 2011. 9. 26. 11:51

Composite

자바 디자인 패턴 7 - Composite

1. Composite 패턴은..

파일 데이터와 같은 일반적인 트리 구조의 데이터 타입을 만드는 것이 Composite 패턴입니다. Composite 패턴에서 주요등장 인물은 3개입니다. 첫째는 상위 컴포넌트. 둘째는 상위 컴포넌트를 상속 받으며 자식 컴포넌트를 가질 수 있는 Composite. 세째는 상위 컴포넌트를 상속 받으며, 하위 컴포넌트를 가질 수 없는 Leaf. 디렉토리가 Composite라면, 파일은 Leaf라고 보시면 됩니다.

2. 예제

이번 것은 소스가 좀 깁니다. 색칠된 부분만 중점적으로 보세요.

----------------- 상위 Component ----------------- 
package ch07_Composite;
import java.util.ArrayList;
import java.util.List;
public abstract class Component {
    private String componentName;
    protected List<Component> children = new ArrayList<Component>();
    public Component(String componentName) {
        this.componentName = componentName;
    }
    public String getComponentName() {
        return componentName;
    }
    public abstract void add(Component c);
    public List<Component> getChildren(){
        return children;
    }
    public String getString(){
        return getString(0);
    }
    private String getString(int depth){
        StringBuffer sb = new StringBuffer();
        if (this instanceof Composite) {
            for (int i = 0; i < depth; i++) {
                sb.append("  ");
            }
            sb.append("+"+getComponentName() +"\n");
            for (Component comp: children) {
                sb.append(comp.getString(depth+1));
            }
        }else{
            for (int i = 0; i < depth; i++) {
                sb.append("  ");
            }
            sb.append("-"+getComponentName()+"\n");
        }
        return sb.toString();  
    }
}

----------------- 하위 Composite(하위 노드 가질 수 있음) ----------------- 
package ch07_Composite;
public class Composite extends Component {
    public Composite(String componentName) {
        super(componentName);
    }
    @Override
    public void add(Component c) {
        children.add(c);
    }
}

----------------- 하위 Leaf(하위 노드 가질 수 없음) ----------------- 
package ch07_Composite;
public class Leaf extends Component{
    public Leaf(String componentName) {
        super(componentName);
    }
    @Override
    public void add(Component c) {
        throw new UnsupportedOperationException();
    }
}

----------------- 테스트 클래스 ----------------- 
package ch07_Composite;

public class Test {
    public static void main(String[] args) {
        Composite main = new Composite("Main");
        Composite sub1 = new Composite("sub1");
        Composite sub2 = new Composite("sub2");
        Composite sub11 = new Composite("sub11");
        Composite sub12 = new Composite("sub12");
        Composite sub13 = new Composite("sub13");
        Composite sub21 = new Composite("sub21");
        Composite sub22 = new Composite("sub22");
        Leaf leaf14 = new Leaf("leaf14");
        Leaf leaf121 = new Leaf("leaf121");
        
        main.add(sub1);
        main.add(sub2);
        sub1.add(sub11);
        sub1.add(sub12);
        sub1.add(sub13);
        sub2.add(sub21);
        sub2.add(sub22);
        sub1.add(leaf14);
        sub12.add(leaf121);
        
        System.out.println(main.getString());
    }
}

----------------- 테스트 결과 ----------------- 
+Main
  +sub1
    +sub11
    +sub12
      -leaf121
    +sub13
    -leaf14
  +sub2
    +sub21
    +sub22

Component는 멤버 변수로 List<Component>를 가집니다. 이것이 트리 구조를 만드는 포인트입니다. Component에 있어서 중요한 메쏘드는 add()와 getChildren()입니다. add()의 인자는 Component 이고, getChildren()의 리턴 타입도 List<Component>입니다.  Composite인지 Leaf인지 구분하지 않습니다.

3. add와 getChildren 의 구현 방법

첫째, Component 에서 모든 것을 구현하고, Leaf에서는 add 메쏘드 호출 시 UnsupportedOperationException 을 던집니다. Component-Composite-Leaf 3 개의 구조가 아니라 Component-Leaf의 2개 구조만 있어도 됩니다. 그래서 구조가 간단해집니다. 그러나 Composite에는 있고, Leaf에는 없는 메쏘드를 구현할 방법이 없어집니다. 위의 예제는 단지 트리구조를 구현하는 것이라 상관없지만, 추가 기능을 구현할 가능성이 있는 경우는 이 방법을 쓰면 후에 문제가 생길 수 있습니다.
둘째, Component 에서는 abstract로 선언만 하고 Composite와 Leaf에서 구현을 합니다. Leaf에서는 첫번째 방법과 마찬가지로 UnsupportedOperationException 를 던지면 됩니다. 구조는 복잡하지만, 첫번째 방법에 비해 다른 기능 추가는 상대적으로 쉽습니다.

4. JAVA API에 있는 Composite

java.awt의 Container 는 Component를 상속 받고, Component를 다시 하위 객체로 가집니다. (여기서 하위는 상속의 하위 개념이 아닙니다. UI 구조상의 하위 구조입니다.) 예제에서 설명한 Leaf의 역할을 하는 객체들은 여러가지가 있습니다. Button, Text 등 우리가 알고 있는 일반적인 awt의 컴포넌트는 전부 포함된다고 보시면 됩니다.

'개발 > 디자인패턴(외부글)' 카테고리의 다른 글

Chain of responsibility  (0) 2011.09.26
Decorate  (0) 2011.09.26
Composite  (0) 2011.09.26
Strategy  (0) 2011.09.26
Singleton  (0) 2011.09.26
Template  (0) 2011.09.26

댓글을 달아 주세요

posted by 동건이 2011. 9. 26. 11:32

Strategy

자바 디자인 패턴 6 - Strategy

1. Strategy 패턴은..

Template Method 패턴이 상속을 이용해서 어떤 구현을 했다면, Strategy 패턴은 구성을 이용합니다. Template Method와 마찬가지로 바뀌는 부분과 바뀌지 않는 부분을 나눠서 생각할 수 있습니다. Template Method가 하위 클래스에서 바뀌는 부분을 처리한다면 Starategy는 바뀌는 부분을 인터페이스로 분리하여 처리합니다. 그 인터페이스의 구현체를 바꿈으로서 로직을 변경하는 것입니다. 또 Template Method와 크게 다른 점은 Template Method에서는 외부로 공개되는 것이 Template Method를 가지고 있는 상위 클래스였지만, Strategy에서는 인터페이스를 사용하는 클래스(그 클래스를 Context라고 합니다.)입니다.

2. 예제

------------------------ 상위 인터페이스 --------------------
package ch06_Strategy;

public interface Seller {
    public void sell();
}

------------------------- 인터페이스 구현체1 -----------------
package ch06_Strategy;

public class CupSeller implements Seller {
    public void sell() {
        System.out.println("컵을 팔아요.");
    }
}
------------------------- 인터페이스 구현체2 -----------------
package ch06_Strategy;

public class PhoneSeller implements Seller {
    public void sell() {
        System.out.println("전화기를 팔아요.");
    }
}
------------------------- 인터페이스 사용하는 클래스 -----------------
package ch06_Strategy;

public class Mart {
    private Seller seller;
    public Mart(Seller seller) {
        this.seller = seller;
    }
    public void order(){
        seller.sell();
    }
}
------------------------- 테스트 클래스 -----------------
package ch06_Strategy;

public class Test {
    public static void main(String[] args) {
        Seller cupSeller = new CupSeller();
        Seller phoneSeller = new PhoneSeller();
        Mart mart1 = new Mart(cupSeller);
        mart1.order();
        Mart mart2 = new Mart(phoneSeller);
        mart2.order();
    }
}

위에서 보시다 시피 테스트 클래스에서는 Seller의 sell()을 호출하지 않습니다. Mart의 order()를 호출합니다. Seller의 메쏘드는 외부로 공개되지 않습니다. 
Mart 클래스가 여기서는 외부로 공개되는 Context가 됩니다. Mart는 멤버 변수로 Seller를 가집니다. Mart에서 가지는 Seller를 바꿔치기함으로써 Mart의 order()에서 실제 실행되는 로직이 달라질 수 있습니다.

3. Strategy의 유용성

예제에서는 Context 클래스가 한 개의 Strategy 인터페이스만을 가집니다. Seller 외에 여러가지 인터페이스를 가질 수도 있습니다. 예를 들어 만드는 사람, 운반하는 사람, 파는 사람은 각각 다를 수 있습니다. 예제에서는 코드를 줄이기 위해 파는 사람만 2가지 종류의 클래스를 만들었습니다. 그러나, 만드는 사람 인터페이스와 운반하는 사람 인터페이스 등을 만들고 그 구현체 들을 만들면, 상당히 다양한 로직이 나올 수 있습니다. 만드는 사람의 구현체가 3종류, 운반하는 사람의 구현체가 3종류, 파는 사람의 구현체가 3종류라하면, 만들어서 운반해서 파는 로직은 총 3*3*3= 27가지가 나옵니다. 이를 상속을 이용한 클래스를 제작하면, 27가지의 구현체가 필요합니다. Strategy를 쓰면, 9개의 구현체만 필요하며, 또 인터페이스를 이용한 프로그램이 가능합니다.


4. JAVA API에 있는 Strategy

java.util.Collections 에 sort(List<T> list, Comparator<? super T> c) 라는 메쏘드가 있습니다. List를 Comparator에서 지정한 방법으로 정렬하는 메쏘드입니다. Comparator는 compare(T o1, T o2) 메쏘드 하나만 있는 인터페이스 입니다. 이 인터페이스를 구현하는 방법에 따라서 정렬된 결과가 달라집니다. "101"이 먼저일까요, "11"이 먼저일까요? 일반적인 순서에서는 "101"이 먼저입니다. 그러나 이게 숫자라면, 정렬 방법이 달라져야 합니다. Comparator를 구현함으로써 해결할 수 있습니다.

'개발 > 디자인패턴(외부글)' 카테고리의 다른 글

Decorate  (0) 2011.09.26
Composite  (0) 2011.09.26
Strategy  (0) 2011.09.26
Singleton  (0) 2011.09.26
Template  (0) 2011.09.26
Factory  (0) 2011.09.26

댓글을 달아 주세요

posted by 동건이 2011. 9. 26. 11:23

Singleton

자바 디자인 패턴 5 - Singleton

1. Singleton 패턴은..

각종 설정 등이 저장된 클래스가 하나 있다고 칩시다. 프로그램 내에서 여기저기서 마구 접근해서 설정을 바꾸기도 하고 값을 가져오기도 합니다. 이런 클래스는 인스턴스를 하나만 가져야 합니다. 하나 만들어서 쓰는 곳마다 인자로 전달해주면 되긴 합니다만, 접근하는 곳이 많다면, 계속 인자로 전달하는 것은 그다지 바람직하지 않습니다. 전역변수처럼 아무곳에서나 이 인스턴스에 접근을 하면 편하겠죠. Singleton 패턴을 이용하면, 하나의 객체를 만들어서 아무데서나 접근할 수 있습니다.

2. 예제

---------------------  Singleton으로 구현된 클래스 ----------------
package ch05_Singleton;

public class SingletonCounter {
    private static SingletonCounter singleton = new SingletonCounter();
    private int cnt = 0;
    private SingletonCounter(){
    }

    public static SingletonCounter getInstance(){
        return singleton;
    }

    public int getNextInt(){
        return ++cnt;
    }
}
---------------------- 테스트 클래스 ---------------------
package ch05_Singleton;

public class Test {
    public static void main(String[] args) {
        Test t = new Test();
        t.Amethod();
        t.Bmethod();
    }
    public void Amethod(){
        SingletonCounter sc = SingletonCounter.getInstance();
        System.out.println("Amethod에서 카운터 호출 " + sc.getNextInt() );
    }
    public void Bmethod(){
        SingletonCounter sc = SingletonCounter.getInstance();
        System.out.println("Bmethod에서 카운터 호출 " + sc.getNextInt() );
    }
}

---------------------- 실행 결과 -----------------------
Amethod에서 카운터 호출 1
Bmethod에서 카운터 호출 2


singleton에서 중요한 것은 다음 세 가지입니다.
첫째, private 멤버 변수로 자기 자신의 클래스의 인스턴스를 가집니다. 황토색 부분입니다.
둘째, private 생성자를 지정하여, 외부에서 절대로 인스턴스를 생성하지 못하게 합니다. 보라색 부분입니다.
셋째,getInstance() 메쏘드를 통해 객체를 static하게 가져올 수 있습니다. 파란색 부분입니다.

이는 유일무이한 인스턴스를 만들기 위해 생긴 규약들입니다. 무슨 수를 써도 Singleton 클래스를 수정하지 않는 한 새로운 인스턴스를 만들 수 없습니다. 

3. Singleton을 구현하는 몇 가지 방법

------------- 첫번째 --------------
package ch05_Singleton;

public class Singleton1 {
    private static Singleton1 single = new Singleton1();
    public static Singleton1 getInstance(){
        return single;
    }
    private Singleton1(){
    }
}

클래스 로드시 new가 실행이 됩니다. 항상 1개의 인스턴스를 가지게 되겠죠. 코드가 가장 짧고 쉽습니다. 성능도 다른 방법에 비해 좋습니다. 

-------------- 두번째 --------------
package ch05_Singleton;

public class Singleton2 {
    private static Singleton2 single;
    public static synchronized Singleton2 getInstance(){
        if (single == null) {
            single = new Singleton2();
        }
        return single;
    }
    private Singleton2(){
    }
}

클래스 로드시에는 인스턴스가 생성되지 않습니다. getInstance()가 처음 호출될 때 생성이 되지요. 그러나 synchornized가 걸려 있어서 성능이 안 좋습니다. 인스턴스를 사용할 필요가 없을 때는 인스턴스가 생성되지 않는다는 점이 첫번째 방벙에 비해 장점입니다.

--------------- 세번째 ---------------
package ch05_Singleton;

public class Singleton3 {
    private volatile static Singleton3 single;
    public static Singleton3 getInstance(){
        if (single == null) {
            synchronized(Singleton3.class) {
                if (single == null) {
                    single = new Singleton3();
                }
            }
        }
        return single;
    }
    private Singleton3(){
    }
}

첫번째의 장점인 성능이 좋다(synchronized 가 안 걸려서)와 두번째의 장점인 안 쓸 때는 인스턴스를 아예 만들지 않는다의 장점만 뽑아온 방법입니다. 코드는 제일 깁니다^^.
여기서 중요한 점은 if(single == null) 을 두 번이나 체크합니다. A, B 2개의 thread가 접근을 한다고 가정합니다.
A와 B가 거의 동시에 들어와서 바깥쪽 single== null 인 부분을 통과했다고 칩시다. 그리고 A가 조금 먼저 synchronized 블럭에 진입했습니다. B는 그 앞에서 대기 중이지요. A가 다시 single== null을 체크합니다. 여전히 null이지요. 그러면 인스턴스를 만들고 synchronized 블럭을 탈출합니다. 그러면 B가 synchronized 안으로 진입합니다. single은 더 이상 null이 아닙니다. A가 만들었으니까요. B는 그냥 synchronized 블럭을 빠져나옵니다.
바깥쪽 if(single == null) 가 없다면, 성능 저하가 발생합니다. 매번 synchronized 블럭 안으로 들어가니까요. 두번째 방법과 같다고 보시면 됩니다. 안쪽의 if(single == null) 가 없다면, singleton이 보장되지 않습니다. 
volatile 키워드도 꼭 써줘야 합니다. volatile 키워드는 변수의 원자성을 보장합니다. single = new Singleton3(); 이란 구문의 실행은 원자성이 아닙니다.(원자성이란 JVM이 실행하는 최소단위의 일을 말합니다. 즉 객체 생성은 JVM이 실행하는 최소단위가 몇 번 실행되어야 완료되는 작업이란 뜻입니다.)  JVM에 따라서 single이라는 변수의 공간만을 먼저 생성하고 초기화가 나중에 실행되는 경우도 있습니다. 변수의 공간만 차지해도 null은 아니기 때문에 singleton이 보장된기 어렵습니다. JVM 버전이 1.4(어쩌면 1.5 잘 기억이..--;; ) 이전에서는 volatile 키워드가 정상적으로 작동하지 않을 수도 있다고 합니다.

--------------- 네번째 ---------------
package ch05_Singleton;

public class Singleton4 {
    private Singleton4(){
    }
    private static class SingletonHolder{
        static final Singleton4 single = new Singleton4();
    }
    public static Singleton4 getInstatnce(){
        return SingletonHolder.single;
    }
}

네번째 방법은 내부 클래스를 사용하는 방법입니다. 기존의 3가지 방법에서는 Singleton 클래스가 자기 자신의 타입을 가지는 멤버 변수를 가지고 있는데, 네번째의 경우는 내부 클래스가 가지고 있습니다. 내부 클래스가 호출되는 시점에 최초 생성이 되기 때문에, 속도도 빠르고 필요치 않다면 생성하지도 않습니다.

4. Singleton의 특징

Singleton은 당연히 인스턴스가 1개만 생깁니다. 그러자고 만든 거니까요. 또 하나의 규약은 private 생성자 때문에 상속이 안 된다는 점입니다. (상속받은 하위체는 상위체의 생성자를 호출합니다.) 예를 들어 Singleton에서 설정관련된 xml 파일을 수정한다고 칩시다. 상속을 받아 다른 객체를 만들어서 파일을 수정하는 시도를 하면 안되지요. 상속을 받게 되면 "인스턴스 1개"라는 원칙을 깨게 됩니다.
private 생성자는 외부에서의 직접호출을 통한 생성을 막는 것과 상속을 막는 두 가지 기능을 수행합니다. 둘 다 "인스턴스 1개"라는 원칙을 지키는 것이죠.

Factory 패턴과 사용법이 매우 유사합니다. Singleton은 Factory의 특이 케이스로 볼 수도 있습니다. Factory는 매번 객체를 만들어서 리턴하는 방법이고 Singleton은 한 개만 만들어서 요청이 들어올 때마다 만들어진 객체를 리턴한다는 게 차이점입니다. 또 일반적으로 Factory는 create...과 같은 메쏘드 이름을 사용하고 Singleton은 getInstance라는 메쏘드 이름을 사용합니다.

위에서 말한 세가지 방법 중 첫번째 방법의 경우는 public으로 멤버 변수를 선언하고 외부에서 직접 변수에 접근해서 사용하게 해도 됩니다. (반드시 private이어야할 필요는 없다는 거죠. ) 두번째와 세번째는 초기화가 보장이 안 되어 있지만, 첫번째의 경우는 보장되어있기 때문입니다. 주의할 점은 외부에서 악의적으로 public 멤버 변수는 바꿔치기를 할 수도 있기 때문에 이런 식으로 접근할 때는 final 을 붙여주는 게 좋습니다.(어차피 private 생성자를 가지고 있으니, 외부에서 새로운 객체를 만들어 낼 수는 없지만 null을 대입할 수는 있기 때문에 final이 필요합니다.) 그럼 public static final이 되는군요! 상수란 말이죠. 하지만 일반적인 상수와는 다릅니다. 일반적인 상수는 Immutable 로 구현이 되어있기 때문입니다. 상수로 많이 쓰는 String, Integer, Boolean 등은 전부 Immutalbe입니다. 
물론 이런 접근이 권장사항은 아닙니다. 그냥 가능하긴 하다는 얘깁니다.

5. JAVA API에 있는 Singleton

Boolean에 있는 valueOf 들은 전부 Singleton 비스무레하게 구현되어 있습니다. 다만 인자를 받기 때문에 멤버 변수로 예제처럼 1개만 가지고 있는 것이 아니라 여러개를 가질 수 있습니다. true라는 값을 가지는 Boolean과 false라는 값을 가지는 Boolean 객체 2개가 존재하는 것이죠.
Collections에 있는 empty.. 하는 메쏘드들도 전부 Singleton입니다.

jdk 안에 있는 Singleton은 대부분 위에서 말한 방법 중 첫번째 방법을 쓰고 있습니다. 클래스 로드시 멤버 변수들을 초기화하는 방법입니다. 그래서 대부분 그 멤버 변수들은 public static final 로 선언되어있습니다.

'개발 > 디자인패턴(외부글)' 카테고리의 다른 글

Composite  (0) 2011.09.26
Strategy  (0) 2011.09.26
Singleton  (0) 2011.09.26
Template  (0) 2011.09.26
Factory  (0) 2011.09.26
Adapter  (0) 2011.09.26

댓글을 달아 주세요