~손가락 클릭 부탁드려요~

 

 

 

 

 

 

 

 

 

 

 

월요일에 양평 빙어 축제다녀왔습니다^^

 

저는 핑크하우스 펜션에 방을 잡았어요~

 

라일락 방으로 1박당 7만원에 해결했네요^^

 

평일이라 손님이 없어서 그런지

 

조용하고 좋았어요^^

 

 수미마을 축제장까지는 걸어서 30분 정도 걸린 것 같네요..ㅎㅎ

 

 

 

 

 

제가 묵은 라일락 방입니다ㅎㅎ

 

 

 

 

방마다 테라스가 있긴한데요~

 

날씨가 추우니 저녁에는 비닐하우스에서 고기 구워먹었어요^^

 

맛있게 잘익은 비어치킨~캬

 

 

 

 

 

여기가 제가 묵은 펜션입니다..ㅎㅎ

 

 

 

 

펜션 뒷쪽으로 나오면 강(?)이 하나 있는데요~

 

검색해보니 흑천이네요^^

 

여름에 이곳에서 놀면 정말 좋겠단 생각이..^^

 

 

 

11시 30분경에

 

수미마을 빙어 축제장까지 이동시작했어요~

 

강따라 수미마을까지 걸어갔답니다~ㅎㅎ

 

 

 

 

 

 

지나가다가 포동포동 귀여운 강아지가 귀찮은 표정으로

 

절 보고있더라구요..^^;

 

 

 

 

 

20분쯤 걸어가니 봉상교가 보이네요~ㅎㅎ

 

거의 다왔네요~ㅎㅎ

 

 

 

봉상교를 건너니 행사장 가는길이 보이네요~~

 

저는 뚜벅이라 케리어에 의자 및 담요를 담아서 이동했습니다

 

슬퍼2

 

 

 

세차를 하는 나쁜 어른들이 있나보네요!!

 

no

 

 

 

 

봄에는 딸기

 

여름에는 물놀이

 

가을에는 밤, 고구마

 

겨울에는 김장, 빙어 축제가 열리네요~~ㅎㅎㅎ

 

물이 정말 깨끗하던데 여름에 꼭 다시 와야겠어요^^

 

 

 

 

주차장은 요렇게 되어있어요ㅎㅎ

 

 

 

매표소에 도착했어요 ㅎㅎ

 

저는 펜션할인을 받아서

 

1인당 7,000원에 빙어 낚시를 이용했어요~

 

펜션 아주머니께서 펜션 이용객은 예약 필요없다하시길래

 

11시쯤 갔답니다~

 

 

 

 

수미마을 빙어 축제 사이트 바로가기

 

정가 13,000원

 

인터넷 10,000원

 

펜션이용객 7,000원

 

 

 

 

 

 

 

매표소 앞에 포토존이 있어서

 

찰칵!!

 

눈을 감았네요..^^;;;

 

 

 

 

표를 구매하고~

 

매표소에서

 

3분 정도 이동하시면 달구지라는 트랙터(?) 같은 것을 타고

 

저수지로 이동을 합니다~ㅎㅎ

 

 

 

 

 

 

트랙터를 타고 저수지에 도착했어요 ㅎㅎ

 

 

 

 

시설에 대해 잠시 설명을 들었어요~~ㅎㅎ

 

 

 

 

매표소는 음식 및 렌탈 서비스를 결제하는 곳이에요~

 

 

 

 

저기가 바로 빙어를 잡을 수 있는 저수지!!

 

 

 

출출한 사람들을 위한 먹거리 장터도 마련되어잇어요~

 

 

 

 

 

빙어 잡는 방법에 대해 설명을 듣기 위해

 

잠시 먹거리 장터로 왔습니다~ㅎㅎ

 

대여한 물품 반납시 컵라면 or 수미칩 or 빙어통으로 교환해준다네요^^

 

컵라면 2,000원이에요~

 

 

 

 

티켓에 낚시도구가 포함되어있어서

 

낚시도구를 받아서

 

빙어 낚시 방법에 대해 설명을 열심히 들었어요^^

 

개장한지 얼마 안되어서

 

빙어들이 적응을 못했다고 빙어를 못잡을수도 하셨어요..ㅠㅠ

 

 

 

 

 

 

 

 

 

 

 

빙어 잡는 방법에 대해

 

설명을 다듣고 저수지 입구에서

 

빙어 무료 시식을 했어요 ㅎㅎ

 

회를 안좋아하지만...경험해보고자 먹었는데..

 

입안에서 난리치던 빙어님의 몸부림이 기억나네요..ㅠ.ㅠ

 

바이

 

 

 

 

 

 

 

가져온 의자를 셋팅 했어요~~

 

좋은 자리는 잘 몰라서...

 

사람 없는데에 자리를 잡았답니다 ㅎㅎ

 

 

 

 

미끼는 구데기(?)에요 ㅠ.ㅠ

 

너무 작아서 처음엔 바늘에 끼기 힘들었지만

 

익숙해지니 쑥쑥 껴지더라구요..ㅎㅎ

 

가끔 갯지렁이를 만지다보니 구데기 쯤이야..훗..ㅠㅠ

 

 

 

 

 

 

 

물속에 들어가 있는 구데기!

 

 

 

 

 

 

하도 안잡혀서 저수지 위에 죽어있는 빙어를 통에 담아봤습니다...ㅠㅠ...

 

 

 

캐리어에 담아온 핫팩, 물 등등..

 

 

 

옆에선 아이들이 신나게 썰매를 타고 있어요~^^

 

아이들을 앞에서 열심히 끌고 다니시던 부모님들이

 

안쓰러웠답니다...ㅠㅠ

 

 

 

빙어도 안잡히고

 

배는 고프고~~

 

낚시 접고 배를 채우러 갔어요 ㅎㅎ

 

떡볶이랑 빙어튀김이랑 소주한병을 먹었어요~~ㅋㅋ

 

맛들은 괜찮더라구요~

 

아주 가끔 비린맛이 나는 빙어가 있었지만

 

대체로 비린맛이 안나더라구요^^

 

 

 

 

 

공짜 오뎅 국물!

 

 

 

 

 

 

 

배를 채우고

 

다시 빙어낚시를 시작했어요~~~

 

 

 

 

세마리를 잡은 것 처럼 보이는데요...ㅠㅠ

 

한마리는 빙어 낚시 중 우연하게

 

상태 좋지않은 빙어가 수면 위로 올라와서

 

뜰채로 낚았고...

 

한마리는 얼음위에 살아있는 빙어 주운거고...

 

한마리는 시식대에 있던 빙어 한마리 넣었어요...ㅠㅠ

 

분노2

 

 

 

 

비록 손맛은 느끼지 못했지만

 

나름 재밌었어요~^^

 

내년 여름에는 양평에 놀러와서

 

피래미라도 잡으면서 놀아야겠어요~^^

 

이상 빙어 축제 포스팅을 마치겠습니다!

 

현재 백수인 저는..ㅠ.ㅠ

 

삶의 활력을 느끼고자 무언가를 질르고 싶었어요...

 

통기타를 살까하다가 ..............

 

DSLR 카메라를 사버렸어요...훅

 

캐논 매장가서 사려다가~~ 저희 형이 인터넷으로 사면

 

훨씬싸게 살 수 있다는 솔깃한 정보를 흘려주어서~

 

매장에서 팜플렛만 받고 나왔답니다^^;;

 

 

확실히 인터넷으로 바디+렌즈사고

 

나머지 부품들은 따로 구매하는게 싸더라구요~

 

메모리 4~8기가에 짝퉁(?) 악세서리 부품을

 

10만원 넘게 사기엔 아까워서리...

 

아래와 같이 정품리모콘, 정품후드 등등 웬만한 것들은 정품으로 샀답니다~

 

용어와 용도를 하나도 모르니....하나하나 검색해서..ㅠㅠ...흑흑

 

 

==G마켓==

32gb class10 메모리 18500 + 2500(배송비) = 21000

21000

========================

 ==11번가==

USB3.0 멀티카드 리더기 11,710 + 2500(배송비) = 14210

14210

========================

==네이버 체크아웃 구매==

가방 

21200

청소키트

5000

고급포켓융

5000

정품리모콘 22750

22750

정품후드 ew-63c 20840

20840

========================

==옥션==

mcuv 58, 52mm 28120

28120

Canon 100D 더블렌즈 kit

749540

LCD보호필름 12780

12780

========================

 

 

 

 

 

 

제가 정리를 잘 못해서..

 

급한 마음에 마구자비로 핸드폰 카메라를 눌러댔답니다^^;

 

 

 

 

 

 

 

 

 

무선리모콘은 넥스트랩에 껴놨어요~~

 

잃어버리지 않겠죠?ㅠ.ㅠ

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

고급포켓융(?)에 카메라를 넣고

 

뾱뾱이에 번들 렌즈를 넣었습니다..ㅎ

 

 

 

 

 

 

 

 

핸드폰이랑 역시 다르게 찍히긴 하는군요...ㅎㅎㅎ

 

이제 열심히 카메라 들고다니면서 공부해야겠어요~~ㅎㅎ

 

이벤트는 여자친구님을 위해 라푸마 구스다운 바이올릿으로 신청했어요~~~ㅎㅎ

 

배터리는 뭐...다음에 사면 되겠죠 ㅎㅎㅎ

 

이상 허접한 개봉기였습니다^^;

 

 

 

 

 

10진수를 2진수로 출력

 

 void Bdisp(int dec)
{
 int i=0;
 int isStartFind=0;
 int res=0;

 for(i=31;i>=0;i--)
 {
  res = (dec>>i)&0x1;

  if(res !=0 || (res==0 && isStartFind!=0))
  {
   printf("%d\n", res);
   isStartFind = 1;
  }
 }
}

 

 

 

 

원하는 위치에 원하는 개수만큼 원하는 값으로 변경

 

 //bits : 변경할 비트 수, ex) 3개(111) = 5, 2개(11) = 3, 1개(1) = 1
//val : 변경할 값
//shift : 변경할 비트 위치
void BitSet(int *a, int bits, int val, int shift)
{
 //변경할 비트만큼 Left Shfit하고 반전시키고 값과 &연산을하여
 //변경할 비트를 0으로 변경한다.
 *a = *a & (~(bits<<shift));
 //0으로 변경된 비트 자리에 | 연산을 통해 원하는 값을 SET한다.
 *a = *a | (val<<shift);
}
#define BITSET(a, bits, val, shift) *a = (*a & (~(bits<<shift))) | (val<<shift)

 

 

 

 

원하는 위치 토글

 

 //shift : 변경할 비트 위치
void BitToggle(int *a, int shift)
{
 //원하는 위치를 토글한다.
 *a ^= (0x1<<shift);
}
#define BIT_TOGGLE_SET(a, shift) *a ^= (0x1<<shift)

 

 

 

1.0.3.0 버젼에서는

 

초기화 기능을 추가하였습니다.

 

초기화 버튼 클리기 왼쪽의 Text들이 초기화됩니다.

 

 

 

 

 

 

 

빈줄 제거 프로그램v1.0.3.0.exe

 

 

 

 

 

빈줄 제거 프로그램 v1.0.1.1 바로가기

.NET 개발 표준 가이드는 Microsoft 고객들을 위해 배포되었습니다. 본 문서는 상업적 목적으로 재 활용될 수 없으며, 기업 내부에서 사용할 목적으로는 자유롭게 수정, 보완하실 수 있습니다. .NET 개발 표준 가이드 문서에 대한 모든 권리는 마이크로소프트()에 있으며, 이 개발 표준 가이드에 대한 기술지원은 제공되지 않습니다.

 

 

NET 표준개발가이드.doc

 

프로그램의 유연성 향상에 도움이되는 좋은 자료이다.

 

왜 Interface를 이용해 설계를 해야하는지,

 

IoC(Inversion of Control) 컨테이너를 왜 사용하고 어떻게 구성해야하는지 설명하고 있다.

 

[MSDN 매거진 - 응용 프로그램 유연성 향상을 위한 소프트웨어 종속성 제어 바로가기]

 

 

 

Winform의 MVP, WPF의 MVVM 패턴들은 View와 Business Logic 및 Model 간에 결합도를 낮추기 위해 사용한다.

 

결합도를 낮춘다는 것은 유지보수에 매우 유리하다.

 

이와 같은 장점이 있긴하지만 내 생각에는 MVC, MVP, MVVM 등등의 패턴을 사용하는 중요한 이유는 단위테스트 때문이라고 생각한다.

 

 

이번 프로젝트는 Winform에 MVP 패턴을 적용하여 구현하였지만..

 

".NET 예제로 배우는 단위테스트" 라는 책을 읽으며 내가 작성한 MVP 패턴은 잘못된것임을 알았다.

 

물론, View, Presenter, Model의 역할을 분리함으로써 코드의 가독성과 유연성은 증가 되었지만,

 

몇몇 Model의 Singleton 패턴으로 인한 단위 테스트가 어려운 점이 잘못된 설계라고 생각이 든다.

 

개발 완료 후 열심히 단위테스트를 익히는 도중, 아쉽게도 회사의 경영 악화로 인한 신규 프로젝트 중지로 인해

 

다음달 퇴사를 하게 되었다.

 

이번 프로젝트에는 단위 테스트는 꼭 적용하고 싶었는데... 아쉬움이 많다.

 

아직 퇴사하기까지 1달이 남았으니 단위 테스트를 어떻게 적용해야할지 좀 더 고민해봐야겠다.

 

단위 테스트 작성(효과적인 단위테스트)이 익숙해 진 후의 프로젝트는 TDD(Test Driven Development)로 진행하고 싶다.

 

물론, 이직한 회사의 팀장님이 관심이 있어야겠지만...

 

 

경영악화로 인한 퇴사인 만큼 안정적인 회사로 이직하고싶다. 아~ 좋은 자료 올리려다보니 주저리주저리 적었다.

 

아직 나이도 어리고 2년 경력인 만큼 열심히 하다보면 좋은 날이 있지 않을까?

 

 

 

 

 

혹시위의 링크가 잘릴까봐 기사의 내용을 아래에 붙여넣었다.

 

문제가 될시 삭제하겠습니다(__)

 

 

 

응용 프로그램 유연성 향상을 위한 소프트웨어 종속성 제어
James Kovacs


이 기사에서 다루는 내용:
  • 밀접하게 결합된 아키텍처의 문제점
  • 테스트 및 종속성 문제
  • 종속성 반전
  • 종속성 주입
이 기사에서 사용하는 기술:
.NET Framework
코드 다운로드 위치: DependencyInjection2008_03.exe (5408 KB)
Browse the Code Online

느슨하게 결합된 설계를 위해 애쓰는 것을 바람직하다고 생각하는 사람은 거의 없을 것입니다. 그런데 안타깝게도 일반적으로 설계되는 소프트웨어들은 원래의 의도보다 훨씬 더 밀접하게 결합되어 있습니다. 설계가 밀접하게 결합되어 있는지 여부는 어떻게 확인할까요? NDepend와 같은 정적 분석 도구로 종속성을 분석할 수도 있지만 응용 프로그램의 결합 상태를 알아보는 가장 쉬운 방법은 클래스 중 하나를 격리된 상태로 인스턴스화하는 것입니다.
비즈니스 계층에서 InvoiceService와 같은 클래스를 하나 선택하고 해당 코드를 새 콘솔 프로젝트에 복사합니다. 이 코드를 컴파일합니다. 아마 Invoice, InvoiceValidator 등의 일부 종속성이 손실될 것입니다. 해당 클래스를 콘솔 프로젝트에 복사한 다음 다시 컴파일합니다. 이번에는 다른 손실된 클래스가 발견될 것입니다. 결국 코드베이스의 상당 부분을 새 프로젝트에 추가해야 컴파일할 수 있게 됩니다. 마치 풀린 실 한 가닥을 잡아당기면 스웨터 한 벌이 다 풀려버리는 것과 같습니다. 설계에서 모든 클래스는 다른 모든 클래스와 직간접적으로 결합되어 있습니다. 이러한 시스템에서는 클래스를 하나만 변경해도 나머지 시스템 전체에 영향을 미치기 때문에 시스템을 변경하기가 매우 어렵습니다.
요점은 결합을 완전히 방지하자는 것이 아니며, 그렇게 할 수도 없습니다. 예를 들어 보겠습니다.
string name = "James Kovacs";
이 예에서는 코드를 Microsoft® .NET Framework의 System.String 클래스와 결합했습니다. 이러한 결합이 잘못된 것일까요? 아니라고 생각합니다. System.String 클래스가 바람직하지 않은 방식으로 변경될 가능성은 극히 미미하고, System.String과 상호 작용하는 방식을 수정해야 할만큼 요구 사항이 변경될 가능성도 마찬가지로 낮습니다. 따라서 이 결합에서 문제로 생각되는 부분은 없습니다. 여기서 이야기하고자 하는 요지는 결합을 제거하자는 것이 아니라 신중을 기해 현명하게 결합을 선택해야 한다는 것입니다.
많은 응용 프로그램의 데이터 계층에 자주 사용되는 코드 예를 하나 더 살펴보겠습니다.
SqlConnection conn = new SqlConnection(connectionString);
또는 다음과 같은 예도 가능합니다.
XmlDocument settings = new XmlDocument();
settings.Load("Settings.xml");
데이터 계층이 SQL Server®하고만 통신할 것이라고, 또는 항상 Settings.xml이라는 XML 문서에서만 응용 프로그램 설정을 로드할 것이라고 얼만큼 확신할 수 있습니까? 우리는 지금 무한하게 확장할 수 있지만 엄청나게 복잡하고 실용성이 없는 제네릭 프레임워크를 구축하려는 것이 아닙니다. 논점은 가역성(reversibility)입니다. 결정된 설계에 대한 생각을 얼만큼 쉽게 바꿀 수 있습니까? 변경에 잘 대응하는 응용 프로그램 아키텍처를 보유하고 있습니까?
필자가 이렇게 변경에 신경을 쓰는 이유는 무엇일까요? 사실상 이 업계에서 변하지 않는 유일한 것이 바로 변화 그 자체이기 때문입니다. 요구 사항이 변하고, 기술이 변하고, 개발자가 변하고, 비즈니스가 변합니다. 여러분은 이러한 변화에 대응해야 하는 위치에 있습니까? 느슨하게 결합된 설계를 만들면 소프트웨어는 필연적인, 그리고 많은 경우 예고 없이 발생하는 변화에 보다 효과적으로 대응할 수 있습니다.


내부 종속성 문제
일반적인 계층형 응용 프로그램 아키텍처에서 볼 수 있는 결합도가 매우 높은 전형적인 설계를 살펴보도록 하겠습니다(그림 1 참조). 간단한 계층 구조에서는 UI 계층이 서비스(또는 비즈니스) 계층과 통신하고, 이 서비스 계층이 리포지토리(또는 데이터) 계층과 통신합니다. 이러한 계층 간의 종속성은 동일하게 아래로 흐릅니다. 따라서 리포지토리 계층은 서비스 계층을 인식하지 못하고 서비스 계층은 UI 계층을 인식하지 못합니다.
그림 1 일반적인 계층형 아키텍처 
프레젠테이션 계층이나 워크플로 계층과 같이 몇 개의 계층이 더 있기도 하지만 계층에서 하위 계층만 인식하는 패턴에는 변함이 없습니다. 계층을 일관된 책임 클러스터로 구현하는 것은 바람직한 설계 기법입니다. 하지만 상위 계층을 하위 계층과 직접 결합하면 결합이 증가하고 응용 프로그램 테스트가 어려워집니다.
테스트 용이성에 대해서는 왜 신경을 써야 할까요? 테스트 용이성은 결합에 대한 좋은 지표이기 때문입니다. 테스트에서 클래스를 손쉽게 인스턴스화할 수 없다면 결합 문제가 있는 것입니다. 에를 들어 서비스 계층은 리포지토리 계층에 깊숙히 연관되며 종속적입니다. 서비스 계층은 리포지토리 계층과 분리해서 테스트할 수 없습니다. 실질적으로 이는 대부분의 테스트에서 기본 데이터베이스, 파일 시스템 또는 네트워크에 액세스함을 의미합니다. 결과적으로 낮은 테스트 속도, 높은 유지 관리 비용과 같은 다양한 문제가 발생합니다.
느린 테스트 테스트를 엄격하게 메모리 내에서만 실행할 수 있다면 테스트당 시간은 밀리초 범위가 됩니다. 테스트에서 데이터베이스, 파일 시스템, 네트워크 등의 외부 리소스에 액세스하는 경우 테스트당 시간은 대부분 100밀리초 이상으로 늘어납니다. 면밀한 테스트를 거치는 일반적인 프로젝트의 경우 수백 또는 수천 번의 테스트가 실행된다는 점을 고려하면 이는 테스트 시간이 몇 초냐, 아니면 몇 분 또는 몇 시간이냐의 차이를 의미합니다.
잘못된 오류 격리 데이터 계층 구성 요소의 오류는 많은 경우 상위 계층 구성 요소의 테스트 실패로 이어집니다. 몇 개의 테스트만 실패한다면 문제를 간단히 해결할 수 있겠지만, 그보다는 수백 개의 테스트가 실패할 가능성이 높으므로 문제를 찾아내기도 어렵고 시간도 많이 걸리게 됩니다.
높은 유지 관리 비용 대부분의 테스트에는 일종의 초기 데이터가 필요합니다. 이러한 테스트에서 데이터베이스에 액세스하는 경우 각 테스트 전에 데이터베이스가 알려진 상태인지 확인해야 합니다. 또한 각 테스트의 초기 데이터가 다른 테스트의 초기 데이터에 대해 독립적인지 확인하지 않으면 테스트 순서 문제가 발생하여 순서에 맞지 않게 실행할 경우 특정 테스트가 실패할 수 있습니다. 데이터베이스를 알려진 정상 상태로 유지 관리하는 작업에는 많은 시간이 소모되며 오류가 발생하기도 쉽습니다.
또한 하위 계층의 구현을 변경해야 할 경우 하위 계층에 대한 암시적/명시적 종속성으로 인해 상위 계층까지 변경해야 하는 경우가 많습니다. 응용 프로그램을 계층화했지만 느슨한 결합은 달성하지 못한 것입니다.
구체적인 예로, 송장을 받는 서비스를 살펴보겠습니다(그림 2 참조). InvoiceService.Submit는 송장 전송을 수락하기 위해 클래스의 생성자에 의해 만들어지는 AuthorizationService, InvoiceValidator 및 InvoiceRepository에 의존합니다. 이러한 구체적인 종속성 없이 InvoiceService에 대해 단위 테스트를 실행할 수는 없습니다. 즉, 단위 테스트를 실행하기 전에 InvoiceRepository가 새 송장을 삽입할 때 데이터베이스에서 기본 키 또는 고유 키 위반이 발생하거나 InvoiceValidator가 유효성 검사 실패를 보고하지 않도록 데이터베이스 상태를 확인해야 합니다. 또한 AuthorizationService가 "전송" 작업을 허용하도록 단위 테스트를 실행하는 사용자에게 올바른 사용 권한이 있는지도 확인해야 합니다.
public class InvoiceService {
  private readonly AuthorizationService authoriazationService;
  private readonly InvoiceValidator invoiceValidator;
  private readonly InvoiceRepository invoiceRepository;
 
  public InvoiceService() {
    authoriazationService = new AuthorizationService();
    invoiceValidator = new InvoiceValidator();
    invoiceRepository = new InvoiceRepository();
  }
 
  public ValidationResults Submit(Invoice invoice) {
    ValidationResults results;
    CheckPermissions(invoice, InvoiceAction.Submit);
    results = ValidateInvoice(invoice);
    SaveInvoice(invoice);
    return results;
  }
 
  private void CheckPermissions(Invoice invoice, InvoiceAction action) {
    if(authoriazationService.IsActionAllowed(invoice, action) == false) {
      throw new SecurityException(
        "Insufficient permissions to submit this invoice");
    }
  }
 
  private ValidationResults ValidateInvoice(Invoice invoice) {
    return invoiceValidator.Validate(invoice);
  }
 
  private void SaveInvoice(Invoice invoice) {
    invoiceRepository.Save(invoice);
  }
}

어려운 일이지요. 코드 오류든 데이터 오류든 이러한 종속 구성 요소 중에서 문제가 발생하면 InvoiceService 테스트가 예기치 않게 실패합니다. 테스트에 통과하더라도 데이터베이스에 올바른 데이터를 설정하고 테스트를 실행하고 테스트 시에 생성된 데이터를 정리하기까지 총 실행 시간은 수백 밀리초가 됩니다. 테스트를 일괄 처리로 그룹화한 후 일괄 처리 전후에 스크립트를 실행하는 방법으로 설정 및 정리에 소요되는 비용을 상쇄하더라도 여전히 메모리 내에서 테스트를 실행할 때보다 훨씬 더 많은 시간이 걸립니다.
더 모호한 문제도 있습니다. 예를 들어 InvoiceRepository에 감사 지원을 추가하려면 AuditingInvoiceRepository를 만들거나 InvoiceRepository 자체를 수정할 수밖에 없습니다. InvoiceService와 하위 구성 요소 간의 결합 때문에 시스템에 새 기능을 도입할 때 취할 수 있는 방법이 많지 않습니다.


종속성 반전
다음과 같이 구체적인 클래스가 아니라 인터페이스를 통해 상호 작용하면 하위 구성 요소 종속성으로부터 상위 구성 요소 InvoiceService를 분리할 수 있습니다.
public class InvoiceService : IInvoiceService {
    private readonly IAuthorizationService authService;
    private readonly IInvoiceValidator invoiceValidator;
    private readonly IInvoiceRepository invoiceRepository;
    ...
}
인터페이스 또는 추상 기반 클래스를 사용하도록 하는 이 간단한 변경은 모든 종속성에 대해 대체 구현을 사용할 수 있음을 의미합니다. InvoiceRepository 대신 AuditingInvoiceRepository를 만들 수 있습니다(AuditingInvoiceRepository가 IInvoiceRepository를 구현한다는 가정 하에). 또한 이는 테스트 시에 Fake나 Mock를 대체할 수 있음을 의미합니다. 이 설계 기법을 계약으로의 프로그래밍(programming to contract)이라고 합니다.
필자가 상위 구성 요소와 하위 구성 요소의 분리에 적용하는 원칙은 종속성 반전 원칙입니다. Robert C. Martin이 이 주제에 대한 자신의 칼럼(objectmentor.com/resources/articles/dip.pdf)에서 설명했듯이 "상위 모듈은 하위 모듈에 종속되어서는 안 되고, 두 가지 모두 추상화에 의존해야 합니다."
이 경우 InvoiceService와 InvoiceRepository는 이제 IInvoiceRepository가 제공하는 추상화에 의존합니다. 그러나 문제가 완전히 해결된 것이 아니라 다른 곳으로 이동했을 뿐입니다. 구체적인 구현은 인터페이스에만 의존하지만 구체적인 클래스가 서로를 어떻게 "찾느냐"의 문제가 남아 있습니다.
InvoiceService에는 여전히 종속성의 구체적인 구현이 필요합니다. 단순히 이러한 종속성을 InvoiceService의 생성자에서 인스턴스화할 수 있지만 전보다 별로 나아질 것이 없습니다. AuditingInvoiceRepository를 사용하려면 여전히 InvoiceService를 수정하여 AuditingInvoiceRepository를 인스턴스화해야 합니다. 또한 AuditingInvoiceRepository를 대신 인스턴스화하려면 IInvoiceRepository에 의존하는 모든 클래스를 수정해야 합니다. 전역에 걸쳐 InvoiceRepository를 AuditingInvoiceRepository로 교체하는 손쉬운 방법은 없습니다.
한 가지 해결책은 팩토리를 사용하여 IInvoiceRepository 인스턴스를 만드는 것입니다. 이렇게 하면 중앙 위치에서 팩토리 메서드만 바꾸어 AuditingInvoiceRepository로 전환할 수 있습니다. 이 기법은 서비스 로케이션이라고도 하며, 인스턴스를 관리하는 팩토리 클래스를 서비스 로케이터라고 합니다.
public InvoiceService() {
  this.authorizationService = 
    ServiceLocator.Find<IAuthorizationService>();
  this.invoiceValidator = ServiceLocator.Find<IInvoiceValidator>();
  this.invoiceRepository = ServiceLocator.Find<IInvoiceRepository>();
} 
ServiceLocator 내의 기능은 구성 파일 또는 데이터베이스에서 읽은 데이터를 기반으로 하거나 코드에 직접 연결될 수 있습니다. 어떤 방법을 사용하든 종속성에 대한 중앙 집중식 개체 생성이 가능합니다.
격리된 구성 요소에 대한 단위 테스트는 실제 구현 개체 대신 Fake 또는 Mock 개체를 사용하여 서비스 로케이터를 구성하는 방법으로 실행할 수 있습니다. 예를 들어 테스트 중에 ServiceLocator.Find<IInvoiceRepository>는 FakeInvoiceRepository를 반환할 수 있습니다. FakeInvoiceRepository는 저장 시 송장에 알려진 기본 키를 할당하지만 실제로 송장을 데이터베이스에 저장하지는 않습니다. 복잡한 데이터베이스 설치 및 해체를 없애고 Fake 종속성으로부터 알려진 데이터를 반환할 수 있습니다. 자세한 내용은 "종속성 가장은 현명한 방법인가?" 보충 기사를 참조하십시오.
그러나 서비스 로케이션에는 몇 가지 단점도 있습니다. 우선, 종속성이 상위 클래스에 숨겨져 있습니다. 공개 서명을 통해서는 InvoiceService가 AuthorizationService, InvoiceValidator, InvoiceRepository 중 어디에 종속되는지 알 수 없고, 확인하려면 코드를 검사하는 수밖에 없습니다.
같은 인터페이스에 대해 다른 구체적 형식을 제공해야 하는 경우 오버로드된 Find 메서드에 의지해야 합니다. 이를 위해서는 팩토리 클래스를 구현할 때 대체 형식이 필요한지 여부를 결정해야 합니다. 예를 들어 특정 IInvoiceRepository 요청을 위해 배포 시에 AuditingInvoiceRepository를 대체하도록 ServiceLocator를 다시 구성할 수 없습니다. 하지만 이러한 단점에도 불구하고 서비스 로케이션은 이해하기 쉽고 종속성을 하드 코딩하는 것보다 나은 방법입니다.


종속성 주입
종속성 가장은 현명한 방법인가?
종속성을 가장하는 데 따른 위험성이 없는지 궁금할 것입니다. 가양성 결과가 발생하지는 않을까요? 실제 InvoiceRepository와 같은 종속성이 올바르게 작동하는지를 확인하는 테스트가 이미 있어야 합니다. 이러한 테스트에서는 실제 데이터베이스와 통신하여 InvoiceRepository가 올바르게 작동하는지 확인해야 합니다.
InvoiceRepository.Save가 제대로 작동하고 있음을 아는 상태에서 InvoiceRepository에 종속적인 모든 테스트 항목에 대해 일일이 다시 테스트하는 이유는 무엇일까요? 데이터베이스에 연결함에 따라 상위 테스트 속도가 느려지고, InvoiceRepository에 문제가 있다면 InvoiceRepository 테스트는 물론 InvoiceService 테스트를 비롯하여 InvoiceRepository에 종속적인 다른 모든 구성 요소의 테스트까지 실패하게 됩니다.
InvoiceService 테스트는 실패했지만 InvoiceRepository 테스트는 성공했다면 InvoiceRepository에 대한 테스트가 누락된 것입니다. 이러한 종류의 결함은 구성 요소를 구체적인 종속성과 함께 테스트하는 통합 테스트를 통해 보다 효과적으로 찾을 수 있습니다. 이러한 방법은 속도는 더 느리지만 Fake/Mock 종속성을 사용하는 단위 테스트보다 실행 횟수는 대부분 더 적습니다.
이제 단위 테스트를 통과했으므로 InvoiceRepository가 작동하는 것으로 가정하면 두 가지 중 하나를 선택할 수 있습니다. 첫째, 데이터베이스의 데이터가 올바른지 확인하는 복잡한 스크립트를 작성 및 유지 관리하여 InvoiceRepository가 각 InvoiceService 테스트에서 예상된 데이터를 반환하도록 할 수 있습니다. 둘째, 예상된 데이터를 반환하는 Fake 또는 Mock InvoiceRepository 구현을 만들 수 있습니다. 두 번째 방법이 훨씬 더 쉽고 실제 환경에서 잘 작동합니다.

상위 구성 요소를 단위 테스트할 때는 종속성에 대한 Fake 또는 Mock 구현을 제공해야 합니다. 그러나 Fake 또는 Mock를 사용하여 서비스 로케이터를 구성한 후 상위 구성 요소가 이를 조회하도록 하는 대신 매개 변수화된 생성자를 통해 상위 구성 요소에 종속성을 직접 전달할 수 있습니다. 이 기법을 종속성 주입이라고 합니다. 그림 3에서 예를 볼 수 있습니다.
[Test]
public void CanSubmitNewInvoice() {
  Invoice invoice = new Invoice();
  ValidationResults validationResults = new ValidationResults();
  IAuthorizationService authorizationService = 
    mockery.CreateMock<IAuthorizationService>();
  IInvoiceValidator invoiceValidator = 
    mockery.CreateMock<IInvoiceValidator>();
  IInvoiceRepository invoiceRepository = 
    mockery.CreateMock<IInvoiceRepository>();
 
  using(mockery.Record()) {
    Expect.Call(authorizationService.IsActionAllowed(
      invoice, InvoiceAction.Submit)).Return(true);

    Expect.Call(invoiceValidator.Validate(invoice))
      .Return(validationResults);
    invoiceRepository.Save(invoice);
  }
 
  using(mockery.Playback()) {
    IInvoiceService service = new InvoiceService(authorizationService, 
      invoiceValidator, invoiceRepository);
    service.Submit(invoice);
  }
}

이 예에서는 InvoiceService의 종속성에 대한 Mock 개체를 만든 후 이를 InvoiceService 생성자로 전달합니다. Mock 개체 프레임워크에 대한 자세한 내용은 Mark Seemann의 "Unit Testing: Test Double의 연속성 살펴보기"(msdn.microsoft.com/msdnmag/issues/07/09/MockTesting)를 참조하십시오. 요약하자면, 테스트 실행 후에 InvoiceService의 상태를 확인하는 대신 InvoiceService가 Mock와 상호 작용하는 방식을 정의함으로써 InvoiceService의 동작을 지정합니다.
종속성 주입을 사용하면 단위 테스트에 상위 구성 요소를 종속성과 함께 손쉽게 제공할 수 있습니다. 그러나 단위 테스트 외부, 즉 응용 프로그램 실행 중이나 통합 테스트에서 클래스의 종속성을 어떻게 찾을 것인지의 문제가 여전히 남습니다. UI 계층에서 서비스 계층을 종속성과 함께 제공하거나, 서비스 계층에서 리포지토리 계층을 종속성과 함께 제공할 것으로 기대하는 것은 어리석인 일입니다. 처음보다 훨씬 더 심각한 문제가 발생할 수 있습니다. 하지만 다음과 같이 UI 계층이 종속성과 함께 서비스 계층을 제공하는 역할을 한다고 가정해 보겠습니다.
// Somewhere in UI Layer
InvoiceSubmissionPresenter presenter = 
  new InvoiceSubmissionPresenter(
    new InvoiceService(
      new AuthorizationService(), 
      new InvoiceValidator(), 
      new InvoiceRepository()));
여기에서 볼 수 있듯이 UI가 자체의 종속성뿐만 아니라 데이터 계층에 이를 때까지 모든 종속성의 종속성까지 인식해야 합니다. 이는 물론 바람직한 시나리오가 아닙니다. 이러한 딜레마를 해결하는 가장 손쉬운 방법은 경제적인 종속성 주입이라는 기법을 활용하는 방법입니다.
경제적인 종속성 주입에서는 상위 구성 요소의 기본 생성자를 사용하여 종속성을 제공합니다.
public InvoiceService() :
  this(new AuthorizationService(), 
    new InvoiceValidator(), 
    new InvoiceRepository()) { }
가장 많이 오버로드된 생성자에 위임됨을 볼 수 있습니다. 이렇게 하면 인스턴스를 만드는 데 사용된 생성자에 관계없이 클래스의 초기화 논리가 동일하게 유지됩니다. 클래스는 기본 생성자를 통해서만 구체적인 종속성과 결합됩니다. 단위 테스트 중에 클래스의 종속성을 제공할 수 있게 해 주는 오버로드된 생성자가 있으므로 클래스는 테스트 가능 상태로 유지됩니다.


컨테이너
이제 종속성을 중앙에서 관리할 수 있는 IoC(제어 반전) 컨테이너에 대해 살펴보겠습니다. 실제 환경에서 컨테이너는 인터페이스 대 형식 구현의 복잡한 사전에 불과합니다. 가장 간단한 형태로 보면 IoC 컨테이너는 다른 이름의 서비스 로케이터일 뿐입니다. 서비스 로케이션 외에 컨테이너의 다른 여러 가지 역할에 대해서는 나중에 설명하도록 하겠습니다.
원래의 문제로 돌아와, InvoiceService를 해당 종속성의 구체적인 구현과 완전히 분리해야 합니다. 모든 소프트웨어 문제가 그렇듯이 이 문제 역시 다른 간접 계층을 추가하여 해결할 수 있습니다. 인터페이스를 구체적인 구현에 매핑하는 종속성 확인자 개념을 도입하는 것입니다. 그런 다음 인터페이스 T를 받아 해당 인터페이스를 구현하는 형식을 반환하는 제네릭 메서드를 사용합니다.
public interface IDependencyResolver {
    T Resolve<T>();
}
사전을 사용하여 인터페이스와 해당 인터페이스를 구현하는 개체 간의 매핑 정보를 저장하는 SimpleDependencyResolver를 구현해 보겠습니다. 처음에 사전을 채울 방법이 필요한데, 여기에는 Register<T>(object obj) 메서드가 사용됩니다(그림 4 참조). SimpleDependencyResolver의 작성자만 종속성을 등록하므로 Register 메서드는 IDependencyResolver 인터페이스에 있을 필요가 없습니다. 일반적으로 이 작업은 응용 프로그램 시작 시에 Main 메서드에서 호출되는 도우미 클래스에 의해 이루어집니다.
public class SimpleDependencyResolver : IDependencyResolver 
{
  private readonly Dictionary<Type, object> m_Types = 
    new Dictionary<Type, object>();
    
  public T Resolve<T>() {
    return (T)m_Types[typeof(T)];
  }

  public void Register<T>(object obj) {
    if(obj is T == false) {
      throw new InvalidOperationException(
        string.Format("The supplied instance does not implement {0}",
        typeof(T).FullName));
    }
    m_Types.Add(typeof(T), obj);            
  }
}

CompanyService는 어떻게 SimpleDependencyResolver를 찾아 해당 종속성을 찾을 수 있도록 할까요? 필요로 하는 모든 클래스에 IDependencyResolver를 전달할 수도 있지만 이렇게 하면 작업 부담이 금방 커집니다. 가장 손쉬운 해결책은 구성된 SimpleDependencyResolver 인스턴스를 전역에서 액세스 가능한 위치에 넣는 방법입니다. 이는 정적 게이트웨이 패턴을 사용하여 수행할 수 있습니다. 단일 항목 패턴을 사용할 수도 있지만 단일 항목은 테스트하기가 너무 어렵습니다. 본질적으로 전역 변수와 다를 바 없는 단일 항목은 테스트하기 어려운 밀접하게 결합된 코드가 발생하는 가장 큰 이유 중 하나입니다. 가능하면 사용하지 마십시오.
정적 게이트웨이에 대해 살펴보겠습니다. 정적 게이트웨이는 IoC로 칭하겠습니다. DependencyResolver라고 할 수도 있지만 IoC가 더 짧으니까요. IoC의 정적 메서드는 IDependencyResolver의 메서드와 일치합니다. 정적 클래스는 인터페이스를 구현할 수 없으므로 IoC는 IDependencyResolver를 구현하지 않습니다. 실제 IDependencyResolver를 받는 Initialize 메서드도 있습니다. IoC 정적 게이트웨이는 구성된 IDependencyResolver로 모든 Resolve<T> 요청을 전달하는 역할만 합니다.
public class IoC {
  private static IDependencyResolver s_Inner;

  public static void Initialize(IDependencyResolver resolver) {
    s_Inner = resolver;
  }

  public static T Resolve<T>() {
    return s_Inner.Resolve<T>();
  }
}
응용 프로그램을 시작하는 동안 구성된 SimpleDependencyResolver를 사용하여 IoC를 초기화합니다. 이제 기본 생성자에서 경제적인 종속성 주입을 IoC.Resolve로 대체할 수 있습니다.
public InvoiceService() :
  this(IoC.Resolve<IAuthorizationService>(), 
  IoC.Resolve<IInvoiceValidator>(), 
  IoC.Resolve<IInvoiceRepository>()) { }
내부 IDependencyResolver는 응용 프로그램 시작 후에 읽히기만 하고 업데이트되지는 않으므로 이 개체에 대한 액세스를 동기화할 필요는 없습니다.
IoC 클래스는 추가적인 장점도 제공합니다. 즉, 응용 프로그램에서 손상 방지 계층 역할을 합니다. 다른 IoC 컨테이너를 사용하려면 IDependencyResolver를 구현하는 어댑터만 구현하면 됩니다. 응용 프로그램 전반에 걸쳐 IoC가 광범위하게 사용되지만 특정 컨테이너에 결합되지는 않습니다.


완전한 IoC 컨테이너
SimpleDependencyResolver와 같은 단순한 IoC 컨테이너를 사용하여 느슨하게 결합된 구성 요소를 서로 연결할 수 있습니다. 그러나 이렇게 하면 완전한 IoC 컨테이너가 제공하는 다음과 같은 여러 가지 기능을 사용할 수 없습니다.
  • XML, 코드 또는 스크립트와 같은 폭넓은 구성 옵션
  • 단일 항목, 임시, 스레드별, 풀링 등의 수명 관리
  • 종속성 자동 연결
  • 새 기능을 연결하는 기능
이러한 각 기능을 자세히 살펴보도록 하겠습니다. 널리 사용되는 공개 소스 IoC인 Castle Windsor를 구체적인 예로 사용하겠습니다. 많은 컨테이너는 외부 XML 파일을 통해 구현이 가능합니다. 예를 들어 Windsor는 다음과 같이 구성할 수 있습니다.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <components>
    <component id="Foo"
      service="JamesKovacs.IoCArticle.IFoo, JamesKovacs.IoCArticle"
      type="JamesKovacs.IoCArticle.Foo, JamesKovacs.IoCArticle"/>
  </components>
</configuration>
XML 구성의 경우 변경 내용을 적용하려면 응용 프로그램을 다시 시작해야 하는 경우가 많지만 응용 프로그램을 다시 컴파일하지 않고도 수정할 수 있다는 장점이 있습니다. 물론 XML 구성은 복잡해지기 쉽고, 런타임까지 오류를 감지하지 못하며, 제네릭 형식이 익숙한 C# 제네릭 표기법 대신 CLR의 백틱(backtick) 표기법을 사용하여 선언된다는 단점도 있습니다(Company.Application.IValidatorOf<Invoice>가 Company.Application.IValidatorOf`1[[Company.Application.Invoice, Company.Application]], Company.Application으로 작성됨).
XML 외에 C#이나 다른 Microsoft .NET Framework 호환 언어를 사용하여 Windsor를 구성할 수 있습니다. 구성 코드를 별도의 어셈블리에 격리하면 구성 어셈블리를 다시 컴파일하고 응용 프로그램을 다시 시작하기만 하면 구성을 변경할 수 있습니다.
Windsor 구성은 Binsor를 사용하여 스크립팅할 수 있습니다. Binsor는 Windsor 구성을 위해 특별히 제작된 DSL(Domain-Specific Language)입니다. Binsor를 사용하면 구성 파일을 Boo로 작성할 수 있습니다. Boo는 정적 형식 CLR 언어로, 언어와 컴파일러의 확장성에 중점을 두었기 때문에 DSL 작성에 적합합니다. Binsor에서는 앞의 XML 구성 파일을 다음과 같이 다시 작성할 수 있습니다.
import JamesKovacs.IoCArticle
Component("Foo", IFoo, Foo)
Boo가 완전한 프로그래밍 언어이고, 따라서 XML 기반 구성과 마찬가지로 구성 요소 등록을 수동으로 추가할 필요 없이 Binsor를 사용하여 Windsor에 형식을 자동으로 등록할 수 있다는 사실을 알고 나면 더욱 흥미롭습니다.
import System.Reflection
serviceAssembly = Assembly.Load("JamesKovacs.IoCArticle.IoCContainer")
for type in serviceAssembly.GetTypes():  
  continue if type.IsInterface or type.IsAbstract or 
    type.GetInterfaces().Length == 0
  Component(type.FullName, type.GetInterfaces()[0], type)
Boo에 익숙하지 않더라도 코드의 목적은 쉽게 이해할 수 있습니다. JamesKovacs.IoCArticle.Services 네임스페이스에 새 서비스를 추가하기만 하면 해당 서비스가 서비스 인터페이스에 대한 기본 구현으로 자동 등록됩니다. 다음과 같은 클래스를 만든다고 가정해 보겠습니다.
public class AuthorizationService : IAuthorizationService {
   ...
}
다른 클래스에서 IAuthorizationService를 생성자의 매개 변수로 포함하여 IAuthorizationService에 대한 종속성을 선언할 경우 구성 파일에 해당 종속성을 명시적으로 지정하지 않아도 Binsor가 자동으로 연결합니다. Binsor에 대한 자세한 내용은 ayende.com/Blog/category/451.aspx를, Boo에 대한 자세한 내용은 boo.codehaus.org를 참조하십시오.


수명 관리
SimpleDependencyResolver는 항상 인터페이스에 대해 등록된 동일한 인스턴스를 반환하며, 이로써 해당 인스턴스는 사실상 단일 항목이 됩니다. 인스턴스가 아니라 구체적인 형식을 등록하도록 SimpleDependencyResolver를 수정할 수 있습니다. 그러면 다른 팩토리를 사용하여 구체적인 형식의 인스턴스를 만들 수 있습니다. 단일 항목 팩토리는 항상 동일한 인스턴스를 반환하고 임시 팩토리는 항상 새 인스턴스를 반환합니다. 스레드별 팩토리는 요청 스레드당 하나의 인스턴스를 유지합니다.
인스턴스 전략은 상상력만 있다면 무궁무진합니다. 이러한 부분이 바로 Windsor가 제공하는 것입니다. XML 구성 파일에 특성을 적용하면 특정한 구체적인 형식의 인스턴스를 만드는 데 사용되는 팩토리 형식을 변경할 수 있습니다. 기본적으로 Windsor는 단일 항목 인스턴스를 사용합니다. 컨테이너에서 IFoo가 요청될 때마다 새 Foo를 반환하려면 구성을 다음과 같이 변경하기만 하면 됩니다.
<component id="Foo"
  service="JamesKovacs.IoCArticle.IFoo, JamesKovacs.IoCArticle"
  type="JamesKovacs.IoCArticle.Foo, JamesKovacs.IoCArticle"
  lifestyle="transient"/>


종속성 자동 연결
종속성 자동 연결은 컨테이너가 요청된 형식의 종속성을 확인하고 개발자가 기본 생성자를 제공하지 않아도 해당 종속성을 자동으로 만드는 것을 의미합니다.
public InvoiceService(IAuthorizationService authorizationService,
  IInvoiceValidator invoiceValidator, 
  IInvoiceRepository invoiceRepository) {
  ...
}
클라이언트가 컨테이너에서 IInvoiceService를 요청하면 컨테이너는 구체적인 형식에는 IAuthorizationService, IInvoiceValidator 및 IInvoiceRepository의 구체적인 구현이 필요하다는 점을 인식합니다. 컨테이너는 적절한 구체적 형식을 조회하여 이러한 형식에 있는 종속성을 해결하고 형식을 생성합니다. 그런 다음 이러한 종속성을 사용하여 InvoiceService를 만듭니다. 자동 연결은 기본 생성자를 유지 관리할 필요성을 제거하므로 코드가 단순화되고 IoC 정적 게이트웨이에 대한 여러 클래스의 종속성이 제거됩니다.
구체적인 구현 대신 계약으로 코딩하고 컨테이너를 사용하면 아키텍처의 유연성과 변경에 대한 적응성을 크게 높일 수 있습니다. InvoiceRepository에 대해 구성 가능한 감사 로깅을 구현하려면 어떻게 해야 할까요? 밀접하게 결합된 아키텍처에서는 InvoiceRepository를 수정해야 합니다. 또한 감사 로깅의 사용 여부를 지정하기 위해 몇 가지 응용 프로그램 구성도 설정해야 합니다.
느슨하게 결합된 아키텍처에서는 더 나은 방법이 있을까요? IInvoiceRepository를 구현하는 AuditingInvoiceRepositoryAuditor를 구현할 수 있습니다. 이 감사자는 감사 기능만 구현하고 생성자에 제공되는 실제 InvoiceRepository로 위임합니다. 이러한 패턴을 데코레이터라고 합니다(그림 5 참조).
public class AuditingInvoiceRepository : IInvoiceRepository {
  private readonly IInvoiceRepository invoiceRepository;
  private readonly IAuditWriter auditWriter;

  public AuditingInvoiceRepository(IInvoiceRepository invoiceRepository, 
    IAuditWriter auditWriter) {
    this.invoiceRepository = invoiceRepository;
    this.auditWriter = auditWriter;
  }

  public void Save(Invoice invoice) {
    auditWriter.WriteEntry("Invoice was written by a user.");
    invoiceRepository.Save(invoice);
  }
}

감사를 활성화하려면 IInvoiceRepository에 대한 요청을 받을 때 AuditingInvoiceRepository가 지정된 InvoiceRepository를 반환하도록 컨테이너를 구성합니다. 클라이언트는 여전히 IInvoiceRepository와 상호 작용하므로 이에 대해 알지 못합니다. 이 방식에는 다음과 같은 여러 이점이 있습니다.
  1. InvoiceRepository가 수정되지 않으므로 코드가 손상될 염려가 없습니다.
  2. AuditingInvoiceRepository를 InvoiceRepository에 독립적으로 구현하고 테스트할 수 있습니다. 따라서 실제 데이터베이스가 있는지 여부에 관계없이 감사가 정상적으로 이루어집니다.
  3. InvoiceRepository의 복잡성을 높이지 않고 감사, 보안, 캐싱 등의 용도로 여러 데코레이터를 작성할 수 있습니다. 즉, 느슨하게 결합된 시스템의 데코레이터 방식은 새 기능을 추가할 때 그 확장성이 부각됩니다.
  4. 컨테이너가 유용한 응용 프로그램 확장 메커니즘을 제공합니다. AuditingInvoiceRepository를 InvoiceRepository 또는 IInvoiceRepository와 동일한 어셈블리에 구현할 필요가 없고, 구성 파일에서 참조되는 타사 어셈블리에 쉽게 구현할 수 있습니다.


변화를 위한 느슨한 결합
소프트웨어 아키텍처가 계층화된 경우에도 계층이 서로 밀접하게 결합되어 있으면 응용 프로그램 테스트나 평가에 방해가 될 수 있습니다. 그러나 설계를 분리할 수 있습니다. 종속성 반전과 종속성 주입을 사용하면 구체적인 구현이 아니라 계약으로 코딩하는 데 따른 이점을 얻을 수 있습니다. 컨트롤 컨테이너의 반전 개념을 도입하면 아키텍처의 유연성을 높일 수 있습니다. 결과적으로 여러분의 느슨하게 결합된 설계는 변화에 보다 잘 대응할 수 있게 됩니다.


James Kovacs는 다재다능한 독립 설계자, 개발자, 강사로 .NET Framework를 활용한 민첩한 개발 분야의 전문가이며 알베르타주 캘거리에 거주하고 있습니다. 또한 Solutions Architecture의 Microsoft MVP이기도 한 그는 하버드 대학에서 박사 학위를 취득했습니다. 문의 사항이 있으면 jkovacs@post.harvard.edu 또는 www.jameskovacs.com을 통해 연락하시기 바랍니다.

 

 

 

 

알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.
스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경 할 수 있다.

 

 

 

 

 

 

 

 

 

static void Main(string[] args)

        {

            Man badGuy = CreateBadGuy();

 

            Man superMan = CreateSuperMan();

 

            badGuy.Attack();

            superMan.Attack();

 

            Console.ReadKey();

        }

 

        private static Man CreateSuperMan()

        {

            SuperMan superMan = new SuperMan();

            superMan.AttackBehavior = new SuperManAttackBehavior();

            return superMan;

        }

 

        private static Man CreateBadGuy()

        {

            BadGuy badGuy = new BadGuy();

            badGuy.AttackBehavior = new BadGuyBehavior();

            return badGuy;

        }

  

 

 

 

 

AboutStrategyPattern.zip

 

2013년 10월5일 오후 3시에 대학로 상상아트홀 화이트관으로

 

친구 커플과 총 4명이 만두와 깔창이라는 연극을 보러갔습니다.

 

 

 

 

 

 

 

만두와 깔창 연극은

 

김만두 역할의 김헌근님, 유깔창 역할의 유순웅님의 명품 연기로 진행됩니다^^

 

유순웅님은

 

영화 의뢰인에서 작은 구멍가게 할아버지로 뵜었어요^^

 

 

손가락 클릭 부탁드려요^^

 

 

 

 

 

 

 

 

 

 

연극보러 대학로 온것은 처음이었습니다...ㅠㅠ부끄럽군요

 

30분 전에 도착했더니 저희가 1등이더라구요 ㅎㅎ

 

직원분께서 친절하게 맨앞자리로 주셨어요슈퍼맨

 

 

 

 

 

 

 

기다리면서 팜플렛을 감상했어요~ㅎㅎ

 

 

 

 

 

 

 

 

 

 

상상아트홀 내부 모습이에요~ㅎㅎ

 

아담하고 깔끔해요^^ 

 

 

 

 

 

 

 

 

연극이 시작이 되고~

 

저희는 나열 맨앞에서 앉아있었습니다.

 

갑자기 유깔창님께서 가게 좀 봐달라고 하셔서

 

아르바이트생 역할로

 

무대에 나가서 땀을 삐질삐질 흘렸습니다...ㅋㅋㅋㅋ

 

연극 도중에 수고하셨다고 아래의 쩨쩨한 로맨스 초대권 2장을 주셨어요ㅎㅎ홧팅2

 

 

 

 

 

 

조그마한 소극장에서 관객들의 참여와 함께 진행이 되어서

 

엄청난 몰입감과 재미를 주더라구요^^

 

여자친구는 극중 주모가 되었었어요...ㅋㅋㅋ

 

제 커플 친구들은 돌쇠, 마누라 등등...ㅋㅋㅋ

 

1시간 30분이라는 시간이 너무 빠르게 흘러가더라구요ㅠㅠ

 

 

 

 

여자친구는 도중에 머리핀을 공짜로 얻었답니다..ㅎㅎㅎ

 

 

 

 

 

 

 

연극이 끝난 후 사진 촬영 시간이 있더라구요^^

 

직원분께서 친절하게 찍어주셨어요!!!

 

셀카

 

 

 

 

연극도 너무 재밌었고

 

직원분들도 너무 친절했고

 

너무너무 만족스러웠습니다^^

 

대학로로 자주 연극보러 가야겠어요~ㅎㅎㅎ

 

이상 후기를 마칩니다 ㅎㅎ

 

 

 

 

테스트할 Class의 접근자가 inernal일 경우에는 InternalsVisibleTo Attribute를 이용해서 처리하였습니다.

   - 다른 어셈블리에서 internal 접근하기

 

Rhino.Mocks의 목 또는 스텁 개체를 생성할 때 아래의 Exception이 발생하게 됩니다.

 

 

 

 

 GeneratorException

  - Type is not public, so a proxy cannot be generated. Type: LogAnalyzer.IWebService

 

 

 

 

 

공개키를 잘못 설정했나..확인해도 동일하게 동작이 안되서 구글링 하였습니다.

 

DynamicProxyGenAssembly2라는 Assembly명과 아래의 공개키를 작성하시면 완료됩니다.

(목 개체는 런타임 중 DynamicProxyGenAssembly2라는 임시 어셈블리에서 파생되기 때문?)

 

 

 

//해당 Assembly에는 internal 접근 가능

[assembly: InternalsVisibleTo("LogAnalyzerTests, PublicKey="+

    "002400000480000094000000060200000024000052534131000400000100010039118d1ce536a1" +

    "bc2b937c38ae739474d1397586518a1932e237d5243285bbb084200abb9a75e6aea5cead38d57d"+

    "5835744489bbc3bbac3f97fe9bb8def768aa73d34f937eb7e6f1feed54e59bdbaddd3a825e6a69"+

    "a40c2f18c830921782bcaa1b2123e12bca5eb2280b51d976da14786b5ab81e7e2d907792c1765c"+

    "6d9101cd")]

 

[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2," +

    "PublicKey=00240000048000009400000006020000002400005" +

    "25341310004000001000100c547cac37abd99c8db225ef2f6c8" +

    "a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7" +

    "852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0" +

    "a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15" +

    "605093924cceaf74c4861eff62abf69b9291ed0a340e113be11" +

    "e6a7d3113e92484cf7045cc7")]

 

 

 

참조 : Rhino Mocks Internal Members

'.Net > Unit Test' 카테고리의 다른 글

Visual Studio 2013 Unit Test Generator  (0) 2014.04.03

Singleton 구조를 생성하는 코드조각입니다.

 

 

-결과화면-

 

 

 

VS2010 기준으로 설명하겠습니다.

 

 

 

1. Singleton.snippet 파일을 다운로드 받습니다.

 

                 Singleton.snippet

 

 

2. VS2010 도구->코드 조각 관리자를 선택합니다.

 

 

 

 

 

3. My Code Snippets 항목을 선택하시면 위치가 나오게됩니다. 해당 위치에 다운로드 받은 Singleton.snippet 파일을 이동하시면

완료됩니다. 추가를 클릭하셔서 특정 위치의 폴더에 넣으실 수 있습니다.

 

 

 

 

4. ShutCut은 sgt로 설정하였습니다. 필드에 커서를 두신 후 sgt를 입력하시고 tab을 두번 누르시게되면 코드가 작성됩니다.

 

 

 

 

 

5. Class명, 감출 필드명, 노출할 속성명을 입력하시면 완료됩니다.

 

 

 

 

 

 

 

 

코드조각을 만드는 방법은 MSDN을 참조하세요.

http://msdn.microsoft.com/ko-kr/library/ms165394(v=vs.100).aspx

 

 

+ Recent posts