Getter 및 Setter 메서드가 나쁜 이유

나는 "사악하다"시리즈를 시작할 생각이 없었지만, 몇몇 독자들이 지난달 칼럼 "왜 연장이 악인가"에서 get / set 메소드를 피해야한다고 언급 한 이유를 설명해달라고 요청했습니다.

getter / setter 메서드는 Java에서 일반적이지만 특히 객체 지향 (OO)은 아닙니다. 사실, 코드의 유지 보수성을 손상시킬 수 있습니다. 더욱이, 수많은 getter 및 setter 메서드가 존재한다는 것은 프로그램이 OO 관점에서 반드시 잘 설계되지 않았 음을 나타내는 위험 신호입니다.

이 기사에서는 게터와 세터를 사용하지 말아야하는 이유 (그리고 사용할 수있는 경우)를 설명하고 게터 / 세터 정신에서 벗어나는 데 도움이되는 설계 방법론을 제안합니다.

디자인의 본질

또 다른 디자인 관련 칼럼 (도발적인 제목 포함)을 시작하기 전에 몇 가지를 명확히하고 싶습니다.

나는 지난 달의 칼럼 "Why extends Is Evil"(기사 마지막 페이지의 Talkback 참조)에서 나온 일부 독자의 코멘트에 놀랐습니다. 어떤 사람들 extends은 두 개념이 동등하다고 생각하는 것처럼 단순히 문제 가 있기 때문에 객체 지향이 나쁘다고 주장했다고 ​​믿었습니다 . 그건 내가 무엇을 확실히 아니다 생각 때문에 나에게 몇 가지 메타 문제를 명확히하자, 나는 말했다.

이 칼럼과 지난 달 기사는 디자인에 관한 것입니다. 디자인은 본질적으로 일련의 절충안입니다. 모든 선택에는 좋은면과 나쁜면이 있으며 필요에 따라 정의 된 전체 기준의 맥락에서 선택합니다. 그러나 선과 악은 절대적인 것이 아닙니다. 어떤 상황에서는 좋은 결정이 다른 상황에서는 나쁠 수 있습니다.

문제의 양면을 이해하지 못한다면 현명한 선택을 할 수 없습니다. 사실, 당신이 당신의 행동의 모든 결과를 이해하지 못한다면, 당신은 전혀 디자인하지 않은 것입니다. 당신은 어둠 속에서 걸려 넘어지고 있습니다. Gang of Four의 디자인 패턴 책 의 모든 장에 패턴 사용이 부적절한시기와 이유를 설명하는 "결과"섹션이 포함되어있는 것은 우연이 아닙니다 .

일부 언어 기능이나 일반적인 프로그래밍 관용구 (접근 자와 같은)에 문제가 있다고 말하는 것은 어떤 상황에서도 절대 사용해서는 안된다고 말하는 것과는 다릅니다. 기능이나 관용구가 일반적으로 사용된다고해서 반드시 사용해야 하는 것은 아닙니다 . 지식이없는 프로그래머는 많은 프로그램을 작성하고 단순히 Sun Microsystems 또는 Microsoft에 고용되어 있다고해서 다른 사람의 프로그래밍 또는 디자인 능력이 마술처럼 향상되지는 않습니다. Java 패키지에는 훌륭한 코드가 많이 포함되어 있습니다. 그러나 그 코드의 일부도 저자가 자신이 작성한 것을 인정하는 것이 부끄럽다 고 확신합니다.

마찬가지로 마케팅이나 정치적 인센티브는 종종 디자인 관용구를 밀어 붙입니다. 때로는 프로그래머가 잘못된 결정을 내리지 만 회사는 기술이 할 수있는 일을 홍보하기를 원하므로 사용자가 수행하는 방식이 이상적이지 않다는 점을 강조하지 않습니다. 그들은 나쁜 상황을 최대한 활용합니다. 결과적으로, 당신이 프로그래밍 관행을 채택 할 때 당신은 단순히 "그것이 당신이해야 할 일이기 때문에"무책임하게 행동합니다. 실패한 많은 EJB (Enterprise JavaBeans) 프로젝트가이 원칙을 증명합니다. EJB 기반 기술은 적절하게 사용하면 훌륭한 기술이지만 부적절하게 사용하면 문자 그대로 회사를 무너 뜨릴 수 있습니다.

내 요점은 맹목적으로 프로그래밍해서는 안된다는 것입니다. 기능이나 관용구가 초래할 수있는 혼란을 이해해야합니다. 그렇게함으로써 해당 기능을 사용해야할지 관용구를 사용할지 결정하는 데 훨씬 더 나은 위치에 있습니다. 귀하의 선택은 정보에 입각하고 실용적이어야합니다. 이 기사의 목적은 눈을 뜨고 프로그래밍에 접근하는 데 도움이되는 것입니다.

데이터 추상화

OO 시스템의 기본 원칙은 객체가 구현 세부 사항을 노출해서는 안된다는 것입니다. 이렇게하면 개체를 사용하는 코드를 변경하지 않고도 구현을 변경할 수 있습니다. 그런 다음 OO 시스템에서는 대부분 구현 세부 정보에 대한 액세스를 제공하므로 getter 및 setter 함수를 피해야합니다.

그 이유를 알아 보려면 getX()프로그램 의 메서드에 대한 호출이 1,000 개있을 수 있으며 각 호출은 반환 값이 특정 유형이라고 가정합니다. getX()예를 들어의 반환 값을 지역 변수에 저장할 수 있으며 해당 변수 유형은 반환 값 유형과 일치해야합니다. X 유형이 변경되는 방식으로 객체가 구현되는 방식을 변경해야한다면 큰 문제가됩니다.

X가 int이지만 이제이어야한다면 long1,000 개의 컴파일 오류가 발생합니다. 반환 값을로 캐스팅하여 문제를 잘못 수정하면 int코드가 깔끔하게 컴파일되지만 작동하지 않습니다. (반환 값은 잘릴 수 있습니다.) 이러한 1,000 개의 호출을 둘러싼 코드를 수정하여 변경 사항을 보완해야합니다. 나는 확실히 그렇게 많은 일을하고 싶지 않다.

OO 시스템의 기본 원칙 중 하나는 데이터 추상화 입니다. 객체가 메시지 처리기를 구현하는 방식을 프로그램의 나머지 부분에서 완전히 숨겨야합니다. 이것이 모든 인스턴스 변수 (클래스의 상수가 아닌 필드)가이어야하는 한 가지 이유 private입니다.

인스턴스 변수 public를 만들면 필드를 사용하는 외부 코드가 손상되므로 시간이 지남에 따라 클래스가 진화함에 따라 필드를 변경할 수 없습니다. 단순히 클래스를 변경한다고해서 클래스의 1,000 개의 용도를 검색하고 싶지는 않습니다.

이 구현 은닉 원칙은 OO 시스템의 품질에 대한 좋은 산성 테스트로 이어집니다. 클래스 정의를 대폭 변경할 수 있습니까? 심지어 전체를 버리고 완전히 다른 구현으로 대체 할 수도 있습니다.이를 사용하는 코드에 영향을주지 않습니다. 클래스의 객체? 이러한 종류의 모듈화는 객체 지향의 중심 전제이며 유지 관리를 훨씬 쉽게 만듭니다. 구현을 숨기지 않으면 다른 OO 기능을 사용하는 데 별 의미가 없습니다.

Getter 및 setter 메서드 (접근 자라고도 함)는 public필드가 위험한 것과 동일한 이유로 위험합니다. 구현 세부 정보에 대한 외부 액세스를 제공합니다. 액세스 된 필드의 유형을 변경해야하는 경우 어떻게합니까? 접근 자의 반환 유형도 변경해야합니다. 이 반환 값을 여러 위치에서 사용하므로 해당 코드도 모두 변경해야합니다. 변경의 영향을 단일 클래스 정의로 제한하고 싶습니다. 나는 그들이 전체 프로그램에 파급되는 것을 원하지 않습니다.

접근자는 캡슐화 원칙을 위반하기 때문에 접근자를 많이 사용하거나 부적절하게 사용하는 시스템은 단순히 객체 지향이 아니라고 합리적으로 주장 할 수 있습니다. 코딩이 아닌 디자인 프로세스를 거치면 프로그램에서 접근자를 거의 찾을 수 없습니다. 과정이 중요합니다. 이 문제에 대해서는 기사 끝 부분에 더 많은 이야기가 있습니다.

getter / setter 메서드가 없다고해서 일부 데이터가 시스템을 통해 흐르지 않는다는 의미는 아닙니다. 그럼에도 불구하고 가능한 한 데이터 이동을 최소화하는 것이 가장 좋습니다. 내 경험에 따르면 유지 관리 가능성은 개체간에 이동하는 데이터 양에 반비례합니다. 아직 방법을 알지 못하더라도 실제로 이러한 데이터 이동의 대부분을 제거 할 수 있습니다.

신중하게 설계하고 수행 방법보다 수행해야하는 작업에 집중함으로써 프로그램에서 대부분의 getter / setter 메서드를 제거 할 수 있습니다. 작업을 수행하는 데 필요한 정보를 요청하지 마십시오. 당신을 위해 일을 할 정보가있는 물건에게 물어보십시오.대부분의 접근자는 디자이너가 동적 모델, 즉 작업을 수행하기 위해 서로에게 보내는 메시지와 런타임 개체에 대해 생각하지 않았기 때문에 코드를 찾습니다. 그들은 클래스 계층 구조를 설계하여 (잘못) 시작한 다음 해당 클래스를 동적 모델에 통합하려고합니다. 이 접근 방식은 작동하지 않습니다. 정적 모델을 구축하려면 클래스 간의 관계를 검색해야하며 이러한 관계는 메시지 흐름과 정확히 일치합니다. 한 클래스의 개체가 다른 클래스의 개체에 메시지를 보낼 때만 두 클래스간에 연결이 존재합니다. 정적 모델의 주요 목적은 동적으로 모델링 할 때이 연관 정보를 캡처하는 것입니다.

명확하게 정의 된 동적 모델이 없으면 클래스의 개체를 어떻게 사용할지 추측 할뿐입니다. 결과적으로 접근 자 메서드는 필요한지 여부를 예측할 수 없기 때문에 가능한 한 많은 액세스를 제공해야하기 때문에 모델에 종종 포함됩니다. 이러한 종류의 추측에 의한 설계 전략은 기껏해야 비효율적입니다. 쓸모없는 메서드를 작성하는 데 시간을 낭비하거나 클래스에 불필요한 기능을 추가합니다.

접근자는 습관에 의해 디자인으로 끝납니다. 절차 적 프로그래머가 Java를 채택 할 때 익숙한 코드를 작성하는 것으로 시작하는 경향이 있습니다. 절차 적 언어에는 클래스가 없지만 C는 있습니다 struct(생각 : 메서드없는 클래스). 따라서 struct사실상 메서드가없고 public필드 만있는 클래스 정의를 구축 하여 a를 모방하는 것이 자연스러워 보입니다 . 이러한 절차 적 프로그래머는 필드가이어야하는 어딘가에서 읽으 private므로 필드를 private만들고 public접근 자 메서드를 제공 합니다. 그러나 그들은 대중의 접근을 복잡하게 만들었습니다. 그들은 확실히 시스템 객체 지향을 만들지 않았습니다.

너 자신을 그려라

전체 필드 캡슐화의 한 가지 결과는 사용자 인터페이스 (UI) 구성입니다. 접근자를 사용할 수없는 경우 UI 빌더 클래스가 getAttribute()메서드를 호출하도록 할 수 없습니다 . 대신 클래스에는 drawYourself(...)메서드 와 같은 요소가 있습니다.

getIdentity()방법은 작업 물론,이 객체가 구현하는 그 반환 제공 할 수 Identity인터페이스를. 이 인터페이스에는 drawYourself()(또는 JComponent귀하의 신원을 나타내는) 메소드가 포함되어야합니다. getIdentity"get"으로 시작 하지만 단순히 필드를 반환하지 않기 때문에 접근자가 아닙니다. 합리적인 동작을 가진 복잡한 객체를 반환합니다. Identity객체 가 있어도 내부적으로 정체성이 어떻게 표현되는지 알 수 없습니다.

물론 drawYourself()전략이란 내가 UI 코드를 비즈니스 로직에 넣는 것을 의미합니다. UI의 요구 사항이 변경되면 어떤 일이 발생하는지 고려하십시오. 완전히 다른 방식으로 속성을 표현하고 싶다고 가정 해 보겠습니다. 오늘날 "정체성"은 이름입니다. 내일은 이름과 ID 번호입니다. 그 다음날 이름, ID 번호, 사진입니다. 이러한 변경 사항의 범위를 코드의 한 위치로 제한합니다. JComponent당신의 신분을 표현하는 나에게 부여하는 클래스가 있다면, 시스템의 나머지 부분에서 신원이 표현되는 방식을 분리 한 것입니다.

실제로 비즈니스 로직에 UI 코드를 넣지 않았습니다. 저는 추상화 레이어 인 AWT (Abstract Window Toolkit) 또는 Swing 측면에서 UI 레이어를 작성했습니다. 실제 UI 코드는 AWT / Swing 구현에 있습니다. 이것이 비즈니스 로직을 서브 시스템의 메커니즘과 분리하는 추상화 계층의 요점입니다. 코드를 변경하지 않고도 다른 그래픽 환경으로 쉽게 이식 할 수 있으므로 문제는 약간 복잡합니다. 모든 UI 코드를 내부 클래스로 이동하거나 Façade 디자인 패턴을 사용하여 이러한 혼란을 쉽게 제거 할 수 있습니다.

자바빈

"하지만 JavaBeans는 어떻습니까?"라고 이의를 제기 할 수 있습니다. 그들은 어떻습니까? getter 및 setter없이 확실히 JavaBeans를 빌드 할 수 있습니다. BeanCustomizer, BeanInfo그리고 BeanDescriptor바로이 목적을 위해 클래스의 모든 존재한다. JavaBean 사양 디자이너는 빈을 빠르게 만드는 것이 쉬운 방법이라고 생각했기 때문에 getter / setter 관용구를 그림에 넣었습니다. 올바른 방법을 배우는 동안 할 수있는 일입니다. 불행히도 아무도 그렇게하지 않았습니다.

접근자는 특정 속성에 태그를 지정하는 방법으로 만 만들어 졌으므로 UI ​​작성기 프로그램 또는 이와 동등한 프로그램에서 식별 할 수 있습니다. 이러한 메서드를 직접 호출해서는 안됩니다. 자동화 도구를 사용하기 위해 존재합니다. 이 도구는 Class클래스 의 내부 검사 API를 사용하여 메서드를 찾고 메서드 이름에서 특정 속성의 존재를 추정합니다. 실제로이 내성 기반 관용구는 작동하지 않았습니다. 코드를 너무 복잡하고 절차 적으로 만들었습니다. 데이터 추상화를 이해하지 못하는 프로그래머는 실제로 접근자를 호출하므로 코드를 유지 관리하기가 어렵습니다. 이러한 이유로 메타 데이터 기능이 Java 1.5에 통합됩니다 (2004 년 중반 예정). 그래서 대신 :

private int 속성; public int getProperty () {return property; } public void setProperty (int value} {property = value;}

다음과 같은 것을 사용할 수 있습니다.

private @property int 속성; 

UI 구성 도구 또는 이와 동등한 도구는 메서드 이름을 검사하고 이름에서 속성의 존재를 유추하는 대신 속성을 찾기 위해 인트로 스펙 션 API를 사용합니다. 따라서 런타임 접근자가 코드를 손상시키지 않습니다.

접속자는 언제 괜찮습니까?

첫째, 앞서 논의했듯이, 객체가 구현하는 인터페이스의 관점에서 메서드가 객체를 반환하는 것은 괜찮습니다. 그 인터페이스는 구현 클래스에 대한 변경으로부터 사용자를 격리시키기 때문입니다. 이러한 종류의 메서드 (인터페이스 참조를 반환)는 필드에 대한 액세스 만 제공하는 메서드의 의미에서 실제로 "게터"가 아닙니다. 공급자의 내부 구현을 변경하는 경우 변경 사항을 수용하기 위해 반환 된 객체의 정의 만 변경하면됩니다. 인터페이스를 통해 개체를 사용하는 외부 코드를 여전히 보호합니다.