인터페이스와 상속 : 두 세계의 최고?


10

나는 인터페이스를 '발견'했고 그것들을 사랑하기 시작했다. 인터페이스의 장점은 계약이라는 것입니다. 계약을 이행하는 모든 개체는 해당 인터페이스가 필요한 모든 곳에서 사용할 수 있습니다.

인터페이스의 문제점은 기본 구현을 할 수 없다는 것입니다. 이는 평범한 속성에 대한 고통이며 DRY를 물리칩니다. 이것은 구현과 시스템이 분리 된 상태를 유지하기 때문에 좋습니다. 한편 상속은 더 긴밀한 결합을 유지하며 캡슐화를 깨뜨릴 수있는 가능성이 있습니다.

사례 1 (개인 구성원의 상속, 우수한 캡슐화, 밀접하게 결합)

class Employee
{
int money_earned;
string name;

public:
 void do_work(){money_earned++;};
 string get_name(return name;);
};


class Nurse : public Employee: 
{
   public:
   void do_work(/*do work. Oops, can't update money_earned. Unaware I have to call superclass' do_work()*/);

};

void HireNurse(Nurse *n)
{
   nurse->do_work();
)

사례 2 (인터페이스 만)

class IEmployee
{
     virtual void do_work()=0;
     virtual string get_name()=0;
};

//class Nurse implements IEmployee.
//But now, for each employee, must repeat the get_name() implementation,
//and add a name member string, which breaks DRY.

사례 3 : (두 세계에서 가장 좋은가?)

사례 1과 유사합니다 . 그러나 (가설 적으로) C ++에서 순수한 가상 메소드를 제외 하고 메소드를 대체 할 수 없다고 상상해보십시오 .

따라서 사례 1 에서 do_work ()를 재정의하면 컴파일 타임 오류가 발생합니다. 이 문제를 해결하기 위해 do_work ()를 순수 가상으로 설정하고 별도의 메소드 increment_money_earned ()를 추가합니다. 예로서:

class Employee
{
int money_earned;
string name;

public:
 virtual void do_work()=0;
 void increment_money_earned(money_earned++;);
 string get_name(return name;);
};


class Nurse : public Employee: 
{
   public:
   void do_work(/*do work*/ increment_money_earned(); ); .
};

그러나 이것조차도 문제가 있습니다. 3 개월 후 Joe Coder가 Doctor Employee를 작성하지만 do_work ()에서 incremental_money_earned ()를 호출하는 것을 잊어 버린 경우 어떻게해야합니까?


질문:

  • 케이스 (3) 우수 사례 1 ? '더 나은 캡슐화'또는 '더 느슨하게 결합 된'또는 다른 이유 때문입니까?

  • 사례 3은 우수 사례 2 는 DRY을 준수하기 때문에?


2
... 당신은 추상 수업을 재발 명합니까?
ZJR

답변:


10

잊어 버리는 슈퍼 클래스 문제를 해결하는 한 가지 방법은 슈퍼 클래스에 제어권을 다시주는 것입니다! 방법을 보여주기 위해 첫 번째 예제를 다시 작성했습니다 (컴파일;)). 나는 또한 do_work()in Employeevirtual첫 번째 예 라고 가정합니다 .

#include <string>

using namespace std;

class Employee
{
    int money_earned;
    string name;
    virtual void on_do_work() {}

    public:
        void do_work() { money_earned++; on_do_work(); }
        string get_name() { return name; }
};

class Nurse : public Employee
{
    void on_do_work() { /* do more work. Oh, and I don't have to call do_work()! */ }
};

void HireNurse(Nurse* nurse)
{
    nurse->do_work();
}

이제 do_work()재정의 할 수 없습니다. 당신이 그것을 확장 할 경우에 당신은을 통해 그것을해야 on_do_work()하는 do_work()제어가 있습니다.

물론 두 번째 예제의 인터페이스와 함께 사용할 수도 있습니다 Employee. 따라서 올바르게 이해하면이 사례 3을 만들지 만 가상 C ++을 사용할 필요가 없다고 생각합니다! DRY이며 강력한 캡슐화가 있습니다.


3
그리고 이것이 "템플릿 방법"( en.wikipedia.org/wiki/Template_method_pattern )으로 알려진 디자인 패턴 입니다.
Joris Timmermans

예, 사례 3을 준수합니다. 이것은 유망 해 보인다. 자세하게 검토합니다. 또한 이것은 일종의 이벤트 시스템입니다. 이 '패턴'의 이름이 있습니까?
MustafaM

@ MadKeithV 이것이 '템플릿 방법'인지 확실합니까?
MustafaM

@illmath-예, 구현 세부 사항의 일부를 가상 보호 / 개인 메소드에 위임하는 비가 상 공개 메소드입니다.
Joris Timmermans

@illmath 이전에는 템플릿 방법으로 생각하지 않았지만 기본 방법이라고 생각합니다. 난 그냥 발견 이 문서 가 아닌 가상 인터페이스 관용구 : 저자가 자신의 이름을받을 권리가 믿고 위치를 읽어보십시오
기안 게리 Buyn 일명를

1

인터페이스의 문제점은 기본 구현을 할 수 없다는 것입니다. 이는 평범한 속성에 대한 고통이며 DRY를 물리칩니다.

제 생각에는 인터페이스에는 기본 구현이없는 순수한 메소드 만 있어야합니다. 인터페이스는 어떤 엔티티에 액세스하는 방법을 보여주기 때문에 DRY 원칙을 어 기지 않습니다. 참고로, 여기서는 DRY 설명을보고 있습니다 .
"모든 지식은 시스템 내에서 하나의 명백하고 권위있는 표현을 가져야합니다."

반면에 SOLID 는 모든 클래스에 인터페이스가 있어야 함을 알려줍니다.

사례 3이 사례 1보다 우수합니까? '더 나은 캡슐화'또는 '더 느슨하게 결합 된'또는 다른 이유 때문입니까?

아니요, 사례 3은 사례 1보다 우수하지 않습니다. 마음을 정해야합니다. 기본 구현을 원하면 그렇게하십시오. 순수한 방법을 원하면 함께하십시오.

3 개월 후 Joe Coder가 Doctor Employee를 작성하지만 do_work ()에서 incremental_money_earned ()를 호출하는 것을 잊어 버린 경우 어떻게해야합니까?

그런 다음 Joe Coder는 실패한 단위 테스트를 무시하기 위해 자격을 갖추어야합니다. 그는이 수업을 테스트하지 않았습니까? :)

40,000 줄의 코드를 가진 소프트웨어 프로젝트에 가장 적합한 사례는 무엇입니까?

하나의 크기는 모든 것에 맞지 않습니다. 어느 것이 더 낫다는 것을 말하는 것은 불가능합니다. 하나는 다른 것보다 더 잘 맞는 경우가 있습니다.

자신 만의 것을 발명하는 대신 디자인 패턴을 배워야 할 수도 있습니다 .


비가 상 인터페이스 디자인 패턴을 찾고 있다는 것을 깨달았습니다 .


의견 주셔서 감사합니다. 의도를보다 명확하게하기 위해 사례 3을 업데이트했습니다.
MustafaM

1
여기서 -1해야합니다. 모든 인터페이스가 순수해야하거나 모든 클래스가 인터페이스에서 상속 되어야 한다고 말할 이유가 없습니다 .
DeadMG

@DeadMG ISP
BЈовић

@VJovic : SOLID와 "모든 것이 인터페이스에서 상속해야합니다" 는 차이가 있습니다.
DeadMG

"하나의 크기가 모두 맞지는 않습니다"와 "일부 디자인 패턴을 배우십시오"는 정확합니다. 나머지 답변은 하나의 크기가 모두 맞지 않는다는 자신의 제안을 위반합니다.
Joris Timmermans

0

인터페이스는 C ++에서 기본 구현을 가질 수 있습니다. 함수의 기본 구현이 다른 가상 멤버 (및 인수)에만 의존하지 않으므로 어떤 종류의 커플 링도 증가시키지 않습니다.

사례 2의 경우 DRY가 대체됩니다. 캡슐화는 프로그램이 다른 구현에서 변경되지 않도록 보호하기 위해 존재하지만이 경우에는 다른 구현이 없습니다. YAGNI 캡슐화.

실제로 런타임 인터페이스는 일반적으로 컴파일 타임에 비해 열등한 것으로 간주됩니다. 컴파일 타임의 경우 여러 가지 장점은 말할 것도없이 동일한 번들에 사례 1 사례 2를 모두 가질 수 있습니다 . 또는 런타임에도 동일한 이점을 효과적으로 활용할 수 있습니다. 그러한 것들을 다루는 방법은 여러 가지가 있습니다.Employee : public IEmployee

Case 3: (best of both worlds?)

Similar to Case 1. However, imagine that (hypothetically)

나는 독서를 멈췄다. 야 그니. C ++는 C ++의 표준이며, 표준위원회는 훌륭한 변경으로 이러한 변경을 구현하지 않을 것입니다.


"다른 구현이 없습니다"라고 말합니다. 그러나 나는한다. 직원의 간호사 구현이 있으며 나중에 다른 구현 (의사, 관리인 등)이있을 수 있습니다. 내가 의미하는 바를 더 명확하게하기 위해 사례 3을 업데이트했습니다.
MustafaM

@illmath :하지만 다른 구현은 없습니다 get_name. 제안 된 모든 구현은 동일한 구현을 공유합니다 get_name. 내가 말했듯이, 선택할 이유가 없으며 둘 다 가질 수 있습니다. 또한 사례 3은 전혀 가치가 없습니다. 순수하지 않은 가상을 재정의 할 수 있으므로 디자인을 잊어 버리십시오.
DeadMG

인터페이스는 C ++에서 기본 구현을 가질 수있을뿐만 아니라 기본 구현을 가질 수 있으며 여전히 추상적 일 수 있습니다! 즉 가상 무효 IMethod () = 0 {std :: cout << "Ni!" << std :: endl; }
Joris Timmermans

@ MadKeithV : 인라인으로 정의 할 수 있다고 생각하지 않지만 요점은 여전히 ​​동일합니다.
DeadMG

@ MadKeith : 마치 Visual Studio가 표준 C ++의 특히 정확한 표현 인 것처럼.
DeadMG

0

사례 3이 사례 1보다 우수합니까? '더 나은 캡슐화'또는 '더 느슨하게 결합 된'또는 다른 이유 때문입니까?

구현에서 볼 수 있듯이 Case 3 구현에는 순수 가상 메소드를 구현할 수있는 추상 클래스가 필요합니다.이 클래스는 나중에 파생 클래스에서 변경 될 수 있습니다. 파생 클래스가 do_work의 구현을 변경할 수 있고 필요할 때 파생 되며 모든 파생 인스턴스는 기본적으로 기본 추상 유형에 속하므로 사례 3 이 더 좋습니다 .

4,000 줄의 코드를 가진 소프트웨어 프로젝트에 가장 적합한 사례는 다음과 같습니다.

나는 그것이 구현 디자인과 달성하려는 목표에 순전히 달려 있다고 말합니다. 추상 클래스 및 인터페이스는 해결해야 할 문제에 따라 구현됩니다.

질문 수정

3 개월 후 Joe Coder가 Doctor Employee를 작성하지만 do_work ()에서 incremental_money_earned ()를 호출하는 것을 잊어 버린 경우 어떻게해야합니까?

각 클래스가 예상되는 동작을 확인하는지 확인하기 위해 단위 테스트를 수행 할 수 있습니다. 따라서 적절한 단위 테스트를 적용하면 Joe Coder가 새 클래스를 구현할 때 버그를 방지 할 수 있습니다.


0

각 구현이 서로 중복되는 경우 인터페이스를 사용하면 DRY 만 중단됩니다. 인터페이스 상속을 모두 적용하여이 딜레마를 해결할 수 있지만, 여러 클래스에서 동일한 인터페이스를 구현하고 싶을 수도 있지만 각 클래스의 동작을 다양하게 유지해야하는 경우가 있습니다. 드라이 설명한 3 가지 접근 방법 중 어느 것을 사용하든 주어진 상황에 맞는 최상의 기술을 적용하기 위해 선택해야 할 사항이 있습니다. 반면에 시간이 지남에 따라 인터페이스를 더 많이 사용하고 반복을 제거하려는 경우에만 상속을 적용한다는 것을 알 수 있습니다. 이것이 이것이 유일한 것이라고는 말할 수 없습니다. 상속 이유를 설명하지만 나중에 디자인을 변경해야 할 경우 옵션 변경을 유지하고 변경되는 영향으로 인해 하위 클래스에 미치는 영향을 최소화하려는 경우 상속 사용을 최소화하는 것이 좋습니다. 부모 클래스에서 소개합니다.

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