태그 보관물: object-oriented-design

object-oriented-design

추상 클래스의 인터페이스 없다면 클래스가

동료와 기본 클래스와 인터페이스의 관계에 대한 의견이 다릅니다. 인터페이스 구현이 필요할 때 해당 클래스를 사용할 수 없다면 클래스가 인터페이스를 구현해서는 안된다고 생각합니다. 즉, 다음과 같은 코드를보고 싶습니다.

interface IFooWorker { void Work(); }

abstract class BaseWorker {
    ... base class behaviors ...
    public abstract void Work() { }
    protected string CleanData(string data) { ... }
}

class DbWorker : BaseWorker, IFooWorker {
    public void Work() {
        Repository.AddCleanData(base.CleanData(UI.GetDirtyData()));
    }
}

DbWorker는 IFooWorker 인터페이스를 얻는 것인데, 인터페이스의 인스턴스화 가능한 구현이기 때문입니다. 계약을 완전히 이행합니다. 내 동료는 거의 동일한 것을 선호합니다.

interface IFooWorker { void Work(); }

abstract class BaseWorker : IFooWorker {
    ... base class behaviors ...
    public abstract void Work() { }
    protected string CleanData(string data) { ... }
}

class DbWorker : BaseWorker {
    public void Work() {
        Repository.AddCleanData(base.CleanData(UI.GetDirtyData()));
    }
}

기본 클래스가 인터페이스를 얻는 곳에서 이로 인해 기본 클래스의 모든 상속자도 해당 인터페이스에 있습니다. 이것은 나에게 버그가 있지만 “기본 클래스 외부에서 인터페이스 구현으로 독자적으로 설 수는 없습니다”라는 구체적인 이유를 생각해 낼 수 없습니다.

그의 방법 대 나의 방법의 장단점은 무엇이며 왜 하나를 다른 것보다 사용해야합니까?



답변

인터페이스 구현이 필요할 때 해당 클래스를 사용할 수 없다면 클래스가 인터페이스를 구현해서는 안된다고 생각합니다.

BaseWorker그 요구 사항을 충족시킵니다. BaseWorker객체를 직접 인스턴스화 할 수 없다고 해서 계약을 이행 하는 BaseWorker 포인터 를 가질 수 없다는 의미는 아닙니다 . 사실, 그것은 추상 클래스의 요점입니다.

또한 게시 한 단순화 된 예제에서 말하기는 어렵지만 문제의 일부는 인터페이스와 추상 클래스가 중복되어있을 수 있습니다. 당신이 구현하는 다른 클래스가 없다면 IFooWorker않아도 되지 에서 파생를 BaseWorker, 당신은 모두의 인터페이스가 필요하지 않습니다. 인터페이스는 다중 상속을 쉽게하는 몇 가지 추가 제한 사항이있는 추상 클래스입니다.

단순화 된 예에서, 보호 된 메소드의 사용, 파생 클래스의 기본을 명시 적으로 언급하고 인터페이스 구현을 선언 할 명확한 장소가 부족하다는 것은 다시 말하지만 상속 대신 부적절한 상속을 사용하고 있다는 경고 신호입니다. 구성. 그 상속이 없다면, 당신의 모든 질문은 혼란스러워집니다.


답변

당신의 동료와 동의해야합니다.

두 예제 모두에서 BaseWorker는 추상 메소드 Work ()를 정의합니다. 이는 모든 서브 클래스가 IFooWorker의 계약을 충족시킬 수 있음을 의미합니다. 이 경우 BaseWorker가 인터페이스를 구현해야하며 해당 구현은 해당 서브 클래스에 의해 상속 될 것이라고 생각합니다. 이렇게하면 각 서브 클래스가 실제로 IFooWorker (DRY 원칙)임을 명시 적으로 표시하지 않아도됩니다.

Work ()를 BaseWorker의 메소드로 정의하지 않았거나 IFooWorker에 BaseWorker의 서브 클래스가 원하지 않거나 필요하지 않은 다른 메소드가있는 경우 (분명히) 어떤 서브 클래스가 실제로 IFooWorker를 구현하는지 표시해야합니다.


답변

나는 일반적으로 당신의 동료에 동의합니다.

기본 클래스가 IFooWorker 메서드의 노출을 강제하더라도 인터페이스는 자식 클래스로만 구현됩니다. 우선, 중복입니다. 자식 클래스가 인터페이스를 구현하는지 여부에 관계없이 노출 된 BaseWorker 메서드를 재정의해야하며 IFooWorker 구현은 BaseWorker의 도움을 받든 관계없이 기능을 노출해야합니다.

이것은 또한 계층을 모호하게 만듭니다. “모든 IFooWorkers는 BaseWorkers입니다”는 반드시 실제 진술 일 필요는 없으며 “All BaseWorkers는 IFooWorkers”도 아닙니다. 따라서 IFooWorker 또는 BaseWorker를 구현할 수있는 인스턴스 변수, 매개 변수 또는 반환 유형을 정의하려는 경우 (처음에 상속해야하는 이유 중 하나 인 공통 노출 기능을 활용) 이것들은 모든 것을 포괄하는 것으로 보장됩니다. 일부 BaseWorkers는 IFooWorker 유형의 변수에 지정할 수 없으며 그 반대도 가능합니다.

동료의 모델은 사용 및 교체가 훨씬 쉽습니다. “모든 BaseWorkers는 IFooWorkers입니다”는 이제 진정한 진술입니다. IFooWorker 종속성에 BaseWorker 인스턴스를 문제없이 제공 할 수 있습니다. 반대말 “모든 IFooWorkers는 BaseWorkers입니다”는 사실이 아닙니다. BaseWorker를 BetterBaseWorker로 바꿀 수 있으며 소비 코드 (IFooWorker에 따라 다름)는 차이를 말할 필요가 없습니다.


답변

이 답변에 경고를 추가해야합니다. 기본 클래스를 인터페이스에 연결하면 해당 클래스의 구조에 힘이 생깁니다. 기본 예제에서 두 가지를 결합해야한다는 것은 결코 쉬운 일이 아니지만 모든 경우에 해당되는 것은 아닙니다.

Java의 컬렉션 프레임 워크 클래스를 사용하십시오.

abstract class AbstractList
class LinkedList extends AbstractList implements Queue

계약이 LinkedList에 의해 구현 되었다는 사실은 AbstractList에 관심을 가지지 않았습니다 .

두 경우의 차이점은 무엇입니까? BaseWorker 의 목적은 IWorker 에서 작업을 구현하기 위해 항상 (이름과 인터페이스로 전달 된) 입니다. AbstractListQueue 의 목적 은 다양하지만 전자의 후손은 여전히 ​​후자의 계약을 구현할 수 있습니다.


답변

IFooWorker새로운 방법 추가와 같이 변경하면 어떻게됩니까 ?

경우 BaseWorker인터페이스 구현, 기본적으로는 추상적 인 경우에도, 새로운 방법을 선언해야합니다. 반면, 인터페이스를 구현하지 않으면 파생 클래스에서 컴파일 오류 만 발생합니다.

이러한 이유로, 나는 기본 클래스가 인터페이스를 구현 할 것 I 이후, 수있는 파생 클래스를 건드리지 않고 기본 클래스의 새로운 방법에 대한 모든 기능을 구현 할 수 있습니다.


답변

먼저 추상 기본 클래스가 무엇인지, 인터페이스가 무엇인지 생각하십시오. 둘 중 하나를 사용할 때와 사용하지 않을 때를 고려하십시오.

사람들이 매우 유사한 개념이라고 생각하는 것이 일반적이며, 실제로는 일반적인 인터뷰 질문입니다 (두 사람의 차이점은 무엇입니까 ??).

따라서 추상 기본 클래스는 인터페이스가 할 수없는 것을 제공합니다. 인터페이스는 기본 메소드 구현입니다. (C #에는 인터페이스가 아닌 추상 클래스에 정적 메소드를 가질 수 있습니다).

예를 들어, 이것의 일반적인 사용은 IDisposable입니다. abstract 클래스는 IDisposable의 Dispose 메소드를 구현합니다. 이는 기본 클래스의 파생 버전이 자동으로 일회용임을 의미합니다. 그런 다음 몇 가지 옵션으로 재생할 수 있습니다. 파생 클래스를 강제로 구현하여 Dispose abstract를 만듭니다. 가상 기본 구현을 제공하여 가상 또는 추상이 아닌 가상 구현을 제공하고 예를 들어 BeforeDispose, AfterDispose, OnDispose 또는 Disposing과 같은 가상 메소드를 호출하도록하십시오.

따라서 모든 파생 클래스가 인터페이스를 지원해야 할 때마다 기본 클래스로 진행됩니다. 하나 또는 일부만 해당 인터페이스가 필요한 경우 파생 클래스로 이동합니다.

그것은 모두 실제로 단순화에 대한 총체입니다. 또 다른 대안은 파생 클래스가 인터페이스를 구현하지 않고 어댑터 패턴을 통해 제공하는 것입니다. 내가 최근에 본 이것의 예는 IObjectContextAdapter에 있습니다.


답변

나는 여전히 추상 클래스와 인터페이스의 차이점을 완전히 이해하지 못한다. 기본 개념을 다룰 때마다 stackexchange를 살펴보고 두 단계 뒤로 물러납니다. 그러나 주제와 OP 질문에 대한 몇 가지 생각 :

먼저:

인터페이스에 대한 일반적인 두 가지 설명이 있습니다.

  1. 인터페이스는 모든 클래스가 구현할 수있는 메서드 및 속성 목록이며, 인터페이스를 구현하면 클래스가 해당 클래스 또는 그 클래스의 객체. 인터페이스는 계약입니다.

  2. 인터페이스는 아무것도하지 않거나 할 수없는 추상 클래스입니다. 부모 클래스와 달리 둘 이상을 구현할 수 있기 때문에 유용합니다. 마찬가지로 BananaBread 클래스의 객체가 될 수 있고 BaseBread에서 상속 할 수 있지만 IWithNuts와 ITastesYummy 인터페이스를 모두 구현할 수는 없습니다. 빵이 아니기 때문에 IDoesTheDishes 인터페이스를 구현할 수도 있습니다.

추상 클래스에 대한 두 가지 일반적인 설명이 있습니다.

  1. 추상 클래스는, 할 수 있는 일이 아니라는 것입니다. 그것은 본질적으로 전혀 실제가 아닙니다. 잠시만 기다려주세요. 도움이됩니다. 보트는 추상 클래스이지만 섹시한 Playboy Yacht는 BaseBoat의 하위 클래스입니다.

  2. 나는 추상 수업에 관한 책을 읽었을 것입니다. 아마도 당신은 그 책을 읽지 않으면 아마도 그것을 얻지 못하고 잘못 할 것이기 때문에 그 책을 읽어야 할 것입니다.

그건 그렇고, Citers라는 책은 내가 여전히 혼란스럽게 걸어가더라도 항상 인상적으로 보입니다.

둘째:

그래서 누군가 가이 질문의 간단한 버전, 고전적인 “인터페이스를 사용하는 이유는 무엇입니까? 차이점은 무엇입니까? 한 답변은 공군 조종사를 간단한 예로 사용했습니다. 그것은 꽤 착륙하지는 않았지만 훌륭한 의견을 촉발했습니다.이 중 하나는 takeOff, pilotEject 등과 같은 메소드를 가진 IFlyable 인터페이스를 언급했습니다. 그러나 결정적입니다. 인터페이스는 객체 / 클래스를 직관적으로 만들거나 적어도 그 의미를 제공합니다. 인터페이스는 객체 나 데이터의 이점이 아니라 해당 객체와 상호 작용해야하는 무언가를위한 것입니다. 고전적인 과일-> 사과-> 후지 또는 모양-> 삼각형-> 상속의 등변 예는 주어진 자손을 기반으로 주어진 객체를 분류 학적으로 이해하기위한 훌륭한 모델입니다. 소비자와 프로세서에게 일반적인 특성, 동작, 객체가 사물 그룹인지 여부, 잘못된 장소에 시스템을 배치 할 때 시스템을 호스 핑하거나 감각 데이터, 특정 데이터 저장소에 대한 커넥터 또는 급여 지급에 필요한 재무 데이터.

특정 비행기 객체는 비상 착륙 방법이 있거나 없을 수 있지만, 비상 사태를 가정하면 DumbPlane으로 덮여 깨어나 개발자가 quickLand 그들은 모두 같다고 생각했기 때문입니다. 모든 나사 제조업체가 올바른 타이트에 대한 자체 해석을 가지고 있거나 TV에 볼륨 감소 버튼 위에 볼륨 증가 버튼이없는 경우 좌절감을 느끼는 것과 같습니다.

추상 클래스는 모든 하위 개체가 해당 클래스로 규정해야하는 항목을 설정하는 모델입니다. 오리의 모든 특성이 없다면 IQuack 인터페이스가 구현되어 있는지 여부는 중요하지 않습니다. 인터페이스는 다른 것을 확신 할 수없는 경우에도 의미가있는 것입니다. Jeff Goldblum과 Starbuck은 인터페이스가 상당히 유사하기 때문에 외계인 우주선을 비행 할 수있었습니다.

제삼:

때로는 처음에 특정 방법을 시행해야하기 때문에 동료에게 동의합니다. Active Record ORM을 작성하는 경우 저장 방법이 필요합니다. 인스턴스화 할 수있는 서브 클래스가 아닙니다. 또한 ICRUD 인터페이스가 하나의 추상 클래스에 독점적으로 연결될 수 없을 정도로 이식성이 좋으면 다른 클래스에 의해 구현되어 해당 추상 클래스의 하위 클래스에 이미 익숙한 모든 사람이 신뢰할 수 있고 직관적으로 만들 수 있습니다.

반면에 모든 목록 유형이 큐 인터페이스를 구현하지 않거나 구현해야하는 것은 아니기 때문에 인터페이스를 추상 클래스에 연결하지 않는 좋은 예가있었습니다. 이 시나리오는 절반의 시간이 걸리며, 이는 귀하와 동료 모두 절반의 시간이 잘못되었다는 것을 의미하므로 가장 좋은 방법은 논쟁, 토론, 고려 및 그들이 옳은 것으로 판명되면 커플 링을 인정하고 수락하는 것입니다 . 그러나 자신에게 가장 적합한 철학이 아니라도 철학을 따르는 개발자가되지 마십시오.