항상 내부 데이터 구조를 완전히 캡슐화해야합니까?


11

이 수업을 고려하십시오 :

class ClassA{

    private Thing[] things; // stores data

    // stuff omitted

    public Thing[] getThings(){
        return things;
    }

}

이 클래스는 데이터를 저장하는 데 사용하는 배열을 관심있는 클라이언트 코드에 노출합니다.

작업중 인 앱 에서이 작업을 수행했습니다. 나는 ChordProgression일련의 Chords 를 저장하고 다른 것들을 하는 클래스를 가지고있었습니다 . Chord[] getChords()코드 배열을 반환 하는 메서드가 있었습니다 . 데이터 구조가 배열에서 ArrayList로 변경되어야 할 때 모든 클라이언트 코드가 손상되었습니다.

이로 인해 다음과 같은 접근 방식이 더 좋습니다.

class ClassA{

    private Thing[] things; // stores data

    // stuff omitted

    public Thing[] getThing(int index){
        return things[index];
    }

    public int getDataSize(){
        return things.length;
    }

    public void setThing(int index, Thing thing){
        things[index] = thing;
    }

}

데이터 구조 자체를 노출하는 대신, 데이터 구조에 제공되는 모든 오퍼레이션은 데이터 구조를 위임하는 공용 메소드를 사용하여이를 둘러싸는 클래스에 의해 직접 제공됩니다.

데이터 구조가 변경되면 이러한 메소드 만 변경하면되지만 변경 후에도 모든 클라이언트 코드가 계속 작동합니다.

배열보다 복잡한 컬렉션은 내부 데이터 구조에 액세스하기 위해 엔 클로징 클래스가 세 개 이상의 메서드를 구현해야 할 수도 있습니다.


이 접근법은 일반적입니까? 이거 어떻게 생각해? 다른 단점이 있습니까? 내부 데이터 구조에 위임하기 위해 엔 클로징 클래스가 최소한 세 개의 공개 메소드를 구현하도록하는 것이 합리적입니까?

답변:


14

다음과 같은 코드 :

   public Thing[] getThings(){
        return things;
    }

액세스 방법이 내부 데이터 구조를 직접 반환하는 것 외에는 아무것도하지 않기 때문에 의미가 없습니다. 당신은 또한로 선언 Thing[] things할 수 있습니다 public. 액세스 방법의 기본 개념은 내부 변경으로부터 클라이언트를 격리하고 인터페이스에서 허용하는 신중한 방법을 제외하고 실제 데이터 구조를 조작하지 못하게하는 인터페이스를 만드는 것입니다. 모든 클라이언트 코드가 깨졌을 때 발견 한 것처럼 액세스 방법은 그렇게하지 않았습니다. 코드 낭비 일뿐입니다. 많은 프로그래머들이 액세스 코드로 모든 것을 캡슐화해야한다는 것을 어딘가에서 배웠기 때문에 코드를 작성하는 경향이 있다고 생각합니다. 그러나 그것이 내가 설명 한 이유입니다. 액세스 방법이 목적에 맞지 않을 때 "양식"을 따르기 만하면 소음이 발생합니다.

캡슐화의 가장 중요한 목표 중 일부를 달성하는 제안 된 솔루션을 확실히 추천합니다. 클래스의 내부 구현 세부 정보에서 클라이언트를 격리시키고 내부 데이터 구조를 건드릴 수없는 강력하고 신중한 인터페이스 제공 "가장 필요한 특권의 법칙"이라는 적절한 방식으로 결정하십시오. CLR, STL, VCL과 같이 널리 사용되는 OOP 프레임 워크를 살펴보면 제안한 패턴이 바로 그 이유 때문입니다.

당신이해야 항상 그렇게? 반드시 그런 것은 아닙니다. 예를 들어, 기본적으로 주 작업자 클래스의 구성 요소이고 "직면"이 아닌 도우미 또는 친구 클래스가있는 경우 필요하지 않습니다. 불필요한 코드를 많이 추가하는 것은 과잉입니다. 그리고 그 경우에는 액세스 방법을 전혀 사용하지 않을 것입니다. 설명 된 것처럼 의미가 없습니다. 데이터 구조를 사용하는 기본 클래스에만 적용되는 방식으로 데이터 구조를 선언하기 만하면됩니다. 대부분의 언어는이를 수행하는 방법을 지원 friend하거나 기본 작업자 클래스와 같은 파일에서 선언 하는 방식 등입니다.

내가 당신의 제안에서 볼 수 있는 유일한 단점 은 코딩하는 것이 더 많은 작업이라는 것입니다 (이제 소비자 클래스를 다시 코딩해야하지만 어쨌든 그렇게해야합니다.)하지만 실제로 단점은 아닙니다. -제대로해야하고 때로는 더 많은 작업이 필요합니다.

훌륭한 프로그래머가 좋은 점 중 하나는 추가 작업이 가치가있을 때와 그렇지 않을 때를 아는 것입니다. 장기적으로 여분을 추가하는 것은 미래에 큰 배당금을 지불 할 것입니다-이 프로젝트가 아니라면 다른 사람들에게 지불하십시오. 로봇이 규정 된 형태를 따르는 것뿐만 아니라 올바른 방법으로 코딩하고 머리를 사용하는 법을 배우십시오.

배열보다 복잡한 컬렉션은 내부 데이터 구조에 액세스하기 위해 엔 클로징 클래스가 세 개 이상의 메서드를 구현해야 할 수도 있습니다.

포함 클래스를 통해 전체 데이터 구조를 노출하는 경우 IMO는 단순히 더 안전한 인터페이스 ( "래퍼 클래스")를 제공하지 않는 경우 해당 클래스가 전혀 캡슐화되지 않은 이유를 고려해야합니다. 당신은 포함하는 클래스가 그 목적을 위해 존재하지 않는다고 말하고 있습니다. 아마도 디자인에 옳지 않은 것이있을 것입니다. 클래스를보다 신중한 모듈로 나누고 계층화하는 것을 고려하십시오.

클래스는 명확하고 신중한 목적을 가지고 있어야하며 더 이상 해당 기능을 지원할 수있는 인터페이스를 제공해야합니다. 함께 속해 있지 않은 것들을 함께 묶으려고했을 수도 있습니다. 그렇게하면 변경을 구현해야 할 때마다 문제가 발생합니다. 수업이 작고 신중할수록 주변 상황을 쉽게 바꿀 수 있습니다. LEGO를 생각해보십시오.


1
대답 해줘서 고마워. 질문 : 내부 데이터 구조에 5 개의 퍼블릭 메서드가 있다면 클래스의 퍼블릭 인터페이스에서 모두 제공해야하는 것은 어떻습니까? 예를 들어, 자바의 ArrayList는 다음과 같은 방법이있다 : get(index), add(), size(), remove(index),와 remove(Object). 제안 된 기술을 사용하여이 ArrayList를 포함하는 클래스에는 내부 컬렉션에 위임하기위한 공용 메서드가 5 개 있어야합니다. 그리고 프로그램 에서이 클래스의 목적은이 ArrayList를 캡슐화하지 않고 다른 것을 수행하는 것입니다. ArrayList는 세부 사항입니다. [...]
Aviv Cohn

내부 데이터 구조는 일반적인 멤버이므로 위의 기술을 사용하면 추가 5 개의 공개 메소드를 제공하는 클래스가 포함되어 있어야합니다. 당신의 의견으로는-이것이 합리적입니까? 또한-이것이 일반적입니까?
Aviv Cohn

@Prog- 내부 데이터 구조에 5 개의 공개 메소드가있는 경우는 어떻습니까? IMO 메인 클래스 내부의 전체 헬퍼 클래스를 랩핑하고 그런 식으로 노출해야하는 경우 다시 생각해야합니다. 디자인-공개 수업이 너무 많거나 적절한 인터페이스를 제공하지 않습니다. 클래스는 매우 신중하고 명확하게 정의 된 역할을 가져야하며 해당 인터페이스는 해당 역할과 해당 역할 만 지원해야합니다. 수업을 해체하고 계층화하는 것에 대해 생각하십시오. 클래스는 캡슐화 이름으로 모든 종류의 객체를 포함하는 "주방 싱크"가되어서는 안됩니다.
벡터

랩퍼 클래스를 통해 전체 데이터 구조를 노출하는 경우 IMO는 더 안전한 인터페이스를 제공하지 않는 경우 해당 클래스가 캡슐화되는 이유에 대해 생각해야합니다. 당신은 포함하는 클래스가 그 목적을 위해 존재하지 않는다고 말하고 있습니다. 그래서이 디자인에 옳지 않은 것이 있습니다.
벡터

1
@Phoshi- 읽기 전용 키워드입니다 -동의합니다. 그러나 OP는 읽기 전용에 대해 이야기하고 있지 않습니다. 예를 들어 remove읽기 전용이 아닙니다. 내 이해는 OP가 제안 된 변경 전에 원본 코드와 같이 모든 것을 공개하고 싶어한다는 public Thing[] getThings(){return things;}것입니다.
벡터

2

당신은 물었다 : 나는 항상 내부 데이터 구조를 완전히 캡슐화해야합니까?

간단한 답변 : 예, 대부분은 아니지만 항상 그렇습니다 .

긴 답변 : 수업은 다음과 같은 범주로 진행됩니다.

  1. 간단한 데이터를 캡슐화하는 클래스 예 : 2D 점. X 및 Y 좌표를 가져 오거나 설정할 수있는 공용 함수를 쉽게 만들 수 있지만 문제없이 내부 데이터를 쉽게 숨길 수 있습니다. 이러한 클래스의 경우 내부 데이터 구조 세부 사항을 공개 할 필요가 없습니다.

  2. 컬렉션을 캡슐화하는 컨테이너 클래스 STL에는 클래식 컨테이너 클래스가 있습니다. 나는 그중에서도 고려 std::string하고 std::wstring있습니다. 그들은 추상화를 처리하지만, 할 수있는 풍부한 인터페이스를 제공 std::vector, std::string그리고 std::wstring또한 원시 데이터에 접근 할 수있는 기능을 제공합니다. 나는 그것들을 제대로 디자인되지 않은 클래스라고 부르지 않을 것입니다. 나는 원시 데이터를 노출하는 이러한 클래스에 대한 타당성을 모른다. 그러나 필자의 작업에서 성능상의 이유로 수백만 메쉬 노드 및 해당 메쉬 노드의 데이터를 처리 할 때 원시 데이터를 노출해야한다는 것을 알게되었습니다.

    클래스의 내부 구조를 노출하는 데있어 중요한 것은 녹색 신호를주기 전에 길고 열심히 생각해야한다는 것입니다. 인터페이스가 프로젝트 내부에있는 경우 나중에 변경하는 것이 비용이 많이 들지만 불가능하지는 않습니다. 인터페이스가 프로젝트 외부에있는 경우 (예 : 다른 응용 프로그램 개발자가 사용할 라이브러리를 개발할 때) 클라이언트를 잃지 않고 인터페이스를 변경하지 못할 수 있습니다.

  3. 본질적으로 기능적인 클래스. 예 : std::istream, std::ostream상기 STL 컨테이너 반복자. 이러한 클래스의 내부 세부 정보를 공개하는 것은 어리석은 일입니다.

  4. 하이브리드 클래스. 이들은 일부 데이터 구조를 캡슐화하지만 알고리즘 기능을 제공하는 클래스입니다. 개인적으로, 나는 이것이 잘못된 생각 디자인의 결과라고 생각합니다. 그러나이를 찾은 경우 내부 데이터를 사례별로 공개하는 것이 적절한 지 결정해야합니다.

결론 : 클래스의 내부 데이터 구조를 노출 해야하는 유일한 시점은 성능 병목 현상이 발생한 시점입니다.


STL이 내부 데이터를 노출시키는 가장 중요한 이유는 포인터를 기대하는 모든 기능과의 호환성 때문이라고 생각합니다.
Siyuan Ren

0

원시 데이터를 직접 반환하는 대신 다음과 같이 시도하십시오.

class ClassA {
  private Things[] things;
  ...
  public Things[] asArray() { return things; }
  public List<Thing> asList() { ... }
  ...
}

따라서, 당신은 본질적으로 세상의 모든 얼굴을 원하는 맞춤형 컬렉션을 제공하고 있습니다. 그러면 새로운 구현에서

class ClassA {
  private List<Thing> things;
  ...
  public Things[] asArray() { return things.asArray(); }
  public List<Thing> asList() { return things; }
  ...
}

이제 적절한 캡슐화가 구현 구현 세부 사항을 숨기고 이전 버전과의 호환성 (비용)을 제공합니다.


이전 버전과의 호환성을위한 영리한 아이디어. 그러나 이제는 적절한 캡슐화가 구현 구현 세부 정보를 숨 깁니다 . 고객은 여전히 ​​뉘앙스를 다루어야합니다 List. 더 견고하게 만들기 위해 캐스트를 사용하더라도 단순히 데이터 멤버를 반환하는 액세스 방법은 실제로 캡슐화 IMO가 아닙니다. 작업자 클래스는 클라이언트가 아닌 모든 것을 처리해야합니다. 클라이언트가 "멍청한"상태 일수록 더욱 강력 해집니다. 여담으로, 나는 당신이 질문 ... 대답 잘 모르겠어요
벡터

1
@Vector-당신이 맞습니다. 반환 된 데이터 구조는 여전히 변경 가능하며 부작용으로 인해 정보 숨기기가 중단됩니다.
BobDalgleish

반환 된 데이터 구조는 여전히 변경 가능하며 부작용으로 인해 정보가 숨겨져 있습니다 . 그렇습니다. 위험합니다. 나는 단순히 고객의 요구에 대해 생각하고 있었는데, 이것이 질문의 초점이었습니다.
벡터

@BobDalgleish : 원본 컬렉션의 사본을 반환하지 않으시겠습니까?
Giorgio

1
@ BobDalgleish : 좋은 성능상의 이유가 없다면 내부 데이터 구조에 대한 참조를 반환하여 사용자가 매우 나쁜 설계 결정으로 변경할 수 있도록 고려할 것입니다. 객체의 내부 상태는 적절한 공개 방법을 통해서만 변경해야합니다.
조르지오

0

그런 것들에 대한 인터페이스를 사용해야합니다. Java의 배열은 이러한 인터페이스를 구현하지 않으므로 지금은 도움이되지 않지만 지금부터 수행해야합니다.

class ClassA{

    public ClassA(){
        things = new ArrayList<Thing>();
    }

    private List<Thing> things; // stores data

    // stuff omitted

    public List<Thing> getThings(){
        return things;
    }

}

그런 식 ArrayList으로 LinkedList또는 다른 것으로 변경할 수 있으며 (의사?) 랜덤 액세스 권한이있는 모든 Java 컬렉션 (배열 제외)이 아마도 구현하기 때문에 코드를 손상시키지 않습니다 List.

을 사용하여 Collection메서드를 제공 List하지만 무작위 액세스없이 컬렉션을 지원하거나 Iterable스트림을 지원할 수는 있지만 액세스 방법 측면에서 많이 제공하지는 않습니다.


-1-타협이 불충분하고 특히 안전하지 않은 IMO : 내부 데이터 구조를 클라이언트에 노출시키고 있습니다. "Java 컬렉션은 아마도 목록을 구현할 것이므로"마스킹하고 최선을 다하기 만하면됩니다 . 솔루션이 진정 다형성 / 상속 기반이라면 모든 컬렉션이 List파생 클래스로 구현 되는 것이 더 합리적이지만 "최고를 추구"하는 것은 좋은 생각이 아닙니다. "좋은 프로그래머는 일방 통행로에서 두 가지 방법을 모두 본다".
벡터

@Vector 예, 향후 Java 컬렉션이 구현 List(또는 Collection적어도 Iterable) 한다고 가정 합니다. 그것은이 인터페이스의 요점이며 Java Arrays가 구현하지 않는 것은 부끄러운 일이지만 Java 컬렉션의 공식 인터페이스이므로 Java 컬렉션이 구현되지 않는다고 가정하지는 않습니다. 이 List경우 AbstractList 로 감싸는 것이 매우 쉽습니다 .
Idan Arye

당신은 당신의 가정이 사실상 사실임을 보장한다고 말하고 있습니다. OK-설명하기에 충분하기 때문에 다운 투표를 제거 할 것입니다 (나는 허용되지 않을 때). 나는 Java 사람이 아닙니다. 삼투를 제외하고. 그럼에도 불구하고 내부 데이터 구조를 노출하는 방법에 관계없이 내부 데이터 구조를 노출한다는 아이디어를 지원하지 않으며 OP의 질문에 직접 대답하지 않았습니다. 이는 실제로 캡슐화에 관한 것입니다. 즉, 내부 데이터 구조에 대한 액세스를 제한합니다.
벡터

1
@Vector 예, 사용자는 캐스트 할 수 List로를 ArrayList하지만, 구현이 100 % 보호 할 수 있습니다처럼 아니에요 - 당신은 항상 액세스 민간 분야에 반사를 사용할 수 있습니다. 그렇게하는 것은 눈살을 찌푸리게하지만, 캐스팅도 눈살을 찌푸리게합니다. 캡슐화의 요점은 악의적 인 해킹을 막는 것이 아니라 사용자가 변경하려는 구현 세부 사항에 따라 사용자를 막는 것입니다. List인터페이스를 사용하면 정확하게 수행됩니다. 클래스의 사용자는 변경 될 수 List있는 구체적인 ArrayList클래스 대신 인터페이스에 의존 할 수 있습니다.
Idan Arye

항상 리플렉션을 사용하여 개인 필드에 액세스 할 수 있습니다. 누군가가 잘못된 코드를 작성하고 디자인을 파괴하려는 경우 그렇게 할 수 있습니다. 오히려 그것은 사용자를 막는 것입니다 ... 캡슐화 의 가지 이유입니다. 다른 하나는 클래스의 내부 상태의 무결성과 일관성을 보장하는 것입니다. 문제는 "악의적 인 해킹"이 아니라 불충분 한 버그로 이어지는 열악한 조직입니다. "최소한의 필요한 권한의 법칙"-클래스의 소비자에게 필수 사항 만 제공하십시오. 내부 데이터 구조 전체를 공개해야하는 경우 디자인 문제가 있습니다.
벡터

-2

이것은 외부 데이터로부터 내부 데이터 구조를 숨기는 것이 일반적입니다. 때때로 그것은 DTO에서 특별히 과잉입니다. 도메인 모델에 권장합니다. 노출해야 할 경우 불변 사본을 반환하십시오. 이와 함께 get, set, remove 등과 같은 메소드를 갖는 인터페이스를 만드는 것이 좋습니다.


1
이것은 3 가지 이상의 사전 답변을 제공하지 않는 것 같습니다
gnat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.