OOD:데이터 접근 루틴 작성 가이드 :  이번 포스트의 주제는 데이터 접근 루틴을 어떻게 작성하는 것이 좋은가 입니다.

Low Coupling 포스트에서 언급 했듯이 하이-레벨 모듈에서, 커플링을 완전히 제거할 수 없습니다. 커플링을 제거하는 유일한 방법은 소프트웨어 전체를 한 덩어리의 모듈로 만드는 것 뿐이죠. 이렇게 해도 내부적으로는 더욱 많은 커플링이 발생하게 될 뿐이죠.

그래서 여러개의 모듈로 나누게 되는데, 나누어진 모듈은 서로 통신을 해야 합니다. 상대방의 데이터 또는 인터페이스를 사용하는 것이죠. 노출 방법의 결정은 객체 설계 및 클래스 작성 초기 단계에 요구되어 지는데, 어떻게 노출 할 것이냐, 데이터 접근 방법을 설명하는 글 Martin Fowler 의 Data Access Routines 의 번역 입니다.


기본값은 Private !!!



요약 :


클래스 또는 객체가 노출하는 데이터의 종류는 두가지가 있습니다. 하나는 단일값(single-value) 다른 하나는 콜렉션(collection) 입니다. 단일값은 접근자(accessor)를 이용해서 노출 하는 것이 좋습니다. 접근의 일관성을 유지해 주며, 행위를 부가할 수 있는 장점이 있기 때문이죠. 콜렉션의 경우 복잡한데, 외부로부터 콜렉션 요소(element)가 변경 되는 것을 막아야 하기 때문이죠. 그 방법 으로는 세가지가 있으며 복사본(copy), 보호 프록시(protection proxy), 반복자(iterator) 순으로 추천합니다. 비어있는 객체(empty-object)를 생성하고 멤버에 값을 대입하는 것이 좋은지, 모든 인자를 받아들이는 객체 생성자를 제공하는 것이 좋은지에 대해 후자를 추천합니다. 주의사항으로 접근자(accessor)는 꼭 필요한 경에만 사용해야 하며, 데이터 접근이라는 행위가 다른 오브젝트가 해야 할 행위를 하지 않도록 각별히 신경 써야 합니다.

 

번역 :

Data Access Routines


November/December 2003 IEEE SOFTWARE
Martin Fowler

좋은 설계의 가장 중요한 요소중 하나는 모듈성(modularity) 입니다-시스템을 조각으로 분리 하는 것이며, 모듈의 수정이 전체 시스템에 영향을 주지 않도록 할 목적을 가집니다.
일찍이, David Parnas 는 모듈들은 서로 비밀을 감추어야 함을 관찰했습니다. 그렇게 하면 비밀이 변경되어도 파급을 피할 수 있습니다.

오늘날 비밀을 감추는 가장 일반적인 것은 자료구조(data structure)입니다. 데이터는 언제나 비공개이어야 한다는 것은  객체지향설계의 격언입니다. 하지만, 데이터를 감추는 아이디어는 객체의 범위를 넘어섭니다. 저는 기본적인 데이터 은닉의 규칙을 이야기 하려 합니다.
저의 모든 예제는 객체를 이용합니다.(저는 거의 객체 맹신도 이죠). 하지만, 이 논의는 non-OO 모듈에도 잘 들어맞습니다.

데이터 접근 절차(data access routines)에 대해 생각할 때, 저는 두개의 주요 경우를 발견했습니다:
단일값을 캡슐화 하는 경우(person's name 같은 것) 또는 컬렉션( 주문의 상품 목록 같은 것). 이 두개가 약간 다릅니다. 컬렉션의 경우가 더 복잡합니다. 그래서 단일 값 부터 시작하도록 하시죠.

그림1. Encapsulating a single-valued field with accessors (in Java).
class Person {
private String name;
public String getName() {return name;}
public void setName(String arg)
{name = arg;}
}


그림2. Altering the data structure in Figure 1 while (almost) preserving the interface (in Java).
class Person {
private String lastName;
private String firstName;
public String getName() {
return firstName + “ “ + lastName;
}
public void setName(String arg) {
String[] words = arg.split(“ “);
if (words.length != 2)
throw new IllegalArgumentException
(“name must have two words”);
firstName = words[0];
lastName = words[1];
}


그림3. Encapsulating a single-valued field with a property (in C#).
class Person {
public String Name;
public static void Main() {
Person martin = new Person();
martin.Name = “Martin Fowler”;
System.Console.WriteLine(martin.Name);
}
}


그림4. Altering the data structure in Figure 3 while (almost) preserving the interface (in C#).
class Person {
public String Name {
get {
return _firstName + “ “ + _lastName;
}
set {
String[] words = value.Split(‘ ‘);
if (words.Length != 2)
throw new Exception (“name can only
have two words”);
_firstName = words[0];
_lastName = words[1];
}
}
private String _firstName;
private String _lastName;


그림5. A class that doesn’t fully encapsulate its collection field (in Java).
class Album {
private List tracks =new ArrayList();
public List getTracks() {
return tracks;
}
}


그림6. Preserving collection encapsulation by copying (in Ruby).
class Album
def initialize
@tracks = []
end
def tracks
return @tracks.clone
end
end
그림7. Using a protection proxy to encapsulate a collection (in Java).
class Album {
private List tracks = new ArrayList();
public List getTracks() {
return Collections.unmodifiableList
(tracks);
}
}


그림8. Using an iterator to encapsulate a collection (in C#).
class Album {
private IList tracks = new ArrayList();
public IEnumerator TrackEnumerator {
get {return tracks.GetEnumerator();}
}


 

단일값 접근하기 Accessing single values

가장 단순한 데이터 캡슐화 접근법은 모듈 바깥에서 데이터를 접근 불가능하게 하는 것 입니다. 그리고, 클라이언트에게 데이터를 얻을 수 있는 접근자(accessor)를 제공하는 것입니다.
그래서 Java 에서 그림1 과 같은 코드를 발견할 수 있습니다. 우리는 다음과 같은 코드로 데이터를 관리 할 수 있습니다.
aPerson.setName("Martin Fowler");
어떤 것을 이렇게 캡슐화 하는 가치는 접근자 함수에 행위(behavior)를 추가하는 것에 있습니다. 성 과 이름을 따로 저장하는 클래스를 수정한다면 이 방법을 통하여 전체 이름을 한번에 얻을 수 있습니다. 그림1 의 인터페이스를 예약해 놓고도 그림2 와 같은 방법으로 처리 할 수있습니다.
클라이언트의 인터페이스를 수정하지 않고도 내부 데이터 구조를 변경할 수 있는 능력은 데이터 캡슐화의 엑기스 입니다.(인터페이스가 정말로 예약 되진 않습니다.  기존의 코드는 '이름'을 두개의 단어가 아닌 것을 수용하고, 새로운 코드는 거절합니다. 저는 예제를 위해 이 코드를 남겨 두었습니다.)

이것이 일반적인 접근법 일지라도, 이렇게 작업하는 것은 편리하지 않습니다. 캡슐화된 데이터가 공개 데이터인 것처럼 보이는 것이 더욱 좋습니다. 그러면 우리는 다음과 같이 코드를 작성할 수 있습니다.
aCustomer.name = "Martin Fowler".

어떤 언어에서는 이 방법을 쓸 수 없지만, 속성(properties) 개념(notion) 으로 동일하게 처리할 수있습니다. 예를 들어 그림3 처럼 간단한 접근자를 C# 코드로 작성할 수 있습니다. 행위 추가가 필요해지면 그림4 처럼 하면 됩니다. 클라이언트는 속성(property)을 여전히 같은 방법으로 접근할 수 있습니다.(리플렉션을 사용한다면 .NET 에서 약간 다릅니다.) 결과는 훨씬 더 자연스럽게 느껴집니다. 적어도 저에게는 말이죠.

 

컬렉션의 값 접근하기 Accessing a collection of values


단일값을 캡슐화 하는 것은 매우 쉽게 이해 되지만, 다중값은 동일하게 이해되지 않습니다. 거기에는 미묘함이 있으며, 언어차원의 지원이 단일값 때 보다  적어 보입니다.

단일값과 다중값에 같은 인터페이스 스타일을 사용하는 것은 초보자의 가장 일반적인 실수 입니다-그렇게 하는 것이 종종 캡슐화를 깨뜨린다는 것을 알아차리지 못합니다.
그림5 의 코드를 보자면, track 은 완전하게 캡슐화 되지 않았습니다: 클라이언트는 track의 목록에 접근할 수 있고, album을 모르고도 track을 추가 , 삭제 할 수 있습니다. 콜렉션에 대한 접근을 캡슐화 라고 한다면, 누군가 아이템을 추가,제거 하려는 것을 우리는 항상 알아야 합니다. 고로, 무방비의 자료구조를 전달 해서는 안됩니다.

저는 캡슐화 하면서도 값을 읽는 능력을 지원하는 세가지 방법을 알고 있습니다: 복사 (copying), 보호 프록시(protection proxy), 이터레이터 사용(iterator).

셋중 가장 간단한 방법은 자료구조에 기반한 복사값을 전달하는 것입니다, 그림6. 업데이트를 방지하도록 감싸진 콜렉션을 전달하는 보호 프록시(protection proxy) 입니다(그림 7). 이 두가지 기술은 클라이언트에 콜렉션을 전달 합니다. 복사의 경우 클라이언트는 복사본을 변경할 수도 있지만, album 저장소에는 영향을 미치지 못합니다. 보호 프록시의 경우 , 콜렉션을 변경하련느 시도는 런타임 에러를 발생 시킵니다(C++ 의 경우 const 키워드는 같은 기능을 합니다.)

클라이언트는 종종 콜렉션을 반복하고, 그 엘리먼트로 어떤 작업을 하고 싶어 합니다. 그래서 다른 좋은 전략은 itorator를 제공하는 것입니다(그림 8을 보세요). 가장 흔한 것이 외부 iterator 입니다. 사용자가 콜렉션의 끝을 테스트 할 수 있고, 현재 객체를 접근해서 향상된 작업을 할 수 있도록 말이죠.

세가지 방법은 각기 장단점을 가지고 있습니다. 저는 무슨 일이 벌어지는지 명확한 신호를 주는 보호 프록시(protection proxy)를 좋아합니다. 이게 쉽지 않다면 복사본 방법을 사용합니다. 대부분의 사람들은 빈번한 복사에 의한 성능 문제에 대해 걱정합니다. 하지만 실무에서는 사람들이 생각하는 것보다 훨신 작은 문제 입니다(참조를 복사하는 것이지 객체를 참조하는 것이 아니기 때문이죠).
iterator는 반복만을 허용합니다-콜렉션의 다른 기능은 제공하지 않죠(size, contains, 등등). 저는 C# iterator의 접근방법을 선택하지 않을 겁니다. foreach 구문을 사용할 수 없게 되어 있기 때문이죠.

저는 종종 콜력션을 전혀 보호하지 않는 사람들을 보게됩니다. 오로지 콜렉션을 기반한 수정을 회피하라는 관례에 의존하는 사람들이죠. 전에 말했다시피 이것은 진정한 캡슐화가 아닙니다. 콜렉션을 직접 수정하려는 사람들을 곤란한 버그로 이끌 뿐이죠. 그러나 대안은 그 가치보다 곤란한 상황을 만듭니다.

iterator를 사용한다면 멤버가 콜렉션을 호출하는 것에 대해 걱정하지 않아도 됩니다. 콜렉션의 캡슐화는 멤버가 콜렉션을 수정하지 못하게 하는 것이 아닙니다. 콜렉션의 멤버쉽 변경(역주:콜렉션이 가지고 있는 의미)을 보호하는 것이지요.
반면에 add 와 remove 와 같은 modify 메소드는 쉽게 지원됩니다. 아마 addAll 메소드를 본적이 있을 겁니다. 대입(Assignment)는 드물게 발생합니다; 객체를 생성 할때 또는 추가, 수정 행위가 없다면 좋은 방법이죠. 그런 행위가 있다면, 대입은 매우 복잡해 집니다.

이런 종류의 콜렉션 캡슐화가  발생하기 쉬운 지역은 직접 접근을 기대하는 framework를 사용할 때 입니다. GUI 프레임워크가 가장 일반적인 경우 입니다. 콜렉션 인터페이스에 데이터방인딩을 하는 것이죠. 여기에서 불쾌한 두가지 방법이 발생합니다 :
캡슐화를 유지하고 편리한 바인딩을 포기한다. 또는 바인딩을 사용한다. 바인딩을 사용한다면 여러분은 class 의 캡슐화를 깨뜨리거나 콜렉션에 특화된 하부 클래스나 이벤트 같은 지저분한 기술을 사용하게 됩니다. 이것을 일반화 하는 것도 매우 어렵습니다. GUI 플랫폼과 도구 와 트레이드-오프 하려는 경향이 발생하기 때문이죠. 다음번에는 이것에 대해 다룰 생각입니다.: 다음에 다룬다면 저의 결론을 http://martinfowler.com 게시 하겠습니다.

 

자체 캡슐화 Self-encapsulation

여러분이 클래스에 접근자를 사용하는 것과 관계 없이 발생하는 공통적인 이슈가 있습니다. 접근자를 사용하는 것은 원리적으로 클래스에서 지속적으로 부가적인 행위를 한다는 것이죠. 여러분이 하위클래스(subclass) 와 접근자의 행위를 오버라이드 하고 싶다면 일을 매우 쉽게 만들어 줍니다. 안좋은 점은 코드를 좀더 뒤죽박죽으로 만듭니다. 저는 어느쪽을 사용하라고 강하게 주장하지는 않습니다.


객체 생성  Object construction

사람들은 종종 빈 객체를 만들고 setter를 사용하는 것이 나은지, 여러개의 argument를 받아 들이는 생성자를 사용하는 것이 나은지 묻습니다. 일반적으로 완전하게 형식을 갖춘 객체를 추천합니다. 그래서 항상 여러개의 인자를 받는 생성자를 추천합니다. 이는 특히 불변(immutable) 데이터에 유용합니다. 여러분은 setter를 포함하지 않더라도 명확한 신호를 전달 할 수 있습니다. 어떤 경우에는 빈-객체(empty-object) 방법이 좋을 수도 있습니다. 이것은 그래서 철벽같은 규칙은 아닙니다.

객체 생성시에 콜렉션 필드를 비어있는 콜렉션으로 초기화 한다면 삶이 더욱 편해 진다는 것을 항상 느낍니다. 이 방법은 이후의 코드에서 null 검사를 하지 않아도 된다는 것을 뜻하지요.

 

접근자에 세심하라 Be wary of accessors

이제 가장 중요한 경고를 해야겠죠 : 꼭 필요할 때만 접근자를 제공하세요. 접근자는 한객체가 다른 객체로 부터 데이터를 꺼내고, 어떠한 행위를 하게 되는데, 원래 소유자가 직접 해야하는 일을 할때가 종종 발생합니다. 접근자만을 가지고 있는 모듈은 대부분 나쁩니다. 진정으로 데이터를 감추고 접근자를 전혀 제공하지 않는 모듈이 좋은 모듈입니다. 이는 거의 불가능하고, 특히 data-oriented 어플리케이션의 경우가 그렇습니다. 하지만, 접근자를 호출할 때면 데이터와 함께 행위도 이동되지 않았는지 자문(ask yourself)해야 합니다. 최고의 규칙은 어떤 상황이든 한 객체가 다른 객체의 접근자를 여러개 호출하지 않는지 의문을 갖는 것입니다.

 ## 끝.

저작자 표시
신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트

Protected Variation : '보호된 변수?, 변경? 변화? 으로 부터의 보호', 어렵습니다. 설계에 대한 용어는 매번 모호함으로 시작되는 듯합니다. 대충 private 으로 변수나 메소드의 접근을 외부로 부터 숨기는 것. 이라는 감이 옵니다. Variation을 어떤 단어로 대응해야 하는지는 Protected Variation 의 진짜 의미를 이해한 후에나 가능할 것 같습 합니다. Protected Variation 보다  정보은닉( Information Hiding) 이라는 용어가 훨씬 많이 사용됩니다. 정보은닉도 대표적으로 잘 못 해석되고 있는 용어이죠. 소프트웨어 설계에서 제대로 해석되는 용어가 존재하는지 의문이 들 정도입니다.

PV 는 David Parnas 가 소개한 용어로써, GRASP 의 아홉번째 항목으로 등장합니다. 변화로 부터 보호하는 패턴 이라는 것으로 말이죠.  Craig Larman 이 Protected Variation: The Importance of Being Closed (May/June 2001 IEEE SOFTWARE) 의 제목으로 PV를 설명하는 글을 기고 하였는데요, PV의 개념을 잘 이해시키는 글 입니다.

요약하자면 :
Protected Variation , Information Hiding, OCP는 같은 개념이다. 외부 모듈로 부터 '설계 결정' 을 감추어서 변화의 충격을 최소화 하는 것이 PV 이며, Data Encapsulation 과는 다른 의미이다. 유연성(flexibility)은 PV 로 얻어지는 산물중 하나이다.

Protected Variation 또는 Information Hiding 은  field 와 메소드를 private 으로 설정한다 정도의 지엽적이기 보다는 , 설계 결정 또는 설계 정보를 감추어서 외부의 변화로 부터 내부가 변경되지 않도록 보호한다 는 큰 개념. 으로 생각하면 이해하기 쉬울 듯 합니다.

보호는 그냥 되는 것이 아니다. 사소한 필드 부터 적절한 전략을 취해야 한다.



번역 :

변화로 부터의 보호 : 닫힘의 중요성.

Protected Variation: The Importance of Being Closed

by Craig Larman , May/June 2001 IEEE SOFTWARE

The Pattern Almanac 2000 (Addison-Wesley, 2000) 에는 소프트웨어와 관련된 패턴이 500여개 넘게 담겨져 있습니다. 호기심 많은 개발자는 프로그램을 작업할 시간이 없을 정도죠!
물론 이렇게 많은 패턴들은 개발자들이 오랜 시간동안 고민하고 논쟁해오던 주제와 원칙들을 단순화 할 목적을 가지고 있습니다.


Larry Constantine 의 coupling 과 cohesion 가이드라인 ("Structured Design," IBM Systems J., vol. 13, no. 2, 1974) 을 예로 들수 있듯이, 이 원칙들은 새로운 세대의 개발자와 아키텍트들이 무수히 많은 디자인 아이디어와 통합을 달성할 수 있도록  지속적으로 재 탐구 되어야 함이 당연 합니다.

Bertrand Meyer 가 Object-Oriented Software Construction (IEEE Press, 1988)에 기술한  Open–Closed Principle : 모듈은 확장(extention)과 적응(adaptation) 에는 열려(open) 있어야 하고, 클라이언트에 의한 변경 에는 닫혀 있으야 한다.
, 이 바로 그런 것입니다.

OCP는 본질적으로 Protected Variation pattern 과 동일 합니다. : 변경이 예측되는 지점을 판별하고 , 그것들을 위한 안정된 인터페이스를 생성하는 것.

Alistair Cockburn 은 PV("Prioritizing Forces in Software Design," Patterns Languages of Program Design, vol. 2, Addison-Wesley,1996)를 최초 기술할 때 OCP에 대해 알지 못했습니다.
David Parnas 의 OCP는 information hiding 을 뜻 하는 것이었습니다.("On the Criteria to Be Used in Decomposing Systems into Modules" Comm. ACM, vol.12, no. 2, Dec. 1972).

OCP 와 PV 는 많은 형태로 묘사되는 기반 설계 원칙을 형식화, 일반화 합니다. OCP 와 PV 는 작은 차이를 강조하는 것이 다를 뿐, 같은 원칙을 다르게 표현 한 것 이죠.-변화와 진화로 부터 기존의 코드와 디자인의 변경을 보호 하는 것.


저는 protected variation을 일반적 용어로 사용 하기를 제안합니다.개념을 짧고 명확하게 만드는 용어로 말이죠.

OCP 에서는 모듈에 포함된 소트프웨어의 모든 추상적인 요소를 칭하는 용어입니다. 메소드 , 클래스, 서브시스템, 어플리케이션, 기타 등등 말이죠.
또한 "X에 대해서 닫혀있다"라는 문구는 X의 변경이 클라이언트에 영향을 주지 않는다는 뜻입니다. 예를 들어 "클래스는 인스턴스 필드 정의에 대해 닫혀있다." PV 에서는 접근 관점으로의 광의적 표현으로 interface (java 나 COM의 인터페이스를 특정 하는 것은 아닙니다.) 라는 용어를 사용합니다.


정보은닉(information hiding)은 PV 이지 data encapsulation 이 아닙니다.


고전인 "On the Criteria To Be Used in Decomposing Systems Into Modules" 는 많이 참조 되지만 많이 읽혀지지는 않았습니다.
Parnas 는 거기에서 information hiding 을 소개 했습니다. 많은 사람들은 이 용어를 data encapsulation으로 잘 못 해석하고 있고, 몇몇 책에서는 유사어 개념으로 잘못 정의하고 있습니다.
Parnas 는 다른 모듈 내부의 어려운 것 또는 변경되기 쉬운 것들의 설계로 부터 정보은닉을 의미하려고 의도하였습니다.


정보은닉에 대한 설계 원칙 가이드로서 그의 논의를 빌자면 : 우리는 어려운 설계 결정의 목록으로써의 시작점, 변경되기 쉬운 설계 결정  대신에 제안하였습니다. 각각의 모듈은 다른 것에 대하여 그러한 결정을 감추도록 설계 되어야 합니다. Parnas의 정보은닉은 PV 나 OCP의 표현과 같은 원칙입니다. - 단순히 data encapsulation 이 아니라 설계 정보를 감추는 많은 기술중의 하나 입니다.
어쨋든, 이 용어는 data encapsulation 의 유사어 인 것 처럼 너무 광범위 하게 사용되어져서, 이제는 원래 의미로 사용 될 수 조차 없습니다.
Parnas의 PV 원칙에 대한 존경을 표시하자면 이 글을 "정보은닉의 중요성"이라고 불러야 마땅 합니다.
Dijkstra는 일찍이 이 원칙을 "THE" 프로젝트에서 암시했습니다. 하지만 Parnas 는 이것을 중요하게 생각하고 형태를 만들었죠. (Dijkstra, "The Structure of the 'THE' Multiprogramming System," Comm. ACM, 1968).


PV에 의해 동기를 얻은 메커니즘들 Mechanisms motivated by PV


PV는 수많은 프로그래밍 패턴 과 메커니즘들이 변화로 부터 설계의 유연성과 보호성을 제공하도록 동기를 부여한 근원 원칙 입니다.
여기에 몇가지 예가 있습니다. 데이터 캡슐화, 인터페이스, 다형성, 간접성, 표준성은 PV 메커니즘과 유사하며 PV가 동기를 부여한 것들 입니다.
브로커(broker)와 가상머신(virtual machine)과 같은 컴포넌트는 간접성의 복잡한 예 입니다.


통일된 접근 Uniform access

Ada, Eiffel, C# 과 같은 언어는 메소드와 필드에 대해 동일한 접근을 지원합니다. 예를 들어 aCircle.radius 는 radius() 를 실행합니다 :
클래스 정의에 기반한 public 필드에 대해 유동적인 메소드나 직접적인 참조를 의미.
여러분은 클라이언트 코드의 변경없이 public 필드가 메소드에 접근하도록 바꿀수 있습니다.


데이터-드리븐 디자인 Data-driven designs

데이터-드리븐 디자인은 코드, 값, 클래스 파일 경로, 클래스 이름 등 넓은 기술을 커버합니다. 다른 종류로는 외부 코드의 행위를 변경, 런타임에 "파라미터화" 를 위한 것, 객체-관계 매핑을 위한 meta-data, style sheet, 속성 파일 같은 것, 창 레이아웃을 읽는 것을 포함해서 말이죠.
시스템은 데이터, 메타데이터, 외부화된 변수의 선언을 읽는 행동 영향으로 부터 보호되어야 합니다.


서비스 룩업 Service lookup

서비스 룩업은 네이밍 서비스(예:JNDI), 서비스를 찾기 위한 트레이더(예: Jini) 기술을 포함합니다. 이 접근 방식은 클라이언트에게 안정적인 인터페이스를 제공함으로써 지역서비스에서의 변경으로부터 서비스를 찾는 것을 보호합니다. 이것은 데이터-드리븐 디자인의 특수한 경우 입니다.


인터프리터-드리븐 디자인 Interpreter-driven designs

인터프리터-드리븐 디자인은 외부의 소스,스크립트 또는 프로그램을 읽고 실행하는 규칙 자체를 해석하는 규칙을 포함합니다.
넷을 실행시키는 신경망 엔진. 이러한 접근은 외부 로직을 통하여 시스템을 변경시키는 파라미터화된 시스템을 가능케 합니다.
시스템은 외부 로직 표현의 변화로 부터 보호됩니다.

 

Reflective or metalevel designs

reflective, metalevel design 은 자바에서 bean 객체의 정보를 조회하기 위한 beans.Introspector 를 포함합니다.
bean 속성 X 를 얻기 위해 getter 메소드를 이용합니다(getXXX). 그리고 Method.invoke를 호출 합니다. introspection 과 metalanguage 서비스를 이용하는 Reflective는 외부 코드 변화의 충격으로 부터 시스템을 보호합니다.
우리는 이것 또한 데이터-드리븐 디자인이 포함하고 있는 특별한 경우라고 생각합니다.

Pick your battles

PV 어플리케이션의 예로써, 클라이언트에게 항공운항 논리를 설명하는 것은 머리아픈 일 입니다. 물류 비즈니스 로직을 지원하기 위한  변경이 매우 빈번하게  발생하기 때문이죠.
이런 변화에 대해 어떻게 시스템을 보호 할까요? PV를 지원하는 메커니즘(데이터 캡슐화, 인터페이스, 간접성)을 이용한 룰-기반 디자인(Rule-Based design)이 선택됩니다  :
룰 엔진은 시스템 소스 코드의 변경을 요구하지 않고도 룰을 업데이트 할 수 있게 합니다. 

Low coupling 과 protection

변화에 대항하는 것은 그냥 되는 것이 아닙니다. macro-architectural 레벨과 사소한 인스턴스 필드에서 부터 여러분은 설계에 적절한 전략을 취해야 합니다.
좋은 설계자는 변화와 불안전성이 존재하는 곳에는 PV 를 적용합니다. 그렇지 않으면 고생은 수포로 돌아가고, 복잡성은 증가할 것입니다. 또는 결함이 발생할 가능성이 높아지죠.
예를 들어 자바의 static public final field의 빈번한 사용으로 인해 놀랐던 경험을 회상합니다. 어떤 사람들은 잘 상상하지 못할 것입니다.
red, black, white등의 Color 로서의 static field 를 사용하는 것은 매우 안정합니다. 비슷하게 객체 순혈주의 입장에서 그것들을  private 또는 접근하는 메소드를 별도로 만들면 불안정하게 됩니다.
반대되는 경우로 유연성을 제공하기 위해 예쁜 스크립트 언어와 인터프리터를 추가한  pager-message-handling 시스템 아키텍트를 알고 있는데, 버전 향상 작업 동안에  복잡성과 비효율성을 이유로 스크립팅 부분은 제거 돼야만 했습니다.

 

현명한 PV 와 금강반야바라밀경 Judicious PV and the Diamond Sutra

low coupling 에 대한 Constantine의 설계 가이드라인은 설계의 핵심 원칙 이며, PV 는 여기에서 출발 했다고 말할 수 있습니다.
우리는 다음의 목표와 전락에 따라 우선순위를 정할 수 있습니다:
1. 우리는 시간과 비용을 줄이길 바랍니다. 새로운 결함을 줄이고, 중노동의 고통으로 부터 개발자를 구하길 희망합니다.
2. 이를 달성하기 위해 변경의 충격을 최소화 하도록 설계 해야 합니다.
3. 변경의 충격을 최소화 하기 위해 low coupling 이 달성되도록 목표해야 합니다.
4. low coupling을 달성하기 위해 PV로 설계 해야 합니다.

 Low coupling 과 PV 는 시간, 비용을 줄이기 위한 융합된 메커니즘 입니다. 이 목표를 달성하기 위한 불확실한 미래 방어 비용은 매우 높습니다.
highly couple 된 "깨지기 쉬운" 설계는 언제나 변화에 의한 압박으로 재작업이 요구됩니다. 진화 단계에서의 보호 엔지니어링 작업 비용은 단순한 설계를 재작업 하는 것보다 높습니다.


제 주장은 재작업과 깨지기 쉬운 설계를 변호할 목적이 아닙니다. 유연성이 필요하고, PV가 즉시 적용 가능하다면 PV를 적용하는 것이 옳습니다. 그러나, 불확실한 미래를 방어 하려거나, 재사용을 위해 이 전략을 사용하려는 것은 현명한 행동이 아닙니다. 풋내기 개발자는 깨지기 쉬운 설계를 하려는 경향이 있습니다. 예쁘고 일반화 시킨 유연성을 중간에 삽입하려 하죠(전혀 사용되지 않을 텐데도 말이죠).
전문가는 통찰력을 이용하여 단순하고, 깨지기 쉬운 디자인의 변경 비용 사이의 균형점을 찾습니다. 이는 잘 알려진 금강반야바라밀경을 수도하는 것과 같습니다:
선(zen, 禪) 을 수행하기 전에는 산은 산이요, 강물은 강물이더라.
선을 수행하는 동안에는 산은 더이상 산이 아니오, 강물은 더이상 강물이 아니다.
깨우친 후에는 또다시 산은 산이오, 강물은 강물이로다.

### 끝.


더 읽을 꺼리 :
Information Hiding : 이터너티 님의 블로그 포스트

The Difference Between Encapsulation and Information Hiding by stefanoricciardi

신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트

객체지향설계는 객체를 구별하고, 관계를 구성하는 표현하는 것입니다.  객체들의 관계를 일반화 시켜서는 연관(Association)이라고 하며, 4가지의 Multiplicity, Aggregation, Composition, Dependency 패턴으로 나눌수 있습니다.
 

역시 연관(Association)은 쉽지 않아~



이 4가지의 관계를 구분하는 것은 매우 어려운 일이 지만, 설계에 대한 문서들과 구현상에서 미묘한 차이가 이들 관계들이 가진 특성입니다.


Multiplicity

하나의 인스턴스  또는 인스턴스 컬렉션이 어떤 클래스의 하나의 인스턴스에 링크 되어 있는 것.
1, 0..1, *, 0..*, 1..* 의 관계를 가질 수 있습니다. (일대일, 일대영, 다대다 ....)

Multiplicity 예
public class Circle {
    private Point pointObj;
 
}
 
public class Point {
	private int X_POS = 0;
	private int Y_POS = 0;
 
        public int getXpos() {
            return this.X_POS;
        }
 
        public void setXpos(int xpos) {
            this.X_POS = xpos;
        }
 
        public int getYpos() {
            return this.Y_POS;
        }
 
        public void setYpos(int ypos) {
            this.Y_POS = ypos;
        }
}




Aggregation

A 객체가 B 객체를 포함한다. 메인 클래스는 포함하는 클래스 없이는 존재 할 수 없지만, 포함되는 클래스는 메인 클래스 없이도 존재할 수 있습니다.

main class
public class Car {
	Engine eng;
}

sub class
public class Engine {
	private int CC = 0;
	private int make = 0;
	private String ModelNo = null;
 
	public Engine(int cc,int make,String modelNo) {
		this.CC = cc;
		this.make = make;
		this.ModelNo =  modelNo;
	}
}



Composition

A 객체가 B 객체를 포함한다. B 객체의 생명은 A 객체에 의해 좌우된다.
포함하고 있는 하위 클래스에 의존하는 메인 클래스를 의미합니다. 메인 클래스는 하위 클래스 없이는 존재할 수 없고, 하위 클래스는 메인클래스 없이는 존재할 수 없는 관계.

Main Class
public class Circle {
    private Point pointObj;
 
    public Circle(int radius) {
        this.pointObj.setRadius(radius);
    }
 
    protected void finalize() throws Throwable {
        pointObj = null;
        super.finalize();
    }
}


Sub Class
public class Point {
	private int radius = 0;
 
        public int getRadius() {
            return this.radius;
        }
 
        public void setRadius(int radius) {
            this.radius = radius;
        }
}


 

Dependency

A 객체의 메소드에서 B 객체를 사용한다.
클래스의 속성으로 존재하는 것이 아닌, 메소드에서 포함한다.



객체의 생명시간에 대한 논의도 염두에 두어야 합니다.

Hybrid 님의 블로그 포스트: Association, Aggregation and Composition 에서 3가지를 구분하는 요소로서의 객체 생명시간 에 대한 논의 글이 참고할 만 합니다.

저도 사실 듣기만 했지 럼바우의 원서적은 본 적이 없어서 정확한 출처는 밝히기 어렵네요
다만 아래 링크의 OMT 표기법을 보면
http://students.cs.byu.edu/~pbiggs/survey.html
여기선 아예 집합화(aggregation)와 합성화(composition)의 구분 없이 모두 집합화로만 보며
그것이 필수적인 것인가 부가적인 것인가로 구분하고 있는 것 같고

제가 알기로도 집합화라고 해서 생명시간(lifetime) 자체가 주 객체와 별개로 살아남을 수는 없고
다만 그 생명시간이 주 객체와 얼마나 밀접한가에 따라 구분하는 것으로 알고 있습니다

제가 참고한 책은 'K교수의 객체지향 이야기' 252~254 페이지인데 여기선 이렇게 설명하고 있네요

- 부속품 관계를 집합화와 합성화로 구별하는 조건은 주 객체와 부속 객체간의 생명 시간이 얼마나 밀접하게 연관되어 있는가에 대한 것이다.
- 합성화의 경우 주객체 생성시 부속객체도 반드시 생성되어야 하며 주객체 소멸시 부속객체도 소멸되어야 한다. 즉, 합성화의 경우 주객체와 부속객체의 생명 시간은 정확히 일치한다.
- 생명시간의 관점에서 볼 때 집합화로 모델링된 부속객체와 주객체의 생명시간이 정확히 일치하지는 않는다. 그러나 집합화의 경우에도 주객체가 소멸되면 부속객체도 없어진다는 사실을 인식해야 한다.

이 책에선 사람의 몸에서 머리와 몸통은 필수적인 것으로 합성화에 비유하고, 팔이나 다리는 필요하지만 필수적이진 않다는 점에서 집합화에 비유하고 있습니다.

-- 댓글 중


참조 :
http://www.hiteshagrawal.com/uml/association-in-uml
http://www.hiteshagrawal.com/uml/aggregation-in-uml
http://www.hiteshagrawal.com/uml/composition-in-uml
신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트

커플링 (Coupling) 은 응집도(Cohesion)과 함께 소프트웨어 설계의 품질을 판단하는 척도 입니다. 매우 중요한 개념이지만 정확하게 설명하는 글을 찾기가 쉽지 않습니다. 

모든 설계에 관한 글에서 커플링을 느슨하게 하라고 말합니다. 느슨한 커플링 이더라도 너무 많은 커플링은 전체를 타이트 하게 만듭니다. 

 커플링이란 완벽하게 제거되야할 악의 축인 것일 까요?

아래의 포스트는 IEEE SOFTWARE 에 2001년   파울러가 기고한 Reducing Coupling 글의 번역한 것 입니다.

커플링이란 무엇이며, 커플링을 줄이는 방법은 무엇인지 마틴 파울러 로 부터 배우 봅니다.

요런게 커플링? (couple rings ?, coupling !)




번역 :

Reducing Coupling

by 마틴 파울러.
IEEE SOFTWARE J u l y / A u g u s t 2 0 0 1 , Reducing Coupling , by Martin Fowler


커플링(Coupling)은 응집력(Cohesion)과 함께 설계의 품질을 결정하는 가장 오래된 지표 중 하나 입니다. 구조적 설계 때 부터 그 유산은 사라지지 않았습니다. 저는 소프트웨어를 설계 할때 항상 염두에 둡니다. 커플링을 설명하는 방법이 여러개가 있으나 다음으로 요약됩니다: 어떤 모듈을 변경할때 다른 모듈의 변경이 요구된다면 커플링이 존재합니다. 한 측면에서 두개의 모듈이 비슷한 일을 한다면 다른 코드에 중복되기 쉽습니다. 이는 명확하게 중복의 예 입니다.  중복은 언제나 커플링을 수반합니다. 두개의 코드 조각이 명확한 관계를 맺고 있지 않기 때문에 중복 지점을 집어내기란 쉽지 않습니다.

커플링은 또 어떤 모듈이 다른 모듈의 코드를 사용할 때 발생합니다. 아마도 데이터를 접근하거나 함수를 호출할 때 말이죠. 이런 경우에는 코드 중복과는 다르게 명확하게 집어 낼 수 있습니다. 커플링이란 완전히 제거 해야 하는 요소로 대할 수 없습니다. 여러분은 하나의 프로그램을 여러개의 모듈로 나눌 수 있지만 모듈들은 어떠한 방법으로든 커뮤니케이트가 필요합니다. - 다른 측면으로, 다수의 프로그램을 가지고 있을 때, 커플링은 바람직 합니다. 커플링을 완전히 제거하려 한다면 모든 것을 하나에 집어 넣은 거대한 모듈만이 남게 될 것입니다. 그리고 거기에는 매우 많은 커플링이 존재하게 됩니다. - 단지 러그(rug)로 덮어서 감추어 둔 것일 뿐이죠.

커플링은 우리가 통제해야 할 대상인 것입니다. 그러면 어떻게 통제 할 까요? 모든 곳에서 걱정해야 할 대상일까요? 아니면 더 중요한 특정 위치가 있는 걸 까요? 어떤 요소가 나쁜 커플링이라고 생각되며, 용인되는 커플링이란 어떤 것일 까요?

의존에서 살펴보기

높은 레벨의 모듈에서의 커플링에 대해 생각해 보았습니다. 우리가 시스템을 12개 정도의 큰 모듈 조각으로 나누었을때 이것들을 어떻게 묶을 까요?  저는 coarser-gained 모듈에 집중하였습니다. 모든 커플링을 고려하는 것은 불가항력이기 때문이죠. 가장 큰 문제는 상위(upper level)에서의 커플링을 통제할 수 없을 때 라는 것입니다. 저는 여러개의 모듈이 서로 묶여 있다 하더라도 걱정하지 않습니다, 하지만 모듈간의 의존(dependency) 관계의 패턴을 찾습니다. 이때 다이어그램을 이용하는 것이 매우 유용합니다.

제가 의존(dependency) 이라는 용어를 사용할 때는 UML에서 정의 된 것을 의미합니다. 고로 UI 모듈이 도메인 모듈의 어떤 코드를 참조 한다면, UI 모듈이 도메인 모듈에 의존한다고 말 할 수 있습니다.- 함수 호출, 데이터 이용, 도메인 모듈에 정의된 타입을 이용하는 것 말이죠.
어떤 사람이 도메인 모듈을 변경한다면 UI 모델의 변경이 요구됩니다. 의존(dependency)은 단방향성 입니다. : UI 모듈은 항상 도메인 모듈을 의존합니다. 다른 방법은 존재하지 않습니다. 도메인 모듈 또한 UI 모듈을 의존 한다면 우리는 두번째 의존을 가지는 것입니다.

UML 의존은 전이 불가(non transitive)를 의미합니다. UI 모듈이 도메인 모듈을 의존하고, 도메인모듈이 데이터베이스 모듈을 의존 한다면, 우리는 UI 모듈이 데이터베이스 모듈을 의존한 다고 가정할 수 없습니다. 그렇게 가정한다면 우리는 UI 모듈과 데이터베이스 모델을 직접적인 부가 의존을 기술해야 합니다. 이 비전이성(nontransivity)은 도메인모델이 데이터베이스의 변경으로 부터 UI를 격리시킨다는 것을 보여주기 때문에 매우 중요합니다. UI 는 오로지 도메인 모델의 인터페이스가 변경될 만큼 매우 많은 데이터베이스의 변경이 발생 했을 때에만 변경됩니다. 그림 1a 는 UML 표기를 이용한 다이어그램을 보여 줍니다. UML은 객체지향 시스템을 위해 설계 되었지만, 모듈의 기본 개념과 의존은 대부분의 소프트웨어에 적용됩니다.

이런 종류의 고-레벨(high-level) 모듈의 UML 이름은 패키지(Pakage) 입니다. 그래서 이후 부터는 이 용어를 사용하겠습니다. 패키지들 이기 때문에 저는 이런 종류의 다이어그램을 패키지 다이어그램(pakage diagram) 이라고 부르겠습니다. (UML 로 엄격하게 말해서는 클래스 다이어그램 입니다.)

여기에서 기술하려 하는 것은 레이어드 아키텍처(layered architecture) 입니다. 정보 시스템(information system)에서 종사하는 사람은 누구나 익숙 할 겁니다. 정보시스템에서  레이어는 우리가 의존을 생각할 때 반드시 고려해야 하는 어떤 것을 기술하는 좋은 재료입니다.

의존구조를 심사숙고하는 충고의 가장 공통적인 것은 순환을 회피 하라는 것 입니다. 순환은 여러분의 모든 변경이 다시 최초 패키지의 변경을 요구하기 때문에 매우 큰 문제입니다. 그런 시스템은 수차례에 걸쳐 순환하며 살펴 봐야 하기 때문에 이해하기가 매우 어렵습니다.  저는 엄격한 규칙으로서 패키지들 사이의 순환 회피를 보여주여줄 필요를 느끼지 않습니다.-그것들이 지역화 되어 있다면 저는 관대할 겁니다.




매퍼 패키지(A mapper pakage)

그림 1a, 모든 의존은 한 방향으로 작동합니다. 이는 잘 통제되고 있는 의존 집합임을 나타냅니다.(필수 요건은 아닙니다만). 그림 1b,는 매퍼 패키지가 데이터베이스로 부터 도메인을 분리시켜주는 정보시스템의 또다른 일반적인 요소를 보여 줍니다(매퍼 패키지는 양방향 격리를 제공합니다.), 이것은 도메인과 데이터베이스의 변경이 서로 독립적이 될 수 있도록 합니다. 그 결과로 여러분은 더욱 복잡한 객체지향 모델에서 이런 스타일을 자주 발견할 수 있습니다.

물론, 여러분이 데이터를 불러오거나 저장할때 이 그림이 정확하게 옳지는 않단느 것을 알게 될 겁니다.  도메인의 모듈이 데이터베이스로 부터 데이터가 필요할 때 어떻게 요청하지? 매퍼에게 물어 볼 수 없습니다. 그게 가능하다면 도메인에서 매퍼로의 의존이 나타나게 되고, 순환 의존이 됩니다. 이 문제를 풀기 위해서 우리는 다른 종류의 의존이 필요합니다.

그래서, 저는 다른 부분의 코드를 사용하는 용어로서의 의존을 말해 왔습니다. 하지만, 다른 종류의 관계가 존재 합니다.- 인터페이스(Interface) 와 구현 (implementation)과의 관계 처럼 말이죠. 구현은 인터페이스를 의존 하지만 역(반대)은 아닙니다. 오로지 인터페이스 만을 의존하는 인터페이스를 호출하는 특정 상황에서도, 심지어는 분리된 모듈이 그것을 구현한다고 해도 말이죠.



그림 2는 이 아이디어를 묘사합니다. 도메인은 구현이 아니라 인터페이스를 의존합니다. 도메인은 몇몇 매퍼 구현 없이는 일을 할 수 없습니다. 하지만, 오로지 인터페이스의 변경만이 도메인의 변경을 유발 할 수 있습니다.

이런 상황에서 분리된 패키지가 존재하지만 꼭 필요하지는 않습니다.



그림3은 도메인에 포함된 저장 패키지(store pakage)를 보여줍니다. 매퍼까지 포함된 저장 구현을 구현합니다. 이 경우에, 도메인은 매퍼의 인터페이스를 정의 합니다. 도메인 패키지는 저장 인터페이스를 구현한느 어떤 매퍼라도 선택해서 함께 일 할 것이라는 것을 말해 줍니다.


모듈에 분리된 모듈의 구현을 의도하는 인터페이스를 정의하는 것은 의존을 깨고 커플링을 감소시키는 기반 방법입니다.  이런 접근 방법은 다양한 형태로 나타납니다. 가장 기본적인 형태가 call back 입니다. 이 형태에서 호출자는 특정 시그니처를 포함한 함수의 참조를 요구합니다. 자바 세상에서 일반적인 예는 리스너 입니다. 리스너는 어떤 것을 명확화하는 보다 명시적인 클래스 입니다.

다른 예는 모듈에 전달(passes out) 될 수 있는 이벤트(event)를 정의하는 것입니다. 여러분은 모듈의 확인을 듣고(linstening)있는 인터페이스를 정의하는 이벤트를 생각해 볼 수 있습니다. call back 함수의 호출자는 리스너 인터페이스를 정의하고 있는 녀석입니다. 그리고 이벤트의 생성자는 모듈의 호출 되는 명확한 장소를 모릅니다. 결과적으로 여기에는 의존이 없습니다.


저는 뭔가 마무리 하지 못한  느낌이 듭니다. "잘-통제된 의존" 과 같은 단어를 사용하여 모호하게 만들었기 때문이죠. 잘 통제된 의존 집합 과 같은 것을 정의 하려 시도 할때 견고한 가이드를 제공하는 것은 매우 어렵습니다. 명확하게도 이글은 의존의 양을 줄이는 것에 대한 것입니다. 하지만 그것이 전체의 이슈는 아닙니다. 의존의 방향(direction) 과 흐르는(flow) 방법, 큰 순환을 제거하는 것들 또한 중요합니다. 덧붙여서 , 저는 모든 의존을 동일하게 취급합니다. 인터페이스의 넖이를 제외하고는 말이죠.

거기에 의존이 있다는 사실 보다 그 의존이 무엇이냐를 지나치게 걱정하는 것처럼 보일 수 있습니다.


제가 따르는 기본 규칙은 고-레벨 의존들을 시각화하고 합리화 한 다음, 내가 원치 않는 의존을 깨기 위해 구현으로 부터 인터페이스를 분리 하는 것입니다. 설계에 대한 많은 경험적인 판단들 처럼, 이것 또한 끔찍하게 불완전 하게 보입니다. 하지만, 저는 이것이 유용하다는 것을 알게 됐고, 결국에는 상황에 달려 있습니다.

### 끝.


IEEE SOFTWARE J u l y / A u g u s t 2 0 0 1 , Reducing Coupling , by Martin Fowler

신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트


마틴 파울러 PoEAA(패턴 오브 엔터프라이즈 어플리케이션 아키텍처) 요약.

마틴 파울러의 PoEAA는 엔터프라이즈 어플리케이션의 아키텍처가 어떻게 구성되는 것인가에 대한 최고의 책입니다. 도입부의 [도메인 로직을 구성하는 패턴]은 조금은 어렵게 느껴지는 내용입니다. 전체적인 내용을 파악한 후 몇번에 걸쳐 정독 하는 것이 이 책의 내용을 이해하는 데 도움이 될 것입니다.

아래는 PoEAA 의 전체적인 요약 포스트의 번역 입니다.

엔터프라이즈 하면 역시 스타트랙.PoEAA




원문주소 : http://moshfegh.spaces.live.com/blog/cns!40F968A11C62F49A!192.entry

제목 : Summary notes from Martin Fowler's PoEAA

도메인 로직을 구성하는 패턴
1. 트랜잭션 스크립트 (Transaction Script) : 프로시저럴, 많은 중복, 모든 개발자가 이해하기 쉽고 간단합니다.
2. 테이블 모듈 (Table Module) : .NET 에서의 DataSet 처럼 트랜잭션 스크립트와 도메인 모델 사이의 중간이며, 도메인 로직을 핸들링하기가 트랜잭션 스크립트 보다 쉽습니다.
3. 도메인 모델 (Domain Model) : 복잡한 비즈니스 로직을 가지는 프로젝트에 적당합니다. 코드 중복 문제를 해결합니다.


데이터 소스로 부터 간단한 입출력만을 하는 프로젝트의 경우에는 트랜잭션 스크립트가 적합 할수 있으며, 복잡한 비즈니스 로직을 처리해야 한다면 도메인 모델이 적합합니다.


아이덴티티 맵 (Identity Map)

이름 그대로 오브젝트들을 구분할수 있는 Map, 오브젝트에 아이디를 부여 하여 메모리에 로딩 하여 맵으로 가지고 있는 형태. Look Up 하여 오브젝트를 참조할 수 있도록 지원.
메모리에 로드된 오브젝트는 상태가 변경될 수 있는데, 그 변경된 상태에 대해서도 다른 오브젝트에서 접근 가능해야 한다. 아이덴티티 맵은 세션 스코프를 가지며, 각각의 세션 내에서 인스턴스는 격리됨을 보장 받아야 합니다.
Unit of Work를  사용한다면 아이덴티티 맵이 위치할 수 있는 최적의 장소이며, Unit of work를 사용하지 않는 다면 세션에 결합된 Registry 가 적절한 장소입니다.

아이덴티티 맵의 다른 가치는 데이터베이스에 대한 캐쉬 역할을 하는 것입니다.


게이트웨이 (Gateway)
외부 시스템 또는 자원에 대한 접근을 캡슐화 하는 오브젝트. API와 같은 외부 자원을 취급하는데 좋습니다. API 코드들을 감쌈으로써 일반적인 클래스의 인터페이스로 보이게 할 수 있습니다.

 

분리된 인터페이스 (Separated Interface)
구현으로부터 분리된 인터페이스를 정의 합니다. 인터페이스 정의는 어떤 패키지에서 사용하고, 다른 패키지에서는 구현을 합니다. 이러한 구현은 클라이언트의 인터페이스에 대한 의존을 구현으로 부터 완벽히 분리할 수 있습니다.

분리된 인터페이스의 구현은 팩토리 오브젝트(Factory Object)를 이용하여 로드할 수 있습니다. 컨파일-타임(compile-time) 의존을 제거 하기 위해서 팩토리는 리플렉션(Reflection)을 이용하여 런-타임(run-time)에 구현을 로드할 수 있습니다.


이 패턴은 두 부분으로 구성되는 시스템에서 의존을 제거하기 위해서 필요하다. 예를 들어 도메인 코드가 데이터 매퍼(Data Mapper)를 호출하는 것 처럼 한 레이어에서 다른 레이어의 코드를 호출할 때 필요합니다.


레지스트리 (Registry)
오브젝트 또는 서비스를 다른 오브젝트들이 사용할 수 있도록 잘 알려진 공용 오브젝트.


특수 케이스 (Special Case)
특수한 경우에 특별한 행위를 제공하는 클래스. 일반 어플리케이션에서 null 값을 검사하는 패턴 등에서 이용할 수 있습니다. 다수의 경우에 매우 많은 곳에서 null 값을 검사할 필요가 있다면 이 패턴을 이용할 수 있습니다.
null 값을 반환하는 대신에 호출자가 기대하는 특별한 오브젝트를 반환할 수 있다. 단, 반환되는 오브젝트는 해로운 행위를 하지 않아야 합니다.


플러그인 (Plugin)
컴파일시가 아닌 설정(Configuration)에서 클래스를 연결 하는 것. 런타임 환경에서 다른 행위가 요구되어 질때 이 패턴을 사용 할 수 있다. 설정은(Configuration)은 리빌드 또는 재배포를 요구하지 않아야 합니다.


레이어링 (Layering)
관계 데이터를 단순히 보여주고, 입출력을 수행하는 프로젝트에서는 레이어링을 적용할 필요가 없습니다. 문제는 도메인 로직(비즈니스 규칙, 유효성 검사, 연산을 수행하는)에서 발생합니다. 클라이언트 UI에 포함된 비즈니스 코드들은 도메인이 복잡해 감에 따라 함께 일하기가 매우 어려워지고, 중복된 코드들을 양산합니다. 이 말은 작은 변경에도 수많은 화면이 문제를 일으킬수 있음을 의미합니다.

대안으로은 비즈니스 로직은 저장프로시저(Stored Procedure) 에 집어 넣는 것입니다. 저장프로시저는 서투른 코드를 유발하는 제한된 구조화된 메커니즘을 제공합니다.

교과서적인 3-Layer는 프리젠테이션, 도메인, 데이터소스 입니다.

 

레이어드 디자인

웹 어플리케이션에서의 레이어드 디자인과 Cross-Cutting Concern.


 


신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트
 객체지향프로그래밍 이란 무엇인가 (OOP)?  포스트에서 객체지향프로그램의 정의를 알아 보았습니다.

객체지향 프로그래밍 이란 캡슐화, 다형성, 상속 을 이용하여 코드 재사용을 증가시키고, 유지보수를 감소시키는 장점을 얻기 위해서 객체들을 연결 시켜 프로그래밍 하는 것 입니다.


정의를 알고 있다 해도 실제 구현에 적용하려 할때 모호함은 여전히 남아 있습니다. 객체지향설계는 나쁜 냄새(bad smell)을 제거하고 5개의 큰 원칙을 (Single Responsiblility Principle, Open-Closed Principle, Liskov Substitution Principle, Dependency Inversion Principle, Interface Segregation Principle) 지키려고 노력 할때 비로서 그 모양을 갖어갑니다.  Robert C. Martin 이 소개한 5가지 거대 원칙은 객체지향 설계의 근간이 되며, 소프트웨어 일반 책임 적용 패턴, 원칙 General Responsibility Assignment Software Patterns, GRASP  이라고 부르는 원칙에서 9가지 항목으로 세분화 됩니다.   

이번 포스트에서는 소프트웨어를 실패로 만드는 설계의 나쁜 냄새가 무엇이며, 객체지향으로 구현하기 위한 설계(Design) 원칙이 무엇인지 얘기해 보겠습니다.

 

Introduction

 우리는 무엇을 발견하려고 UML 다이어그램을 읽을까? 좋은 다이어 그램인지 아닌지 평가하는 기준은 무엇일까?

설계의 품질 - Quality of Design

잘 설계한 시스템은 이해하기 쉽고, 바꾸기 쉽고, 재사용하기 쉽습니다. 개발하는데 특별히 어렵지도 않고, 단순하고 간결하며 경제적입니다.

 

나쁜 설계의 냄새 - Smell of Bad Design

  1. 경직성 - 무엇이든 하나를 바꿀 때마다 반드시 다른 것도 바꿔야 하며, 그러고 나면 또 다른 것도 바꿔야 하는 변화의 사슬이 끊이지 않기 대문에 시스템을 변경하기 어렵다.

  2. 부서지기 쉬움 - 시스템에서 한 부분을 변경하면 그것과 전혀 상관없는 다른 부분이 작동을 멈춘다.( 바보같은 말이지만, 상관 있는 부분은 당연히 변경해야 한다.)

  3. 부동성 - 시스템을 여러 컴포넌트로 분해해서 다른 시스템에 재사용하기 힘들다.

  4. 끈끈함 - 개발 환경이 배관용 테이프나 풀로 붙인 것처럼 꽉 달라붙은 상태다. 편집-컴파일-테스트 순환을 한 번 도는 시간이 엄청나게 길다.

  5. 쓸데 없는 복잡함(과도한 디자인) - 괜히 머리를 굴려서 짠 코드 구조가 굉장히 많다. 이것들은 대개 지금 당장 하나도 필요 없지만 언젠가는 굉장히 유용할지도 모른다고 기대하고 만들었다.

  6. 필요 없는 반복 - 코드를 작성한 프로그래머 이름이 마치 '복사'와 '붙여넣기'같다.

  7. 불투명함 - 코드를 만든 의도에 대한 설명을 볼 때 그 설명에 '표현이 꼬인다'. 라는 말이 잘 어울린다.

  

Agile Software Development - Principles, Patterns, and Practices , Robert C. Martin 

 

로버트 c 마틴의 5원칙을 SOLID 라고 한다. SOLID Motivational Posters, by Derick Bailey

 

The Single Responsibility Principle - SRP

어떤 클래스를 변경해야 하는 이유는 오직 하나뿐 이어야 한다.

 어떤 객체가 너무 많은 다른 종류의 행동을 할 수 있다면, 그것은  다른 객체가 변경 될 때 함께 변경되어야 할 가능성이 많다는 것을 의미합니다. 

가능한 하나 또는 명확하게 관련이 있는 책임을 부여 함으로써 객체는 보다 명확해(Explicit) 지고, 표현가능해 지며(Expressive), 느슨한 커플링(Loose Coupling) 과 높은 응집력(High Cohesion) 을 얻게 됩니다.

 

 The Open-Closed Principle - OCP

 소프트웨어 엔터티(클래스, 모듈, 함수 등등)는 Interface에 대해서는 개방되어야 하지만, 변경에 대해서는 폐쇄되어야 한다.

 단위 테스트를 할 때는 종종 환경에 생기는 변화를 제어하고 싶은 경우가 발생합니다. 예를 들어 Emploee를 어떻게 테스트 할지 생각해 보시죠. Emploee객체는 데이터베이스를 변경합니다. 하지만 테스트 환경에서 실제 데이터베이스를 바꾸고 싶지 않습니다. 그렇다고 해서 단위 테스트를 하기 위해 테스트용 더미 데이터베이스를 만들고 싶지도 않습니다. 그렇다면, 테스트 환경으로 환경을 변경해서 테스트 할 때 Emploee가 데이터베이스에 하는 모든 호출을 가로챈 다음 이 호출들이 올바른지 검증하면 좋을 것입니다.

 엔터티가 외부로 부터 영향을 받지 않고, 내부의 필요에 의해서만 변경하게 하려면 보호막으로 감싸고 노출되지 않아야 합니다. 그러나 다른 객체와 상호 작용하기 위해서는 선택적인 노출이 필요 합니다. 다른 객체와 대화하기 위한 인터페이스와 확장을 위해서는 Open 하고, 이외의 경우에는 Closed 해야 정보가 감추어지고 (Information Hiding) , 객체들끼리 연결하게 되는 끈이 줄어듦으로써 커플링이 타이트 해질 가능성이 줄어 듭니다.

Liskov Substitution Principle - LSP

서브타입(Sub Type)은 언제나 자신의 기반 타입(Base Type)으로 교체할 수 있어야 한다.

LSP에 따르면, 기반 클래스의 사용자는 그 기반 클래스에서 유도된 클래스를 기반 클래스로써 사용할때, 특별한 것을 할 필요 없이 마치 원래 기반 클래스를 사용하는 양 그대로 사용할 수 있어야 한다. 더 자세히 말하자면, instanceof나 다운캐스트를 할 필요가 없어야 합니다. 강조하건데, 사용자는 파생클래스에 대해서 아무것도 알 필요가 없어야 합니다. 파생 클래스가 있다는 사실 조차도.

 LSP는 상속(Inheritance), 다형성(Polymorphism) 과 관련된 원칙이죠. 상속은 코드 재사용 이라는 이유로 과용 될수 있는 기능입니다. 과잉 사용된 상속은 복잡한 계층구조와 커플링을 타이트하게 함으로써 객체지향으로 얻기 위한 유지관리비용 감소에 악영향을 미치는 요소중 하나 입니다. LSP란 상속을 사용할때 지켜야 하는 원칙을 말하는데요. 상속은 코드 재사용을 위해서가 아니라 명확한 가족 관계가 있을때 사용해야 하며, 그 행위(Behavior) 와 연관이 있습니다. 부모와 파생된 객체들이 존재 할때 가능한 모두 동일한 메소드 이름과 갯수를 가지게 함으로써  언제든지 서브 타입이 기반타입으로 교체될 수 있어야 함을 말합니다. 

다시 말하면, 상속의 오용은
가족이 아닌 객체를 비슷한 일을 하는 것을 보고 코드 재사용 욕심에 가족 관계로 묶는 것.
다층 상속으로 계층 구조가 깊고 복잡하게 하는 것.
파생 타입에 부모, 형제들과는 전혀 동떨어진 능력을 부여 함으로써 돌연변이를 만들어 버리는 것.
을 의미하며 LSP는 돌연변이(부모, 형제들의 형질과 관련이 없는 능력을 가진 파생타입 이며 이렇게 될때는 이미 가족이 아님을 의미)가 발생하지 않도록 관리해서 상속 관계의 타입들이 유연하게 대체 될 수 있게 하는 원칙 입니다. 

LSP의 더욱 자세한 설명은 LSP in Depth  포스트를 참조

 

Dependency Inversion Principle - DIP

  1. 고차원의 모듈은 저차원의 모듈에 의존하면 안된다. 이 두 모듈 모두 다른 추상화된 것에 의존한다.
  2. 추상화 된 것은 구체적인 것에 의존하면 안 된다. 구체적인 것이 추상화된 것에 의존해야 한다. 

자주 변경되는 구상 클래스(Concreate class)에 의존하지 마라. 만약 어떤 클래스에서 상속받아야 한다면, 기반 클래스를 추상 클래스로 만들어라. 만약 어떤 클래스의 참조(Reference)를 가져야 한다면, 참조 대상이 되는 클래스를 추상 클래스로 만들어라. 만약 어떤 함수를 호출해야 한다면, 호출 되는 함수를 추상 함수로 만들어야 합니다.

DIP는 그 용어 자체의 어려움 때문에 이해하기가 쉽지 않지만 내용은 단순합니다. 추상화된 것 (Absctract Class, Interface) 을 사용해야 커플링을 느슨하게 만들 수 있다는 말 입니다. 예를 들어

abstract class Car {} 로 부터 상속 받은 class Truck : Car {}, class Bus : Car {} 가 존재 할때,

Truck porter = new Truck() 과 같이 사용하지 말고,

Car myCar = new Truck();  과 같이 추상 클래스를 기반으로 작업하거나.


    abstract class Car
    {
       public abstract void Run();
       public abstract void Stop();
    }

    class Truck : Car
    {


        public override void Run()
        {
            throw new NotImplementedException();
        }

        public override void Stop()
        {
            throw new NotImplementedException();
        }
    }

    class Bus : Car
    {

        public override void Run()
        {
            throw new NotImplementedException();
        }

        public override void Stop()
        {
            throw new NotImplementedException();
        }
    }


    static void main()
    {
        Truck myCar = new Truck();
        myCar.Run();

        Car myCar = new Truck();
        myCar.Run();

    }

 인터페이스를 사용하는 경우.

  abstract class Car
    {

    }

    interface IDrive
    {
        public void Run();
        public void Stop();

    }

    class Truck : Car, IDrive
    {



        #region IDrive 멤버

        public void Run()
        {
            throw new NotImplementedException();
        }

        public void Stop()
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    class Bus : Car, IDrive
    {


        #region IDrive 멤버

        public void Run()
        {
            throw new NotImplementedException();
        }

        public void Stop()
        {
            throw new NotImplementedException();
        }

        #endregion
    }


    static void main()
    {
        Car myCar = new Truck();
        IDrive drive = (IDrive)myCar;

        drive.Run();
        drive.Stop();
    }

 

와 같이 추상적인 것에 구체적인 것을 대입해 사용함으로써 커플링을 느슨하도록 만드는 원칙 입니다.

DIP 에 대한 상세한 개념에 대해서는 '의존 관계 역전의 원칙 Dependency Inversion Principle' 포스트를 읽어 보세요.


Interface Segregation Principle - ISP

클라이언트는 자신이 사용하지 않는 메소드에 의존 관계를 맺으면 안된다. 

비대한 클래스(Fat class) 란 메소드를 몇십 몇백개 가지는 클래스를 가르키는 말이다. 대개 시스템 안에 이런 클래스를 두고 싶어 하지 않지만, 가끔 피 할 수 없는 경우도 있습니다.

비대한 클래스가 거대하고 보기 흉하다는 사실 말고도, 한 사용자가 이 비대한 클래스의 메소드를 다 사용하는 일이 매우 적다는 것도 문제입니다. 즉. 메소드를 몇십개 선언한 클래스에서 사용자는 단지 두세 개만 호출 할 지도 모르죠. 불행하게도 이 사용자들은 호출하지도 않는 메소드에 생긴 변화에도 영향을 받습니다.


ISP 인터페이스 분리의 원칙은 가족관계가 아닐때 같은 행동을 하는 객체들에게 인터페이스를 할당 하고 사용 함으로써 DIP 를 가능하도록 만들고 , 그 결과로 느슨한 커플링과 명시성과 표현성을 얻습니다.





더 읽을 꺼리 :
* 객체지향 5원칙을 예제까지 포함하여 설명한 사내양 님의 글 
* SOLID 설명 캐스트

신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트
객체지향설계에 있어서 맨 처음 하는 일은 도메인 분석에 의해서 객체를 뽑아 내는 작업 입니다.

명사 는 객체로, 동사는 메소드로, 상태는 객체의 속성으로 구별해 내는 것이지요. 하지만 이 작업이 말처럼 쉽지는 않습니다. 어떤 명사는 명확하게 객체로 구별이 되는데, 어떤 명사는 모호하며, 더 어려운 일은 메소드가 어느 객체에 할당 되어야 하는지 구별하는 것입니다. 또한, 도메인 분석에 의해 구별된 객체 이외에도  관계 및 로직을 구현하기 위해서는 부가 객체들도 필요 합니다. 


어떤 객체가 어떤 책임을 질것 이며, 그 책임에 따라 객체에 할당할 메소드를 찾고, 다른 객체와 공유해야 하는 정보를 구별해 내는 방법론이 Rebecca Wirfs-Brock 와 Brian Wilkerson 이 제안한 Responsibility-driven design, RDD 입니다.

Responsibility-driven design 은 client/server 모델에서 영감을 얻었는데요, 질의에 의한 계약에 집중합니다 : 

1. 이 객체가 책임질 액션은 무엇인가?
2. 이 객체가 공유(Share) 해야할 정보(Information)는 무엇인가?


객체에 책임을 할당 해야 하는데 그 할당하는 패턴 또는 원칙이 General Responsibility Assignment Software Patterns , GRASP 패턴, 또는 원칙 입니다.

객체의 책임을 부여하는 일은 항상 어려운 일 입니다. 마틴 파울러은 '리팩토링 Refactoring' 에서 책임부여의 어려움을 이렇게 말하고 있습니다.

객체 디자인에서 가장 기본이 되는 것 중의 하나(원칙은 아닐지라도)는 책임을 어디에 둘지를 결정하는 것이다. 나는 십년 이상 객체를 가지고 일했지만 처음 시작할 때는 여전히 적당한 위치를 찾지 못한다. 늘 이런 점이 나를 괴롭혔지만, 이제는 이런 경우에 리팩토링을 사용하면 된다는 것을 알게 되었다.
- "리팩토링", 마틴 파울러 , 윤성준 역, 대청미디어, 2002, p.169


GRASP은 이 어려움을 돕기 위한 책임 부여 원칙인 것이지요.

GRASP 은 Information Expert, Creator, Controller, Low Coupling, High Cohesion, Polymorphism, Pure Fabrication, Indirection, Protected Variations. 의 9가지 항목이며, 객체지향디자인 5원칙의 각론으로 볼수 있습니다.

책임은 매우 중요하다. 책임 진다고 하면 의외로 쉽게 해결되는 것도 있다.




9가지 항목에 대한 기술은 김대곤님의 글로 대체 합니다.

김대곤 님의 GRASP 패턴 에서 발췌

GRASP 패턴은 아홉 가지로 구성되어 있다. 사실 각 패턴들이 너무 간단해서 싱거울 정도이다.
  1. Information Expert: 역할을 수행할 수 있는 정보를 가지고 있는 객체에 역할을 부여하자. 단순해 보이는 이 원칙은 객체지향의 기본 원리 중에 하나이다. 객체는 데이터와 처리로직이 함께 묶여 있는 것이고, 자신의 데이터를 감추고자 하면 오직 자기 자신의 처리 로직에서만 데이터를 처리하고, 외부에는 그 기능(역할)만을 제공해야 하기 때문이다.

  2. Creator: 객체의 생성은 생성되는 객체의 컨텍스트를 알고 있는 다른 객체가 있다면, 컨텍스트를 알고 있는 객체에 부여하자. A 객체와 B 객체의 관계의 관계가 다음 중 하나라면 A의 생성을 B의 역할로 부여하라.
    - B 객체가 A 객체를 포함하고 있다.
    - B 객체가 A 객체의 정보를 기록하고 있다.
    - A 객체가 B 객체의 일부이다.
    - B 객체가 A 객체를 긴밀하게 사용하고 있다.
    - B 객체가 A 객체의 생성에 필요한 정보를 가지고 있다.

  3. Controller: 시스템 이벤트(사용자의 요청)를 처리할 객체를 만들자. 시스템, 서브시스템으로 들어오는 외부 요청을 처리하는 객체를 만들어 사용하라. 만약 어떤 서브시스템안에 있는 각 객체의 기능을 사용할 때, 직접적으로 각 객체에 접근하게 된다면 서브시스템과 외부간의 Coupling이 증가되고, 서브시스템의 어떤 객체를 수정할 경우, 외부에 주는 충격이 크게 된다. 서브시스템을 사용하는 입장에서 보면, 이 Controller 객체만 알고 있으면 되므로 사용하기 쉽다.

  4. Low Coupling: 객체들간, 서브 시스템들간의 상호의존도가 낮게 역할을 부여하자. Object-Oriented 시스템은 각 객체들과 그들 간의 상호작용을 통하여 요구사항을 충족시키는 것을 기본으로 한다. 그러므로, 각 객체들 사이에 Coupling이 존재하지 않을 수는 없다. 이 패턴은 요구사항은 충족시키면서도 각 객체들, 각 서브시스템 간의 Coupling를 낮은 수준으로 유지하는 방향으로 디자인하라고 말하고 있다. Low Coupling은 각 객체, 서브시스템의 재 사용성을 높이고, 시스템 관리에 편하게 한다.

  5. High Cohesion: 각 객체가 밀접하게 연관된 역할들만 가지도록 역할을 부여하자. 이 패턴은 Low Coupling 패턴과 동전의 양면을 이루는 것으로, 한 객체, 한 서브시스템이 자기 자신이 부여받은 역할만을 수행하도록 짜임새 있게 구성되어 있다면, 자신이 부여 받은 역할을 충족시키기 위해 다른 객체나 시스템을 참조하는 일이 적을 것이고, 그것이 곧 Low Coupling이기 때문이다.

  6. Polymorphism: 객체의 종류에 따라 행동양식이 바뀐다면, Polymorphism 기능을 사용하자. Object-Oriented 시스템은 상속과 Polymorphism(다형성)을 지원한다. 만약 객체의 종류에 따라 행동이 바뀐다면 객체의 종류를 체크하는 조건문을 사용하지 말고, Object-Oriented 시스템의 Polymorphism 기능을 사용하라.

  7. Pure Fabrication: Information Expert 패턴을 적용하면 Low Coupling과 High Cohesion의 원칙이 깨어진다면, 기능적인 역할을 별도로 한 곳으로 모으자. 데이터베이스 정보를 저장하거나, 로그 정보를 기록하는 역할에 대해 생각해 보자. 각 정보는 각각의 객체들이 가지고 있을 것이다. 이 때 Information Expert 패턴을 적용하면, 각 객체들이 정보를 저장하고, 로그를 기록하는 역할을 담당해야 하지만, 실제로 그렇게 사용하는 사람들은 없다. 이것은 그 기능들이 시스템 전반적으로 사용되고 있기 때문에 각 객체에 그 기능을 부여하는 것은 각 객체들이 특정 데이터베이스에 종속을 가져오거나, 로그을 기록하는 매커니즘을 수정할 경우, 모든 객체를 수정해야 하는 결과를 가져온다. 즉 Low Coupling의 원칙이 깨어지게 된다. 이럴 경우에는 공통적인 기능을 제공하는 역할을 한 곳으로 모아서 가상의 객체, 서브시스템을 만들어라.

  8. Indirection: 두 객체 사이의 직접적인 Coupling을 피하고 싶으면, 그 사이에 다른 객체를 사용하라. 여기서 말하는 다른 객체란 인터페이스가 될 수 있고, 주로 인터페이스인 경우가 많다. 그런 특별한 경우는 아래에 설명된 Protected Variations 패턴이라고 부를 수 있다.

  9. Protected Variations: 변경될 여지가 있는 곳에 안정된 인터페이스를 정의해서 사용하자. JDBC에 대해서 생각해 보자. JDBC는 일련의 인터페이스들로 구성되어 있으며, 각 데이터베이스 벤더들이 인터페이스를 구현한 Concrete 클래스를 제공하고 있다. 데이터베이스 기능을 사용하는 시스템의 입장에선 각 벤더들이 구현방식을 바꾸었을 때, 자신의 코드를 수정하고 싶지 않을 것이다. 그래서 Driver를 로딩하는 코드를 제외하고는 모두 인터페이스를 사용함으로서 데이터베이스의 변경시에도 Driver 로딩만 바꾸어 주면 되도록 데이터베이스 관련 작업이 필요한 곳에는 안정된 JDBC 인터페이스를 사용한 것이다.


http://en.wikipedia.org/wiki/GRASP_(object-oriented_design)
http://scottlee.tistory.com/13
http://blog.naver.com/eatist/10013301919
http://davidhayden.com/blog/dave/archive/2005/03/27/895.aspx
신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트
TAG GRASP, OOD, RDD
ASP.NET 에서 '이벤트 뷰어'의 목록 중에 [스레드가 취소 되었습니다. Thread was being aborted] 로그를 만나는 때가 있습니다.

이 예외는 Response.Redirect , Server.Transfer 를 사용 했을때 발생하는 알려진 문제 입니다.


다음과 같은 시도를 했을 때 발생하는 예외인데요

FormsAuthentication.SignOut();
Response.Redirect("Login.aspx");


아래와 같은 로그가 남게 됩니다.

Source        : mscorlib
Method        : AbortInternal
Date        :
Time        :
Computer    :
Error        : Thread was being aborted.
Stack Trace    :    at System.Threading.Thread.AbortInternal()
   at System.Threading.Thread.Abort(Object stateInfo)
   at System.Web.HttpResponse.End()
   at System.Web.HttpResponse.Redirect(String url, Boolean endResponse)
   at System.Web.HttpResponse.Redirect(String url)


해결방법으로는 Server.Execute 를 사용, 또는

Response.Redirect ("nextpage.aspx", false);

를 사용하는 것 입니다.


URL 다음에 boolean 파라미터는 현재 페이지를 종료할 것이냐를 의미하는 것으로, 종료하지 않음  파라미터를 전달하여 스레드가 종료되지 않도록 알립니다.

        //
        // Summary:
        //     Redirects a client to a new URL. Specifies the new URL and whether execution
        //     of the current page should terminate.
        //
        // Parameters:
        //   url:
        //     The location of the target.
        //
        //   endResponse:
        //     Indicates whether execution of the current page should terminate.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     url is null.
        //
        //   System.ArgumentException:
        //     url contains a newline character.
        //
        //   System.Web.HttpException:
        //     A redirection is attempted after the HTTP headers have been sent.
        //
        //   System.ApplicationException:
        //     The page request is the result of a callback.
        public void Redirect(string url, bool endResponse);


다음의 링크는 MS , PRB 주소 입니다.
PRB: ThreadAbortException Occurs If You Use Response.End, Response.Redirect, or Server.Transfer


스레드는 실 이야!.


신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트

도메인-드리븐 디자인 요약의 또하나의 포스트 입니다. DDD를 설명하는 포스트의 번역인데요, 원문에서도 언급하듯이 DDD를 확실히 이해하려면 전반적인 모든 것을 알고 있어야

하고, 알고 있더라도 개인적인 경험에 의존하기 때문에 매우 어렵습니다. 다행이도 전체를 조망하는 통찰력을 가진 글을 읽음으로써 우리는 좀더 쉽게 DDD를 이해할 수 있습니다.

아래에 번역한 포스트가 바로 그런 글입니다.

번역 :

Domain-driven Design
http://powerdream5.wordpress.com/2007/10/15/domain-driven-design/

Domain-Driven Design(DDD)은 제가 존경하는 전문가중의 한명인 Eric Evans(http://domainlanguage.com/)가 변론한 소프트웨어 개발 접근법입니다.  DDD의 주요 목적은 도메인과 도메인 로직에 집중하여 요구사항에서 빗나가지 않게 하는 것입니다.
 
DDD는 유용하다는 것과 전통적인 소프트웨어 개발 방법론의 문제점을 극복할 수 있음을 보여 줍니다. 불행한 점은 DDD를 완전히 이해하는 것은 매우 어렵우며, 여러분의 경험에 매우 의존적이라는 것입니다. 여러분은 또한 실제 프로젝트에 적용하고, 여러분의 경험을 반복해서 집약해야 합니다. 솔직히 말해서 저도 DDD를 조금만 알 뿐입니다. 하지만, DDD에 대한 저의 생각을 나눌것이며 여러분의  댓글을 환영합니다.

Domain-driven design Building Blocks



우리는 도메인 모델에서 그들의 책임을 기준으로 나눌수 있는 5개의 역할을 가지고 있습니다. 5개의 역할은 Enity, Value Object, Factory, Repository 그리고 Services  입니다. 이것들을 저의 지식에 근거하여 설명해 보려 합니다.

       Entity: 엔터티는 사용자와 소프트웨어 개발자 모두에게 관련이 되어 있기 때문에 도메인 모델에서 가장 중요한 부분입니다. 엔터티는 소트프웨어의 상태 변화와 관계없는 아이텐티티를 가지고 있습니다. 다른 말로 하면, 우리는 엔터티를 데이터베이스와 같은 그 어딘가에 저장해야 합니다.

       Value Object: 엔터티는 아이텐티티를 가지고 있고 아이덴티티는 변경될 수 없기 때문에 어플리케이션이 엔터티를 생성하고 추적하는 데에는 비용이 많이 듭니다. 성능을 고려해 보면 도메인 모델의 모든 명사(noun)들을 엔터티로 매핑하는 것은 비합리적 입니다.  Value object 는 아이텐터티가 없으며, 여러분의 도메인 모델의 다른 오브젝트 들이 공유할 수 있는 오브젝트 입니다.

 Domain-Driven Design의 장점을 취하려 할때  엔터티와 value object를 구별하는 것이 우리가 만나는 첫번째 장애물입니다 . 어떤 오브젝트가 아이덴터티를 가져야 할지 말아야 할

지는 전적으로 소프트웨어 개발자가 결정하며, 거기에는 어떤 직접적인 규칙이 없습니다. 저의 의견은 엔터티를 인식하는 일은 오브젝트를 테이블에 매핑하는 것과 유사합니다.

Embedded value 패턴을 기억합니까? 어떤 종류의 오브젝트가 자신의 테이블을 가지고 매핑되어야 하고, 어떤 종류의 오브젝트가 부모 테이블에 매핑되어야 할지? 이 질문에 대한

답은 여러분의 판단 능력과 경험에 달려 있습니다.

         Factory: Factory는 엔터티를 생성할 책임이 있습니다. 일반적으로, 여러분의 도메인 모델에서 엔터티는 항상 다른 오브젝트들과 관계를 가지고 있기 때문에 엔터티를 생성하

는 과정은 복잡합니다. 엔터티를 생성할 때 우리는 관계망을 초기화 해야 합니다. 그래서 엔터티를 생성하는 프로시저를 캡슐화 하는 오브젝트를 정의하는 것은 매우 훌륭합니다.

         Repository:리파지터리는 엔터티를 관리할 책임을 가집니다. 관리란 데이터베이스로 부터 엔터티를 업데이트, 저장, 로딩하는 것을 의미합니다. 리파지터리는 특정 인터페이

스와 인터페이스를 구현하는 구상클래스 집합을 가집니다. 구상 클래스들은 어플리케이션의 영속화 레이어(persistent layer)를 캡슐화 합니다.

              Service: 도메인을 기술한 후에, 명사는 엔터티/value object 로, 동사는 메소드로 매핑할 수 있습니다. 때때로 어떤 동사들은 엔터티/value object 의 메소드로 매핑하는

것은 불합리합니다. 이런 경우에  이런 메소드들을 담는 service라는 이름의 새로운 역할을 만들 수 있습니다. 서비스는 어플리케이션의 과잉을 정의 할 것입니다. 서비스는 클라이언트에 의해 접근 될 것입니다.

이 글을 읽어 주셔서 감사합니다. DDD에 대해 잘못된 이해가 있다면, 어떤 의견도 좋으니 남겨주세요, DDD에 대한 더 많은 정보를 원한다면 이 링크(http://www.infoq.com/minibooks/domain-driven-design-quickly)를 열어 보세요.

신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트
TAG ddd

Specification Pattern 설명과 구현의 이해

Specification Pattern 은 DDD와 PoEAA에서 동시에 다루어지는 명세서 라는 패턴입니다. 도메인 모델은 다양한 종류의 조건 쿼리를 실행시키려 할 때  DAL( Data Access Layer ) 과 같은 프로바이더에 구현한 메소드를 호출할 필요성이 발생합니다.

실행조건마다 그 명세와 일치하는 메소드가 프로파이더에 작성되고, 도메인 오브젝트와 프로바이더는 점점 강하게 연결됩니다. 명세조건만 약간씩 다른 중복 코드들이 양산되게 되죠.

Specification Pattern 은 도메인 오브젝트가 일관성있는 인터페이스로 프로바이더를 이용하게 함으로써 커플링을 느슨하게 만들며, 정확한 명세가 무엇인지를 연결된 객체의 '코드' 가 아닌 모델 자체가 설명하도록 하면서 응집성과 SoC를 달성하도록 하는 패턴 입니다.


Specification Pattern 을 비유해 설명하자면 , 내가 필요한 책을 도서관의 서가마다 돌아다니며 찾는 것이 아니라, 책의 목록을 '사서'에게 전달하고 사서로 부터 책을 받기만 하면 도서관 마다의 대출 방법과 서가의 위치 및 구조를  일일이 알고 있지 않더라도 목적을 달성 할수 있다는 아이디어를 구현하는 패턴 입니다. 책의 목록이 Specification 이며,  사서가 프로바이더의 인터페이스입니다. 나는 도서관의 구조를 알필요가 없이 목록규칙(Criteria)만 지키면 되고, 나중에 동일한 책을 빌리고 싶다면 이전에 작성해 놓은 목록을 사서에게 건네 주기만 하면 됩니다.


명세는 도메인 모델에 구현하며, 프로바이더에 Specification 인터페이스에 기준( Criteria )을 구현한 인스턴스를 전달함으로써 목적을 달성합니다.

ISpecificaton 인터페이스와 쿼리 조합 클래스 구현 다이어그램.



다른 여러 패턴에 대해 우리는 "왜 이리 공부해야 할 패턴이 많아~ 아휴 이 패턴은 그냥 넘어 갈까" 하고 지나치는 일이 많습니다. Specification Pattern 은 그 가치를 알기 전까지 가장 간과하기 쉬운 패턴 일 듯 합니다.

Specification Pattern 의 개념과 구현 설명이 매우 잘 되어 있는 블로그 포스트가 있어 번역 포스트 합니다.


번역 :

The Specification Pattern: A Primer
Posted March 25th 2005 by Matt Berther

Specification Pattern 은 클래스의 인터페이스에서 잡동사니들을 제거해주는 동시에 확장성을 증가시켜주고 커플링을 낮춰주는  매우 강력한 디자인 패턴입니다.

이것의 주된 쓰임은 어떤 기준에 기반한 개체의 부분집합을 선택하는 것과 다양한 상황에서 선택을 리프레쉬 시키는 것입니다.

예를 들어, 나는 이와 비슷한 것으로 보이는 인터페이스를 가진 클래스를 매우 많이 봤습니다.


public class User
{
    public string Company;
    public string Name;
    public string City;
}

public class UserProvider
{
    public User[] GetUserByName(string name)
    {
    }

    public User[] GetUsersByCity(string name)
    {
    }

    public User[] GetUsersByCompany(string company)
    {
    }
}


이 모델을 이용함으로써, 사용자검색을 위한 새로운 조건을 부여하고 싶을 때마다 유저프로바이더 클래스에 메소드를 추가해야 하고, 이는 인터페이스를 혼란스럽게 하는 것을 알

수 있습니다.

이제 specification pattern을 사용한 똑같은 사례를 살펴봅시다.


public class User
{
    public string Company;
    public string Name;
    public string City;
}



public class UserSpecification
{
    public virtual bool IsSatisfiedBy(User user)
    {
        return true;
    }
}

public class UserProvider
{
    public User[] GetBySpecification(UserSpecification spec)
    {
        ArrayList list = new ArrayList();

        UserCollection coll = SomeMethodToPopulateTheUserCollection();
        foreach (User user in coll)
        {
            if (spec.IsSatisfiedBy(user))
            {
                list.Add(user);
            }
        }

        return (User[])list.ToArray(typeof(User));
    }
}

class UserCompanySpecification : UserSpecification
{
    private readonly string companyName;

    public UserCompanySpecification(string companyName)
    {
        this.companyName = companyName;
    }

    public override bool IsSatisfiedBy(User user)
    {
        return user.Company.Equals(companyName);
    }
}



이 패턴을 사용함으로써, 유저프로바이더 클래스로부터 특화된 모든 메소드들을 제거합니다. 

또한, 느슨한 커플링으로 인해  검색을 위한 부가적인 조건을 언제든 추가할 수 있습니다. 우리는 기존의 인터페이스를 오염시키기 보다는 새로운 UserSpecification 을 구현하고,

GetBySpecification  메소드에 이 인스턴스를 던져주기만 하면 됩니다.

이는 프로바이더 코드가 사용자가 원하는 방법을 알고 있다고 가정하기보다는 호출하는 코드가 어떤 컬렉션이든 어떻게 필터할 것인지 정확히 결정하도록 해줍니다.

물론, API 디자이너가 소수의 잘쓰이는 Specification을 API 그 자체에  집어넣는 것은 허용됩니다.

이 패턴은 매우 강력하지만, 다른 것처럼 과용될 수 있습니다. 이 패턴을 쓰거나 쓰지 말아야 할 때에 대한 연결된 설명을 고려하여 결과를 리뷰해보는 것을 명심하십시오. 


 

신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트


티스토리 툴바