태그 보관물: encapsulation

encapsulation

항상 내부 데이터 구조를 완전히 캡슐화해야합니까? 다음과 같은 접근 방식이

이 수업을 고려하십시오 :

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;
    }

}

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

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

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


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



답변

다음과 같은 코드 :

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

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

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

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

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

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

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

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

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


답변

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

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

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

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

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

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

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

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

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


답변

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

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; }
  ...
}

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


답변

그런 것들에 대한 인터페이스를 사용해야합니다. 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스트림을 지원할 수는 있지만 액세스 방법 측면에서 많이 제공하지는 않습니다.


답변

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


답변