jemalloc 라이브러리가 정상 작동하므로 솔루션에 포함되어 있는 test_threads 프로젝트를 빌드해본다.

별 설정 없이도 잘 빌드되고 실행된다.

….만, 이게 도대체 뭔 내용인지는 나도 잘 모르겠다. test_threads.cpp 파일에는 테스트용 코드가 잔뜩 있다. 다 읽어보기도 힘들어서 읽어보다가 관둿다. 대략 je_malloc, je_free 등의 함수를 이용하는 예제코드인 것 같다.

github에는 간단한 예제코드가 있다. ( https://github.com/jemalloc/jemalloc/wiki/Getting-Started )

….는 별로 도움이 되지 않았다.

그냥 쉽게 얘기하면 new/malloc 을 할 때 je_malloc 을 하면 되고 delete/free 를 할 때 je_free 를 하라면 되는 말.

그래서 new와 delete를 오버라이딩하는 클래스를 만든다.

#pragma once
class MemoryPool
{
public:
MemoryPool(void) {}
virtual ~MemoryPool(void) {}
void* operator new(size_t size)
{
return je_malloc(size);
}
void* operator new[](size_t size)
{
return je_malloc(size);
}
void operator delete(void* ptr)
{
je_free(ptr);
}
void operator delete[](void* ptr)
{
je_free(ptr);
}
private:
};
view rawMemoryPool.cpp hosted with ❤ by GitHub

new 를 사용하는 곳에서 이 오버라이딩 클래스를 상속 받게 해주면 끝.

그리고 헤더에서 #include <jemalloc/jemalloc.h> 를 넣어주고 추가 포함 디렉터리에는 jemalloc의 include 디렉토리를 지정해주고 빌드하면 된다.

이렇게하면 빌드가 성공하고 실행파일에 있는 위치에 jemalloc 빌드 후 생성된 .dll 파일을 같이 넣어주면 된다.

….인줄 알았는데 빌드가 안된다.

코드를 보니 jemalloc.h 파일에서

#include <string.h> 부분이 문제가 되었다. 이 문제에 대해 찾아보니…

https://stackoverflow.com/questions/15512790/error-about-finding-strings-h-in-htmlcxx

아마 유닉스쪽에서 쓰는 헤더파일일 거라고 한다.

해당 부분을

  1. #ifdef _WIN32
  2. #include <string.h>
  3. #else
  4. #include <strings.h>
  5. #endif

로 교체하면 잘 작동한다.

프로젝트의 구성속성을 보면 Debug, Debug-static 식으로 나뉘어 있는데, 전자는 .dll 파일을 쓰는 동적 링크, 후자는 .lib 파일을 쓰는 정적 링크이다. 각자 원하는 것으로 사용.

원글 : https://blog.dongbumkim.com/archives/5088


일단 https://github.com/jemalloc/jemalloc 에서 소스코드를 Clone 한다. (내 경우에는 D:\Library\jemalloc 으로 다운로드했다.)

master 브랜치를 다운로드하고 안에 들어가서 살펴보면 msvc 라는 디렉토리가 있고 그 안에 jemalloc_vc2015.sln 파일이 있다. Visual Studio 2015용 솔루션이 있으니 얼마나 감사한가.

솔루션을 열어보면 이미 jemalloc 프로젝트와 test_threads 프로젝트가 추가되어있다.

jemalloc 프로젝트를 빌드해보면…

C1083 포함 파일을 열 수 없습니다. 'jemalloc/internal/jemalloc_preamble.h': No such file or directory jemalloc d:\library\jemalloc\src\witness.c 2
오류와 함께 빌드가 되지 않는다. 실제로 저 경로에 가서 살펴보면 jemalloc_preamble.h 파일이 없다.

솔루션 파일에 추가되어 있는 ReadMe.txt 파일을 열어보면 다음과 같은 내용이 있다.
How to build jemalloc for Windows
=================================

1. Install Cygwin with at least the following packages:
* autoconf
* autogen
* gawk
* grep
* sed

2. Install Visual Studio 2015 with Visual C++

3. Add Cygwin\bin to the PATH environment variable

4. Open "VS2015 x86 Native Tools Command Prompt"
(note: x86/x64 doesn't matter at this point)

5. Generate header files:
sh -c "CC=cl ./autogen.sh"

6. Now the project can be opened and built in Visual Studio:
msvc\jemalloc_vc2015.sln

메뉴얼이 있으니 따라가야지.

Cygwin을 구글에서 검색해서 설치한다. Cygwin 설치법은 검색해서 찾자.

하나 참고해둘 것은 Cygwin은 설치프로그램을 실행해서 구성파일을 인터넷에서 다운로드하며 설치한다. 근데 이 과정이 무지막지하게 느리다. 기가인터넷이고 지랄이고 이런거 소용 없더라. 미러를 선택할 때 ftp.kaist.ac.kr을 선택했지만 그래도 느리다. 한참 설치하더니 몇가지 패키지가 설치 안되었다고 나온다. 설치프로그램을 다시 실행시켜서 미러를 ftp.jaist.ac.jp 로 바꾸고 다시 설치. 이 과정을 몇번 반복해서야 겨우 설치 완료했다.

Cygwin을 디폴트로 설치하고나서 설치프로그램의 검색창을 이용해 위 메뉴얼에 있는 autoconf, autogen, gawk, grep, sed를 설치해줘야 한다. 잊지말것.

아마 Cygwin을 처음 보거나 잘 쓰지 않는 사람이라면 이 과정에서 굉장히 스트레스 받을 것이다. 나도 Cygwin 설치에만 하루 넘게 걸렸다. 젠장…

이걸 설치하고 나서는 메뉴얼대로 제어판을 열고 ‘시스템’의 ‘고급 시스템 설정’의 ‘환경 변수’에 가서 ‘변수’ 중 Path 항목의 값에 Cygwin의 bin 경로를 추가해준다. 내 경우에는 C:\cygwin64\bin 을 추가해줬다.

이제 시작메뉴에서 ‘VS2015 x64 네이티브 도구 명령 프롬프트’를 실행한다.

경로를 jemalloc이 설치된 폴더로 이동한다. 내 경우에는 D:\Library\jemalloc 으로.

메뉴얼에 있는대로 sh -c “CC=cl ./autogen.sh” 를 입력한다.

각종 환경설정 사항을 체크하고 컴파일 과정이 지나간다. (다중프로세서 컴파일은 하지 않는듯하다. 적당히 웹서핑하며 몇분 기다리면 된다.)

이 화면이 나오면 컴파일이 끝난 것이다.

아까 jemalloc_preamble.h 파일이 없던 경로에 가보면 jemalloc_preamble.h 파일이 생성되어 있다.

이제 다시 Visual Studio 2015를 켜서 솔루션을 열고 jemalloc 프로젝트를 빌드해본다.

당연히 잘된다. ㅎㅎㅎ

결론 : Cygwin을 설치하는게 제일 애먹었다. 이것만 설치하고 나머지는 메뉴얼대로 설정하면 된다.

한 사이트에서 개발한 프로그램이 가끔 이유없이 죽고 있었다.

다른 사이트는 그런 일이 없는데 그 사이트에서만 발생하고 있는 일이었다.

보통은 로그를 이용하여 원인을 파악하는데,

그 사이트는 로그가 너무 많이 쌓여서 로그 유지 기간이 짧았고,

반면 프로그램이 다운되는 일은 어쩌다 (한, 두 달에 한 번정도) 발생되는 일이라서 골치가 아팠는데

이 기회에 Breakpad를 적용해서 해결해 보려고 마음 먹었다.

1. Breakpad 소개

Google breakpad home page : https://code.google.com/p/google-breakpad/

Google Chrome처럼, 여러 플랫폼을 지원하는 프로그램을 위한 크래시 덤프를 다루기 위한 툴이다.

Win32 개발자들이 접하는 minidump나, *nix 개발자들이 접하는 coredump를 breakpad 포맷(이라기보단 함수 맵)으로 변경하고, 이를 이용해서 플랫폼이 바뀌어도 같은 형태의 스택 트레이스(stack trace)를 볼 수 있게 해주는 툴이다.

좀 더 세부적으로 보면 다음과 같은 부분으로 되어 있다.

(개별 사용자 용) 크래시가 발생했을 때, 이를 breakpad 에서 사용하는 포맷으로 덤프를 남겨주는 부분: in-process 덤프만 있는 게 아니라, 다른 프로세스에서 dump를 남기는 방식(out-of-process dump)도 지원한다
(Build-System 용) 디버그 정보를 읽어서 breakpad 내부 형식으로 바꾸는 부분 : Win32 pdb 나 –g 옵션을 넣고 빌드한 *nix 바이너리에서 심볼 데이터를 뽑아낸다
(Crash Collector 용) 1에서 나온 정보를 가지고 2를 이용하여 스택 트레이스를 뽑아내는 부분

사실 2, 3 부분은 Windows 환경에서만 프로그래밍 한다면 그다지 중요하지 않다. 어차피 breakpad 도 덤프 자체는 minidump를 쓰고 있고, breakpad 소개의 Build / User / CrashCollector system 다이어그램에 나온 과정도, 디버그 정보가 애초에 분리되어 빌드 되는 환경(/Z7 같은 걸 쓰면 모르겠지만)에선 그다지 더 편해질 건 없다.

그렇지만 1에서 Windows named-pipe를 써서, 크래시가 발생한 프로세스가 아니라, 안전하게 동작 중인 프로세스에서 덤프를 남길 수 있다는 점(Out-of-process 덤프), 그리고 이 덤프 남기는 부분의 코드에서 제공하는 callback 지점들이 적당해서, 이걸 이용해서 간단하게(!) 실제 배치된 시스템의 덤프를 중앙의 서버로 모으는 작업을 간편하게 작성할 수 있었다.

일단 out-of-process로 덤프를 남길 수 있기에, 크래시가 발생한 바이너리에서 할 수 없는 일들 – 메모리 신규 할당, heap 메모리 참조, 기타 등등 – 을 맘대로 해도 되기 때문에 웹 서버나 다른 서버로 덤프를 보내는 일이 쉬워진다.

– rein’s world

out-of-process

breakpad 의 가장 큰 특징은 out-of-process 를 지원하는 것이다. 크래시를 처리하는 함수에서 앞에서 말한 다양한 기능을 추가한다면 제대로 동작하지 않을 수도 있다. 간단한 예로, 사용자에게 의견을 받는 윈도우를 띄운다고 할 때 메인 스레드의 메시지 루프가 깨지면 윈도우 자체를 볼 수 없게 된다.

크래시가 발생한 프로세스 밖(out-of-process)에서 처리하면 이와 같은 문제를 줄일 수 있다. 그래서 크래시 발생을 처리하기 위해 보다 많은 것을 제공할 수 있다. 물론 in-process 도 지원하기 때문에 필요에 맞게 골라 쓰면 된다.

크로스 플랫폼

멀티플랫폼 지원을 고려한다면 breakpad 는 좋은 선택이 될 수 있다. breakpad 를 사용한다면 크로스 플랫폼에 대한 고민을 줄여줄 수 있다.

– 야드버즈의 개발 로그

breakpad를 현재 사용하고 있는 대표적인 프로그램은 Chrome Browser, Firefox Browser, Picasa, Google Earth 등이다.

2. Breakpad 받기

Breakpad는 SVN을 이용하여 내려 받을 수 있다.

SVN은 서버까지 필요 없으므로 Windows용 tortoiseSVN을 설치하면 된다.

tortoiseSVN을 설치했으면 다음과 같이 내려 받는다.

1) Command 창을 열어 먼저 설치하고 싶은 폴더로 이동한뒤, 다음과 같이 실행한다.

svn checkout http://google-breakpad.googlecode.com/svn/trunk/ google-breakpad-read-only

그러면 google-breakpad-read-only 폴더가 생기면서 그 폴더로 checkout을 하고, 소스를 내려 받는다.

2) 탐색기에서 설치하고 싶은 폴더를 만든 뒤, 마우스 오른쪽 버튼을 눌러 로 ‘SVN Checkout’을 선택한다.

URL of Repository 항목에 ‘http://google-breakpad.googlecode.com/svn/trunk’ 를 넣고  OK를 누른다.

그러면 그 폴더로 checkout을 하고, 소스를 내려 받는다.

3. Breakpad build 하기

  1. 1. python 2.x버전 설치: breakpad는 gyp를 이용하여 빌드 시스템을 생성한다. gyp를 사용하기 위해서는 python 2.x버전이 필요하다. gyp가 아직 python 3.x버전과는 호환성 문제가 있는 것 같다. (gyp 자체 문제 + gyp 설정 파일문제가 복합적인듯 하다.)
  2. gyp 소스 가져오기: breakpad 소스 내에 gyp가 내장되어 있지만, 내장 gyp는 버전관리가 안되어 있는듯 하다. gyp 최신 버전이 적용이 안되어 있다. 현재 작성 시간 기준 gyp 최신 소스에는 Visual studio 2012까지 빌드 시스템을 생성할 수 있도록 되어있다. gyp 최신버전을 사용하기 위해서 gyp를 google 저장소에서 checkout하면 된다.
  3. gyp로 빌드 시스템 생성: src\client\windows 폴더에서 ..\..\tools\gyp\gyp.batbreakpad_client.gyp 를 실행하면 솔루션 파일과 프로젝트 파일이 생성되는 것을 볼 수 있다.
  4. Visual Studio로 빌드: 만들어진 솔루션 파일을 열어 그 안에 있는 build_all 프로젝트를 빌드하면 된다.

4. Breakpad test

테스트용 프로그램(crash_generation_app 프로젝트)이 솔루션에 포함돼 있으므로 따로 만들 필요는 없다. 이 테스트 프로그램을 이용하면 in-process 뿐만 아니라 out-of-process 미니덤프도 만들어 볼 수 있다. 여기에서는 out-of-process 미니덤프를 만드는 방법을 소개하겠다.

프로그램을 실행하기에 앞서 C:\Dumps 폴더를 만들어야 한다. 이 폴더를 만들지 않으면 미니덤프를 저장하다가 실패한다.

준비가 다 됐다면 프로그램을 실행해 보자. 제대로 된 out-of-process 를 테스트하기 위해서는 프로세스를 두 개 띄워야 한다. 두 번째 프로세스를 띄울 때 서버를 실행할 수 없다는 에러 메시지가 뜨지만 무시해도 좋다. 그런 다음 서버가 실행되지 않는 프로세스에서 Client -> Deref Zero 메뉴를 선택해 강제로 크래시를 발생시키면 C:\Dumps 폴더에 미니덤프 파일이 생성되는 것을 볼 수 있다.

– 야드버즈의 개발 로그

참고자료

Breakpad 로 CrashReporter 만들기 – rein’s world

원도우즈 환경에서 breakpad 사용하기 – 야드버즈의 개발 로그


[추가]

../gyp/gyp.bat breakpad_client.gyp --no-circular-check

위 옵션을 꼭 넣어야 솔루션파일이 만들어진다.


출처

https://arodream.wordpress.com/2015/04/17/google-breakpad%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-creash-dump-%EB%96%A8%EC%96%B4%EB%9C%A8%EB%A6%AC%EA%B8%B0/



장시간 프로그램 실행시 메모리가 증가하는 경우, 자원 해제를 빼먹은 부분을 찾아주는 유용한 툴 중 하나인,

"Visual Leak Detector for Visual C++ 2008/2010/2012/2013"라는 유용한 라이브러리를 소개합니다.


설치 후 간단하게 프로젝트에 헤더파일 추가와 경로설정만하면 끝!


1. 다운로드 & 설치




2. C++ 프로젝트에 VLD 경로 추가


Visual C++ 프로젝트 속성을 클릭하여 VC++ 디렉터리의 포함/라이브러리 디렉터리에 VLD의 경로를 추가합니다.


1) 포함 디렉터리 추가

ex) "C:\Program Files (x86)\Visual Leak Detector\include"



2) 라이브러리 디렉터리 추가

ex) "C:\Program Files (x86)\Visual Leak Detector\lib\Win32" (개발하는 프로그램이 32bit인 경우)




3. VLD 헤더파일 추가


VLD를 사용하기 위한 헤더파일을 소스에 추가합니다.



4. 메모리 누수 체크하기


간단한 샘플 코드를 실행하여 종료시킨 후의 DEBUG 로그를 확인합니다.

디버그로그에 보면 파일의 경로와 함께 "memoryleaktest.cpp (6)"이라고 출력이되었고, 

해당 파일의 6번째 라인을 보면 누수된 코드를 확인 할 수 있습니다.


* 메모리 누수가 체크된 경우




* 메모리 누수가 없다고 체크된 경우


메모리 누수가 없는 경우는 아래와 같이 출력됩니다.

Visual Leak Detector Version 2.3 installed.

    Outputting the report to the debugger and to F:\Project\Test\MemLeakTestCpp\memory_leak_report.txt

No memory leaks detected.

Visual Leak Detector is now exiting.



* 샘플 코드



◆ VLD 출력 설정하기


출력 데이터를 파일로, callstack level 설정 등을 변경하고 싶다면 vld.ini 파일을 수정합니다.

(설치된 경로에 위치)


* 출력 데이터 경로 설정하기 : 파일 및 debugger

1) ReportTo 값을 Both로 변경하여 debugger와 파일로 동시 출력하게 설정

2) 파일 확인





출처: http://dg087.tistory.com/9 []

- 클래스(구조체)의 바이트 패딩 -
멤버 변수를 메모리에서 CPU 레지스터로 한번에 읽을 수 있도록
CPU 레지스터의 읽기 블록에 맞춰 정렬하는 컴파일러의 최적화 작업
컴파일러가 패딩을 하지 않아 레지스터가 읽는 블록의 경계에 걸쳐 멤버 변수가 생긴다면
메모리를 읽을 때 두개의 블록을 읽어는 문제가 발생
CPU 레지스터는
32비트 운영체제일때 4바이트
64비트 운영체제일때 8바이트 단위로 메모리를 읽는다

구조체를 4바이트 단위로 끊어주는것을 "바이트 패딩" 이라고 한다.
 

구조체는 메모리에 어떤 식으로 저장될까
다음과 같은 소스를 보자.
 
  1. #include <stdio.h>   
  2.   
  3. typedef struct _TEST{   
  4.     char cData;   
  5.     short sData;   
  6.     int iData;   
  7. }TEST;   
  8.   
  9. int main()   
  10. {   
  11.     TEST TData={0,};   
  12.   
  13.     printf("cData size : %d\n"sizeof(TData.cData));   
  14.     printf("sData size : %d\n"sizeof(TData.sData));   
  15.     printf("iData size : %d\n"sizeof(TData.iData));   
  16.     printf("TData size : %d\n"sizeof(TData));   
  17.        
  18.     return 0;   
  19. }  


TEST 구조체 변수인 TData는 char형 데이터(1byte), short형 데이터(2byte), int형 데이터(4byte)를 가지고 있으므로 1+2+4=7byte의 크기를 가질 것 처럼 보인다. 하지만 이 소스를 컴파일하고 실행을 해보면 다음과 같은 결과가  나온다.

 

분명히 cData(1) + sData(2) + iData(4) = 7임에도 불고하고 TData의 크기는 8바이트라고 하니 참 이상한 일이다.

이는 현재 우리가 쓰는 32비트 컴퓨터에서 32비트 컴파일러를 사용하였기 때문에 32비트 즉, 4바이트로 데이터를 처리하는 것에 가장 최적화되어 있기 때문에 데이터를 4바이트 공간으로 저장하기 때문이다.

이 TData란 구조체는 8바이트에 다음과 같이 저장되어 있다.

cData
??
sData
sData
iData
iData
iData
iData

cData를 저장하고, 4바이트중에 3바이트가 남아있기 때문에 sData를 3바이트 중에 2바이트의 공간에 저장하고,
iData를 저장하려 하니 1바이트밖에 남아있지 않기 때문에 4바이트의 공간을 따로 만들어 저장하게 되는 것이다.

그럼 이제 위의 소스에서 변수 선언의 순서를 한 번 바꿔 보자.

 
  1. typedef struct _TEST{   
  2.     char cData;   
  3.     int iData;   
  4.     short sData;   
  5. }TEST;  

 
변수 선언의 순서를 바꿨을 뿐인데 신기하게도 같은 구조체의 크기가 8에서 12로 늘어나버렸다.
이 TData 구조체 변수는 다음과 같이 저장되어 있을 것이다.

cData
(1byte)
empty
(3byte)
sData
(2byte)
empty
(2byte)
iData
(4byte)

이처럼 컴파일러는 4바이트에 맞춰서 데이터를 저장하는 것을 볼 수 있다. 이것을 막으려면 어떻게 해야할까.

이것을 해결하려면 #pragma pack() 이라는 전처리어를 사용하면 된다.
구조체 하나를 더 추가한 다음 소스를 보자.

 
  1. #include <stdio.h>   
  2.   
  3. typedef struct _TEST{   
  4.     char cData;   
  5.     int iData;   
  6.     short sData;   
  7. }TEST;   
  8.   
  9. #pragma pack(1)   
  10. typedef struct _TEST2{   
  11.     char cData;   
  12.     int iData;   
  13.     short sData;   
  14. }TEST2;   
  15.   
  16. int main()   
  17. {   
  18.     TEST TData={0,};   
  19.     TEST2 TData2={0,};   
  20.   
  21.     printf("TData size : %d\n"sizeof(TData));   
  22.     printf("TData2 size : %d\n"sizeof(TData2));   
  23.        
  24.     return 0;   
  25. }  

 
#pragma pack(1)에서 1은 1바이트 단위로 저장하겠다는 것이다. 따라서 TData와 TData2의 내용물은 같으나 크기는 다른 것을 확인할 수 있다.

 그렇다면, 왜 모두 1바이트로 해서 메모리의 낭비가 없도록 하지 않는 것일까?
 그것은, 아까도 이야기하였듯이 32비트 CPU에서는 4바이트(32비트)의 단위로 데이터를 처리하는 것이 가장 빠르게 때문이다. 즉, #pragma pack(1) 이라고 선언해놓고 원래대로 돌려놓지 않는다면 속도저하의 문제가 생길 수 있다.
 따라서, 위의 소스에서 구조체 선언이 끝나는 부분에 #pragma pack(4)라고 선언해주어 할 것이다.

 하지만, 여기에도 문제가 있다.
 만약, 이 소스를 32비트의 PC가 아닌 다른 CPU가 장착된 장비에서 컴파일하게 된다면 어떻게 될 것인가. 예를 들면 임베디드 시스템 같은 8~16비트 CPU에서 말이다.
 소스를 일일히 찾아서 CPU에 맞게 고쳐주고 다시 컴파일해야 되는 불편함과 어려움이 생기게 된다.

 이럴때를 위해서 좀 더 우아하게 쓰는 코드가 있다.

 
  1. #pragma pack(push, 1)   
  2. typedef struct _TEST2{   
  3.     char cData;   
  4.     int iData;   
  5.     short sData;   
  6. }TEST2;   
  7. #pragma pack(pop)  


 기존의 바이트를 스택에 push하고 1바이트 단위로 처리한다음 끝나는 부분에 원래의 바이트 단위를 pop해주는 코드이다. 보통은 이렇게 사용하면 되겠다.


 

아래와 같은 코드를 사용하면 구조체 크기는 1바이트가된다.

  1. #pragma pack(push, 1)   
  2. typedef struct DATA{   
  3.     char cData;      
  4. }DATA;   
  5. #pragma pack(pop)  



출처: http://javawoo.tistory.com/30 []



▶ 시스템이 생성하는 객체의 클래스로 시스템을 매개변수화하는 일반적인 방법은 두가지가 있습니다.
1) 객체를 생성하는 클래스를 상속해서 서브클래스를 만드는 방법   <팩토리 메서드>
2) 객체 합성으로 시스템을 매개변수화하는 방법                          <추상 팩토리, 빌더, 원형 패턴>
-> 제품 객체를 생성하는 책임을 갖고 있는 새로운 "팩토리 객체"를 만듭니다.

▶ "그리기 편집기 프레임워크" 를 통해 알아보자.
그리기 편집기 프레임워크는 GraphicTool이 제품 클래스로 매개변수화되는 다양한 방법을 보여준다.

>> 팩토리 메서드를 적용하여, 팔레트에 Graphic의 서브클래스 각각에 대해 GraphicTool의 서브클래스를 생성합니다. GraphicTool은 NewGraphic() 연산은 갖는데, 이 연산은 각 GraphicTool 서브클래스가 재정의합니다.
>> 추상 팩토리 패턴을 적용한 결과, 여러 GraphicFactory의 클래스 계통이 만들어집니다. 클래스 계통은 Graphic 서브클래스 별로 한 개씩입니다. 이때, 각 팩토리는 단지 하나의 제품만을 생성합니다. 예) CircleFactory는 circle, LinFactory는 line을...
>> 원형 패턴을 적용하면 각 Graphic의 서브클래스가 Clone() 연산을 구현하며, GraphicTool은 그것이 생성하는 Graphic의 원형으로 매개변수화 됩니다.


▶ 정리하기
: 팩토리 메서드는 설계를 사용자가 입맛에 맞게 고칠 수 있도록 해 주면서 그 설계가 복잡해지지 않게 합니다. 다른 디자인 패턴은 새로운 클래스가 필요한 반면, 팩토리 메서드 패턴에서는 새로운 연산만 정의하면 됩니다. 개발자들은 객체를 생성하는 표준 방식으로 팩토리 메서드를 자주 사용하고는 합니다. 그러나 인스턴스화 할 클래스가 변하지 않거나 초기화 연산처럼 서브클래스들이 쉽게 재정의 할 수 있는 연산에서 인스턴스화가 된다면 상속으로도 쉽게 해결할 수 있으므로 이때는 꼭 팩토리 메서드를 사용할  필요는 없습니다.
: 추상 팩토리, 원형 또는 빌더 패턴을 사용하는 설계는 팩토리 메서드를 사용하는 설계보다 더 유연할 때가 많습니다. 팩토리 매서드를 사용해서 시작한 설계에 좀 더 유연성을 부가 할 필요가 있다면 다른 생성 패턴을 사용하는 설계로 진화합니다.




1. 단일체(Singleton) 패턴이란?
오직 한 개의 클래스 인스턴스만을 갖도록 보장하고, 이에 대한 전역적인 접근점을 제공합니다. 클래스 자신이 자기의 유일한 인스턴스로 접근하는 방법입니다.
ex) 프린터 스풀, 윈도우 관리자 등.


2. 단일체 패턴은 언제 사용되는가?
- 클래스의 인스턴스가 오직 하나여야 함을 보장하고, 잘 정의된 접근점으로 모든 사용자가 접근할 수 있도록 해야 할 때
- 유일한 인스턴스가 서브클래싱으로 확장되어야 하며, 사용자는 코드의 수정 업이 확장된 서브클래스의 인스턴스를 사용할 수 있어야 할 때


3. 단일체 패턴의 다이어그램


☆ Singleton
: Instance() 연산을 정의하여, 유일한 인스턴스로 접근할 수 있도록 합니다.
  Instance() 연산은 클래스 연산입니다. 유일한 인스턴스를 생성하는 책임을 맡습니다.

4. 단일체 패턴을 쓰면...
1) 유일하게 존재하는 인스턴스로의 접근을 통제합니다.
: Singleton 클래스 자체가 인스턴스를 캡슐화하기 때문에, 이 클래스에서 사용자가 언제, 어떻게 이 인스턴스에 접근 할 수 있는지 제어 할 수 있습니다.

2) 네임스페이스를 좁힙니다.
단일체 패턴은 전역 변수보다 더 좋습니다. 전역 변수를 사용해서 네임스페이스를 망치는 일을 없애주기 때문입니다. 즉, 전역 변수를 정의하여 발생하는 디버깅의 어려움 등 문제를 없앱니다.

3) 연산 및 표현의 정체를 허용합니다.
: Singleton 클래스는 상속될 수 있기 때문에, 이 상속된 서브클래스를 통해서 새로운 인스턴스를 만들 수 있습니다. 또한 이 패턴을 사용하면, 런타임에 필요한 클래스의 인스턴스를 써서 응요프로그램을 구성할 수도 있습니다.

4) 인스턴스의 개수를 변경하기가 자유롭습니다.
: 마음이 바뀌어서 Singleton 클래스의 인스턴스가 하나 이상 존재할 수 있도록 변경해야 할 때도 있는데, 이 작업도 어렵지 않습니다. 게다가, 응용프로그램이 사용하는 인스턴스가 다수여야 할 때도 똑같은 방법을 쓸 수 있습니다.
  즉, Singleton 클래스의 인스턴스에 접근할 수 있는 허용 범위를 결정하는 연산만 변경하면 됩니다. 왜냐하면 기존에는 하나의 인스턴스로만 접근을 허용했다면, 이제는 여러 개의 인스턴스를 생성해서 그 각각의 인스턴스로 접근할 수 있도록 연산의 구현을 바꾸면 되기 때문입니다.

5) 클래스 연산을 사용하는 것보다 훨씬 유연한 방법입니다.
: 단일체 패턴과 동일한 기능을 발휘하는 방법이 클래스 연산을 사용하는 것입니다. 그러나 이 두 언어(C++, 스몰토크)에서 클래스의 인스턴스가 하나 이상 존재할 수 있도록 설계를 변경하는 것은 어려습니다.


cf) 구현방법 中
: 단일체에 대한 레지스트리를 사용하는 것입니다. Instance() 연산에 가능한 Singleton 클래스 집합을 정의하는 대신에 Singleton 클래스는 이 단일체 인스턴스를 레지스트리에 이름을 갖는 인스턴스로 등록합니다.
  레지스트리는 문자열로 정의된 이름을 해당 단일체 인스턴스로 대응시켜 둡니다. Instance() 연산에서 단일체가 필요할 때 레지스트리를 뒤져서 이름으로 해당 단일체를 찾아달라고 의뢰하면 레지스트리는 해당하는 단일체를 찾아서 돌려주는 것입니다. 이런 방식을 취하면 Instance() 연산이 모든 달일체 클래스와 인스턴스를 알 필요가 업습니다.




1. 원형(프토로타입) 패턴이란?
  프로토타입 패턴(prototype pattern)은 생성할 객체들의 타입이 프로토타입인 인스턴스로부터 결정되도록 하며, 인스턴스는 새 객체를 만들기 위해 자신을 복제(clone)하게 된다. 프로토타입 패턴은 추상 팩토리 패턴과는 반대로, 클라이언트 응용 프로그램 코드 내에서 객체 창조자(creator)를 서브클래스(subclass)하는 것을 피할 수 있게 해준다.  프로토타입 패턴은 새로운 객체는 일반적인 방법(예를 들어, new를 사용해서라든지)으로 객체를 생성(create)하는 고유의 비용이 주어진 응용 프로그램 상황에 있어서 불가피하게 매우 클 때, 이 비용을 감내하지 않을 수 있게 해준다. 패턴을 구현하려면, 우선 clone() 메소드를 선언하는 추상 베이스 클래스를 하나 만든다. 다형적 생성자(polymorphic constructor) 기능이 필요한 클래스가 있다면, 그것을 앞에서 만든 클래스를 상속받게 한 후, clone() 메소드 내의 코드를 구현한다.


2. 원형 패턴은 언제 사용되는가?
제품의 생성, 복합, 표현 방법에 독립적인 제품을 만들고자 할 때
- 어떤 클래스의 인스턴스를 만드는 것이 자원/시간을 많이 잡아먹거나 복잡 할 때
- 모두 클래스로 만들기에는 종류가 너무 많은 경우
인스턴스 생성이 어려운 경우(인스턴스화할 클래스를 런타임에 지정할 때, 동적 로딩)- framework와 생성하는 인스턴스를 분리하고 싶은 경우
- 제품 클래스 계통과 병렬적으로 만드는 팩토리 클래스를 피하고 싶을 때
클래스의 인스턴스들이 서로 다른 상태 조합 중에 어느 하나일 때
미리 원형으로 초기화해 두고, 나중에 이를 복제해서 사용하는 것이 매번 필요한 상태 조합의 값들을 수동적으로 초기화하는 것보다 편리 할 수도 있습니다.


3. 원형 패턴의 다이어그램



☆ Prototype
:  자신을 복제하는 데 필요한 인터페이스를 정의합니다.

☆ ConcretePrototype
: 자신을 복제하는 연산을 구현합니다.

☆ Client
: 원형에 자기 자신의 복제를 요청하여 새로운 객체를 생성합니다.


4. 원형 패턴을 쓰면..
1) 런타임에 새로운 제품을 추가하고 삭제할 수 있습니다.
: 원형 패턴을 이용하면 사용자에게 원형으로 생성되는 인스턴스를 등록하는 것만으로도 시스템에 새로운 제품 클래스르 추가할 수 있게 됩니다. 런타임에 새로운 원형을 넣고 빼기가 쉽다는 접에서 다른 생성 패턴에 비해 유연성을 지니고 있습니다.

2) 값들을 다양화함으로써 새로운 객체를 명세합니다.
: 고도로 동적화된 시스템에서는 새로운 클래스를 생성할 필요 없이 객체 합성으로 새로운 행동을 정의할 수 있습니다. 객체의 변수가 다른 클래스에 대한 참조자를 정의하고 있다면, 이 참조자가 합성한 새로운 클래스만 정의하고, 그 클래스에 인스턴스에 대한 참조자만을 넘겨주면, 새로운 행동이 정의되는 것처럼 보인다는 것입니다.

3) 구조를 다양화함으로써 새로운 객체를 명세할 수 있습니다.
: 많은 응용프로그램은 구성요소와 부분 구성요소의 복합을 통해 객체를 구축합니다. 예를 들어, 회로설계를 위한 편집기는 세부 회로를 모아서 큰 회로를 만듭니다. 이런 응용프로그램에서는 편의를 위한 복잡한 사용자 정의 구조를 사용자가 인스턴스화 하여 그 상황에 맞는 세부 회로를 계속 이용할 수 있도록 배려해 줄 때가 많습니다. 복합 회로 객체가 Clone() 연산을 구현함으로써 다른 구조를 갖는 회로의 기본 골격을 만듭니다.

4) 서브클래스의 수를 줄입니다.
: 팩토리 메서드를 보면 Creator 클래스의 계통이 처리할 제품 관련 클래스의 계통과 병렬로 복합되는 것을 알 수 있습니다. 원형 패턴에서는 팩토리 메서드에 새로원 객체를 만들어 달라고 요청하는 것이 아니라 원형을 복제하는 것으로, Creator 클래스에 따른 새로운 상속 계층이 필요 없습니다.

5) 동적으로 클래스에 따라 응용프로그램을 설정할 수 있습니다.
: 몇몇 런타임 환경에서는 동적으로 클래스들을 응용프로그램으로 등록할 수 있도록 해 줍니다. 동적으로 로드된 클래스의 인스턴스를 생성하고 싶은 응용프로그램은 정적으로 그 클래스이 생성자를 참조할 수 없습니다. 그 대신 런타임 환경이 그 클래스의 인스턴스를 자동으로 생성하고 원형 관리자에게 등록합니다. 그러면 응용프로그램은 이 원형 관리자에게서 필요한 클래스의 인스턴스를 얻게 됩니다.


§ 관련 패턴 §
: 원형 패턴과 추상 팩토리 패턴은 어떤 면에서는 경쟁적인 관계입니다. 하지만 함게 사용 될 수도 있습니다. 추상 팩토리 패턴은 원형 집합을 저장하다가 필요할 때 복제하여 제픔 객체를 반환하도록 사용할 수 도 있습니다. 만약 복합체 패턴과 장식자 패턴을 많이 사용해야 하는 설계에서 원형 패턴을 쓰면 종종 재미를 볼 수 있습니다.




1. 빌더(Builder) 패턴이란?
  빌더 패턴이란 복잡한 객체를 생성하는 방법과 표현하는 방법을 정의하는 클래스를 별도로 분리하여, 서로 다른 표현이라도 이를 생성할 수 있는 동일한 절차를 제공할 수 있도록 합니다. 하나의 소스객체에 복잡한 여러개의 객체를 만들 수 있도록 하는 패턴으로 소스 객체는 복잡한 객체를 생성하기 위한 기능을 여러 부분으로 제공합니다.
  즉, 객체를 구성하는 부분을 먼저 생성하고, 이를 조합함으로써 전체 객체를 생성하기 때문에 생성할 객체가 손 쉽게 추가, 확장 가능하게 된다.


2. 빌더는 언제 사용되나?
- 복합 객체의 생성 알고리즘이 이를 합성하는 요소 객체들이 무엇인지 이들의 조립 방법에 독립적일 때
- 합성할 객체들의 표현이 서로 다르더라도 생성 절차에서 이를 지원해야 할 때

§ 예시 - 세트메뉴 서빙 §
  에피타이저 - 메인 - 디저트" 순으로 음식이 제공된다.
  에피타이저 종류는 ○○ 샐러드, ☆☆ 샐러드, □□ 샐러드 ... 가 있고, 메인 메뉴는 ●● 스테이크, ★★ 스테이크, ◆◆ 스테이크 ...가 있다. 마지막 디저트로는 △△ 아이스크림, ♡♡ 아이스크림, ♧♧ 아이스크림 ... 이 있다.
  손님이 원하는 종류는 선택해서 먹을 수 있다. 이처럼 세트 메뉴를 에피타이저 -> 메인 -> 디저트 순으로 음식이 나오지만 그 결과가 달라진다. 이때 bulider 패턴을 사용한다.


3. 빌더 패턴의 다이어그램 
▲ 구조 다이어그램

▲ 상호작용 다이어그램

☆ Builder
: Product 객체의 일부 요소들을 생성하기 위한 추상 인터페이스를 정의합니다.

☆ ConcreteBuilder
: Builder 클래스에 정의된 인퍼페이스를 구현하며, 제품의 부품들을 모아 빌더를 복합니다. 생성한 요소의 표현을 정의하고 관리합니다. 또한 제품을 검색하는데 필요한 인터페이스를 제공합니다.
: 제품의 내부 표현을 구축하고 복합 객체가 어떻게 구성되는지에 관한 절차를 정의합니다.
: 구상 빌더에서는 실제 제품을 만들어서 Product라는 복합 구조에 집어 넣습니다.

☆ Director (Client)
: Builder 인터페이스를 사용하는 객체를 합성합니다.

☆ Product
 : 생성할 복합 객체를 표현합니다.




4. 빌터 패턴을 쓰면....
1) 제품에 대한 내부 표현을 다양하게 변화할 수 있습니다.
  Builder 객체는 디렉터를 제공하고 제품을 복합하기 위해 필요한 추상 인터페이스를 정의합니다. 빌더를 사용하면 제품이 어떤 요소에서 복합되는지, 그리고 각 요소들의 표현 방법이 무엇인지 가릴 수 있게 됩니다. 즉, 어떤 요소로 전체 제품을 복합하고 그 요소들이 어떤 타입들로 구현되는지 알고 있는 쪽은 빌더뿐입니다. 제품을 복합할 때는 빌더에 정의된 추상 인터페이스를 통해 사용자가 동작하기 때문에, 새로운 제품의 표현 방법이나 제품의 복합 방법이 바뀔 때 추상 인터페이스를 정의한 Builder 클래스에서 상속을 통해 새로운 서브클래스를 정의하면 됩니다.

2) 생성과 표현에 필요한 코드를 분리합니다.
  빌더 패턴을 사용하면, 복합 객체를 생성하고 복합 객체의 내부 표현 방법을 별도의 모듈로 정의 할 수 있습니다. 사용자는 제품의 내부 구조를 정의한 클래스는 전혀 모른 채, 빌더와 상호작용을 통해서 필요한 복합 객체를 생성하게 됩니다. 왜냐하면, 이러한 제품 구조에 대한 상세한 정의를 담은 클래스는 클래스에 정의된 어떤 연산의 매개변수로도 정의되지 않기 때문입니다.

3) 복합 객체를 생성하는 절차를 좀 더 세밀하게 나눌 수 있습니다.
  한 번에 복합 객체를 생성하는 것 처럼, 빌더 패턴은 디렉터의 통제 아래 하나의 내부 구성요소들을 만들어 나갑니다. 디렉터가 빌더에서 만든 전체 복합 객체를 되돌려받을 때까지 제품 복합의 과정은 계속됩니다.

4) 클라이언트에서는 추상 인터페이스만 볼 수 있기 때문에 제품은 구현한 코드를 쉽게 바꿀 수 있습니다. 또한 제품의 내부 구조를 보호 할 수 있습니다.

5) 팩토리를 사용하는 경우에 비해 객체를 만들기 위해서 클라이언트에 대해 더 많이 알아야 합니다.

cf) 관련패턴
: 복잡한 객체를 생성할 때 추상 팩토리 패턴은 빌더 패턴과 비슷한 모습을 보입니다. 근복적인 차이가 있다면 빌더 패턴은 복잡한 객체의 단계별 생성에 중점을 둔 반면, 추상 팩토리 패턴은 제품의 유사군들이 존재할 때 유연한 설계에 중점을 둔다는 것입니다. 빌더 패턴은 생성의 마지막 단계에서 생성한 제품을 반환하는 반면, 추상 팩토리 패턴에서는 만드는 즉시 제품을 반환합니다. 추상 팩토리 패턴에서 만드는 제품은 꼭 모여야만 의미 있는 것이 아니라 하나만으로도 의미기 있기 때문입니다.




1. 추상 팩토리(Abstract Factory)란?
  추상 팩토리 패턴(Abstract factory pattern)은 다양한 구성 요소 별로 '객체의 집합'을 생성해야 할 때 유용합니다. 이 패턴을 사용하여 상황에 알맞은 객체를 생성할 수 있습니다.
  추상 팩토리를 통해서 제품군(객체의 집합)을 생성하기 위한 인터페이스를 제공할 수 있습니다. 인터페이스를 이용하는 코드를 만들면 코드를 제품을 생산하는 실제 팩토리와 분리 시킬 수 있습니다. 서로 다른 상황별로 적당한 제품을 생산할 수 있는 다양한 팩토리를 구현할 수 있게 됩니다.
  즉, 공통된 부분은 객체들의 동적 메모리 할당을 책임지고 관리하는 팩토리를 만들어서 확장 및 관리를 용이하게 할 수 있게 하는 패턴입니다.인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않도고 생성할 수 있습니다.


2. 추상 팩토리는 언제 사용되나?
- 여러 객체군들 중에서 특정 객체군을 선택하여 생성하고자 할 때
- 관련된 제품 객체들이 함께 사용되도록 설계되어서, 이 부분에 대한 규약이 외부에서 사용할 시에도 지켜지게 하고 싶을 때
- 객체가 생성되거나 구성/표현되는 방식과 무관하게 시스템을 독립적으로 만들고자 할 때
- 여러 제품군 중 하나를 선택해서 시스템을 설정해야 하고 한번 구성한 제품을 다른 것으로 대체할 수 있을 때
- 제품에 대한 클래스 라이브러리를 제공하고, 그들의 구현이 아닌 인터페이스를 노출시키고 싶을 때


3. 추상 팩토리 패턴의 다이어그램



☆ AbstractFactory 인터페이스
: 모든 구상 팩토리에서 구현해야 하는 인터페이스입니다. 제품을 생산하기 위한 일련의 메소드들이 정의되어 있습니다.

☆ AbstractProducetA 인터페이스
: 제품군으로 각  구상 팩토리에서 필요한 제품들을 모두 만들 수 있습니다.

☆ Client
: 클라이언트를 만들 때는 추상 팩토리를 바탕으로 만듭니다. 실제 팩토리는 실행시에 결정됩니다.

☆ ConcreateFactory 1/2
: 구상 팩토리에서는 서로 다른 제품군을 구현합니다. 클라이언트에서 제품이 필요하면 이 팩토리 가운데 적당할 걸 골라서 쓰면 되기 때문에 제품 객체의 인스턴스를 직접 만들 필요가 없게됩니다.

☆ ConcreateProduct A/B
:  구체적으로 팩토리가 생성할 객체를 정의하고, AbstractProduct가 정의하는 인터페이스를 구현합니다.


§ 협력방법 §
- 일반적으로 ConcreteFactory 클래스의 인스턴스 한 개가 런타임에 만들어집니다. 이 concreate factory는 어떤 특정 구현을 갖는 제품 객체를 생성합니다. 서로 다른 제품 객체를 생성하려면 사용자는 서로 다른 concreate factory를 사용해야 합니다.
- AbstractFactory는 필요한 제품 객체르르 생성하는 책임을 ConcreateFactory 서브 클래스에 위임합니다.


4. 추상 팩초리 패턴을 쓰면....
1) 구체적인 클래스를 분리합니다.
  추상 팩토리 패턴을 쓰면 응용프로그램이 생성 할 각 객체의 클래스를 제어할 수 있습니다. 팩토리는 제품 객체를 생성하는 과정과 책임을 캡슐화한 것이기 때문에, 구체적인 구현 클래스가 사용자에게서 분리됩니다. 일반 프로그램은 추상 인터페이스를 통해서만 인스턴스를 조작합니다. 제품 클래스 이름이 구체 팩토리의 구현에 분리되므로, 사용자 코드에는 나타나지 않는 것입니다.

2) 제품군을 쉽게 대체할 수 있습니다.
  구체 팩토리의 클래스는 응용프로그램에서 한 번만 나타나기 때문에 응용프로그램이 사용할 구체 팩토리를 변경하기는 쉽습니다. 또한, 구체 팩토리를 변경함르오써 응용프로그램은 서로 다른 제품을 사용할 수 있게 변경됩니다. 추상 팩토리는 필요한 모든 것을 생성하기 때문에 전체 제품군은 한번에 변경이 가능합니다.

3) 제품 사이의 일관성을 증진시킵니다.
  하나의 군 안에 속한 제품 객체들이 함께 동작하도록 설계되어 있을 때, 응용프로그램은 한 번에 오직 한 군에서 만든 객체를 사용하도록 함으로써 프로그램은 한 번에 오직 한 군데에서 만든 객체를 사용하도록 함으로써 프로그램의 일관성을 갖도록 해야 합니다.

4) 새로운 종류의 제품을 제공하기 어렵습니다.
  새로운 종류의 제품을 만들기 위해 기존 추상 팩토리를 확장하기가 쉽지 않습니다. 생성되는 제품은 추상 팩토리가 생성할 수 있는 제품 집합에만 고정되어 있기 때문입니다. 만약 새로운 종류의 제품이 등장하면 팩토리의 구현을 변경해야 합니다. 이는 추상 팩토리와 모든 서브클래스의 변경을 가져옵니다. 즉, 인터페이스가 변경되는 새로운 제품을 생성하는 연산이 추가되거나, 기존 연산의 반환 객체 타입이 변경되었으므로, 이를 상속받는 서브클래스 모두 변경되어야 합니다.

cf) 관련패턴
: AbstractFactory 클래스는 팩토리 메서드 패턴을 이용해서 구현되는데, 원형 패턴을 이용할 때도 있습니다. 구체 팩토리는 단일체 패턴을 이용해 구현하는 경우가 많습니다.


+ Recent posts