많은 작은 클래스 대 논리적 (그러나) 복잡한 상속


24

좋은 OOP desing, 깨끗한 코드, 유연성 및 미래의 코드 냄새 방지 측면에서 무엇이 더 좋은지 궁금합니다. 클래스로 표현해야하는 매우 유사한 객체가 많이있는 이미지 상황. 이러한 클래스는 특정 기능이없고 데이터 클래스 만 있으며 이름 (및 컨텍스트)에 따라 다릅니다. 예 :

Class A
{
  String name;
  string description;
}

Class B
{
  String name;
  String count;
  String description;
}

Class C
{
  String name;
  String count;
  String description;
  String imageUrl;
}

Class D
{
  String name;
  String count;
}

Class E
{
  String name;
  String count;
  String imageUrl;
  String age;
}

그것들을 별도의 클래스로 유지하고, "더 나은"가독성을 얻는 것이 더 좋지만, 많은 코드 반복을 재치하거나 상속을 사용하여 더 건조하게하는 것이 더 좋을까요?

상속을 사용하면 다음과 같은 결과가 나오지만 클래스 이름은 문맥상의 의미를 잃어 버릴 것입니다 (is-a 때문에 has-a 때문).

Class A
{
  String name;
  String description;
}

Class B : A
{
  String count;
}

Class C : B
{
  String imageUrl;
}

Class D : C
{
  String age;
}

상속은 코드 재사용에 사용되지 않아야하며 코드 재사용에 사용되어서는 안된다는 것을 알고 있지만 그러한 경우 코드 반복을 줄이는 방법은 없습니다.

답변:


58

일반적인 규칙은 "모든 상속을 피하는 것"이 ​​아니라 "상속에 대한 위임 선호"를 읽습니다. 객체가 논리적 관계를 가지고 있고 A가 필요할 때마다 B를 사용할 수있는 경우 상속을 사용하는 것이 좋습니다. 그러나 개체에 이름이 같은 필드가 있고 도메인 관계가없는 경우 상속을 사용하지 마십시오.

디자인 프로세스에서 "변경 사유"에 대한 질문을하는 것이 종종 도움이됩니다. 클래스 A가 추가 필드를 얻는 경우 클래스 B도 마찬가지라고 생각하십니까? 그것이 사실이라면 객체는 관계를 공유하며 상속은 좋은 생각입니다. 그렇지 않다면, 별개의 개념을 별개의 코드로 유지하기 위해 약간의 반복이 필요합니다.


물론, 변화의 이유는 좋은 점이지만, 이러한 클래스가 항상 있고 항상 일정한 상황에서는 무엇입니까?
user969153

20
@ user969153 : 클래스가 진정 일정 할 때는 중요하지 않습니다. 모든 훌륭한 소프트웨어 엔지니어링은 변경을해야하는 미래의 관리자를위한 것입니다. 미래의 변화가 없다면 모든 것이 진행되지만 나를 믿으십시오 . 휴지통을 프로그래밍하지 않으면 항상 변화 가 있습니다.
thiton

@ user969153 : "변화의 이유"는 휴리스틱입니다. 그것은 더 이상 결정하는 데 도움이됩니다.
thiton

2
좋은 대답입니다. 변경해야하는 이유는 bob 삼촌의 SOLID 약어에서 S이므로 좋은 소프트웨어를 디자인하는 데 도움이됩니다.
Klee

4
@ user969153, 내 경험상, "이 클래스는 어디에 있고 항상 일정합니까?" 유효한 유스 케이스가 아닙니다. :) 완전히 예상치 못한 변화가 발생하지 않은 의미있는 크기의 응용 프로그램에서 아직 작업하지 않았습니다.
cdkMoose

18

상속을 사용하면 다음과 같은 결과가 나오지만 클래스 이름은 문맥상의 의미를 잃어 버릴 것입니다 (is-a 때문에 has-a 때문).

정확하게. 맥락에서 이것을보십시오 :

Class D : C
{
    String age;
}

D는 어떤 속성을 가지고 있습니까? 기억 나니? 계층 구조를 더 복잡하게 만드는 경우 :

Class E : B
{
    int numberOfWheels;
}

Class F : E
{
    String favouriteColour;
}

그리고 공포에 대한 공포에서 imageUrl 필드를 F에 추가하고 E에는 추가하지 않으려는 경우 C와 E에서 파생시킬 수 없습니다.

다양한 구성 요소 유형에 모두 이름, ID 및 설명이있는 실제 상황을 보았습니다. 그러나 이것은 그들의 구성 요소의 본질이 아니라 단지 우연의 일치였습니다. 각각 데이터베이스에 저장 되었기 때문에 ID가있었습니다. 이름과 설명이 표시되었습니다.

제품에도 비슷한 이유로 ID, 이름 및 설명이 있습니다. 그러나 그들은 구성 요소가 아니거나 구성 요소 제품이 아닙니다. 두 가지를 함께 볼 수는 없습니다.

그러나 일부 개발자는 DRY에 대해 읽고 코드에서 중복의 모든 힌트를 제거하기로 결정했습니다. 그래서 그는 모든 것을 제품이라고 불렀고 그 분야를 정의 할 필요가 없었습니다. 이들은 제품에 정의되어 있으며 해당 필드에 필요한 모든 것이 제품에서 파생 될 수 있습니다.

나는 이것을 충분히 강하게 말할 수 없다. 이것은 좋은 생각이 아니다.

DRY는 공통 속성의 필요성을 제거하는 것이 아닙니다. 제품이 제품이 아닌 경우 제품이라고 부르지 마십시오. 제품에 설명이 필요하지 않은 경우 제품의 특성상 설명을 제품의 속성으로 정의하지 마십시오.

결국 설명이없는 구성 요소가 생겼습니다. 그래서 우리는 그 객체에 널 설명을 갖기 시작했습니다. 널 입력 가능하지 않습니다. 없는. 항상. X ... 이상 Y ... 및 N 유형의 모든 제품

이것은 Liskov 대체 원칙을 심각하게 위반 한 것입니다.

DRY는 한곳에서 버그를 수정하고 다른 곳에서는 버그를 잊을 수있는 위치에 자신을 두지 않는 것입니다. 동일한 속성을 가진 두 개의 개체가 있기 때문에 이런 일이 발생하지 않습니다. 못. 따라서 걱정하지 마십시오.

클래스의 상황에 따라 매우 드물지만 공통 부모 클래스를 고려해야한다는 것이 분명합니다. 그러나 속성 인 경우 대신 인터페이스를 사용하지 않는 이유를 고려하십시오.


4

상속은 다형성 행동을 달성하는 방법입니다. 클래스에 동작이 없으면 상속은 쓸모가 없습니다. 이 경우 객체 / 클래스가 아니라 구조로 간주합니다.

동작의 다른 부분에 다른 구조를 배치하려면 IHasName, IHasAge 등과 같은 get / set 메소드 또는 속성이있는 인터페이스를 고려하십시오. 이렇게하면 디자인이 훨씬 더 깨끗 해지고 더 잘 구성 될 수 있습니다. hiearchies의 종류.


3

인터페이스가 무엇입니까? 기능은 다르지만 비슷한 속성을 가진 관련없는 클래스.

IName = Interface(IInterface)
  function Getname : String;
  property Name : String read Getname
end;

Class Dog(InterfacedObject, IName)
private
  FName : String;
  Function GetName : String;
Public
  Name : Read Getname;
end;

Class Movie(InterfacedObject, IName)
private
  FCount;
  FName : String;
  Function GetName : String;
Public
  Name : String Read Getname;
  Count : read FCount; 
end;

1

다중 상속을 지원하지 않지만 구체적으로 지정하지 않는 언어의 맥락에서 질문이 틀린 것 같습니다. 다중 상속을 지원하는 언어로 작업하는 경우 단일 상속 수준으로 해결하기가 쉽지 않습니다.

다음은 다중 상속을 사용하여이를 해결하는 방법의 예입니다.

trait Nameable { String name; }
trait Describable { String description; }
trait Countable { Integer count; }
trait HasImageUrl { String imageUrl; }
trait Living { String age; }

class A : Nameable, Describable;
class B : Nameable, Describable, Countable;
class C : Nameable, Describable, Countable, HasImageUrl;
class D : Nameable, Countable;
class E : Nameable, Countable, HasImageUrl, Living;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.