상속과 null 값을 가진 추가 속성


12

선택적 필드가있는 클래스의 경우 상속 또는 널 입력 가능 특성을 사용하는 것이 더 낫습니까? 이 예제를 고려하십시오.

class Book {
    private String name;
}
class BookWithColor extends Book {
    private String color;
}

또는

class Book {
    private String name;
    private String color; 
    //when this is null then it is "Book" otherwise "BookWithColor"
}

또는

class Book {
    private String name;
    private Optional<String> color;
    //when isPresent() is false it is "Book" otherwise "BookWithColor"
}

이 3 가지 옵션에 따른 코드는 다음과 같습니다.

if (book instanceof BookWithColor) { ((BookWithColor)book).getColor(); }

또는

if (book.getColor() != null) { book.getColor(); }

또는

if (book.getColor().isPresent()) { book.getColor(); }

첫 번째 접근법은 나에게 더 자연스럽게 보이지만 캐스팅을해야하기 때문에 읽기 어려울 수 있습니다. 이 동작을 수행하는 다른 방법이 있습니까?


1
해결책이 비즈니스 규칙의 정의로 귀결되는 것을 두려워합니다. 모든 책에 색상이있을 수 있지만 색상이 선택 사항 인 경우 모든 책에 색상 속성이 존재하기를 원하기 때문에 상속이 과도하고 실제로 불필요합니다. 반면, 색에 대해 전혀 모르는 책과 색을 가진 책을 모두 가질 수 있다면 전문적인 수업을 만드는 것이 나쁘지 않습니다. 그럼에도 불구하고, 아마도 상속 이상의 구성을 원할 것입니다.
Andy

좀 더 구체적으로 만들기 위해 두 가지 유형의 책이 있습니다. 모든 책에 색상이있는 것은 아닙니다.
Bojan VukasovicTest가

1
책에 색이 전혀없는 경우가 실제로 있다면 상속은 화려한 책의 속성을 확장하는 가장 간단한 방법입니다. 이 경우 기본 책 클래스를 상속하고 색상 속성을 추가하여 무언가를 깨뜨리는 것처럼 보이지 않습니다. 클래스 확장이 허용되는 경우와 그렇지 않은 경우에 대해 자세히 알아 보려면 liskov 대체 원칙에 대해 읽을 수 있습니다.
Andy

4
색칠 한 책에서 원하는 행동을 식별 할 수 있습니까? 채색이 있거나없는 책에서 채색이있는 책이 약간 다르게 동작하는 일반적인 동작을 찾을 수 있습니까? 그렇다면, 이것은 다른 유형의 OOP에 대한 좋은 경우이며, 속성 존재와 가치를 외부 적으로 묻지 않고 행동을 클래스로 옮기는 것이 좋습니다.
Erik Eidt

1
가능한 책 색상을 미리 알고 있다면 BookColor.NoColor 옵션이있는 BookColor의 Enum 필드는 어떻습니까?
Graham

답변:


7

상황에 따라 다릅니다. BookWithColor실제 프로그램에서 호출 된 서브 클래스가 없기 때문에 특정 예제는 비현실적 입니다.

그러나 일반적으로 특정 서브 클래스에만 적합한 속성은 해당 서브 클래스에만 존재해야합니다.

예를 들어 경우가 Book있다 PhysicalBookDigialBook후손으로, 다음 PhysicalBook있을 수 있습니다 weight재산 및 속성을. 그러나 이되지 않습니다 반대의 경우도 마찬가지. 클래스에는 모든 자손이 공유하는 속성 만 있어야하므로 속성이 없습니다.DigitalBooksizeInKbDigitalBookweightBook

더 좋은 예는 실제 소프트웨어의 클래스를 보는 것입니다. JSlider구성 요소는 필드가 있습니다 majorTickSpacing. 슬라이더에만 "틱"이 있으므로이 필드는 JSlider그 하위 항목 에만 적합 합니다. 같은 다른 형제 구성 요소 JButtonmajorTickSpacing필드 가 있으면 매우 혼란 스러울 것 입니다.


또는 두 가지를 모두 반영 할 수 있도록 무게를 좁힐 수는 있지만 너무 많을 수 있습니다.
Walfrat

3
@Walfrat : 같은 필드가 다른 서브 클래스에서 완전히 다른 것을 나타내는 것은 정말 좋지 않습니다.
JacquesB

책에 실제 판과 디지털 판이 모두 있다면? 각 선택적 필드가 있거나없는 모든 순열에 대해 서로 다른 클래스를 작성해야합니다. 가장 좋은 해결책은 책에 따라 일부 필드를 생략하거나 생략하는 것입니다. 두 클래스가 기능적으로 다르게 동작하는 완전히 다른 상황을 언급했습니다. 그것은이 질문이 의미하는 것이 아닙니다. 데이터 구조 만 모델링하면됩니다.
4castle

@ 4 캐슬 : 각 에디션마다 가격이 다를 수 있으므로 에디션마다 별도의 클래스가 필요합니다. 선택적 필드로는이 문제를 해결할 수 없습니다. 그러나 op가 컬러 또는 "무색 서적"을 가진 서적에 대해 다른 동작을 원한다면이 예제에서 알 수 없으므로이 경우 올바른 모델링이 무엇인지 알 수 없습니다.
JacquesB

3
@JacquesB에 동의하십시오. "책 모델링"에 대해 보편적으로 올바른 솔루션을 제공 할 수는 없습니다. 해결하려는 문제와 비즈니스에 따라 달라지기 때문입니다. 나는 (그래 그래,하지만 당신은 liskov을 위반하는 클래스 계층 구조를 정의하는 것은 절대적으로 잘못된 (TM) 인 말을 꽤 안전하다고 생각하는 당신의 경우는 아마 매우 특별하고 보증을 (나는 그것을 의심하지만))
사라

5

언급되지 않은 중요한 점 : 대부분의 언어에서 클래스의 인스턴스는 해당 클래스를 변경할 수 없습니다. 색상이없는 책이 있고 색상을 추가하려면 다른 클래스를 사용하는 경우 새 객체를 만들어야합니다. 그런 다음 이전 객체에 대한 모든 참조를 새 객체에 대한 참조로 바꿔야 할 것입니다.

"컬러가없는 책"과 "컬러가있는 책"이 같은 클래스의 인스턴스 인 경우 색상을 추가하거나 색상을 제거하면 문제가 훨씬 줄어 듭니다. (사용자 인터페이스에 "컬러가있는 책"및 "컬러가없는 책"목록이 표시되면 해당 사용자 인터페이스가 분명히 변경되어야하지만 "빨간색 목록"과 유사하게 처리해야 할 것으로 예상됩니다. 도서 "및"녹색 도서 목록 ").


이것은 정말 중요한 포인트입니다! 클래스 속성 대신 선택적 속성 모음을 고려해야하는지 여부를 정의합니다.
염색체

1

JavaBean (과 같은 Book)을 데이터베이스의 레코드로 생각하십시오 . 선택적 열은 null가치가없는 경우이지만 완벽하게 합법적입니다. 따라서 두 번째 옵션은 다음과 같습니다.

class Book {
    private String name;
    private String color; // null when not applicable
}

가장 합리적입니다. 1

Optional수업 사용 방법에주의하십시오 . 예를 들어, Serializable일반적으로 JavaBean의 특성 인 이 아닙니다 . Stephen Colebourne의 팁은 다음과 같습니다 .

  1. 유형의 인스턴스 변수를 선언하지 마십시오 Optional.
  2. null클래스의 개인 범위 내에서 선택적 데이터를 나타내는 데 사용 합니다.
  3. Optional선택적 필드에 액세스하는 게터에 사용하십시오 .
  4. Optional세터 나 생성자 에는 사용하지 마십시오 .
  5. Optional선택적 결과가있는 다른 비즈니스 로직 메소드의 리턴 유형으로 사용하십시오 .

따라서 클래스 null 에서 필드가 존재하지 않음을 나타 내기 위해 사용해야 하지만 color Book(반환 유형으로) 으로 감싸 야합니다 Optional.

return Optional.ofNullable(color); // inside the class

book.getColor().orElse("No color"); // outside the class

이것은 명확한 디자인과 더 읽기 쉬운 코드를 제공합니다.


(1) A는 당신이하려는 경우 BookWithColor한 책의 전체 "클래스"캡슐화하는 기능을 전문 다른 책 이상을, 다음 은 사용 상속에 대한 감각을 만들 것입니다.


1
누가 데이터베이스에 대해 말했습니까? 모든 OO 패턴이 관계형 데이터베이스 패러다임에 적합해야한다면 많은 OO 패턴을 변경해야합니다.
alexanderbird

사용하지 않는 속성을 "추가하는 경우"를 추가하는 것이 가장 명확한 옵션이라고 주장합니다. 다른 사람들이 말했듯이, 그것은 유스 케이스에 달려 있지만 가장 바람직한 상황은 외부 응용 프로그램이 존재하는 속성이 실제로 사용되는지 여부를 알아야하는 속성을 옮기지 않는 것입니다.
Volker Kueffel

@Volker Optional이 정확한 목적을 위해 클래스를 Java 에 추가하는 데 도움을 준 여러 사람들을 인용 할 수 있기 때문에 동의하지 않기로 합의하자 . OO가 아닐 수도 있지만 확실히 올바른 의견입니다.
4castle 18

@ Alexanderbird 나는 JavaBean을 다루고 있었는데 직렬화되어야합니다. JavaBeans를 가져 와서 주제를 벗어난 경우 내 실수였습니다.
4castle

@Alexanderbird 모든 패턴이 관계형 데이터베이스와 일치 할 것으로 기대하지는 않지만 Java Bean은 다음과 같습니다.
4castle

0

인터페이스 (상속이 아님) 또는 일종의 속성 메커니즘 (자원 설명 프레임 워크와 같은 기능)을 사용해야합니다.

그래서

interface Colored {
  Color getColor();
}
class ColoredBook extends Book implements Colored {
  ...
}

또는

class PropertiesHolder {
  <T> extends Property<?>> Optional<T> getProperty( Class<T> propertyClass ) { ... }
  <V, T extends Property<V>> Optional<V> getPropertyValue( Class<T> propertyClass ) { ... }
}
interface Property<T> {
  String getName();
  T getValue();
}
class ColorProperty implements Property<Color> {
  public Color getValue() { ... }
  public String getName() { return "color"; }
}
class Book extends PropertiesHolder {
}

설명 (편집) :

엔티티 클래스에 옵션 필드를 추가하기 만하면됩니다.

특히 Optional-wrapper (편집 : 4castle 의 답변 참조 )를 사용하면이 (원래 엔터티에 필드 추가)가 작은 규모로 새 속성을 추가하는 실용적인 방법이라고 생각합니다. 이 방법의 가장 큰 문제점은 낮은 커플 링에 대해 작동 할 수 있다는 것입니다.

책 클래스가 도메인 모델 전용 프로젝트에 정의되어 있다고 가정하십시오. 이제 도메인 모델을 사용하여 특별한 작업을 수행하는 다른 프로젝트를 추가합니다. 이 작업에는 서적에 추가 속성이 필요합니다. 상속으로 끝나거나 (아래 참조) 새 작업을 수행 할 수 있도록 공통 도메인 모델을 변경해야합니다. 후자의 경우 책 클래스에 추가 된 자체 속성에 의존하는 여러 프로젝트가 생길 수 있지만 책 클래스 자체는 이러한 프로젝트에 의존합니다. 왜냐하면 책 클래스가 없으면 책 클래스를 이해할 수 없기 때문입니다. 추가 프로젝트.

추가 속성을 제공 할 때 상속이 왜 문제가됩니까?

"Book"예제 클래스를 볼 때 종종 많은 선택적 필드와 하위 유형이있는 도메인 개체에 대해 생각합니다. CD가 포함 된 책의 속성을 추가한다고 가정 해보십시오. 컬러 책, 책, CD와 책, 색상 책 : 네 책의 종류 지금있다 CD는. Java에서 상속으로이 상황을 설명 할 수 없습니다.

인터페이스를 사용하면이 문제를 피할 수 있습니다. 인터페이스를 사용하여 특정 책 클래스의 속성을 쉽게 구성 할 수 있습니다. 위임과 작문은 원하는 수업을 쉽게 얻을 수 있도록 도와줍니다. 상속을 사용하면 일반적으로 형제 클래스에 필요하기 때문에 클래스에있는 선택적 속성이 생깁니다.

상속이 종종 문제가되는 아이디어 인 이유를 더 읽어보십시오.

상속이 OOP 지지자들에 의해 일반적으로 나쁜 것으로 여겨지는 이유는 무엇입니까?

JavaWorld : 확장이 악인 이유

일련의 속성을 구성하기 위해 일련의 인터페이스를 구현할 때의 문제

확장을 위해 인터페이스를 사용할 때 작은 세트 만 있으면 모든 것이 좋습니다. 특히 객체 모델이 다른 개발자 (예 : 회사)에서 사용되고 확장되면 인터페이스의 양이 늘어납니다. 그리고 결국 당신은 동료들이 완전히 관련없는 유스 케이스 인 Ugh를 위해 동료들이 이미 고객 X 프로젝트에서 사용한 방법을 추가하는 새로운 공식 "속성 인터페이스"를 만들게됩니다.

편집 : 또 다른 중요한 측면은 gnasher729에 의해 언급됩니다 . 기존 객체에 선택적 속성을 동적으로 추가하려는 경우가 종종 있습니다. 상속 또는 인터페이스를 사용하면 선택 사항과는 다른 클래스를 사용하여 전체 객체를 다시 만들어야합니다.

이러한 정도까지 객체 모델에 대한 확장을 기대할 경우 동적 확장 의 가능성을 명시 적으로 모델링하면 더 나아질 것 입니다. 각 "확장"(이 경우 속성)에 자체 클래스가있는 위와 같은 것을 제안합니다. 클래스는 확장의 네임 스페이스 및 식별자로 작동합니다. 따라서 패키지 명명 규칙을 설정하면이 방법으로 원래 엔터티 클래스의 메서드에 대한 네임 스페이스를 오염시키지 않으면 서 무한한 확장이 가능합니다.

게임 개발에서는 종종 행동과 데이터를 다양한 형태로 구성하려는 상황에 처하게됩니다. 그렇기 때문에 게임 패턴 분야에서 아키텍처 패턴 엔터티 구성 요소 시스템 이 인기를 얻었습니다. 이것은 또한 객체 모델에 대한 많은 확장을 기대할 때보고 싶은 흥미로운 접근법입니다.


그리고 "이러한 옵션을 결정하는 방법"이라는 질문은 다루지 않았으며 또 다른 옵션 일뿐입니다. 이것이 OP가 제시 한 모든 옵션보다 항상 더 나은 이유는 무엇입니까?
alexanderbird

네 말이 맞아, 나는 대답을 향상시킬 것이다.
염색체

@ 4castle 자바 인터페이스에서 상속되지 않습니다! 인터페이스를 구현하는 것은 클래스를 상속하는 것이 기본적으로 동작을 확장하는 동안 계약을 준수하는 방법입니다. 한 클래스에서 여러 클래스를 확장 할 수없는 동안 여러 인터페이스를 구현할 수 있습니다. 이 예에서는 상속을 사용하여 원래 엔터티의 동작을 상속하는 동안 인터페이스로 확장합니다.
염색체

말이된다. 귀하의 예에서 PropertiesHolder아마도 추상 클래스 일 것입니다. 하지만 당신의 구현 할 것을 제안 getProperty이나 getPropertyValue? 데이터는 여전히 어딘가에 일종의 인스턴스 필드에 저장되어야합니다. 또는 Collection<Property>필드를 사용하는 대신 a 를 사용하여 속성을 저장 하시겠습니까?
4castle 22

1
내가 만든 Book확장 PropertiesHolder명확성의 이유로. 는 PropertiesHolder일반적으로이 기능을 필요로하는 모든 클래스의 구성원이어야합니다. 구현은 Map<Class<? extends Property<?>>,Property<?>>이와 같은 것을 가질 것 입니다. 이것은 구현의 추악한 부분이지만 JAXB 및 JPA와 함께 작동하는 정말 멋진 프레임 워크를 제공합니다.
염색체

-1

Book아래와 같이 색상 속성없이 클래스를 파생 할 수있는 경우

class E_Book extends Book{
   private String type;
}

상속은 합리적입니다.


나는 당신의 모범을 이해하지 못합니까? color비공개 인 경우 파생 클래스는 color어쨌든 걱정할 필요가 없습니다 . Book무시 하는 생성자를 color모두 추가 하면 기본값이로 설정됩니다 null.
4castle
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.