왜 수업을 "공개"하도록 설계해서는 안됩니까?


44

다양한 스택 오버플로 질문과 다른 사람의 코드를 읽을 때 클래스 디자인 방법에 대한 일반적인 합의가 마감됩니다. 이것은 기본적으로 Java 및 C #에서 모든 것이 비공개이며, 필드는 최종적이며 일부 메소드는 최종적이며 때로는 클래스도 최종적 입니다.

그 뒤에 아이디어는 구현 세부 사항을 숨기는 것인데, 이는 매우 좋은 이유입니다. 그러나 protected대부분의 OOP 언어와 다형성이 있기 때문에 작동하지 않습니다.

수업에 기능을 추가하거나 변경할 때마다 나는 보통 사방에 배치되어 있고 사적으로 배치되어 있습니다. 여기에서 구현 세부 사항이 중요합니다. 구현을 수행하고 확장하여 결과가 무엇인지 완전히 알고 있습니다. 그러나 개인 및 최종 필드 및 메소드에 액세스 할 수 없으므로 세 가지 옵션이 있습니다.

  • 클래스를 확장하지 말고 문제를 해결하여 더 복잡한 코드로 연결하십시오.
  • 전체 클래스를 복사하여 붙여 넣어 코드 재사용 성
  • 프로젝트를 포크

그것들은 좋은 옵션이 아닙니다. protected지원 언어로 작성된 프로젝트에서 왜 사용 되지 않습니까? 일부 프로젝트가 클래스에서 상속을 명시 적으로 금지하는 이유는 무엇입니까?


1
Java Swing 구성 요소 에이 문제가 있음에 동의합니다. 정말 나쁩니다.
Jonas


3
이 문제가 발생하면 클래스가 처음에 제대로 설계되지 않았거나 잘못 사용하려고 할 가능성이 큽니다. 당신은 수업에 정보를 요구하지 않고, 당신을 위해 무언가를하도록 요구합니다. 따라서 당신은 일반적으로 데이터가 필요하지 않아야합니다. 이것이 항상 사실은 아니지만, 클래스 데이터에 액세스해야 할 경우 많은 기회가 잘못되었습니다.
Bill K

2
"대부분의 OOP 언어?" 수업을 닫을 수없는 곳을 훨씬 더 많이 알고 있습니다. 네 번째 옵션 인 언어 변경을 생략했습니다.
케빈 클라인

@Jonas Swing의 주요 문제점 중 하나는 너무 많은 구현을 노출한다는 것입니다. 그것은 정말로 개발을 죽입니다.
Tom Hawtin-tackline

답변:


55

확장 할 때, 특히 확장을 수행하는 프로그래머가 클래스의 작동 방식을 완전히 이해하지 못하는 경우 클래스가 올바르게 작동하도록 설계하려면 상당한 노력이 필요합니다. 개인 정보를 모두 공개하여 공개하거나 보호 할 수는 없습니다. 다른 사람이 변수의 값을 변경할 수있게하려면 가능한 모든 값이 클래스에 어떤 영향을 미치는지 고려해야합니다. (변수를 null로 설정하면 어떻게됩니까? 빈 배열? 음수?) 다른 사람이 메소드를 호출 할 수있게하는 경우에도 마찬가지입니다. 신중한 생각이 필요합니다.

따라서 수업을 열면 안되는 것은 아니지만 때로는 수업을 여는 데 가치가 없습니다.

물론, 도서관 저자들이 게으른 것일 수도 있습니다. 당신이 말하는 라이브러리에 따라 다릅니다. :-)


13
예, 이것이 클래스가 기본적으로 봉인 된 이유입니다. 클라이언트가 클래스를 확장 할 수있는 많은 방법을 예측할 수 없으므로 클래스를 확장 할 수있는 무한한 방법을 지원하는 것보다 봉인하는 것이 안전합니다.
Robert Harvey


18
수업이 어떻게 재정의 될지 예측할 필요는 없으며 수업을 확장하는 사람들이 자신의 행동을 알고 있다고 가정하면됩니다. 클래스를 확장하고 null이 아니어야 할 때 무언가를 null로 설정하면 오류가 아닌 오류입니다. 이 주장이 의미가있는 유일한 장소는 필드가 널 (null) 인 경우 무언가 잘못 될 수있는 매우 중요한 응용 프로그램입니다.
TheLQ

5
@ TheLQ : 그것은 꽤 큰 가정입니다.
Robert Harvey

9
@TheLQ 누구의 잘못인지는 매우 주관적인 질문입니다. 변수가 겉보기에 합리적인 값으로 설정되어 있고 허용되지 않았다는 사실을 문서화하지 않았고 코드에서 ArgumentException (또는 이와 동등한)이 발생하지 않았지만 서버가 오프라인이되거나 리드가 발생하는 경우 보안 익스플로잇의 경우, 당신의 잘못이라고 생각합니다. 그리고 유효한 값을 문서화하고 인수 확인을 한 경우, 내가 말하고있는 것과 정확히 같은 노력을 기울였으며, 그 노력이 실제로 시간을 잘 활용했는지 스스로에게 물어야합니다.
Aaron

24

기본적으로 모든 것을 비공개로 만드는 것은 거칠게 들리지만 반대쪽에서 살펴보십시오. 기본적으로 모든 것이 비공개 인 경우 무언가를 공개 (또는 거의 같은 것)로 만드는 것은 의식적인 선택입니다. 수업 사용 방법에 대한 귀하와 소비자와의 수업 저자 계약입니다. 인터페이스는 변경되지 않는 한 저자는 클래스의 내부 작업을 자유롭게 수정할 수 있습니다. 수업의 어느 부분을 신뢰할 수 있고 어떤 부분이 변경 될 수 있는지 정확하게 알고 있습니다.

기본 개념은 '느슨한 커플 링'( '좁은 인터페이스'라고도 함)입니다. 그 가치는 복잡성을 낮추는 데 있습니다. 구성 요소가 상호 작용할 수있는 방법의 수를 줄임으로써 구성 요소 간의 상호 의존도도 줄어 듭니다. 상호 의존성은 유지 관리 및 변경 관리와 관련하여 최악의 복잡한 문제 중 하나입니다.

잘 설계된 라이브러리에서 상속을 통해 확장 할 가치가있는 클래스는 적절한 장소에서 보호 및 공개 멤버를 보호하고 다른 모든 것을 숨 깁니다.


그것은 어떤 의미가 있습니다. 매우 유사한 것이 필요하지만 구현에서 1 또는 2 가지 변경 사항 (내부 작업)이 여전히 문제가 있습니다. 또한 클래스를 재정의하는 경우 이미 구현에 크게 의존하고 있지 않다는 것을 이해하기 위해 고심하고 있습니다.
TheLQ

5
느슨한 결합의 이점을 위해 +1. @ TheLQ, 그것은 구현이 아니라 크게 의존 해야하는 인터페이스 입니다.
Karl Bielefeldt

19

비공개 가 아닌 모든 것은 클래스의 모든 향후 버전에서 변경되지 않은 동작 으로 존재 해야합니다 . 실제로, 문서화 여부에 관계없이 API의 일부로 간주 될 수 있습니다. 따라서 세부 정보를 너무 많이 노출하면 나중에 호환성 문제가 발생할 수 있습니다.

"최종"담당자에 대하여. "밀봉 된"클래스, 일반적인 경우는 불변 클래스입니다. 프레임 워크의 많은 부분은 변경 불가능한 문자열에 의존합니다. String 클래스가 최종 클래스가 아닌 경우, 변경 불가능한 String 클래스 대신 사용되는 경우 다른 많은 클래스에서 모든 종류의 버그를 유발하는 변경 가능한 String 서브 클래스를 쉽게 만들 수 있습니다.


4
이것이 실제 답변입니다. 다른 답변은 중요한 것들에 접촉하지만, 실제 관련 비트는 이것이다 : 아니다 모든 final및 / 또는 private장소에 잠겨 절대 변경할 수 없습니다 .
Konrad Rudolph

1
@ Konrad : 개발자가 모든 것을 개선하고 이전 버전과 호환되지 않기로 결정할 때까지.
JAB

그것은 사실이지만, 그것이 public회원들 보다 훨씬 더 약한 요구 사항임을 주목할 가치가 있습니다 . protected회원은 자신과 즉시 파생 된 클래스를 공개하는 클래스 사이에서만 계약을 형성합니다. 대조적으로, public가능한 모든 현재 및 미래의 상속 클래스를 대신하여 모든 소비자와 계약을 맺은 회원.
supercat

14

OO에는 기존 코드에 기능을 추가하는 두 가지 방법이 있습니다.

첫 번째는 상속에 의한 것입니다. 당신은 수업을 듣고 그것을 파생시킵니다. 그러나 상속은주의해서 사용해야합니다. 기본과 파생 클래스 사이에 isA 관계 가있을 때 주로 공용 상속을 사용해야합니다 (예 : Rectangle Shape). 대신 기존 구현을 재사용하기 위해 공용 상속을 피해야합니다. 기본적으로 공용 상속은 파생 클래스가 기본 클래스 (또는 더 큰 클래스)와 동일한 인터페이스를 갖도록하기 위해 사용되므로 Liskov의 대체 원칙을 적용 할 수 있습니다 .

기능을 추가하는 다른 방법은 위임 또는 구성을 사용하는 것입니다. 기존 클래스를 내부 객체로 사용하고 구현 작업의 일부를이 클래스에 위임하는 자체 클래스를 작성합니다.

사용하려는 라이브러리의 모든 결승전의 아이디어가 무엇인지 잘 모르겠습니다. 아마도 프로그래머는 코드를 확장하는 가장 좋은 방법이 아니기 때문에 클래스에서 새 클래스를 상속받지 않기를 원했을 것입니다.


5
구성 대 상속의 장점.
Zsolt Török

+1이지만 상속을위한 "형상"예를 좋아하지 않았습니다. 사각형과 원은 매우 다른 두 가지 일 수 있습니다. 나는 더 많이 사용하기를 원한다. 정사각형은 구속 된 직사각형이다. 사각형은 셰이프처럼 사용할 수 있습니다.
tylermac

@ tylermac : 실제로. Square / Rectangle 사례는 실제로 LSP의 반대 사례 중 하나입니다. 아마도 더 효과적인 예를 생각해야합니다.
knulp

3
정사각형 / 직사각형은 수정을 허용하는 경우에 반대의 예일뿐입니다.
starblue

11

대부분의 대답은 옳습니다 : 객체가 설계되거나 확장되지 않을 때 클래스는 봉인되어야합니다 (최종, NotOverridable 등).

그러나 적절한 코드 디자인을 모두 닫는 것을 고려하지는 않습니다. SOLID의 "O"는 "개방형 원리"를위한 것으로 클래스는 수정하려면 "폐쇄"해야하지만 확장에는 "개방"되어야합니다. 아이디어는 객체에 코드가 있다는 것입니다. 잘 작동합니다. 기능을 추가하려면 해당 코드를 열고 이전에 작동하는 동작을 방해 할 수있는 외과 적 변경을하지 않아도됩니다. 대신, 상속 또는 의존성 주입을 사용하여 클래스를 "확장"하여 추가 작업을 수행 할 수 있어야합니다.

예를 들어, "ConsoleWriter"클래스는 텍스트를 가져 와서 콘솔에 출력 할 수 있습니다. 이것은 잘합니다. 그러나 어떤 경우에는 파일에 기록 된 것과 동일한 출력이 필요합니다. ConsoleWriter의 코드를 열고 외부 인터페이스를 변경하여 매개 변수 "WriteToFile"을 기본 기능에 추가하고 추가 파일 작성 코드를 콘솔 작성 코드 옆에 배치하는 것은 일반적으로 나쁜 것으로 간주됩니다.

대신 두 가지 중 하나를 수행 할 수 있습니다. ConsoleWriter에서 파생하여 ConsoleAndFileWriter를 형성하고 Write () 메서드를 확장하여 먼저 기본 구현을 호출 한 다음 파일에 쓸 수도 있습니다. 또는 ConsoleWriter에서 인터페이스 IWriter를 추출하고 해당 인터페이스를 다시 구현하여 두 개의 새 클래스를 작성할 수 있습니다. FileWriter 및 "MultiWriter"는 다른 IWriter에 제공 될 수 있으며 해당 메소드에 대한 모든 호출을 지정된 모든 작성자에게 "브로드 캐스트"합니다. 어떤 것을 선택해야하는지에 따라 달라집니다. 간단한 파생은 훨씬 간단하지만, 결국 네트워크 클라이언트, 세 개의 파일, 두 개의 명명 된 파이프 및 콘솔에 메시지를 보내야한다는 어둡고 통찰력이 있다면 계속 진행하여 인터페이스를 추출하고 문제를 해결하십시오. "Y- 어댑터"; 그것'

이제 Write () 함수가 가상으로 선언되지 않았거나 봉인 된 경우 소스 코드를 제어하지 않으면 문제가 발생할 수 있습니다. 때로는 그렇더라도. 이것은 일반적으로 좋지 않은 입장이며 폐쇄 소스 또는 제한 소스 API 사용자에게는 아무런 문제가 없습니다. 그러나 Write () 또는 전체 클래스 ConsoleWriter가 봉인 된 정당한 이유가 있습니다. 보호 된 필드에는 민감한 정보가있을 수 있습니다 (이 정보를 제공하기 위해 기본 클래스에서 차례로 재정의 됨). 유효성 검사가 충분하지 않을 수 있습니다. API를 작성할 때는 코드를 소비하는 프로그래머가 최종 시스템의 평균 "최종 사용자"보다 더 똑똑하거나 자비 롭지 않다고 가정해야합니다. 그렇게 당신은 결코 실망하지 않습니다.


좋은 지적. 상속은 좋거나 나쁠 수 있으므로 모든 클래스를 봉인해야한다고 말하는 것은 잘못입니다. 그것은 실제로 당면한 문제에 달려 있습니다.
knulp

2

나는 아마도 모든 사람들이 c 프로그래밍에 oo 언어를 사용하고 있다고 생각합니다. 당신을보고 있어요, 자바 망치 모양이 물체처럼 보이지만 절차 적 시스템을 원하거나 실제로 수업을 이해 하지 못하면 프로젝트를 잠 가야합니다.

기본적으로 oo 언어로 작성하려는 경우 프로젝트는 확장을 포함하는 oo 패러다임을 따라야합니다. 물론 누군가가 다른 패러다임에 도서관을 썼다면 어쨌든 그것을 확장하지 않아야 할 것입니다.


7
BTW, OO 및 절차는 상호 배타적이지 않습니다. 절차없이 OO를 가질 수 없습니다. 또한 구성은 확장과 마찬가지로 OO 개념입니다.
Michael K

@ 마이클은 물론 아닙니다. 절차 적 시스템, 즉 OO 개념이없는 시스템을 원할 수 있다고 말했습니다 . 사람들이 순수하게 절차 코드를 작성하기 위해 Java를 사용하는 것을 보았고 OO 방식으로 상호 작용하려고 시도하면 작동하지 않습니다.
스펜서 Rathbun

1
Java에 1 급 함수가 있다면 해결 될 수 있습니다. Meh.
Michael K

신입생에게 Java 또는 DOTNet 언어를 가르치는 것은 어렵습니다. 나는 보통 Procedural Pascal 또는 "Plain C"로 절차를 가르치고 나중에 Object Pascal 또는 "C ++"로 OO로 전환합니다. 그리고 나중에 Java를 남겨 두십시오. 프로 시저 프로그램을 사용한 교육 프로그래밍은 단일 (단일) 객체처럼 보입니다.
umlcat

@ Michael K : OOP에서 퍼스트 클래스 함수는 정확히 하나의 메소드를 가진 객체입니다.
조르지오

2

그들은 더 잘 알지 못하기 때문에.

원작자는 아마도 혼란스럽고 복잡한 C ++ 세계에서 비롯된 SOLID 원칙에 대한 오해에 집착하고있을 것입니다.

루비, 파이썬 및 펄 세계에는 여기에 대한 답변이 봉인의 이유라고 주장하는 문제가 없다는 것을 알기를 바랍니다. 동적 타이핑과 직교한다는 점에 유의하십시오. 액세스 수정자는 대부분의 언어에서 쉽게 사용할 수 있습니다. C ++ 필드는 다른 유형으로 캐스트하여 막힐 수 있습니다 (C ++가 더 약합니다). Java와 C #은 리플렉션을 사용할 수 있습니다. 액세스 수정자는 실제로 원하지 않는 한 작업을 수행 할 수 없을 정도로 어렵게 만듭니다.

봉인 강의와 모든 회원을 비공개로 표시하는 것은 간단한 일이 단순하고 어려운 일이 가능해야한다는 원칙을 명백히 위반합니다. 갑자기 단순해야 할 것들이 아닙니다.

원저자의 관점을 이해하도록 권유합니다. 현실 세계에서 절대적인 성공을 보여주지 못한 캡슐화에 대한 학문적 아이디어에서 비롯된 것입니다. 어딘가 개발자가 약간 다르게 작동하기를 원하지 않고 변경해야 할 이유가없는 프레임 워크 또는 라이브러리를 본 적이 없습니다. 회원을 봉인하고 비공개로 만든 최초의 소프트웨어 개발자를 괴롭 혔을 가능성이 두 가지 있습니다.

  1. 오만-그들은 확장을 위해 개방되고 수정을 위해 폐쇄되었다고 정말로 믿었습니다.
  2. 만족-그들은 다른 유스 케이스가있을 수 있다는 것을 알았지 만 그 유스 케이스에 대해서는 쓰지 않기로 결정했습니다

회사 프레임 워크에서 # 2가 아마도 그럴 것이라고 생각합니다. 이러한 C ++, Java 및 .NET 프레임 워크는 "완료"되어야하며 특정 지침을 따라야합니다. 이러한 지침은 일반적으로 유형이 유형 계층 구조의 일부로 명시 적으로 설계되고 다른 용도에 유용 할 수있는 많은 것들에 대한 개인 구성원이 아닌 한 봉인 된 유형을 의미합니다. . 새로운 유형을 추출하는 것은 지원, 문서화 등을하기에는 너무 비쌉니다.

액세스 수정 자의 기본 개념은 프로그래머가 스스로 보호해야한다는 것입니다. "C 프로그래밍은 발로 자신을 쏠 수 있기 때문에 나쁘다." 프로그래머로서 동의하는 것은 철학이 아닙니다.

나는 파이썬의 이름 맹 글링 접근법을 선호합니다. 필요한 경우 개인을 쉽게 대체 할 수 있습니다 (반사보다 훨씬 쉽습니다). 그것에 대한 훌륭한 글은 여기에서 볼 수 있습니다 : http://bytebaker.com/2009/03/31/python-properties-vs-java-access-modifiers/

Ruby의 개인용 수정자는 실제로 C #에서 보호되는 것과 비슷하며 개인용 C # 수정 자도 없습니다. 보호는 조금 다릅니다. 여기에 좋은 설명이 있습니다 : http://www.ruby-lang.org/en/documentation/ruby-from-other-languages/

정적 언어는 해당 언어로 작성된 코드의 구식 스타일을 따를 필요가 없습니다.


5
"캡슐화는 절대적인 성공을 보여주지 못했습니다". 캡슐화가 부족하면 많은 문제가 발생했다고 모든 사람들이 동의 할 것이라고 생각했을 것입니다.
Joh

5
-1. 천사들이 밟는 것을 두려워하는 곳에서 바보들이 몰려옵니다. 개인 변수를 변경하는 기능을 축하하면 코딩 스타일에 심각한 결함이 있습니다.
riwalk

2
논란의 여지가 있고 틀린 것 외에도이 게시물은 상당히 거만하고 모욕적입니다. 잘 했어요
Konrad Rudolph

1
지지자들이 잘 지내지 못하는 두 가지 사고 학교가 있습니다. 정적 타이핑 포크는 소프트웨어에 대해 추론하기 쉬워지기를 원하고 동적 포크는 기능을 쉽게 추가하기를 원합니다. 어느 쪽이 더 나은지는 기본적으로 프로젝트의 예상 수명에 달려 있습니다.
Joh

1

편집 : 수업은 개방적으로 설계되어야한다고 생각합니다. 몇 가지 제한 사항이 있지만 상속을 위해 닫혀서는 안됩니다.

왜 : "최종 수업"또는 "밀봉 된 수업"이 이상하게 보입니다. 나중에 자신의 클래스 중 하나를 "최종"( "봉인 된")으로 표시 할 필요가 없습니다. 나중에 해당 클래스를 inhere로 지정해야 할 수도 있습니다.

클래스가있는 타사 라이브러리 (가장 시각적 인 컨트롤)를 구매 / 다운로드했으며 프로그래밍 언어가 지원하더라도 코드가있는 프로그래밍 언어가 지원하더라도 해당 클래스를 확장하지 않았기 때문에 "최종"인 라이브러리는 전혀 감사하지 않습니다. 소스 코드없이 라이브러리를 컴파일 한 경우에도 마찬가지입니다.

때때로, 나는 일부 "밀봉 된"클래스를 다루어야하고, 비슷한 클래스를 가진 주어진 클래스를 포함하는 새로운 클래스 "래퍼"를 만드는 것을 끝내었다.

class MyBaseWrapper {
  protected FinalClass _FinalObject;

  public virtual DoSomething()
  {
    // these method cannot be overriden,
    // its from a "final" class
    // the wrapper method can  be open and virtual:
    FinalObject.DoSomething();
  }
} // class MyBaseWrapper


class MyBaseWrapper: MyBaseWrapper {

  public override DoSomething()
  {
    // the wrapper method allows "overriding",
    // a method from a "final" class:
    DoSomethingNewBefore();
    FinalObject.DoSomething();
    DoSomethingNewAfter();
  }
} // class MyBaseWrapper

스코프 clasifier를 사용하면 상속을 제한하지 않고 일부 기능을 "밀봉"할 수 있습니다.

내가 구조적 Progr에서 전환했을 때 OOP에 개인 클래스 멤버를 사용하기 시작 했지만 많은 프로젝트 후에 protected 사용을 끝내고 결국에는 해당 속성이나 메서드를 public 으로 홍보했습니다 .

추신 : 나는 C #에서 키워드로 "final"을 좋아하지 않고 상속 은유에 따라 Java 또는 "sterile"과 같은 "sealed"를 사용합니다. 또한 "최종"은 여러 상황에서 사용되는 모호한 단어입니다.


-1 : 개방형 디자인을 좋아한다고 말했지만,이 질문에 대한 답은 아닙니다. 클래스가 개방되도록 설계되어서는 안됩니다.
Joh

@Joh 미안, 나는 나 자신을 잘 설명하지 않았다, 나는 반대 의견을 표현하려고 노력했다, 봉인해서는 안됩니다. 동의하지 않는 이유를 설명해 주셔서 감사합니다.
umlcat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.