반복자 패턴-내부 표현을 노출시키지 않는 것이 왜 중요합니까?


24

C # Design Pattern Essentials를 읽고 있습니다. 현재 반복자 패턴에 대해 읽고 있습니다.

구현 방법을 완전히 이해하지만 중요성을 이해하지 못하거나 사용 사례를 보지 못합니다. 이 책에는 어떤 사람이 물건의 목록을 가져와야하는 경우가 있습니다. 그들은 IList<T>또는 과 같은 공공 재산을 노출함으로써이를 수행 할 수있었습니다 Array.

이 책은 씁니다

이것의 문제점은이 두 클래스의 내부 표현이 외부 프로젝트에 노출되었다는 것입니다.

내부 표현은 무엇입니까? 사실은 array또는 IList<T>? 나는 이것이 왜 이것이 소비자 (프로그래머가 이것을 부르는)에게 나쁜지 이해하지 못한다.

이 책은이 패턴이 그 GetEnumerator기능 을 드러냄으로써 작동한다고 말합니다 . 그래서 우리는 GetEnumerator()이런 방식으로 '목록'을 호출 하고 노출시킬 수 있습니다 .

특정 상황에서는이 패턴이 모두 같은 것으로 가정하지만 언제 어디서 볼 수는 없습니다.



4
구현은 소비자 클래스의 변경없이 배열 사용에서 링크 된 목록 사용으로 변경하려고 할 수 있습니다. 소비자가 반환 된 정확한 클래스를 알고 있으면 불가능합니다.
MTilsted

11
소비자가 아는 것은 나쁜 일이 아니며 소비자가 의지하는 것은 나쁜 일입니다 . 정보 숨기기라는 용어가 마음에 들지 않기 때문에 전화 내부와 같이 개인 정보 대신 암호와 같은 "개인 정보"라고 생각하기 때문입니다. 내부 구성 요소는 비밀이 아니기 때문에 사용자와 관련이 없기 때문에 숨겨져 있습니다. 당신이 알아야 할 것은 여기에 전화를 걸고, 여기에 이야기하고, 여기에 들어요.
Captain Man

우리가 좋은 "인터페이스"가 있다고 가정하자 F나에게 지식 (방법을) 제공 a, bc. 모든 것이 좋고 좋고, 다른 것이지만 F나에게 많은 것들이있을 수 있습니다 . 이 방법을 "제약"또는 계약 조항이 F클래스를 수행 한다고 생각하십시오 . d우리가 필요하기 때문에 추가한다고 가정 하십시오. 이것은 우리가 이것을 할 때마다 더 많은 제약을가합니다 F. 결국 (최악의 경우) 단 한 가지만이 될 수 F있으므로 우리는 그것을 가질 수 없습니다. 그리고 F그것을 할 수있는 유일한 방법이 너무 많이 구속됩니다.
Alec Teal

@ captainman, 정말 멋진 의견입니다. 그렇습니다. 왜 많은 것을 '추상'해야하는지 알지만, 알고 알고 의지하는 것은 ... 솔직히 ... 그리고 당신의 게시물을 읽을 때까지 고려하지 않은 중요한 차이점입니다.
MyDaftQuestions

답변:


56

소프트웨어는 약속과 특권 게임입니다. 제공 할 수있는 것 이상 또는 공동 작업자의 요구 이상을 약속하는 것은 결코 좋은 생각이 아닙니다.

이것은 특히 유형에 적용됩니다. 반복 가능한 컬렉션을 작성하는 요점은 사용자가 더 이상 반복 할 수 없다는 것입니다. 구체적 유형을 공개하면 Array일반적으로 Array공동 작업자가 내부에 저장된 데이터 를 변경할 수 있다는 사실은 말할 것도없이 자신이 선택한 기능에 따라 컬렉션을 정렬 할 수 있다는 많은 추가 약속을 만듭니다 .

이것이 좋은 것이라고 생각하더라도 ( "렌더러가 새로운 내보내기 옵션이 누락 된 것을 발견하면 바로 패치 할 수 있습니다! 소프트웨어 엔지니어링의 목표는 코드를 추론하기 쉽게 만드는 것입니다.

이제 공동 작업자가 여러 가지 항목에 액세스하여 누락되지 않도록 보장하는 경우 Iterable인터페이스 를 구현 하고이 인터페이스가 선언하는 메소드 만 노출합니다. 이렇게하면 내년에 표준 라이브러리에 훨씬 더 효율적이고 더 효율적인 데이터 구조가 나타나면 클라이언트 코드를 어디에서나 수정하지 않고도 기본 코드를 전환하여 혜택을 얻을 수 있습니다 . 필요한 것보다 더 많은 것을 약속하지 않으면 다른 이점이 있지만, 이것 하나만으로도 실제로는 더 이상 필요하지 않습니다.


12
Exposing the concrete type Array usually creates many additional promises, e.g. that you can sort the collection by a function of your own choosing...- 훌륭한. 난 그냥 생각하지 않았다. 예, 그것은 '반복'을 다시 가져옵니다.
MyDaftQuestions 11

18

구현을 숨기는 것은 OOP의 핵심 원칙이며 모든 패러다임에서 좋은 아이디어이지만 지연 반복을 지원하는 언어에서 반복자 (또는 특정 언어로 불려지는 것)에 특히 중요합니다.

구체적인 유형의 이터 러블 또는 심지어 인터페이스와 같은 노출 유형의 문제 IList<T>는 그것들을 노출시키는 객체가 아니라 그것을 사용하는 방법에 있습니다. 예를 들어 Foos 목록을 인쇄하는 기능이 있다고 가정 해 봅시다 .

void PrintFoos(IList<Foo> foos)
{
    foreach (foo in foos)
    {
        Console.WriteLine(foo);
    }
}

해당 함수를 사용하여 foo 목록을 인쇄 할 수 있습니다. IList<Foo>

IList<Foo> foos = //.....
PrintFoos(foos);

그러나 목록의 짝수 색인 항목을 모두 인쇄하려면 어떻게해야 합니까? 새 목록을 만들어야합니다.

IList<Foo> everySecondFoo = new List<T>();
bool isIndexEven = true;
foreach (foo; foos)
{
    if (isIndexEven)
    {
        everySecondFoo.Add(foo);
    }
    isIndexEven = !isIndexEven;
}
PrintFoos(everySecondFoo);

이것은 꽤 길지만 LINQ를 사용하면 하나의 라이너로 할 수 있습니다. 실제로 더 읽기 쉽습니다.

PrintFoos(foos.Where((foo, i) => i % 2 == 0).ToList());

자, 당신 .ToList()은 결국을 알았 습니까? 이렇게하면 게으른 쿼리가 목록으로 변환되므로에 전달할 수 있습니다 PrintFoos. 이를 위해서는 두 번째 목록이 할당되고 두 번째 항목이 항목에 전달됩니다 (하나는 첫 번째 목록에 하나는 두 번째 목록을 작성하고 다른 하나는 두 번째 목록에 인쇄). 또한 우리가 이것을 가지고 있다면 :

void Print6Foos(IList<Foo> foos)
{
    int counter = 0;
    foreach (foo in foos)
    {
        Console.WriteLine(foo);
        ++ counter;
        if (6 < counter)
        {
            return;
        }
    }
}

// ........

Print6Foos(foos.Where((foo, i) => i % 2 == 0).ToList());

foos수천 개의 항목이 있으면 어떻게 됩니까? 우리는 그것들을 모두 살펴보고 6 장만 인쇄하기 위해 거대한 목록을 할당해야합니다!

열거 자-C #의 반복자 패턴 버전을 입력하십시오. 우리의 함수가리스트를 받도록하는 대신, 우리는 그것을 받아들입니다 Enumerable:

void Print6Foos(Enumerable<Foo> foos)
{
    // everything else stays the same
}

// ........

Print6Foos(foos.Where((foo, i) => i % 2 == 0));

이제 Print6Foos목록의 처음 6 개 항목을 느리게 반복 할 수 있으며 나머지 항목은 건드릴 필요가 없습니다.

내부 표현을 공개하지 않는 것이 여기의 핵심입니다. 때 Print6Foos그 지원 랜덤 액세스 뭔가 - - 목록을 받아, 우리는 그것에게 목록을 제공했고 서명이 보장되지 않기 때문에, 따라서 우리는 목록을 할당해야한다고 그것을 통해서만으로 반복됩니다. 구현을 숨기면 Enumerable함수가 실제로 필요한 것만 지원 하는보다 효율적인 객체를 쉽게 만들 수 있습니다 .


6

내부 표현을 노출하는 것은 사실상 좋은 생각이 아닙니다 . 코드를 추론하기 어렵게 만들뿐만 아니라 유지 관리하기도 어렵습니다. 내부 표현을 선택했다고 가정 해 봅시다 IList<T>. 코드를 사용하는 사람은 누구나 목록에 액세스 할 수 있으며 코드는 목록 인 내부 표현에 의존 할 수 있습니다.

어떤 이유로 든 내부 표현을 IAmWhatever<T>나중에 변경하기로 결정했습니다 . 대신 단순히 클래스의 내부를 변경하는 대신 유형의 내부 표현에 의존하는 모든 코드 줄과 메소드 를 변경해야 합니다 IList<T>. 클래스를 사용하는 유일한 사람 일 때 번거롭고 오류가 발생하기 쉽지만 클래스를 사용하여 다른 사람들의 코드를 손상시킬 수 있습니다. 내부를 노출시키지 않고 공개 메소드 세트를 방금 노출했다면 클래스 외부의 코드 라인을 변경하지 않고 내부 표시를 변경하여 아무것도 변경되지 않은 것처럼 작동했을 수 있습니다.

이것이 캡슐화가 사소한 소프트웨어 설계에서 가장 중요한 측면 중 하나 인 이유입니다.


6

말을 적게할수록 말을 적게해야합니다.

적게 물어 볼수록 덜 알려야합니다.

코드가 노출 만하는 경우 IEnumerable<T>, 지원 GetEnumrator()하는 다른 코드로 대체 할 수 있습니다 IEnumerable<T>. 이것은 유연성을 추가합니다.

코드 만 사용 IEnumerable<T>하는 경우 구현하는 모든 코드에서 지원할 수 있습니다 IEnumerable<T>. 다시 한 번 더 유연성이 있습니다.

예를 들어, linq-to-objects는 모두에 의존합니다 IEnumerable<T>. 특정 구현을 빠르게 처리하지만 IEnumerable<T>테스트 및 사용 방식으로 항상 사용 GetEnumerator()하고 항상 IEnumerator<T>구현으로 돌아갈 수 있습니다. 이것은 배열이나리스트 위에 구축 된 것보다 훨씬 더 유연합니다.


좋은 대답입니다. 짧게 유지한다는 점에서 매우 적절하므로 첫 번째 문장에서 조언을 따르십시오. 브라보!
Daniel Hollinrake

0

사용 거울에 같은 문구 I @CaptainMan의 코멘트 : ". 그것은 소비자가 내부 세부 사항을 알고 그것은에 고객을 위해 나쁜 괜찮지 필요 . 내부 세부 사항을 알고"

고객이 코드 를 입력 array하거나 입력 해야하는IList<T> 경우 해당 유형이 내부 세부 정보 인 경우 소비자 해당 유형을 올바르게 가져와야 합니다 . 그들이 틀렸다면, 소비자는 컴파일되지 않거나 심지어 잘못된 결과를 얻을 수도 있습니다. 이것은 "구체적으로 세부 사항을 공개해서는 안되기 때문에 항상 구현 세부 사항을 숨기십시오"라는 다른 답변과 동일한 동작을 나타내지 만 노출하지 않는 이점을 파기 시작합니다. 구현 세부 사항을 노출하지 않으면 고객은이를 사용하여 자신을 바인드 할 수 없습니다!

나는이 관점에서 그것을 생각하고 싶다. 왜냐하면 "항상 구현 세부 사항을 숨기고"라기보다는 의미의 음영으로 구현을 숨기는 개념을 열기 때문이다. 구현 노출이 완전히 유효한 상황이 많이 있습니다. 실시간 장치 드라이버의 경우 과거 추상화 계층을 건너 뛰고 바이트를 올바른 위치로 찌르는 기능이 타이밍을 만드는 것의 차이 일 수 있습니다. 또는 "좋은 API"를 만들 시간이없고 즉시 사물을 사용해야하는 개발 환경을 고려하십시오. 이러한 환경에서 기본 API를 노출하는 것은 마감일을 만드는 것과 차이가있을 수 있습니다.

그러나 이러한 특수 사례를 남겨두고보다 일반적인 사례를 살펴보면 구현을 숨기는 것이 점점 더 중요해지기 시작합니다. 구현 세부 사항을 노출하는 것은 로프와 같습니다. 올바르게 사용하면 키가 큰 산을 확장하는 것과 같이 모든 종류의 작업을 수행 할 수 있습니다. 잘못 사용하면 올가미로 묶을 수 있습니다. 제품 사용 방법에 대한 지식이 적을수록 더주의해야합니다.

내가 몇 번이고 다시 보는 사례 연구는 개발자가 구현 세부 사항을 숨기는 데 크게주의하지 않는 API를 만드는 사례입니다. 해당 라이브러리를 사용하는 두 번째 개발자는 API에 원하는 주요 기능이 누락 된 것을 발견했습니다. 몇 달이 소요될 수있는 기능 요청을 작성하는 대신 숨겨진 구현 세부 사항에 대한 액세스를 남용하기로 결정합니다 (몇 초 소요). 이것은 그 자체로는 나쁘지 않지만 종종 API 개발자는 API의 일부가 될 계획을 세우지 않았습니다. 나중에 API를 개선하고 기본 세부 사항을 변경하면 누군가 API의 일부로 사용했기 때문에 이전 구현에 연결되어 있음을 알게됩니다. 최악의 버전은 누군가가 API를 사용하여 고객 중심 기능을 제공하는 곳입니다. 급여는이 결함이있는 API와 연결되어 있습니다! 고객에 대해 충분히 알지 못했을 때 구현 세부 정보를 노출하는 것만으로도 API 주변에 올가미를 묶을 수있는 충분한 밧줄로 판명되었습니다!


1
Re : "또는 '좋은 API를 만들 시간이없고 즉시 사용해야하는 개발 환경을 고려하십시오." 또는 확실하지 않은지 확실하지 않다면, ' Death March : The Complete Software Developer 's Guide to Surviving'Mission Impossible 'Projects' 책을 추천합니다 . 주제에 대한 훌륭한 조언이 있습니다. (또한 종료하지 않기로 마음을 바꾸는 것이 좋습니다.)
ruakh

@ruakh 좋은 API를 만들 시간이없는 환경에서 작업하는 방법을 이해하는 것이 항상 중요하다는 것을 알았습니다. 솔직히, 우리가 상아탑 접근 방식을 좋아하는만큼 실제 코드는 실제로 비용을 지불하는 것입니다. 우리가 더 빨리 인정할수록 정확한 환경에 맞게 API 품질을 생산 코드와 균형을 맞추고 소프트웨어 균형에 더 빨리 접근 할 수 있습니다. 나는 너무 많은 젊은 개발자들이 이상을 어지럽히는 것을 보았지만 그것들을 구부릴 필요가 있다는 것을 깨닫지 못했습니다. (저는 또한 그 젊은 개발자였습니다. 5 년 동안 "좋은 API"디자인을 회복하고 있습니다.)
Cort Ammon-Reinstate Monica

1
실제 코드는 청구서를 지불합니다. 좋은 API 디자인은 실제 코드를 계속 생성 할 수 있도록하는 방법입니다. 프로젝트를 유지 보수 할 수 없게되면 버그를 수정하지 않고 새 버그를 작성하지 않고 버그를 수정하는 것이 불가능 해집니다. (그리고 어쨌든 이것은 잘못된 딜레마입니다. 좋은 코드에 필요한 것은 백본 만큼 시간 이 아닙니다 .)
ruakh

1
@ruakh 내가 찾은 것은 "좋은 API"없이 좋은 코드를 만들 수 있다는 것입니다. 기회가 주어지면 좋은 API는 훌륭하지만 필요로 취급하면 가치보다 더 많은 투쟁을 일으 킵니다. 고려하십시오 : 인체에 대한 API는 끔찍하지만, 우리는 여전히 공학의 작은 위업이라고 생각합니다 =)
Cort Ammon-Reinstate Monica

내가 줄 다른 예는 타이밍을 만드는 것에 대한 논쟁에서 영감을 얻은 것입니다. 내가 작업하는 그룹 중 하나에는 어려운 실시간 요구 사항과 함께 개발에 필요한 일부 장치 드라이버가 있습니다. 작업 할 API가 2 개있었습니다. 하나는 좋았고 하나는 더 적었습니다. 좋은 API는 구현을 숨겨서 타이밍을 만들 수 없습니다. API가 적을수록 구현이 노출되어 프로세서 별 기술을 사용하여 작동하는 제품을 만들 수 있습니다. 좋은 API는 정중하게 선반에 배치되었으며 타이밍 요구 사항을 계속 충족했습니다.
Cort Ammon-복원 모니카

0

데이터의 내부 표현이 무엇인지 또는 미래에 어떻게 될지 반드시 알 필요는 없습니다.

배열로 나타내는 데이터 소스가 있다고 가정하면 정기적 인 for 루프로 반복 할 수 있습니다.

이제 리팩토링하고 싶다고 가정하십시오. 상사가 데이터 소스가 데이터베이스 객체가되도록 요청했습니다. 또한 API, 해시 맵, 링크 된 목록, 회전하는 마그네틱 드럼, 마이크 또는 실시간 몽골 외부에서 발견되는 야크 부스러기에서 데이터를 가져 오는 옵션을 원합니다.

for 루프가 하나만 있으면 액세스가 예상되는 방식으로 데이터 객체에 액세스하도록 코드를 쉽게 수정할 수 있습니다.

데이터 객체에 접근하려고하는 1000 개의 클래스가 있다면, 이제 큰 리팩토링 문제가 있습니다.

반복자는 목록 기반 데이터 소스에 액세스하는 표준 방법입니다. 일반적인 인터페이스입니다. 그것을 사용하면 코드 데이터 소스를 무시할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.