'Encapsulation'에 해당되는 글 1건

  1. 2010.10.08 OOD - 데이터 접근 루틴 작성 가이드 (1)

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 반더빌트


티스토리 툴바