왜 수업이 "추상"또는 "최종 / 봉인"이외의 것이어야합니까?


29

10 년 이상의 java / c # 프로그래밍 후 다음 중 하나를 생성했습니다.

  • 추상 클래스 : 계약은있는 그대로 인스턴스화되지 않습니다.
  • final / sealed classes : 구현은 다른 것에 대한 기본 클래스로 사용되지 않습니다.

간단한 "클래스"(즉, 추상이나 최종 / 봉인되지 않은)가 "현명한 프로그래밍"이되는 상황은 생각할 수 없습니다.

왜 수업이 "추상"또는 "최종 / 봉인"이외의 것이어야합니까?

편집하다

이 위대한 기사 는 내가 할 수있는 것보다 훨씬 더 나은 나의 관심사를 설명합니다.


21
그것은이라고 때문에 Open/Closed principle아닌Closed Principle.
StuperUser

2
전문적으로 작성하는 것은 무엇입니까? 문제에 대한 귀하의 의견에 매우 영향을 줄 수 있습니다.

글쎄, 나는 상속 인터페이스를 최소화하기를 원하기 때문에 모든 것을 밀봉하는 일부 플랫폼 개발자를 안다.
K ..

4
@StuperUser : 그 이유가 아니라, 그것은 소름입니다. OP는 소원이 무엇인지 묻지 않고 이유를 묻습니다 . 원칙 뒤에 이유가 없다면주의를 기울일 이유가 없습니다.
Michael Shaw

1
Window 클래스를 봉인으로 변경하여 UI 프레임 워크가 몇 개나 될지 궁금합니다.
Reactgular

답변:


46

아이러니하게도, 나는 반대를 발견한다 : 추상 클래스의 사용은 규칙이 아니라 예외이며 최종 / 밀봉 클래스에 눈살을 찌푸리는 경향이있다.

인터페이스는 내부를 지정하지 않기 때문에보다 일반적인 계약 별 설계 메커니즘입니다. 걱정하지 않아도됩니다. 계약의 모든 구현이 독립적 일 수 있습니다. 이것은 많은 도메인의 핵심입니다. 예를 들어, ORM을 빌드하는 경우, 동일한 방식으로 데이터베이스에 쿼리를 전달할 수 있어야하지만 구현 방식이 크게 다를 수 있습니다. 이 목적으로 추상 클래스를 사용하면 모든 구현에 적용되거나 적용되지 않을 수있는 구성 요소에 결선이 발생합니다.

최종 / 밀봉 클래스의 경우 클래스를 사용하는 데있어 볼 수있는 유일한 변명은 재정의를 허용하는 것이 실제로 위험한 경우 (암호화 알고리즘 또는 기타) 일 수 있습니다. 그 외에는 지역적인 이유로 기능을 언제 확장 할 것인지 알 수 없습니다. 클래스를 봉인하면 대부분의 시나리오에 존재하지 않는 이익에 대한 옵션이 제한됩니다. 나중에 줄을 확장 할 수있는 방식으로 클래스를 작성하는 것이 훨씬 더 유연합니다.

이 후자의 견해는 클래스를 봉인하는 타사 구성 요소로 작업하여 삶을 훨씬 쉽게 만들 수있는 일부 통합을 방지함으로써 강화되었습니다.


17
마지막 단락에 +1 제 3 자 라이브러리 (또는 표준 라이브러리 클래스)에서 너무 많은 캡슐화가 너무 작은 캡슐화보다 고통의 더 큰 원인이라는 것을 자주 발견했습니다.
메이슨 휠러

5
클래스 봉인에 대한 일반적인 주장은 클라이언트가 클래스를 재정의 할 수있는 다양한 방법을 예측할 수 없으므로 해당 동작을 보장 할 수 없다는 것입니다. blogs.msdn.com/b/ericlippert/archive/2004/01/22/…
Robert Harvey

12
@RobertHarvey 나는 논쟁에 익숙하며 이론적으로는 훌륭하게 들린다. 당신은 사람들이 당신의 클래스를 확장 할 수 있습니다 방법을 forsee 수없는 객체 디자이너로 - 그들이해야하는 이유를 자세히입니다 하지 봉인. 모든 것을 지원할 수는 없습니다. 하지마 그러나 옵션도 빼앗지 마십시오.
Michael

6
@RobertHarvey는 LSP를 깨는 사람들이 자신이 원하는 것을 얻는 것이 아닌가?
StuperUser

2
저는 봉인 된 수업을 좋아하지는 않지만 Microsoft와 같은 일부 회사 (자신을 깨뜨리지 않는 것을 자주 지원해야하는)가 매력적인 이유를 알 수있었습니다. 프레임 워크의 사용자는 반드시 클래스의 내부 지식을 가지고 있지 않아도됩니다.
Robert Harvey

11

입니다 에릭 Lippert의의 뛰어난 기사,하지만 난 당신의 관점을 지원합니다 생각하지 않습니다.

그는 다른 사람들사용하기 위해 노출 된 모든 클래스 는 봉인되거나 확장 할 수 없도록 만들어야 한다고 주장합니다 .

당신의 전제는 모든 수업 이 추상적이거나 인봉되어야한다는 것입니다.

큰 차이.

EL의 기사는 그의 팀이 생산 한 (아마도 많은) 수업에 대해 아무 것도 말하지 않습니다. 일반적으로 프레임 워크에서 공개적으로 노출 된 클래스는 해당 프레임 워크 구현과 관련된 모든 클래스의 하위 집합 일뿐입니다.


그러나 공용 인터페이스의 일부가 아닌 클래스에 동일한 인수를 적용 할 수 있습니다. 수업을 마무리 짓는 주된 이유 중 하나는 코드가 느슨해지지 않도록 안전 조치로 작용하기 때문입니다. 이 주장은 시간이 지남에 따라 다른 개발자가 공유하거나 모든 개발자가 공유하는 모든 코드베이스에 유효합니다. 코드의 의미를 보호합니다.
DPM

클래스가 추상적이거나 봉인되어야한다는 생각은 더 넓은 원칙의 일부라고 생각합니다. 즉, 인스턴스화 가능한 클래스 유형의 변수를 사용하지 않아야합니다. 이러한 회피는 주어진 개인의 모든 개인 구성원을 상속하지 않고도 주어진 유형의 소비자가 사용할 수있는 유형을 작성할 수있게한다. 불행히도 이러한 디자인은 실제로 공용 생성자 구문에서 작동하지 않습니다. 대신 코드 new List<Foo>()는와 같은 것으로 바꿔야 List<Foo>.CreateMutable()합니다.
supercat

5

자바 관점에서 나는 최종 클래스가 똑똑하지 않다고 생각합니다.

많은 도구 (특히 AOP, JPA 등)는로드 시간을 줄이면서 작동하므로 걸쇠를 확장해야합니다. 다른 방법은 .NET이 아닌 델리게이트를 만들어 모든 클래스를 원래 클래스에 위임하여 사용자 클래스를 확장하는 것보다 훨씬 어려울 수 있습니다.


5

밀봉되지 않은 바닐라 수업 이 필요한 두 가지 일반적인 경우 :

  1. 기술 : 2 단계 이상의 계층 구조를 가지고 있고 중간에 무언가를 인스턴스화하려는 경우.

  2. 원칙 : 때로는 안전하게 확장되도록 설계된 클래스를 작성하는 것이 바람직합니다 . (이것은 Eric Lippert와 같은 API를 작성하거나 팀에서 대규모 프로젝트를 수행 할 때 많이 발생합니다). 때로는 스스로 잘 작동하는 클래스를 작성하려고하지만 확장 성을 염두에두고 설계되었습니다.

밀봉 차종에 에릭 Lippert의의 생각은 감지, 그러나 그는 또한 그들이 인정 하지 "열기"클래스를 남겨 확장을 위해 설계.

그렇습니다. BCL에는 많은 수업이 봉인되어 있지만 많은 수업은 그렇지 않으며 모든 종류의 훌륭한 방법으로 확장 될 수 있습니다. 염두에 두는 예는 거의 모든 Control상속을 통해 데이터 나 동작을 추가 할 수있는 Windows Forms 입니다. 물론 이것은 다른 방식 (장식 패턴, 다양한 유형의 구성 등)으로 수행되었을 수도 있지만 상속도 매우 잘 작동합니다.

두 가지 .NET 관련 참고 사항 :

  1. 대부분의 환경에서 상속자는 virtual명시 적 인터페이스 구현을 제외하고는 비 기능을 망칠 수 없기 때문에 안전에 중요하지 않은 경우가 많습니다 .
  2. 때때로 적합한 대안은 internal클래스를 봉인하는 대신 생성자를 만드는 것입니다. 클래스를 코드베이스 내부에서 상속 할 수 있지만 클래스 외부에서는 상속 할 수 없습니다.

4

나는 많은 사람들이 클래스가해야한다 느끼는 진짜 이유가 믿는 final/ sealed대부분의 비 추상적 확장 클래스가되는 것입니다 하지 제대로 문서화 .

자세히 설명하겠습니다. 멀리서부터 OOP의 도구로서의 상속이 널리 남용되고 남용된다는 견해가 일부 프로그래머들 사이에 있습니다. 우리는 모두 Liskov 대체 원칙을 읽었지만 이것이 수백 (아마도 수천 번)을 위반하는 것을 막지는 못했습니다.

문제는 프로그래머가 코드를 재사용하는 것을 좋아한다는 것입니다. 그렇게 좋은 생각이 아니더라도. 그리고 상속은 코드의 "재활용"의 핵심 도구입니다. 최종 / 봉인 된 질문으로 돌아 가기

최종 / 밀봉 클래스에 대한 적절한 문서는 비교적 작습니다. 각 메소드의 기능, 인수, 반환 값 등을 설명합니다.

그러나 확장 가능한 클래스 올바르게 문서화 할 때는 최소한 다음을 포함해야합니다 .

  • 메소드 간의 종속성 (어떻게 메소드를 호출하는지 등)
  • 지역 변수에 대한 의존성
  • 확장 클래스가 존중해야하는 내부 계약
  • 각 방법에 대한 호출 규칙 (예를 들어, 당신이 그것을 무시하면, 당신은 전화를 할 super구현을? 당신이 방법의 시작에 전화하거나 결국하십니까? 소멸자 대 생성자를 생각)
  • ...

이것들은 내 머리 꼭대기에 있습니다. 그리고 나는 이들 각각이 중요한 이유의 예를 제공 할 수 있으며 그것을 건너 뛰면 확장 수업을 망칠 것입니다.

이제 이러한 각 항목을 올바르게 문서화하는 데 얼마나 많은 문서 노력이 필요한지 고려하십시오. 나는 7-8 개의 메소드가있는 클래스 (이상화 된 세계에서는 너무 많지만 실제로는 너무 적음)가 텍스트 전용 5 페이지에 대한 문서를 가지고 있다고 생각합니다. 대신, 우리는 반쯤 빠져 나와 다른 사람들이 사용할 수 있도록 수업을 봉인하지 않습니다. 그러나 시간이 많이 걸리기 때문에 올바르게 문서화하지 마십시오. 어쨌든 확장되지 않으므로 왜 귀찮게합니까?).

강의를 디자인하는 경우 사람들이 예상치 못한 (그리고 준비하지 않은) 방법으로 강의를 사용할 수 없도록 봉인하려는 유혹을 느낄 수 있습니다. 반면에 다른 사람의 코드를 사용하는 경우 (공개 API에서) 클래스가 최종적인 이유는 없으며 "Damn, 해결 방법을 찾는 데 30 분이 소요됩니다"라고 생각할 수도 있습니다.

솔루션의 일부 요소는 다음과 같습니다.

  • 먼저 확인하는 좋은 아이디어입니다 확장 당신이 코드의 클라이언트가되면하고 정말 상속을 통해 구성을 선호한다.
  • 두 번째로 고객이 언급 한 내용을 간과하지 않도록 매뉴얼 전체를 다시 읽어보십시오 .
  • 셋째, 클라이언트가 사용할 코드를 작성할 때 코드에 대한 적절한 문서를 작성하십시오 (그렇습니다). 긍정적 인 예로서, 나는 애플의 iOS 문서를 제공 할 수있다. 항상 제대로 자신의 클래스를 확장에 그들은 사용자에 대한 충분하지,하지만 그들은 적어도 포함 일부 상속에 대한 정보를. 대부분의 API에 대해 말할 수있는 것 이상입니다.
  • 넷째, 실제로 자신의 수업을 확장하여 작동하는지 확인하십시오. API에 많은 샘플과 테스트를 포함시키는 데 큰 지지자이며 테스트를 할 때 상속 체인도 테스트 할 수 있습니다. 결국 계약의 일부입니다!
  • 다섯째, 의심스러운 상황에서는 수업이 확장 될 수 없으며 수업을하는 것이 나쁜 생각 (tm)임을 나타냅니다. 의도하지 않은 사용에 대해 책임을지지 말아야하지만 여전히 수업을 봉인하지는 마십시오. 분명히 이것은 클래스가 100 % 봉인되어야하는 경우에는 적용되지 않습니다.
  • 마지막으로, 클래스를 봉인 할 때 클라이언트가 자신의 수정 된 클래스 버전을 "다시 작성"하고 "봉인 된"클래스를 해결할 수 있도록 인터페이스를 중간 후크로 제공하십시오. 이렇게하면 봉인 된 클래스를 구현으로 바꿀 수 있습니다. 가장 간단한 형태의 느슨한 커플 링이기 때문에 이것은 분명해야하지만 여전히 언급 할 가치가 있습니다.

다음과 같은 "철학적"질문을 언급 할 가치가있다 : 클래스인지 여부가 sealed/ final클래스, 또는 구현 세부 사항에 대한 계약의 일부? 이제 나는 거기에서 밟고 싶지 않지만 이것에 대한 대답은 수업을 봉인할지 여부에 대한 결정에도 영향을 미칩니다.


3

다음과 같은 경우 클래스가 최종 / 봉인 또는 요약되지 않아야합니다.

  • 자체적으로 유용합니다. 즉, 해당 클래스의 인스턴스를 갖는 것이 좋습니다.
  • 해당 클래스가 다른 클래스의 하위 클래스 / 기본 클래스가되는 것이 좋습니다.

예를 들어 ObservableCollection<T>C #클래스를 사용하십시오 . 이벤트 발생을 a의 일반 작업에 추가하기 만하면 Collection<T>되므로 하위 클래스가 Collection<T>됩니다. Collection<T>자체적으로 실행 가능한 클래스이므로 ObservableCollection<T>.


내가이 일을 담당했다면 아마 CollectionBase(추상), Collection : CollectionBase(봉인 된), ObservableCollection : CollectionBase(봉인 된) 것입니다. 당신이 보면 컬렉션 <T는> 밀접하게, 당신은 그것이 절반이 추상 클래스입니다 볼 수 있습니다.
Nicolas Repiquet

2
두 개가 아닌 세 개 수업을 통해 어떤 이점이 있습니까? 또한 Collection<T>"반반 추상 클래스"는 어떻습니까?
FishBasketGordo

Collection<T>보호 된 메소드와 속성을 통해 많은 내부를 노출하며, 특수 컬렉션의 기본 클래스가 될 것입니다. 그러나 구현할 추상 메소드가 없으므로 코드를 어디에 배치해야하는지 명확하지 않습니다. ObservableCollection에서 상속되고 Collection봉인되지 않으므로 다시 상속받을 수 있습니다. Items보호 된 속성에 액세스하여 이벤트 발생 시키지 않고 컬렉션에 항목을 추가 할 수 있습니다 .
Nicolas Repiquet

@NicolasRepiquet : 많은 언어와 프레임 워크에서 객체를 생성하는 일반적인 관용적 수단을 사용하려면 객체를 보유 할 변수의 유형이 생성 된 인스턴스의 유형과 같아야합니다. 대부분의 경우 이상적인 사용법은 추상 형식에 대한 참조를 전달하는 것이지만 생성자 호출시 변수 및 매개 변수에 대해 하나의 형식을 사용하고 다른 구체적인 형식을 사용하도록 많은 코드를 강요합니다. 거의 불가능하지만 약간 어색합니다.
supercat

3

최종 / 밀봉 클래스의 문제점은 아직 발생하지 않은 문제를 해결하려고한다는 것입니다. 문제가있는 경우에만 유용하지만 타사가 제한을가했기 때문에 실망합니다. 봉인 클래스가 현재의 문제를 해결하는 경우는 거의 없으므로 유용하다고 주장하기가 어렵습니다.

클래스를 봉인해야하는 경우가 있습니다. 예를 들어; 이 클래스는 향후 변경으로 인해 해당 관리가 어떻게 변경 될지 예측할 수없는 방식으로 할당 된 리소스 / 메모리를 관리합니다.

수년 동안 캡슐화, 콜백 및 이벤트가 추상화 된 클래스보다 훨씬 유연하고 유용하다는 것을 알았습니다. 캡슐화 및 이벤트가 개발자의 삶을 더 간단하게 만들 수있는 클래스의 큰 계층 구조로 많은 코드를 보았습니다.


1
소프트웨어에서 최종 봉인 된 내용은 "이 코드의 미래 관리자에게 의지를 강요 할 필요가있다"는 문제를 해결합니다.
Kaz

내가 어릴 때 주변에 있었던 것을 기억하지 못하기 때문에 OOP에 추가 된 내용은 최종 / 봉인되지 않았습니다. 그것은 생각 후의 일종의 특징처럼 보입니다.
Reactgular

최종 마무리는 OOP가 C ++ 및 Java와 같은 언어로 무너지기 전에 OOP 시스템에서 툴체인 절차로 존재했습니다. 프로그래머는 유연성이 극대화 된 스몰 토크, 리스프에서 작업 할 수 있습니다. 그런 다음 시스템의 컴파일 된 이미지는 최적화의 대상이됩니다. 즉, 최종 사용자가 시스템을 확장하지 않을 것이라는 가정하에, 어떤 메소드 및 수업이 지금 존재합니다 .
Kaz

이것이 동일한 기능이라고 생각하지 않습니다. 최적화 기능 일뿐입니다. 모든 버전의 Java에서 봉인 된 것을 기억하지는 않지만 많이 사용하지 않아 잘못 될 수 있습니다.
Reactgular

코드에 선언 해야하는 선언으로 표시된다고해서 동일한 것이 아니라는 의미는 아닙니다.
Kaz

2

전체 디스패치 그래프가 알려져 있기 때문에 오브젝트 시스템에서 "밀봉"또는 "최종화"는 특정 최적화를 허용합니다.

다시 말해, 성능의 균형을 유지하기 위해 시스템을 다른 것으로 변경하기가 어렵습니다. (이것이 가장 최적화의 본질입니다.)

다른 모든면에서는 손실입니다. 시스템은 기본적으로 열려 있고 확장 가능해야합니다. 모든 클래스에 새 메소드를 쉽게 추가하고 임의로 확장 할 수 있어야합니다.

향후 확장을 방지하기위한 조치를 취함으로써 현재 새로운 기능을 얻지 못했습니다.

우리가 예방 자체를 위해 그렇게한다면, 우리가하고있는 일은 미래의 관리인의 삶을 통제하려는 것입니다. 자아에 관한 것입니다. "더 이상 여기서 일하지 않아도이 코드는 계속 유지 될 것입니다."


1

시험 수업이 떠오른 것 같습니다. 프로그래머 / 테스터가 달성하려고하는 것에 따라 자동화 된 방식으로 또는 "의지에 따라"호출되는 클래스입니다. 나는 비공개 테스트의 완성 된 클래스를 보거나 들어 본 적이 없다.


무슨 소리 야? 어떤 방법으로 시험 수업이 최종 / 봉인 / 개요가되어서는 안됩니까?

1

확장 할 수업을 고려해야 할 시점은 미래를위한 실제 계획을 세우는 시점입니다. 내 작품에서 실제 사례를 보여 드리겠습니다.

주 제품과 외부 시스템간에 인터페이스 도구를 작성하는 데 많은 시간을 보냅니다. 우리가 새로운 판매를 할 때, 하나의 큰 구성 요소는 규칙적인 간격으로 실행되도록 설계된 수출자 세트입니다. 그런 다음이 데이터 파일은 고객 시스템에서 사용됩니다.

이것은 수업을 연장 할 수있는 훌륭한 기회입니다.

모든 수출의 기본 클래스 인 Export 클래스가 있습니다. 데이터베이스에 연결하고 마지막으로 실행 한 시간을 찾아서 생성하는 데이터 파일의 아카이브를 작성하는 방법을 알고 있습니다. 또한 속성 파일 관리, 로깅 및 간단한 예외 처리 기능도 제공합니다.

이 외에도 각 유형의 데이터로 작업 할 수있는 다른 수출업자가 있습니다. 아마도 사용자 활동, 거래 데이터, 현금 관리 데이터 등이있을 수 있습니다.

이 스택 위에 고객이 필요로하는 데이터 파일 구조를 구현하는 고객 별 레이어를 배치합니다.

이런 식으로 기본 내보내기는 거의 변경되지 않습니다. 핵심 데이터 유형 내보내기 프로그램은 때때로 변경되지만 거의 변경되지 않으며 일반적으로 모든 고객에게 전파되어야하는 데이터베이스 스키마 변경 만 처리합니다. 각 고객에 대해 내가해야하는 유일한 작업은 해당 고객에 특정한 코드 부분입니다. 완벽한 세상!

따라서 구조는 다음과 같습니다.

Base
 Function1
  Customer1
  Customer2
 Function2
 ...

나의 주요 요점은 코드를 이런 식으로 설계함으로써 주로 코드 재사용을 위해 상속을 사용할 수 있다는 것입니다.

3 계층을 넘어 설 이유를 생각할 수 없다.

예를 들어 Table데이터베이스 테이블 쿼리를 구현 하는 공통 클래스를 사용하고 하위 클래스 는 필드를 정의 Table하기 enum위해 각 테이블의 특정 세부 정보를 구현하는 두 가지 계층을 여러 번 사용했습니다 . (가)시키는 enum에 정의 인터페이스 구현 Table클래스 것은 의미 모든 종류의 수 있습니다.


1

추상 클래스가 유용하지만 항상 필요한 것은 아니라는 것을 알았으며 단위 테스트를 적용 할 때 봉인 클래스가 문제가되었습니다. Teleriks justmock과 같은 것을 사용하지 않으면 봉인 된 클래스를 조롱하거나 스터브 할 수 없습니다.


1
이것은 좋은 의견이지만 질문에 직접 대답하지는 않습니다. 여기에서 생각을 넓히는 것을 고려하십시오.

1

나는 당신의 견해에 동의합니다. Java에서는 기본적으로 클래스가 "최종"으로 선언되어야한다고 생각합니다. 최종적으로 작성하지 않으면 구체적으로 작성하여 확장을 위해 문서화하십시오.

그 주된 이유는 클래스의 모든 인스턴스가 원래 디자인하고 문서화 한 인터페이스를 준수하기위한 것입니다. 그렇지 않으면 클래스를 사용하는 개발자는 깨지기 쉽고 일관성이없는 코드를 생성 할 수 있으며, 그 결과 다른 개발자 / 프로젝트로 코드를 전달하여 클래스의 객체를 신뢰할 수 없게 만듭니다.

라이브러리 인터페이스의 클라이언트는 원래 생각했던 것보다 더 유연한 방식으로 클래스를 조정하고 사용할 수 없기 때문에보다 실용적인 관점에서 실제로 이것에 대한 단점이 있습니다.

개인적으로 존재하는 모든 나쁜 품질의 코드에 대해 (그리고 우리는 이것을 더 실용적인 수준에서 논의하고 있기 때문에 Java 개발이 더 쉽다고 주장 할 것입니다.) 코드를 유지하십시오.

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