캡슐화는 여전히 OOP가 서있는 코끼리 중 하나입니까?


97

캡슐화는 모든 또는 거의 모든 필드를 비공개로 만들고 getter / setter에 의해 노출되도록 지시합니다. 그러나 이제는 Lombok 과 같은 라이브러리가 나타나서 하나의 짧은 주석으로 모든 개인 필드를 노출 할 수 @Data있습니다. 모든 개인 필드에 대한 게터, 세터 및 설정 생성자를 만듭니다.

누군가 나에게 모든 분야를 비공개로 숨기고 그 이후에 여분의 기술로 모든 분야를 노출시키는 의미가 무엇인지 설명해 줄 수 있습니까? 그렇다면 왜 우리는 단순히 공공 장소 만 사용하지 않습니까? 나는 우리가 출발점으로 돌아 가기 위해 길고 힘든 길을 걸었다 고 생각합니다.

예, 게터와 세터를 통해 작동하는 다른 기술이 있습니다. 그리고 간단한 공공 장소에서는 사용할 수 없습니다. 그러나 이러한 기술은 공개 게터 / 세터 뒤에있는 개인 필드와 같은 수많은 속성 을 가지고 있기 때문에 나타납니다 . 만약 우리가 그 속성을 가지고 있지 않다면,이 기술들은 다른 방식으로 발전하여 공공 장소를 지원할 것입니다. 그리고 모든 것이 간단하고 지금은 롬복이 필요하지 않습니다.

이주기 전체의 의미는 무엇입니까? 그리고 실제 프로그래밍에서 캡슐화가 실제로 어떤 의미가 있습니까?


19
"It will create getters, setters and setting constructors for all private fields."-이 도구를 설명하는 방식 캡슐화를 유지하는 것처럼 들립니다 . (적어도 느슨하고 자동화 된 다소 빈약 한 모델 의미에서) 문제가 정확히 무엇입니까?
David

78
캡슐화는 공개 계약 (일반적으로 인터페이스) 뒤에 오브젝트의 구현 내부를 숨기고 있습니다. 게터와 세터는 정확히 반대의 일을합니다. 객체의 내부를 노출 시키므로 캡슐화가 아니라 게터 / 세터에 문제가 있습니다.

22
@VinceEmigh 데이터 클래스 에는 캡슐화가 없습니다 . 이들의 인스턴스는 프리미티브가 정확히 의미에서
Caleth

10
자바 빈즈를 사용하는 것은 @VinceEmigh OO하지 , 그 것이다 절차 . 문헌에서 "대상"이라고 부르는 것은 역사의 실수입니다.
Caleth

28
나는 수년 동안 이것에 대해 많은 생각을 해왔습니다. OOP의 의도가 구현과 다른 경우라고 생각합니다. 스몰 토크를 연구 한 후에는 OOP의 캡슐화가 의도 된 바를 분명히 알 수 있습니다 (즉, 각 클래스는 공유 프로토콜과 같은 방법을 가진 독립적 인 컴퓨터와 같음). 개념적 캡슐화 를 제공하지 않는 getter / setter 객체 (아무것도 숨기고 아무것도 관리하지 않으며 데이터 이외의 책임은 없음)는 있지만 여전히 속성을 사용합니다.
jrh

답변:


82

getter / setter를 사용하여 모든 속성을 공개하면 C 또는 다른 절차 언어로 여전히 사용되는 데이터 구조 만 얻게됩니다. 캡슐화 가 아니며 롬복은 절차 코드로 작업하는 것을 덜 고통스럽게 만듭니다. 일반 공공 장소만큼 나쁜 게터 / 세터. 실제로 차이는 없습니다.

그리고 데이터 구조는 객체가 아닙니다. 인터페이스를 작성하여 객체를 만들기 시작하면 인터페이스에 게터 / 세터를 추가하지 않습니다. 속성을 노출하면 데이터 조작이 객체 외부에 있고 코드베이스 전체에 퍼져있는 스파게티 절차 코드가 생성됩니다. 이제 데이터를 다루고 객체와 대화하는 대신 데이터를 조작합니다. 게터 / 세터를 사용하면 데이터 중심의 절차 적 프로그래밍을 수행 할 수 있습니다. 데이터 가져 오기-작업 수행-데이터 설정

OOP 캡슐화는 올바른 방법으로 수행되면 코끼리입니다. 객체가 그에 대한 모든 권한을 갖도록 상태 및 구현 세부 정보를 캡슐화해야합니다. 논리는 객체 내부에 집중되며 코드베이스 전체에 퍼지지 않습니다. 그리고 예-코드를 유지 관리하기 쉽도록 프로그래밍에서 캡슐화가 여전히 필수적입니다.

EDITS

토론이 진행된 후 몇 가지 사항을 추가하고 싶습니다.

  • getter / setter를 통해 얼마나 많은 속성을 노출하고 얼마나 신중하게 수행하는지는 중요하지 않습니다. 더 선택적으로 캡슐화하여 코드 OOP를 만들 수는 없습니다. 노출하는 모든 속성으로 인해 노출 된 데이터로 처리하는 절차가 필요합니다. 더 선택적으로 코드를 느리게 퍼 뜨리십시오. 그것은 핵심을 바꾸지 않습니다.
  • 예, 시스템의 경계에서 다른 시스템이나 데이터베이스에서 데이터를 얻습니다. 그러나이 데이터는 또 다른 캡슐화 지점입니다.
  • 객체는 신뢰할 수 있어야합니다 . 물건의 전체 아이디어는 책임이 있으므로 직설적이고 명령적인 명령내릴 필요가 없습니다 . 대신 계약을 통해 객체가 잘 수행하도록 요청 합니다. 연기 부분 객체에 안전하게 위임 합니다. 객체 상태 및 구현 세부 정보를 캡슐화 합니다.

질문으로 돌아 가면 왜해야합니까? 이 간단한 예를 고려하십시오.

public class Document {
    private String title;

    public String getTitle() {
        return title;
    }
}

public class SomeDocumentServiceOrHandler {

    public void printDocument(Document document) {
        System.out.println("Title is " + document.getTitle());
    }
}

여기 우리는 getter에 의해 내부 세부 사항을 노출시키는 Document를 가지고 있으며, 객체 외부 에서 작동하는 외부 절차 코드를 가지고 있습니다 . 왜 이것이 나쁜가요? 이제 C 스타일 코드 만 있습니다. 예, 구조적이지만 실제로 차이점은 무엇입니까? C 파일을 다른 파일과 이름으로 구성 할 수 있습니다. 그리고 소위 레이어는 정확히 그렇게합니다. 서비스 클래스는 데이터를 다루는 수많은 절차입니다. 이 코드는 유지 관리가 쉽지 않으며 많은 단점이 있습니다.printDocument

public interface Printable {
    void print();
}

public final class PrintableDocument implements Printable {
    private final String title;

    public PrintableDocument(String title) {
        this.title = title;
    }

    @Override
    public void print() {
        System.out.println("Title is " + title);
    }
}

이것과 비교하십시오. 이제 계약서가 작성되었으며이 계약의 구현 세부 사항이 오브젝트 내부 에 숨겨져 있습니다 . 이제 해당 클래스를 실제로 테스트 할 수 있으며 해당 클래스는 일부 데이터를 캡슐화합니다. 해당 데이터를 사용하는 방법은 개체 문제입니다. 개체와 대화 하려면 지금 인쇄 하도록 요청 해야 합니다. 그것은 캡슐화이며 그것은 객체입니다. OOP를 통해 의존성 주입, 조롱, 테스트, 단일 책임 및 많은 이점을 최대한 활용할 수 있습니다.


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
maple_shaft

1
"일반 공공 장소만큼 나쁜 게터 / 세터"-적어도 많은 언어에서는 사실이 아닙니다. 일반적으로 일반 필드 액세스는 무시할 수 없지만 게터 / 세터는 무시할 수 있습니다. 그것은 어린이 수업에 다양성을 제공합니다. 또한 클래스의 동작을 쉽게 변경할 수 있습니다. 예를 들어 개인 필드가 지원하는 getter / setter로 시작한 후 나중에 다른 필드로 이동하거나 다른 값에서 값을 계산할 수 있습니다. 일반 필드로는 수행 할 수 없습니다. 일부 언어는 필드가 본질적으로 자동 게터 및 세터를 갖도록 허용하지만 Java는 그런 언어가 아닙니다.
Kat

아직 확신하지 못했습니다. 귀하의 예는 괜찮지 만 "진정한 것"이 아닌 다른 스타일의 코딩을 나타냅니다. 요즘 대부분의 언어는 다중 패러다임이며 순수한 절차 적 절차는 거의 없습니다. "OO 개념을 순수하게하기"위해 열심히 노력하십시오. 예를 들어, 인쇄 로직을 서브 클래스에 연결했기 때문에 예제에서 DI를 사용할 수 없습니다. 인쇄는 문서의 일부가 아니어야합니다. printableDocument는 getter를 통해 인쇄 가능한 부분을 PrinterService에 노출시킵니다.
T. Sar

이것에 대한 적절한 OO 접근 방식은, "Pure OO"접근 방식으로 가려면 메시지를 통해 지옥이 인쇄되어야 할 것을 요청 하는 추상 PrinterService 클래스 를 구현하는 무언가를 사용하는 것입니다-GetPrintablePart를 사용합니다. PrinterService를 구현하는 것은 모든 종류의 프린터가 될 수 있습니다. PDF, 화면, TXT로 인쇄 할 수 있습니다. 솔루션을 통해 인쇄 로직을 다른 것으로 교체 할 수 없어 결합 성이 떨어지고 유지 보수가 덜됩니다. "잘못된"예보다.
T. Sar

결론 : 게터와 세터는 사악하거나 OOP를 깨뜨리지 않습니다. 사용법을 이해하지 못하는 사람들이 있습니다. 사례 사례는 DI가 어떻게 작동하는지 전체 요점을 놓친 사람들의 교과서 사례입니다. "수정 된"예제는 이미 DI가 가능하고 분리되어 있으며 쉽게 조롱 할 수 있습니다 ... 실제로 "상속보다 컴포지션 선호"항목을 기억하십니까? OO 기둥 중 하나? 코드를 리팩토링하는 방식으로 창에 방금 던졌습니다. 이것은 심각한 코드 검토에서 나는 것이 아닙니다.
T. Sar

76

누군가 나를 설명 할 수 있습니까? 모든 분야를 비공개로 숨기고 그 후에 추가 기술로 모든 분야를 노출시키는 의미는 무엇입니까? 그렇다면 왜 우리는 단순히 공공 장소 만 사용하지 않습니까?

의미는 당신이 그렇게해서는 안된다는 것입니다 .

캡슐화는 실제로 액세스하기 위해 다른 클래스가 필요한 필드 만 노출하고 매우 선택적이고 신중하다는 것을 의미합니다.

마십시오 하지 만 기본적 당 모든 필드의 getter 및 setter를 제공합니다!

이것은 공개 게터와 세터의 개념이 유래 한 아이러니하게도 JavaBeans 사양의 정신에 완전히 반대합니다.

그러나 스펙을 살펴보면 getter 및 setter 작성이 매우 선택적이며 "읽기 전용"특성 (세터 없음) 및 "쓰기 전용"특성에 대해 이야기하고 있음을 알 수 있습니다. 얻는 사람).

또 다른 요인은 게터와 세터가 반드시 개인 필드에 간단하게 액세스 할 필요는 없다는 것입니다 . getter는 임의로 복잡한 방식으로 반환하는 값을 계산할 수 있습니다. 캐시 할 수도 있습니다. 세터는 값을 확인하거나 리스너에게 알릴 수 있습니다.

그래서 당신은 다음과 같습니다 캡슐화 당신이 실제로 노출 할 필요가 해당 기능을 노출 의미한다. 그러나 노출해야 할 것을 생각하지 않고 구문 변환을 수행하여 모든 것을 노출시키지 않으면 당연히 캡슐화되지 않습니다.


20
"[G] etters와 setter는 반드시 간단한 액세스는 아닙니다"라고 생각합니다. 코드가 이름으로 필드에 액세스하는 경우 나중에 동작을 변경할 수 없습니다. obj.get ()을 사용하는 경우 가능합니다.
Dan Ambrogio

4
@ jrh : Java에서 나쁜 습관이라고 들었다는 것을 들어 본 적이 없으며 매우 일반적입니다.
Michael Borgwardt

2
@MichaelBorgwardt 재미있는; 약간의 주제이지만 항상 Microsoft가 C #을 위해 그렇게하지 않는 이유를 궁금해했습니다. 이러한 지침은 C #에서 setter를 사용할 수있는 유일한 방법은 실패하거나 유효하지 않은 값 (예 : PositionInches 속성 및 PositionCentimeters 속성을 갖는 방법)으로 내부에서 사용되는 다른 값으로 지정된 값을 변환하는 것임을 암시합니다 내부에서 mm으로 자동 변환되는 위치)? 좋은 예는 아니지만 지금 당장 생각해 볼 수있는 최선입니다.
jrh

2
@jrh 그 소식통은 세터와는 반대로 게터에게는 그렇지 않다고 말합니다.
Captain Man

3
@ Gangnus와 당신은 누군가가 getter / setter 내부에 어떤 논리를 숨기고 있다는 것을 정말로 알고 있습니까? 우리는 getFieldName()자동 계약이 된 것에 익숙합니다 . 우리는 그 뒤에 복잡한 행동을 기대하지 않을 것입니다. 대부분의 경우 캡슐화가 바로 중단됩니다.
Izbassar 톨 레겐

17

문제의 요점은 귀하의 의견으로 설명됩니다.

나는 당신의 생각에 전적으로 동의합니다. 그러나 어딘가에 데이터가있는 객체를로드해야합니다. 예를 들어, XML에서. 그리고 그것을 지원하는 현재 플랫폼은 getter / setter를 통해 코드의 품질과 사고 방식을 저하시킵니다. 롬복은 실제로 그 자체로는 나쁘지 않지만 그 존재 자체는 우리에게 뭔가 나쁜 것이 있다는 것을 보여줍니다.

문제는 지속성 데이터 모델과 활성 데이터 모델을 혼합한다는 것 입니다.

응용 프로그램에는 일반적으로 여러 데이터 모델이 있습니다.

  • 데이터베이스와 통신 할 데이터 모델
  • 구성 파일을 읽을 수있는 데이터 모델
  • 다른 응용 프로그램과 통신하기위한 데이터 모델
  • ...

실제로 계산을 수행하는 데 사용하는 데이터 모델 위에 있습니다.

일반적으로 외부와의 통신에 사용되는 데이터 모델은 계산이 수행되는 내부 데이터 모델 (Business Object Model, BOM) 과 분리 되고 독립적 이어야합니다 .

  • 독립적 : 모든 클라이언트 / 서버를 변경하지 않고도 요구 사항에 따라 BOM에서 속성을 추가 / 제거 할 수 있습니다 ...
  • isolated : 불변이 존재하는 BOM에서 모든 계산을 수행하고 한 서비스에서 다른 서비스로 변경하거나 한 서비스를 업그레이드해도 코드베이스 전체에 잔물결이 발생하지 않습니다.

이 시나리오에서는 통신 계층에 사용 된 개체가 모든 항목을 공개하거나 게터 / 세터에 의해 노출되는 것이 좋습니다. 그것들은 변하지 않는 평범한 물건입니다.

반면에 BOM에는 변형이 있어야합니다. 일반적으로 세터가 많지 않아야합니다 (게터는 캡슐화를 어느 정도 줄이지 만 불변에는 영향을 미치지 않습니다).


3
통신 객체에 대한 getter 및 setter를 만들지 않습니다. 난 그냥 필드를 공개합니다. 유용한 것보다 더 많은 일을하는 이유는 무엇입니까?
immibis

3
@immibis : OP에 따르면 일반적으로 일부 프레임 워크에는 게터 / 세터가 필요하며,이 경우 준수해야합니다. OP는 단일 속성을 적용하여 자동으로 생성하는 라이브러리를 사용하므로 그다지 효과가 없습니다.
Matthieu M.

1
@ 강 너스 : 여기서 아이디어는 게터 / 세터가 경계 (I / O) 레이어로 격리되어 더러워지는 것은 정상이지만 나머지 응용 프로그램 (순수 코드)은 오염되지 않고 우아하게 유지된다는 것입니다.
Matthieu M.

2
@kubanczyk : 공식 정의는 내가 아는 한 비즈니스 객체 모델입니다. 여기에서 애플리케이션의 "순수한"코어에서 로직을 실행하는 데이터 모델 인 "내부 데이터 모델"에 해당합니다.
Matthieu M.

1
이것은 실용적인 접근 방식입니다. 우리는 하이브리드 세계에 살고 있습니다. 외부 라이브러리가 BOM 생성자에 전달할 데이터를 알 수 있다면 BOM 만 있었을 것입니다.
Adrian Iftode

12

다음을 고려하세요..

User속성 이있는 클래스가 있습니다 int age.

class User {
    int age;
}

User생년월일이 아니라 생년월일을 갖도록 이것을 확장하고 싶습니다 . 게터 사용하기 :

class User {
    private int age;

    public int getAge() {
        return age;
    }
}

int age보다 복잡한 필드를 교체 할 수 있습니다 LocalDate dateOfBirth.

class User {
    private LocalDate dateOfBirth;

    public int getAge() {
        LocalDate now = LocalDate.now();
        int year = ...; // calculate using dateOfBirth and now
        return year;
    }

    // other behaviors can now make use of dateOfBirth
}

계약 위반, 코드 파손 없음

필드 자체가 캡슐화됩니다.


이제 우려를 없애기 위해 ..

롬복의 @Data주석은 Kotlin의 데이터 클래스 와 유사합니다 .

모든 클래스가 행동 객체를 나타내는 것은 아닙니다. 캡슐화 해제는 기능 사용에 따라 다릅니다. getter를 통해 모든 필드를 노출해서는 안됩니다 .

캡슐화는 클래스 내에서 구조화 된 데이터 객체의 값 또는 상태를 숨기는 데 사용됩니다.

보다 일반적인 의미에서 캡슐화는 정보를 숨기는 행위입니다. 당신이 남용을한다면 @Data캡슐화를 깨뜨리고 있다고 생각하기 쉽다. 그러나 그것이 목적이 없다고 말하는 것은 아닙니다. 예를 들어, 자바 빈즈 (JavaBeans)는 일부 사람들에게 눈살을 찌푸립니다. 그러나 엔터프라이즈 개발에 광범위하게 사용됩니다.

Bean을 사용하여 엔터프라이즈 개발이 잘못되었다고 결론 내릴 수 있습니까? 당연히 아니지! 요구 사항은 표준 개발 요구 사항과 다릅니다. 콩이 남용 될 수 있습니까? 물론이야! 그들은 항상 학대받습니다!

롬복은 또한 요구 사항에 맞는 것을 지원 @Getter하고 @Setter독립적으로 사용합니다.


2
이것은 @Data타입에 대한 주석 을 두드리는
것과는

1
아니요, 필드는 숨겨져 있지 않습니다 . 무엇이든 올 수 있고setAge(xyz)
Caleth

9
@Caleth 필드 숨겨져 있습니다. 세터가 사전 및 사후 조건을 가질 수없는 것처럼 작동합니다. 이는 세터를 사용하는 매우 일반적인 사용 사례입니다. C #과 같은 언어에서도 C =에서와 같이 둘 다 지원되는 경향이 있기 때문에 필드 == 속성처럼 작동합니다. 필드를 다른 클래스로 옮길 수 있으며,보다 복잡한 표현으로 교체 할 수 있습니다.
Vince Emigh

1
그리고 내가 말하는 것은 중요하지 않습니다
Caleth

2
@Caleth 어떻게 중요하지 않습니까? 그건 말이되지 않습니다. 당신은 때문에 캡슐화가 적용되지 않는 말을하는지 당신은 이 정의에 적용하고 사용 사례의에도 불구하고, 그것은이 상황에서 중요하지 느낌?
Vince Emigh

11

캡슐화는 모든 또는 거의 모든 필드를 비공개로 만들고 getter / setter에 의해 노출되도록 지시합니다.

이것이 객체 지향 프로그래밍에서 캡슐화가 정의되는 방식이 아닙니다. 캡슐화 란 각 오브젝트가 캡슐과 같아야하며, 외부 쉘 (공용 API)은 내부 (개인 메소드 및 필드)에 대한 액세스를 보호하고 조절하여보기에서 숨 깁니다. 내부를 숨기면 발신자가 내부에 의존하지 않으므로 발신자를 변경하거나 재 컴파일하지 않고도 내부를 변경할 수 있습니다. 또한 캡슐화를 사용하면 호출자가 안전한 작업을 수행 할 수 있도록하여 각 개체가 자체 불변 값을 적용 할 수 있습니다.

따라서 캡슐화는 정보 숨기기의 특수한 경우로, 각 개체는 내부를 숨기고 변수를 적용합니다.

모든 필드에 대해 게터와 세터를 생성하는 것은 내부 데이터의 구조가 숨겨져 있지 않으며 변형을 적용 할 수 없기 때문에 매우 약한 캡슐화 형식입니다. 그러나 호출자를 변경하거나 다시 컴파일하지 않고도 데이터가 내부적으로 저장되는 방식을 변경할 수 있다는 장점이 있습니다 (이전 구조로 변환하거나 이전 구조에서 변환 할 수있는 한).

누군가 나에게 모든 분야를 비공개로 숨기고 그 이후에 여분의 기술로 모든 분야를 노출시키는 의미가 무엇인지 설명해 줄 수 있습니까? 그렇다면 왜 우리는 단순히 공공 장소 만 사용하지 않습니까? 나는 우리가 출발점으로 돌아 가기 위해 길고 힘든 길을 걸었다 고 생각합니다.

부분적으로 이것은 역사적인 사고로 인한 것입니다. 하나는 Java에서 메소드 호출 표현식과 필드 액세스 표현식이 호출 사이트에서 구문 상으로 다르다는 것입니다. 즉, 필드 액세스를 getter 또는 setter 호출로 바꾸면 클래스의 API가 중단됩니다. 따라서 접근자가 필요한 경우 지금 작성하거나 API를 중단 할 수 있어야합니다. 언어 수준의 속성 지원 이 없기 때문에 다른 현대 언어, 특히 C #EcmaScript 와는 대조적 입니다.

Strike 2는 JavaBeans 스펙이 특성을 getter / setter로 정의했으며 필드는 특성이 아니라는 것입니다. 결과적으로 대부분의 초기 엔터프라이즈 프레임 워크는 필드가 아닌 게터 / 세터를 지원했습니다. 오래 전부터 (JPA ( Java Persistence API) , Bean Validation , Java 바인딩 용 Java 아키텍처 (JAXB) , Jackson모든 지원 필드는 지금까지는 괜찮습니다), 이전 자습서와 서적은 계속 남아 있으며 모든 사람이 상황이 바뀌 었다는 것을 인식하지는 못합니다. 언어 수준의 속성 지원이없는 경우 (예 : 공용 필드를 읽을 때 단일 엔터티의 JPA 지연로드가 트리거되지 않기 때문에) 여전히 문제가 될 수 있지만 대부분 공용 필드가 제대로 작동합니다. 요컨대, 우리 회사는 공용 필드로 REST API에 대한 모든 DTO를 작성합니다 (결국 인터넷을 통해 더 많은 공개를 얻지 못합니다 :-).

즉, 롬복 @Data은 게터 / 세터를 생성하는 것 이상을 수행합니다. 또한 toString(), hashCode()및을 생성 equals(Object)합니다.

그리고 실제 프로그래밍에서 캡슐화가 실제로 어떤 의미가 있습니까?

캡슐화는 매우 귀중하거나 전혀 쓸모가 없으며 캡슐화되는 객체에 따라 다릅니다. 일반적으로 클래스 내 논리가 복잡할수록 캡슐화의 이점이 커집니다.

모든 필드에 대해 자동으로 생성 된 게터 및 세터는 일반적으로 과도하게 사용되지만 레거시 프레임 워크를 사용하거나 필드에 지원되지 않는 간헐적 인 프레임 워크 기능을 사용하는 데 유용 할 수 있습니다.

getter 및 명령 메소드를 사용하여 캡슐화를 수행 할 수 있습니다. 고정자는 단일 필드 만 변경할 것으로 예상되기 때문에 일반적으로 적절하지 않습니다. 불변량을 유지하려면 한 번에 여러 필드를 변경해야 할 수도 있습니다.

요약

게터 / 세터는 캡슐화가 다소 불량합니다.

Java에서 getter / setter의 보급은 속성에 대한 언어 수준의 지원 부족과 역사적인 구성 요소 모델에서 많은 교육 자료 및 해당 교사가 가르치는 프로그래머에 의해 의심되는 디자인 선택의 여지가 있습니다.

EcmaScript와 같은 다른 객체 지향 언어는 언어 수준에서 속성을 지원하므로 API를 중단하지 않고도 getter를 도입 할 수 있습니다. 이러한 언어에서는 미리 필요한 경우가 아니라 실제로 필요할 때 게터를 소개 할 수있어 훨씬 즐거운 프로그래밍 경험을 제공합니다.


1
불변량을 강요합니까? 영어인가요? 영국인이 아닌 사람에게 설명해 주시겠습니까? 너무 복잡하지 마십시오. 일부 사람들은 영어를 잘 모르고 있습니다.
Gangnus

나는 당신의 기초를 좋아하고, 당신의 생각을 좋아하지만 (+1) 실제적인 결과는 아닙니다. 오히려 개인 필드와 일부 특수 라이브러리를 사용하여 데이터를 리플렉션하여로드합니다. 나는 당신이 왜 당신의 대답을 나에 대한 논쟁으로 설정하는지 이해하지 못합니까?
Gangnus

@ 강 누스 (Gangnus) 불변변하지 않는 논리적 조건입니다 (의미가 아닌 의미와 변이의 의미는 변 / 변화). 많은 함수에는 실행 전후에 적용되어야하는 규칙이 있습니다 (사전 조건 및 사후 조건이라고 함). 그렇지 않으면 코드에 오류가 있습니다. 예를 들어 함수는 인수가 null이 아닌 (사전 조건) 요구할 수 있으며 인수가 null 인 경우 예외가 발생할 수 있습니다.이 경우 코드에 오류가있을 수 있습니다 (예 : 컴퓨터 과학에서는 코드가 깨졌습니다). 불변).
Pharap

@ Gangus :이 토론에서 어느 부분이 반영되는지 확실하지 않습니다. Lombok은 어노테이션 프로세서, 즉 컴파일 중에 추가 코드를 생성하는 Java 컴파일러의 플러그인입니다. 그러나 롬복을 사용할 수 있습니다. 나는 많은 경우에 공공 필드가 잘 작동하고 설정하기 쉽다고 말합니다 (모든 컴파일러가 주석 프로세서를 자동으로 감지하는 것은 아닙니다 ...).
meriton

1
@ 강 너스 : 불변량은 수학과 CS에서 동일하지만 CS에는 추가적인 관점이 있습니다. 객체의 관점에서 변하지 않는 것을 적용하고 설정해야합니다. 해당 객체의 호출자의 관점에서, 불변은 항상 참입니다.
meriton

7

나는 그 질문을 참으로 스스로에게 물었다.

그래도 완전히 사실은 아닙니다. getter / setters IMO의 보급은 Java Bean 스펙에 의해 발생합니다. 따라서 객체 지향 프로그래밍이 아니라 Bean 지향 프로그래밍의 기능입니다. 이 둘의 차이점은 이들이 존재하는 추상화 계층의 차이점입니다. 빈은 더 많은 시스템 인터페이스, 즉 상위 계층에 있습니다. 그것들은 OO 기반에서 추상화되거나, 적어도 항상 그렇듯이 너무 자주 추진되고 있습니다.

Java 프로그래밍에서 어디에서나 볼 수있는이 Bean에는 해당 Java 언어 기능이 추가되지 않는 것이 다소 불행하다고 말하고 싶습니다 .C #의 Properties 개념과 같은 것을 생각하고 있습니다. 그것을 모르는 사람들에게는 다음과 같은 언어 구성입니다.

class MyClass {
    string MyProperty { get; set; }
}

어쨌든, 실제 구현의 핵심은 여전히 ​​캡슐화로부터 많은 이점을 얻습니다.


파이썬은 속성이 투명한 게터 / 세터를 가질 수있는 비슷한 기능을 가지고 있습니다. 더 많은 언어가 비슷한 기능을 가지고 있다고 확신합니다.
JAB

1
"getters / setters IMO의 보급은 Java Bean 사양에 의해 발생합니다. Java Bean 사양이 필요합니다."예, 맞습니다. 그리고 누가 필요하다고 말했습니까? 이유는 요?
Gangnus

2
C # 방식은 롬복과 완전히 동일합니다. 나는 진짜 차이가 보이지 않습니다. 보다 실용적인 편의성이지만 개념 상으로는 분명히 나쁩니다.
Gangnus

1
@Gangnis 사양에 필요합니다. 왜? 게터가 필드를 노출하는 것과 다른 이유 에 대한 구체적인 예를 보려면 내 대답을 확인하십시오 . 게터 없이도 같은 것을 달성하십시오.
Vince Emigh

@VinceEmigh gangnUs, 제발 :-). (갱 걷기, nus-nut). 그리고 특별히 필요한 곳에만 get / set을 사용하고 다른 경우에 반영하여 개인 필드에 대량로드하는 것은 어떻습니까?
Gangnus

6

누군가 나를 설명 할 수 있습니까? 모든 분야를 비공개로 숨기고 그 후에 추가 기술로 모든 분야를 노출시키는 의미는 무엇입니까? 그렇다면 왜 우리는 단순히 공공 장소 만 사용하지 않습니까? 시작 지점으로 돌아 가기 위해 길고 힘든 길을 걸었다 고 생각합니다.

여기에 간단한 대답은 : 당신은 절대적으로 옳습니다. 게터와 세터는 캡슐화의 가치를 대부분 (전부는 아님) 제거합니다. 이것은 캡슐화를 깨뜨리는 get 및 / 또는 set 메소드가있을 때마다 클래스의 모든 개인 구성원에게 맹목적으로 액세서를 추가하는 경우 잘못하고 있다고 말하는 것은 아닙니다.

예, 게터와 세터를 통해 작동하는 다른 기술이 있습니다. 그리고 간단한 공공 장소에서는 사용할 수 없습니다. 그러나 이러한 기술은 공개 게터 / 세터 뒤에있는 개인 필드와 같은 수많은 속성을 가지고 있기 때문에 나타납니다. 만약 우리가 그 속성을 가지고 있지 않다면,이 기술들은 다른 방식으로 발전하여 공공 장소를 지원할 것입니다. 그리고 모든 것이 간단하고 지금은 롬복이 필요하지 않습니다. 이주기 전체의 의미는 무엇입니까? 그리고 실제 프로그래밍에서 캡슐화가 실제로 어떤 의미가 있습니까?

JavaBean 개념은 기능을 사전 빌드 된 코드에 동적으로 바인딩하는 방법으로 푸시되었으므로 Getter는 Java 프로그래밍에서 어디에나 사용됩니다. 예를 들어, 개체를 검사하고 모든 속성을 찾은 다음 as 필드를 표시하는 애플릿 (누군가 기억합니까?)에 양식을 만들 수 있습니다. 그런 다음 UI는 사용자 입력에 따라 해당 속성을 수정할 수 있습니다. 개발자는 클래스 작성에 대해 걱정하고 유효성 검사 또는 비즈니스 논리 등을 넣습니다.

여기에 이미지 설명을 입력하십시오

예제 Bean 사용

이것은 그 자체로 끔찍한 아이디어는 아니지만 Java에서의 접근 방식에 대한 열렬한 팬이 아닙니다. 그것은 단지 곡물에 반대합니다. Python, Groovy 등을 사용하십시오. 이런 종류의 접근 방식을보다 자연스럽게 지원하는 것이 있습니다.

JavaBean은 JOBOL, 즉 OO를 이해하지 못하는 Java 작성 개발자를 만들었 기 때문에 제어 불능 상태였습니다. 기본적으로 객체는 데이터 백에 지나지 않으며 모든 논리는 긴 방법으로 외부에서 작성되었습니다. 그것이 정상으로 보였기 때문에, 당신과 나 같은 사람들은 이것에 대해 질문을했다. 최근에 나는 변화를 보았고 이것은 외부인만큼 많지 않습니다.

XML 바인딩은 터프 너트입니다. 이것은 아마도 JavaBeans에 대항하기에 좋은 전장이 아닙니다. 이러한 JavaBean을 빌드해야하는 경우 실제 코드에서 유지하십시오. 직렬화 계층의 일부로 처리하십시오.


2
가장 구체적이고 현명한 생각. +1. 그러나 클래스 그룹을 작성하지 않는 이유는 무엇입니까? 각 클래스가 외부 필드에 개인 필드를 대규모로로드 / 저장하는 클래스는 무엇입니까? JSON, XML, 기타 객체 POJO 또는 필드에 대해서만 주석이 표시된 필드에서만 작동 할 수 있습니다. 이제 나는 그 대안에 대해 생각하고 있습니다.
Gangnus

@Gangnus 추가 코드 작성이 많이 필요한 것 같습니다. 리플렉션을 사용하는 경우 일련의 일련 화 코드 만 작성하면되고 특정 패턴을 따르는 모든 클래스를 직렬화 할 수 있습니다. C #은 [Serializable]속성과 관련 항목을 통해이를 지원합니다 . 리플렉션을 활용하여 하나만 작성할 수 있는데 왜 101 개의 특정 직렬화 방법 / 클래스를 작성해야합니까?
Pharap

@Pharap 정말 죄송 합니다만 귀하의 의견이 너무 짧습니다. "반사 강화"? 그리고 하나의 클래스에 너무 다른 것들을 숨기는 느낌은 무엇입니까? 무슨 뜻인지 설명해 주시겠습니까?
Gangnus

@ Gangnus 나는 반사 , 코드가 자체 구조를 검사하는 능력을 의미 합니다. Java (및 기타 언어)에서는 클래스가 노출하는 모든 함수 목록을 가져온 다음이를 직렬화하는 데 필요한 클래스에서 데이터를 추출하는 방법으로 XML / JSON과 같이 사용할 수 있습니다. 클래스별로 구조를 저장하기 위해 끊임없이 새로운 방법을 만드는 대신 일회성 작업 인 리플렉션을 사용합니다. 다음은 간단한 Java 예제 입니다.
Pharap

@Pharap 그것은 내가 말한 것입니다. 하지만 왜 "레버 리징"이라고 부릅니까? 그리고 첫 번째 주석에서 언급 한 대안은 여전히 ​​남아 있습니다-포장되지 않은 필드에는 특수 주석이 있어야합니까?
Gangnus

5

게터없이 얼마나 할 수 있을까요? 완전히 제거 할 수 있습니까? 어떤 문제가 발생합니까? return키워드를 금지 할 수 있습니까?

당신이 일을 기꺼이한다면 많은 일을 할 수 있다는 것이 밝혀졌습니다. 그렇다면 어떻게 완전히 캡슐화 된이 개체에서 정보를 얻습니까? 공동 작업자를 통해.

코드가 질문을하도록하는 대신해야 할 일을 말합니다. 그 물건도 돌려주지 않으면 돌려주는 것에 대해 아무것도 할 필요가 없습니다. 따라서 return대신 도달하려는 경우 수행해야 할 모든 작업을 수행하는 출력 포트 공동 작업자에 도달하십시오.

이런 식으로 일을하면 이점과 결과가 있습니다. 당신은 당신이 돌아 왔을 것 이상을 생각해야합니다. 요청하지 않은 객체에 메시지로 메시지를 보내는 방법에 대해 생각해야합니다. 리턴했을 때와 같은 오브젝트를 전달하거나 메소드를 호출하는 것만으로 충분할 수 있습니다. 그런 생각을하는 데는 비용이 듭니다.

이점은 이제 인터페이스를 향한 대화를하고 있다는 것입니다. 즉, 추상화의 이점을 최대한 활용할 수 있습니다.

또한 당신이 말하는 것을 아는 동안 정확하게 당신이 말하는 것을 알 필요가 없기 때문에 다형성 파견을 제공합니다.

이것은 레이어 스택을 통해 한 방향으로 만 가야한다고 생각할 수도 있지만, 주기적 순환 종속성을 만들지 않고 다형성을 사용하여 뒤로 이동할 수 있습니다.

다음과 같이 보일 수 있습니다 :

여기에 이미지 설명을 입력하십시오

class Interactor implements InputPort {
    OutputPort out;
    int y = 0;

    Interactor(OutputPort out){
        this.out = out;
    }

    void accumulate(int x) {
        y = y + x;
        out.showAsImage(y);
    }
}

그런 코드를 작성할 수 있다면 getter를 사용하는 것이 좋습니다. 필요한 악이 아닙니다.


예, 그러한 게터 / 세터는 의미가 있습니다. 그러나 그것이 다른 것에 관한 것임을 이해합니까? :-) 어쨌든 +1.
Gangnus

1
감사합니다. 당신이 이야기 할 수있는 것들이 많이 있습니다. 적어도 그들에게 이름을 지정하고 싶다면 내가 그들에게 갈 수 있습니다.
candied_orange

5

이것은 많은 논쟁과 오해가 게터와 세터의 문제에 관한 합리적인 우려와 혼합되어 있기 때문에 논란의 여지가있는 문제입니다. 그러나 간단히 말해서 아무런 문제 @Data가 없으며 캡슐화를 깨뜨리지 않습니다.

왜 공공 장소보다는 게터와 세터를 사용 하는가?

getter / setter는 캡슐화를 제공하기 때문입니다. 값을 공용 필드로 표시 한 다음 나중에 값 계산으로 변경하면 해당 필드에 액세스하는 모든 클라이언트를 수정해야합니다. 분명히 이것은 나쁘다. 객체의 일부 속성이 필드에 저장되는지, 즉석에서 생성되는지 또는 다른 곳에서 가져 오는지에 대한 구현 세부 사항이므로 차이가 클라이언트에 노출되어서는 안됩니다. Getter / setters setter는 구현을 숨기므로이 문제를 해결합니다.

그러나 getter / setter가 기본 개인 필드를 반영한다면 나쁘지 않습니까?

아니! 요점은 캡슐화를 통해 클라이언트에 영향을 미치지 않고 구현을 변경할 수 있다는 것입니다. 고객이 알거나 신경 쓸 필요가없는 한, 필드는 여전히 가치를 저장하는 완벽한 방법 일 수 있습니다.

그러나 캡슐화를 깨는 필드에서 getter / setter를 자동 생성하지 않습니까?

캡슐화가 아직 없습니다! @Data주석은 기본 필드를 사용하는 getter / setter-pair를 작성하는 편리한 방법입니다. 클라이언트의 관점에서 이것은 일반적인 getter / setter 쌍과 같습니다. 구현을 다시 작성하기로 결정한 경우에도 클라이언트에 영향을주지 않고 수행 할 수 있습니다. 따라서 캡슐화 간결한 구문 이라는 두 가지 이점을 모두 누릴 수 있습니다.

그러나 어떤 사람들은 게터 / 세터가 항상 나쁘다고 말합니다!

별도의 논쟁이 있는데, 어떤 사람들 은 기본 구현에 관계없이 게터 / 세터 패턴이 항상 나쁘다고 생각합니다 . 아이디어는 객체에서 값을 설정하거나 가져 와서는 안되며, 객체 간의 상호 작용은 한 객체 다른 객체에 무언가를 요구하는 메시지로 모델링되어야한다는 것입니다. 이것은 주로 객체 지향적 사고 초기의 교리입니다. 이제 생각은 특정 패턴 (예 : 가치 객체, 데이터 전송 객체)에 대해 게터 / 세터가 완벽하게 적절할 수 있다는 것입니다.


캡슐화는 다형성과 안전성을 허용해야합니다. Gets / sets는 전나무를 허용하지만 두 번째에 강하게 반대합니다.
Gangnus

내가 어딘가에서 교리를 사용한다고 말하면, 내 텍스트가 교리라는 것이 무엇인지 말하십시오. 그리고 내가 사용하고있는 일부 생각은 내가 좋아하지 않거나 동의하지 않는다는 것을 잊지 마십시오. 모순을 설명하기 위해 사용하고 있습니다.
Gangnus

1
"강 누스":-). 일주일 전에 나는 문자 그대로 여러 계층에 걸쳐 게터와 세터를 부르는 프로젝트로 일했습니다. 그것은 절대적으로 안전하지 않으며 심지어 더 나쁜 것은 안전하지 않은 코딩을하는 사람들을 지원합니다. 사람들이 그런 식으로 익숙해 지기 때문에 직업을 빨리 바꿨습니다 . 그래서, 나는 그것을 생각하지 않는다 그것을 볼 .
Gangnus

2
@ jrh : 공식적인 설명이 있는지 모르겠지만 C #에는 속성 (구문이 좋은 getter / setter)과 속성 있고 동작이없는 익명 유형에 대한 지원 기능이 내장되어 있습니다. 따라서 C #의 디자이너는 의도적으로 메시징 은유에서 벗어나 "말하지 말아라"라는 원칙을 벗어났습니다. 버전 2.0 이후 C #의 개발은 전통적인 OO 순도보다 기능적 언어에서 더 많은 영감을 얻은 것으로 보입니다.
JacquesB

1
@ jrh : 지금 추측하고 있지만 C #은 데이터와 작업이 더 분리 된 기능적 언어에서 영감을 얻은 것으로 생각합니다. 분산 아키텍처에서 관점은 메시지 지향 (RPC, SOAP)에서 데이터 지향 (REST)으로 전환되었습니다. 그리고 "블랙 박스"개체가 아닌 데이터를 노출하고 작동하는 데이터베이스가 널리 보급되었습니다. 요컨대 포커스가 블랙 박스 사이의 메시지에서 노출 된 데이터 모델로 바뀌 었다고 생각합니다.
JacquesB

3

캡슐화에는 목적이 있지만 오용되거나 남용 될 수도 있습니다.

수백 개의 필드가있는 클래스가있는 Android API와 같은 것을 고려하십시오. 이러한 필드를 API 소비자에게 공개하면 탐색 및 사용이 더 어려워지고 사용자는 필드 사용 방법과 충돌 할 수있는 필드로 원하는 작업을 수행 할 수 있다는 잘못된 개념을 사용자에게 제공합니다. 따라서 캡슐화는 유지 관리 성, 유용성, 가독성 및 미친 벌레를 피하는 의미에서 훌륭합니다.

다른 한편으로, 모든 필드가 공개되는 C / C ++의 구조체와 같은 POD 또는 일반 오래된 데이터 형식도 유용 할 수 있습니다. 롬복의 @data 주석에 의해 생성 된 것과 같은 쓸모없는 게터 / 세터를 갖는 것은 "봉지 패턴"을 유지하는 방법 일뿐입니다. Java에서 "쓸모없는"getter / setter를 수행하는 몇 가지 이유 중 하나는 메소드 가 계약을 제공하기 때문 입니다.

Java에서는 인터페이스에 필드를 가질 수 없으므로 getter 및 setter를 사용하여 해당 인터페이스의 모든 구현자가 갖는 공통 특성을 지정하십시오. Kotlin 또는 C #과 같은 최신 언어에서는 속성 개념을 setter 및 getter를 선언 할 수있는 필드로 봅니다. 결국, 쓸모없는 getter / setter는 오라클이 속성을 추가하지 않는 한 Java와 함께 살아야 할 유산입니다. 예를 들어 JetBrains에서 개발 한 또 다른 JVM 언어 인 Kotlin에는 기본적으로 @data 주석이 Lombok에서 수행하는 작업을 수행하는 데이터 클래스가 있습니다.

또한 몇 가지 예가 있습니다.

class DataClass 
{
    private int data;

    public int getData() { return data; }
    public void setData(int data) { this.data = data; } 
}

이것은 캡슐화의 나쁜 경우입니다. 게터와 세터는 사실상 쓸모가 없습니다. 캡슐화는 Java와 같은 언어의 표준이기 때문에 주로 사용됩니다. 실제로 코드 기반에서 일관성을 유지하는 것 외에는 도움이되지 않습니다.

class DataClass implements IDataInterface
{
    private int data;

    @Override public int getData() { return data; }
    @Override public void setData(int data) { this.data = data; }
}

이것은 캡슐화의 좋은 예입니다. 캡슐화는 계약 (이 경우 IDataInterface)을 시행하는 데 사용됩니다. 이 예제에서 캡슐화의 목적은이 클래스의 소비자가 인터페이스에서 제공하는 메소드를 사용하도록하는 것입니다. getter와 setter가 멋진 작업을 수행하지 않더라도 이제 DataClass와 다른 IDataInterface 구현 자 사이에 공통된 특성을 정의했습니다. 따라서 다음과 같은 방법을 사용할 수 있습니다.

void doSomethingWithData(IDataInterface data) { data.setData(...); }

이제 캡슐화에 대해 이야기 할 때 구문 문제를 해결하는 것이 중요하다고 생각합니다. 나는 사람들이 캡슐화 자체보다는 캡슐화를 시행하는 데 필요한 구문에 대해 불평하는 것을 종종 본다. 마음에 오는 한 가지 예는 (당신이 그의 호언 장담 볼 수 케이시 MURATORI에서입니다 여기를 ).

캡슐화를 사용하는 플레이어 클래스가 있고 자신의 위치를 ​​1 단위 이동한다고 가정합니다. 코드는 다음과 같습니다.

player.setPosX(player.getPosX() + 1);

캡슐화가 없으면 다음과 같습니다.

player.posX++;

여기서 그는 캡슐화가 더 많은 이점을 제공하지 않으면 서 더 많은 타이핑이 가능하며 많은 경우에 맞을 수 있지만 무언가를 주목할 수 있다고 주장합니다. 인수는 캡슐화 자체가 아니라 구문에 위배됩니다. 캡슐화 개념이없는 C와 같은 언어에서도 '_'또는 'my'로 미리 접두사 또는 접두어가 붙은 구조체의 변수 또는 API 소비자가 사용하지 않아야 함을 나타내는 모든 변수를 종종 볼 수 있습니다 사유의.

문제는 캡슐화가 코드를 훨씬 더 유지 관리하고 사용하기 쉽게 만드는 데 도움이된다는 것입니다. 이 수업을 고려하십시오.

class VerticalList implements ...
{
    private int posX;
    private int posY;
    ... //other members

    public void setPosition(int posX, int posY)
    {
        //change position and move all the objects in the list as well
    }
}

이 예제에서 변수가 공개 된 경우이 API의 소비자는 posX 및 posY 사용시기와 setPosition () 사용시기에 대해 혼동 될 수 있습니다. 이러한 세부 사항을 숨기면 소비자가 직관적 인 방식으로 API를 더 잘 사용하는 데 도움이됩니다.

구문은 많은 언어에서 제한 사항입니다. 그러나 새로운 언어는 퍼블릭 멤버의 멋진 구문과 캡슐화의 이점을 제공하는 속성을 제공합니다. MSVC를 사용하는 경우 C ++, 심지어 C ++에서도 속성을 찾을 수 있습니다. 다음은 Kotlin의 예입니다.

VerticalList 클래스 : ... {var posX : Int set (x) {field = x; ...} var posY : Int set (y) {필드 = y; ...}}

여기서는 Java 예제와 동일한 결과를 얻었지만 posX와 posY를 마치 공용 변수 인 것처럼 사용할 수 있습니다. 그래도 값을 변경하려고하면 settter set () 본문이 실행됩니다.

예를 들어 Kotlin에서 이것은 getter, setter, hashcode, equals 및 toString이 구현 된 Java Bean과 같습니다.

data class DataClass(var data: Int)

이 구문을 통해 Java Bean을 한 줄로 수행하는 방법에 주목하십시오. Java와 같은 언어가 캡슐화를 구현할 때 발생하는 문제를 올바르게 알았지 만 이는 캡슐화 자체가 아닌 Java의 결함입니다.

당신은 getter와 setter를 생성하기 위해 Lombok의 @Data를 사용한다고 말했습니다. @Data라는 이름을 확인하십시오. 주로 데이터 만 저장하고 직렬화 및 역 직렬화되는 데이터 클래스에서 사용됩니다. 게임의 저장 파일과 같은 것을 생각하십시오. 그러나 UI 요소와 같은 다른 시나리오에서는 변수 값을 변경하는 것만으로는 예상되는 동작을 수행하기에 충분하지 않기 때문에 세터를 가장 명확하게 원합니다.


캡슐화 오용에 대한 예를 여기에 넣을 수 있습니까? 흥미로울 수 있습니다. 귀하의 예는 오히려 캡슐화 부족에 대한 것입니다.
Gangnus

1
@ Gangnus 몇 가지 예를 추가했습니다. 쓸모없는 캡슐화는 일반적으로 오용이며 내 경험에 비추어 볼 때 API 개발자는 API를 특정 방식으로 사용하도록 강요하려고합니다. 발표하기 쉬운 책이 없었기 때문에 이에 대한 예를 들지 않았습니다. 하나를 찾으면 명확하게 추가합니다. 캡슐화에 대한 대부분의 의견은 실제로 캡슐화 자체가 아니라 캡슐화 구문에 대한 것입니다.
BananyaDev

편집 해 주셔서 감사합니다. 그래도 "공개"대신 "공개"라는 사소한 오타가 발견되었습니다.
jrh

2

캡슐화는 유연성을 제공합니다 . 구조와 인터페이스를 분리하면 인터페이스를 변경하지 않고 구조를 변경할 수 있습니다.

예를 들어 구성시 기본 필드를 초기화하는 대신 다른 필드를 기반으로 속성을 계산해야하는 경우 게터를 간단히 변경할 수 있습니다. 필드를 직접 노출 한 경우 대신 인터페이스를 변경하고 모든 사용 사이트에서 변경해야합니다.


이론적으로는 좋은 아이디어이지만, 버전 2의 API로 인해 버전 1에서 예외가 발생한다는 사실을 기억하십시오. 버전 1은 라이브러리 또는 클래스의 사용자를 끔찍하게 (그리고 아마도 자동으로) 중단시키지 않습니다. 나는 대부분의 데이터 클래스에서 중요한 변화가 "뒤에서"이루어질 수 있다는 것에 회의적이다.
jrh

죄송합니다. 설명이 아닙니다. 인터페이스에서 필드 사용이 금지된다는 사실은 캡슐화 아이디어에서 비롯됩니다. 그리고 지금 우리는 그것에 대해 이야기하고 있습니다. 당신은 논의되고있는 사실에 근거하고 있습니다. (주의, 나는 당신의 생각을 찬성하거나 반대하지 않습니다, 단지 여기에서 논쟁으로 사용될 수는 없습니다)
Gangnus

@Gangnus "interface"라는 단어는 Java 키워드 이외의 의미를 가지고 있습니다 : dictionary.com/browse/interface
glennsl

@jrh 그것은 완전히 다른 문제입니다. 특정 상황 에서 캡슐화 반대하는 데 사용할 수는 있지만 어떤 식 으로든 인수를 무효화하지는 않습니다.
glennsl

1
대량의 get / set이없는 오래된 캡슐화는 다형성뿐만 아니라 안전성도 제공했습니다. 그리고 대량 세트 / gets는 우리에게 나쁜 습관을 가르쳐줍니다.
Gangnus

2

캡슐화 및 클래스 디자인의 문제 공간을 설명하고 마지막에 귀하의 질문에 대답하려고 노력할 것입니다.

다른 답변에서 언급했듯이 캡슐화의 목적은 공개 API 뒤에 객체의 내부 세부 정보를 숨기고 계약 역할을하는 것입니다. 객체는 공개 API를 통해서만 호출되므로 내부를 변경하는 것이 안전합니다.

퍼블릭 필드, getter / setter 또는 상위 레벨 트랜잭션 메소드 또는 메시지 전달이 적합한 지 여부는 모델링중인 도메인의 특성에 따라 다릅니다. Akka Concurrency (일부 다소 오래된 경우에도 권장 할 수 있음) 책에서 이것을 설명하는 예제를 찾을 수 있습니다.

사용자 클래스를 고려하십시오.

public class User {
  private String first = "";
  private String last = "";

  public String getFirstName() {
    return this.first;
  }
  public void setFirstName(String s) {
    this.first = s;
  }

  public String getLastName() {
    return this.last;
  }
  public void setLastName(String s) {
    this.last = s;
  }
}

이것은 단일 스레드 컨텍스트에서 잘 작동합니다. 모델링되는 도메인은 사람의 이름이며 해당 이름이 저장되는 방식을 세터가 완벽하게 캡슐화 할 수 있습니다.

그러나 이것이 다중 스레드 컨텍스트에서 제공되어야한다고 상상해보십시오. 하나의 스레드가 주기적으로 이름을 읽고 있다고 가정하십시오.

System.out.println(user.getFirstName() + " " + user.getLastName());

그리고 다른 두 개의 실이 줄다리기 싸움을 벌이고 힐러리 클린턴도널드 트럼프 로 차례로 바뀌 었습니다 . 그들은 각각 두 가지 방법을 호출해야합니다. 대부분 이것은 잘 작동하지만 가끔씩 Hillary Trump 또는 Donald Clinton 이 지나가는 것을 보게 될 것 입니다.

잠금은 이름 또는 성 설정 기간 동안 만 유지되므로 설정자 내에 잠금을 추가하여이 문제점을 해결할 수 없습니다. 잠금을 통한 유일한 솔루션은 전체 객체 주위에 잠금을 추가하는 것입니다. 그러나 호출 코드가 잠금을 관리해야하기 때문에 캡슐화가 중단됩니다 (교착 상태가 발생할 수 있음).

결과적으로 잠금을 통한 깨끗한 솔루션은 없습니다. 깨끗한 해결책은 내부를 더 굵게 만들어 내부를 다시 캡슐화하는 것입니다.

public class UserName {
   public final String first;
   public final String last;
   public UserName(String first, String last) { ... }
}

public class User
   private UserName name;
   public UserName getName() { return this.name; }
   public setName(UserName n) { this.name = n; }
}

이름 자체는 불변이되었으며, 일단 생성 한 후에는 수정할 수없는 순수한 데이터 객체이므로 멤버가 공개 될 수 있습니다. 결과적으로 User 클래스의 공용 API는 하나의 setter 만 남으면서 더 거칠어 지므로 이름은 전체적으로 만 변경할 수 있습니다. API 뒤의 내부 상태를 더 많이 캡슐화합니다.

이주기 전체의 의미는 무엇입니까? 그리고 실제 프로그래밍에서 캡슐화가 실제로 어떤 의미가 있습니까?

이주기에서 보는 것은 특정 상황에 적합한 솔루션을 너무 광범위하게 적용하려는 시도입니다. 적합한 수준의 캡슐화는 모델링되는 도메인을 이해하고 올바른 수준의 캡슐화를 적용해야합니다. 때로는 Akka 응용 프로그램과 같이 모든 필드가 공용임을 의미하며 메시지를 수신하는 단일 방법을 제외하고는 공용 API가 전혀 없음을 의미합니다. 그러나 캡슐화 자체의 개념은 안정적인 API 뒤에 내부를 숨기는 것을 의미하며, 특히 멀티 스레드 시스템에서 대규모 프로그래밍 소프트웨어의 핵심입니다.


환상적인. 나는 당신이 토론을 더 높은 수준으로 올렸다고 말할 것입니다. 아마 두 실제로 캡슐화를 이해하고 때로는 표준 서적을 더 잘 이해한다고 생각했으며 실제로 허용 된 사용자 정의 및 기술로 인해 실제로 잃어 버릴 까봐 두려웠습니다. 좋은 방법),하지만 당신은 나에게 캡슐화의 새로운면을 보여주었습니다. 나는 멀티 태스킹에 능숙하지 않으며 다른 기본 원칙을 이해하는 것이 얼마나 해로운 지 보여주었습니다.
Gangnus

하지만 같은 게시물 표시 할 수 없습니다 은 객체 /에서 대용량 데이터를로드, 콩, 롬복 등의 플랫폼이 표시에 의한되는 문제에 대해하지 않습니다에 대한 답을. 어쩌면 시간을 가질 수 있다면 그 방향으로 생각을 정교하게 할 수 있습니까? 나는 당신의 생각이 그 문제에 초래하는 결과를 아직 생각하지 않았습니다. 그리고 나는 그것이 충분히 적합하지 않다고 확신합니다 (나쁜 멀티 스레딩 배경을 기억하십시오 :-[)
Gangnus

나는 롬복을 사용하지는 않았지만 타이핑을 줄이면서 특정 수준의 캡슐화 (모든 분야의 getter / setter)를 구현하는 방법이라는 것을 이해합니다. 문제 영역에 대한 이상적인 API 형태를 변경하지 않고 더 빨리 작성할 수있는 도구 일뿐입니다.
Joeri Sebrechts

1

이것이 의미가있는 하나의 유스 케이스를 생각할 수 있습니다. 원래 간단한 getter / setter API를 통해 액세스하는 클래스가있을 수 있습니다. 나중에 동일한 필드를 더 이상 사용하지 않지만 여전히 동일한 API를 지원 하도록 확장하거나 수정 합니다 .

다소 인위적인 예 : 데카르트와 쌍으로 밖으로 시작하는 점 p.x()p.y(). 나중에 극 좌표를 사용하는 새로운 구현 또는 서브 클래스를 만드는, 그래서 당신은 또한 호출 할 수 p.r()p.theta()호출 클라이언트 코드 만 p.x()하고 p.y()유효합니다. 클래스 자체가 투명하게, 내부 극 형식으로 변환 즉, y()지금 것이다 return r * sin(theta);. (이 예에서는 설정 만하 x()거나 y()의미가 없지만 여전히 가능합니다.)

이 경우, "Glad I는 필드를 공개하지 않고 자동으로 getter 및 setter를 선언하는 것을 방해했거나 API를 중단해야했습니다."


2
당신이 보는 것처럼 좋지 않습니다. 내부 물리적 표현을 변경하면 객체가 실제로 달라집니다. 그들은 서로 다른 특성을 가지고 있기 때문에 같은 방식으로 보이는 것이 나쁩니다 . 데카르트 시스템에는 특별한 점이 없으며, 극점 시스템에는 하나의 점이 있습니다.
Gangnus

1
특정 예가 마음에 들지 않으면 실제로 여러 개의 동등한 표현을 가진 다른 사람들이나 이전의 것과 변환 할 수있는 새로운 표현을 생각할 수 있습니다.
Davislor

예, 프리젠 테이션에 매우 생산적으로 사용할 수 있습니다. UI에서는 널리 사용될 수 있습니다. 하지만 내 질문에 대답하지 않습니까? :-)
Gangnus

그런 식으로 더 효율적입니까? :)
Davislor

0

누군가 나를 설명 할 수 있습니까? 모든 분야를 비공개로 숨기고 그 후에 추가 기술로 모든 분야를 노출시키는 의미는 무엇입니까?

아무 소용이 없습니다. 그러나이 질문을하면 Lombok의 기능을 이해하지 못하고 캡슐화를 사용하여 OO 코드를 작성하는 방법을 이해하지 못함을 알 수 있습니다. 조금 되 감자 ...

클래스 인스턴스의 일부 데이터는 항상 내부 데이터이므로 절대 노출되지 않아야합니다. 클래스 인스턴스의 일부 데이터는 외부에서 설정해야하며 일부 데이터는 클래스 인스턴스에서 다시 전달해야 할 수도 있습니다. 우리는 클래스가 표면 아래에서 일하는 방식을 변경하고 싶을 수 있으므로 함수를 사용하여 데이터를 가져오고 설정할 수 있습니다.

일부 프로그램은 클래스 인스턴스의 상태를 저장하려고하므로 일부 직렬화 인터페이스가있을 수 있습니다. 클래스 인스턴스가 스토리지에 상태를 저장하고 스토리지에서 상태를 검색 할 수있는 더 많은 함수를 추가합니다. 클래스 인스턴스가 여전히 자체 데이터를 제어하기 때문에 캡슐화가 유지됩니다. 우리는 개인 데이터를 직렬화 할 수 있지만 나머지 프로그램은 액세스 할 수 없으며 (또는 더 정확하게 는 개인 데이터를 고의적으로 손상시키지 않도록 선택 하여 중국 벽 을 유지 ) 클래스 클래스는 할 수 있으며 역 직렬화에 대한 무결성 검사를 수행하여 데이터가 올바르게 돌아 왔는지 확인합니다.

때로는 데이터에 범위 검사, 무결성 검사 또는 이와 유사한 것들이 필요합니다. 이러한 기능을 직접 작성하면 모든 것이 가능해집니다. 이 경우 롬복을 원하지 않거나 필요로하지 않습니다. 모든 일을 스스로하기 때문입니다.

그러나 외부 설정 매개 변수가 단일 변수에 저장되는 경우가 종종 있습니다. 이 경우 해당 변수의 내용을 가져 오거나 설정 / 직렬화 / 직렬화하려면 네 가지 함수가 필요합니다. 이 네 가지 기능을 스스로 작성할 때마다 속도가 느려지고 오류가 발생하기 쉽습니다. Lombok으로 프로세스를 자동화하면 개발 속도가 빨라지고 오류 가능성이 제거됩니다.

예, 변수를 공개 할 수 있습니다. 이 특정 버전의 코드에서는 기능적으로 동일합니다. 그러나 우리가 함수를 사용하는 이유로 돌아가십시오. "우리는 클래스가 표면에서 작동하는 방식을 바꾸고 싶을 수도 있습니다 ..." 인터페이스 그래도 함수를 사용하거나 Lombok을 사용하여 해당 함수를 자동으로 생성하는 경우 나중에 언제든지 기본 데이터와 기본 구현을 자유롭게 변경할 수 있습니다.

이것이 더 명확합니까?


당신은 첫해 SW 학생의 질문에 대해 이야기하고 있습니다. 우리는 이미 다른 곳에 있습니다. 나는 당신이 당신의 대답의 형태로 그렇게 무례하지 않았다면 나는 이것을 말하지 않을 것이고 당신에게 영리한 대답에 대한 플러스를 줄 것입니다.
Gangnus

0

나는 실제로 자바 개발자가 아니다. 그러나 다음은 거의 플랫폼에 구애받지 않습니다.

우리가 쓰는 거의 모든 것은 개인 변수에 액세스하는 공개 getter 및 setter를 사용합니다. 게터와 세터는 대부분 사소합니다. 그러나 setter가 무언가를 다시 계산해야하거나 setter가 일부 유효성 검사를 수행하거나이 클래스의 멤버 변수의 속성으로 속성을 전달해야하는 경우 전체 코드를 완전히 위반하지 않으며 바이너리와 호환됩니다. 하나의 모듈을 교체 할 수 있습니다.

우리가이 속성을 실제로 계산할 때, 그 속성을 보는 모든 코드는 변경 될 필요가없고, 그 코드를 작성하는 코드 만이 변경 될 필요가 있으며 IDE는 우리를 위해 그것을 찾을 수 있습니다. 쓰기 가능한 계산 필드라고 결정하면 (몇 번만 수행해야 함) 그렇게 할 수도 있습니다. 좋은 점은 이러한 변경 사항 중 일부가 바이너리와 호환 가능하다는 것입니다 (읽기 전용 계산 필드로 변경하는 것은 이론적으로는 아니지만 실제로 실제로있을 수 있음).

우리는 복잡한 세터가있는 많은 사소한 게터로 끝났습니다. 우리는 또한 꽤 많은 캐싱 게터로 끝났습니다. 최종 결과는 getter가 합리적으로 저렴하지만 setter는 그렇지 않다고 가정 할 수 있습니다. 다른 한편으로, 우리는 세터가 디스크에 지속되지 않는다고 결정하는 것이 현명합니다.

그러나 모든 멤버 변수를 맹목적으로 속성으로 변경하는 사람을 추적해야했습니다. 그는 원자가 추가되는 것이 무엇인지 알지 못했기 때문에 부동산에 대한 공개 변수가되어야하는 것을 변경하고 코드를 미묘한 방식으로 깨뜨 렸습니다.


자바 세계에 오신 것을 환영합니다. (C #도). *한숨*. 그러나 내부 표현을 숨기기 위해 get / set을 사용하는 경우 조심하십시오. 그것은 외부 형식에 관한 것입니다. 괜찮습니다. 그러나 그것이 수학이나 물리학 또는 다른 실제에 관한 것이라면, 다른 내부 표현은 다른 특성을 가지고 있습니다. viz softwareengineering.stackexchange.com/a/358662/44104에
Gangnus

@ Gangnus : 그 포인트 예제는 가장 불행합니다. 우리는 이것들 중 상당수를 가지고 있었지만 계산은 항상 정확했습니다.
Joshua

-1

게터와 세터는 개발 과정에서 내부 구조 나 액세스 요구 사항이 변경 될 경우 향후 리팩토링을 피하기 위해 추가 된 "경우에 따라"측정됩니다.

예를 들어 출시 후 몇 개월이 지나면 클라이언트는 클래스의 필드 중 하나가 0으로 고정되어야하더라도 때때로 음수 값으로 설정되어 있다고 알려줍니다. 퍼블릭 필드를 사용하면 전체 코드베이스에서이 필드의 모든 할당을 검색하여 설정하려는 값에 클램핑 기능을 적용해야하며이 필드를 수정할 때 항상 그렇게해야한다는 점을 명심해야합니다. 그러나 그것은 짜증나. 대신, getter와 setter를 이미 사용하고 있다면 setField () 메소드를 수정하여이 클램핑이 항상 적용되도록 할 수 있었을 것입니다.

이제 Java와 같은 "구식 화 된"언어의 문제점은 이러한 목적을 위해 메소드를 사용하도록 장려한다는 점입니다. 이는 코드를 훨씬 더 장황하게 만듭니다. 쓰기가 어렵고 읽기가 어렵 기 때문에이 문제를 해결하는 IDE를 사용하고 있습니다. 대부분의 IDE는 자동으로 getter와 setter를 생성하고 달리 언급하지 않는 한 숨길 수 있습니다. 롬복은 한 걸음 더 나아가 컴파일 타임에 코드를 매끄럽게 유지하기 위해 절차 적으로 생성합니다. 그러나 다른 최신 언어는이 문제를 한 가지 방법으로 해결했습니다. 스칼라 또는 .NET 언어 (예 :

예를 들어, VB .NET 또는 C #에서는 단순 필드에 영향을주지 않는 모든 필드를 공개 필드로 설정 한 다음 나중에 공개 필드를 개인화하고 이름을 변경하고 이전 이름을 가진 속성을 공개 할 수 있습니다. 필요한 경우 필드의 액세스 동작을 미세 조정할 수있는 필드. Lombok을 사용하면 게터 또는 세터의 동작을 미세 조정해야하는 경우 필요할 때 해당 태그를 제거하고 새로운 요구 사항으로 자신의 코드를 코딩하여 다른 파일에서 아무 것도 리팩터링하지 않아도된다는 것을 알 수 있습니다.

기본적으로 메소드가 필드에 액세스하는 방식은 투명하고 균일해야합니다. 현대 언어를 사용하면 필드의 동일한 액세스 / 호출 구문을 사용하여 "메소드"를 정의 할 수 있으므로 초기 개발 중에 많은 것을 생각하지 않고도 이러한 수정 작업을 필요에 따라 수행 할 수 있지만 Java는이 작업을 미리 수행해야합니다. 이 기능이 없습니다. 사용하는 언어가 "경우에 따라"방법으로 시간을 절약하고 싶지 않기 때문에 모든 롬복은 시간을 절약합니다.


그 "현대"언어에서, API는 모양 필드 액세스 foo.bar있지만 메소드 호출에 의해 처리 얻을. 당신은 이것이 API를 메소드 호출 처럼 보이게 하는 "구식적인"방법보다 우수하다고 주장 합니다 foo.getBar(). 우리는 퍼블릭 필드에 문제가 있다고 생각하지만, 전체 API가 대칭이기 때문에 "구식 화 된"대안이 "현대적인"대안보다 우수하다고 주장합니다 (모두 메서드 호출 임). "현대적인"접근 방식에서 우리는 어떤 것이 속성이어야하고 어떤 것이 메소드 여야하는지 결정해야합니다.이 방법은 모든 것을 복잡하게 만듭니다 (특히 리플렉션을 사용하는 경우)!
Warbo

1
1. 물리를 숨기는 것이 항상 그렇게 좋은 것은 아닙니다. softwareengineering.stackexchange.com/a/358662/44104에 대한 의견을 찾으십시오 . 2. 게터 / 세터 생성이 얼마나 어려운지 간단하지 않습니다. C #은 우리가 이야기하는 것과 완전히 같은 문제가 있습니다. 대량 정보 로딩의 나쁜 솔루션으로 인한 캡슐화 부족. 3. 예, 솔루션은 구문을 기반으로 할 수 있지만 언급 한 것과는 다른 방식으로하십시오. 그것들은 나쁩니다. 우리는 여기에 설명으로 가득 찬 큰 페이지가 있습니다. 4. 솔루션은 구문을 기반으로하지 않아도됩니다. Java 한계에서 수행 될 수 있습니다.
Gangnus

-1

누군가가 나에게 설명 할 수있는 몇 가지 추가 기술로 모두를 노출 비공개로하고 그 이후 모든 필드를 숨기고의 의미가 무엇인지? 그렇다면 왜 우리는 단순히 공공 장소 만 사용하지 않습니까? 나는 우리가 출발점으로 돌아 가기 위해 길고 힘든 길을 걸었다 고 생각합니다.

네, 모순입니다. 처음에는 Visual Basic의 속성을 사용했습니다. 그때까지, 다른 언어로, 내 경험으로는, 필드 주위에 속성 래퍼가 없었습니다. 공공, 개인 및 보호 분야.

속성은 일종의 캡슐화였습니다. Visual Basic 속성은 명시 적 필드와 데이터 형식을 숨기면서 하나 이상의 필드 출력을 제어하고 조작하는 방법이라는 것을 이해했습니다. 예를 들어 특정 형식의 문자열로 날짜를 출력합니다. 그러나 더 큰 객체 관점에서 "숨김 상태 및 기능 노출"이 아닙니다.

그러나 속성은 별도의 속성 getter와 setter로 인해 자체적으로 정당화됩니다. 속성이없는 필드를 노출하는 것은 전부 또는 아무것도 아니 었습니다. 읽을 수 있으면 변경할 수 있습니다. 이제 보호 설정된 세터로 약한 클래스 디자인을 정당화 할 수 있습니다.

그렇다면 왜 우리는 실제 방법을 사용하지 않았습니까? 그것은 Visual Basic (및 VBScript ) (oooh! aaah!) 이었기 때문에 대중 (!)을 코딩했으며 모든 분노였습니다. 따라서 관용주의는 결국 지배적이었습니다.


예, UI의 속성은 특별한 의미를 지니고 있으며 공개적이고 예쁜 필드 표현입니다. 좋은 지적.
Gangnus

"그래서 왜 우리는 실제 방법을 사용하지 않았습니까?" 기술적으로 그들은 .Net 버전에서했습니다. 속성은 get 및 set 함수를위한 구문 설탕 입니다.
Pharap

"idiotocracy" ? " idiosyncrasy " 를 의미 합니까?
Peter Mortensen

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