추상 클래스 / 메소드가 더 이상 사용되지 않습니까?


37

나는 많은 추상 클래스 / 메소드를 만들었습니다. 그런 다음 인터페이스를 사용하기 시작했습니다.

인터페이스가 추상 클래스를 더 이상 사용하지 않는지 확실하지 않습니다.

완전히 추상적 인 수업이 필요하십니까? 대신 인터페이스를 작성하십시오. 구현이 추상 클래스가 필요하십니까? 인터페이스를 만들고 클래스를 만듭니다. 클래스를 상속하고 인터페이스를 구현하십시오. 또 다른 이점은 일부 클래스는 부모 클래스가 필요하지 않지만 인터페이스 만 구현한다는 것입니다.

그렇다면 추상 클래스 / 메소드는 더 이상 사용되지 않습니까?


선택한 프로그래밍 언어가 인터페이스를 지원하지 않으면 어떻습니까? 나는 이것이 C ++의 경우라고 생각합니다.
Bernard

7
@Bernard : C ++에서 추상 클래스 이름을 제외한 모든 인터페이스입니다. 또한 '순수한'인터페이스 이상의 기능을 수행 할 수 있다는 것은 단점이 아닙니다.
gbjbaanb

@ gbjbaanb : 나는 가정합니다. 인터페이스로 사용하는 것을 기억하지 않고 기본 구현을 제공합니다.
Bernard

인터페이스는 객체 참조의 "통화"입니다. 일반적으로 말하면 그것들은 다형성 행동의 기초입니다. 추상 수업은 deadalnix가 완벽하게 설명한 다른 목적으로 사용됩니다.
jiggy

4
"이제 자동차 운송 수단이 더 이상 사용되지 않습니까?" 예, 대부분의 경우 차를 사용합니다. 그러나 당신이 차 이외의 것을 필요로하든 아니든, "나는 운송 수단을 사용할 필요가 없다"고 말하는 것이 옳지 않을 것입니다. 인터페이스는 구현이없는 추상 클래스 거의 동일 하며 특별한 이름이 있습니까?
Jack V.

답변:


111

아니.

인터페이스는 기본 구현, 추상 클래스 및 메소드 수를 제공 할 수 없습니다. 많은 경우 코드 중복을 피하는 데 특히 유용합니다.

이것은 또한 순차 결합을 줄이는 좋은 방법입니다. 추상 메소드 / 클래스가 없으면 템플리트 메소드 패턴을 구현할 수 없습니다. 이 wikipedia 기사를 참조하십시오 : http://en.wikipedia.org/wiki/Template_method_pattern


3
@deadalnix 템플릿 방법 패턴은 매우 위험 할 수 있습니다. 고도로 결합 된 코드로 쉽게 끝나고 코드 해킹을 시작하여 템플릿 화 된 추상 클래스를 확장하여 "단 하나 이상의 사례"를 처리 할 수 ​​있습니다.
quant_dev

9
이것은 반 패턴처럼 보입니다. 인터페이스에 대해 컴포지션과 똑같은 것을 얻을 수 있습니다. 일부 추상 메소드가있는 부분 클래스에도 동일하게 적용됩니다. 그런 다음 클라이언트 코드를 서브 클래스에 강제로 적용하여 재정의하는 대신 구현을 삽입 해야합니다 . 참조 : en.wikipedia.org/wiki/Composition_over_inheritance#
혜택

4
순차 결합을 줄이는 방법을 전혀 설명하지는 않습니다. 나는이 목표를 달성하기 위해 여러 가지 방법이 존재한다는 데 동의하지만, 맹목적으로 어떤 원칙을 불러 일으키지 않고 말하는 문제에 대답하십시오. 이것이 실제 문제와 관련이있는 이유를 설명 할 수 없으면화물 컬트 프로그래밍을 수행하게됩니다.
deadalnix

3
@deadalnix : 순차적 인 커플 링이 가장 템플릿 패턴으로 감소되는 것을 말하는 것은 입니다 화물 숭배 프로그래밍 (5 월 단지 일뿐만 아니라 국가 / 전략 / 공장 패턴을 사용하여), 등 항상 감소되어야한다는 가정입니다. 순차 결합은 종종 무언가에 대해 매우 세밀한 제어를 노출 한 결과 일 뿐이며, 그저 절충 일뿐입니다. 그래도 컴포지션을 사용하여 순차적으로 연결되지 않은 래퍼를 작성할 수는 없습니다. 실제로, 훨씬 쉬워졌습니다.
back2dos

4
Java는 이제 인터페이스에 대한 기본 구현을 갖습니다. 그 대답이 바뀌나요?
raptortech97

15

추상 메소드가 존재하므로 기본 클래스 내에서 호출 할 수 있지만 파생 클래스에서 구현할 수 있습니다. 따라서 기본 수업은 다음을 알고 있습니다.

public void DoTask()
{
    doSetup();
    DoWork();
    doCleanup();
}

protected abstract void DoWork();

이는 파생 클래스가 설정 및 정리 활동에 대해 알지 못하고 중간 패턴에 구멍을 구현하는 상당히 좋은 방법입니다. 추상적 인 메소드가 없다면 항상 DoTask호출 base.DoSetup()하고 호출하는 것을 구현 하고 기억 하는 파생 클래스에 의존해야합니다 base.DoCleanup().

편집하다

또한 템플릿 방법 패턴에 대한 링크를 게시 한 deadalnix에게 감사드립니다 . :)


2
+1, deadalnix에 대한 귀하의 답변을 선호합니다 '. C #에서 대리자를 사용하여보다 간단한 방법으로 템플릿 메서드를 구현할 수 있습니다.public void DoTask(Action doWork)
Joh

1
@Joh-사실이지만 API에 따라 반드시 좋은 것은 아닙니다. 기본 클래스가 Fruit이고 파생 클래스가 Apple이면 호출 myApple.Eat()하지 않고 호출하려고합니다 myApple.Eat((a) => howToEatApple(a)). 또한 Apple전화를 걸지 않아도됩니다 base.Eat(() => this.howToEatMe()). 추상적 인 방법을 재정의하는 것이 더 깨끗하다고 ​​생각합니다.
Scott Whitlock

1
@Scott Whitlock : 사과와 과일을 사용하는 당신의 예는 현실에서 약간 분리되어 상속을받는 것이 일반적으로 대리자보다 선호되는지 판단합니다. 분명히 대답은 "의존적"이므로 논쟁의 여지가 많이 남아 있습니다 ... 어쨌든, 템플릿 메소드는 리팩토링 중에, 예를 들어 중복 코드를 제거 할 때 많이 발생합니다. 이러한 경우 일반적으로 유형 계층 구조를 망칠 필요가 없으며 상속을 피하는 것이 좋습니다. 이런 종류의 수술은 람다와 함께하기가 더 쉽습니다.
Joh

이 질문은 템플릿 메소드 패턴의 단순한 적용 이상을 보여줍니다. 공개 / 비공개 측면은 C ++ 커뮤니티에서 비가 상 인터페이스 패턴 으로 알려져 있습니다. 공개 된 비가 상 기능을 사용하여 가상 기능을 랩핑하면 (이전에는 전자 기능이 후자를 호출하지만 나중에 호출 할 경우) 나중에 필요할 수있는 설정 / 정리, 로깅, 프로파일 링, 보안 검사 등의 사용자 정의 지점을 남깁니다.
Tony

10

아니요, 그들은 더 이상 사용되지 않습니다.

실제로, 추상 클래스 / 방법과 인터페이스 사이에는 모호하지만 근본적인 차이가 있습니다.

이들 중 하나가 사용되어야하는 클래스 세트가 공유하는 공통 동작 (관련 클래스, 즉)을 갖는 경우 추상 클래스 / 메소드로 이동하십시오.

예 : 사무원, 임원, 이사-이 모든 클래스에는 공통적으로 CalculateSalary ()가 있으며 추상 기본 클래스를 사용합니다 .CalculateSalary ()는 다르게 구현 될 수 있지만 GetAttendance ()와 같은 다른 기본 클래스가 있습니다.

만약 당신의 클래스가 그들 사이에 공통적 인 (선택되지 않은 컨텍스트에서, 관련없는 클래스)를 가지고 있지 않지만 구현에서 크게 다른 액션을 가지고 있다면 인터페이스로 가십시오.

예 : 소, 벤치, 자동차, 텔레 소프 관련 클래스는 아니지만 Isortable은 배열로 정렬 할 수 있습니다.

이 차이는 일반적으로 다형성 관점에서 접근 할 때 무시됩니다. 그러나 개인적으로 위에서 설명한 이유로 하나가 다른 것보다 적절한 상황이 있다고 생각합니다.


6

다른 좋은 답변 외에도 인터페이스와 추상 클래스 사이에 아무도 언급하지 않은 근본적인 차이점이 있습니다. 즉, 인터페이스가 훨씬 덜 안정적 이므로 추상 클래스 보다 테스트 부담훨씬 큽니다 . 예를 들어 다음 C # 코드를 고려하십시오.

public abstract class Frobber
{
    private Frobber() {}
    public abstract void Frob(Frotz frotz);
    private class GreenFrobber : Frobber
    { ... }
    private class RedFrobber : Frobber
    { ... }
    public static Frobber GetFrobber(bool b) { ... } // return a green or red frobber
}

public sealed class Frotz
{
    public void Frobbit(Frobber frobber)
    {
         ...
         frobber.Frob(this);
         ...
    }
}

테스트해야 할 코드 경로 는 두 개 뿐입니다 . Frobbit의 저자는 프로 버가 빨간색 또는 녹색이라는 사실에 의존 할 수 있습니다.

대신에 우리는 말한다 :

public interface IFrobber
{
    void Frob(Frotz frotz);
}
public class GreenFrobber : IFrobber
{ ... }
public class RedFrobber : Frobber
{ ... }

public sealed class Frotz
{
    public void Frobbit(IFrobber frobber)
    {
         ...
         frobber.Frob(this);
         ...
    }
}

나는 지금 Frob에 대한 그 부름의 영향에 대해 전혀 알지 못한다 . Frobbit의 모든 코드는 IFrobber 구현에 대해 강력 해야하며 , 무능 하거나 (나쁜) 사람이나 나 또는 내 사용자에게 적대적 인 (아주 나쁜) 사람들의 구현조차도 강력 해야합니다 .

추상 클래스를 사용하면 이러한 모든 문제를 피할 수 있습니다. 그것을 써!


1
당신이 말하는 문제는 클래스가 개방 / 폐쇄 원칙을 위반하는 지점으로 구부리지 않고 대수 데이터 유형을 사용하여 해결해야합니다.
back2dos

1
첫째, 나는 그것이 하거나 도덕적 이라고 말한 적이 없습니다 . 당신은 내 입에 넣어. 둘째로 (실제 요점) : 언어 기능이 누락 된 것에 대한 잘못된 변명 일뿐입니다. 마지막으로, 추상 클래스가없는 솔루션이 있습니다 : pastebin.com/DxEh8Qfz . 반대로, 접근 방식은 중첩 클래스와 추상 클래스를 사용하여 안전을 요구하는 코드를 제외한 모든 것을 큰 진흙 공에 던집니다. RedFrobber 또는 GreenFrobber를 적용하려는 제약 조건에 묶어야하는 이유는 없습니다. 이점없이 많은 의사 결정에서 커플 링 및 잠금을 증가시킵니다.
back2dos

1
추상 메소드가 더 이상 사용되지 않는다고 말하는 것은 의심의 여지가 없지만 잘못된 반면 인터페이스보다 선호되어야한다고 주장하는 것은 오해입니다. 인터페이스와는 다른 문제를 해결하기위한 도구 일뿐입니다.
Groo

2
"기본적인 차이점이있다 [...] 인터페이스는 추상 클래스보다 훨씬 덜 신뢰할 만하다 ...] 코드에 설명 된 차이점은 액세스 제한에 의존하므로 인터페이스와 추상 클래스간에 크게 다른 이유가 없다고 생각합니다. C #에서는 그럴 수 있지만 질문은 언어에 구애받지 않습니다.
Joh

1
@ back2dos : Eric의 솔루션이 실제로 대수적 데이터 유형을 사용한다는 것을 지적하고 싶습니다. 추상 클래스는 합계 유형입니다. 추상 메서드를 호출하는 것은 변형 집합에 대한 패턴 일치와 같습니다.
Rodrick Chapman

4

당신은 그것을 스스로 말한다 :

구현이 추상 클래스가 필요하십니까? 인터페이스를 만들고 클래스를 만듭니다. 클래스를 상속하고 인터페이스를 구현하십시오.

'추상 클래스 상속'에 비해 많은 작업이 들립니다. '순수한'관점에서 코드에 접근하여 직접 작업 할 수 있지만 실질적인 이점없이 작업 부하에 추가하지 않고 이미 할 일이 충분하다는 것을 알았습니다.


말할 것도없이 클래스가 인터페이스를 상속하지 않으면 (언급하지 않아야 함) 일부 언어로 전달 함수를 작성해야합니다.
Sjoerd

음, 당신이 언급 한 "Lot of work"의 추가는 인터페이스를 만들고 클래스가 그것을 구현했다는 것입니다. 실제로 많은 작업처럼 보입니까? 또한 인터페이스는 추상 기본 클래스와 작동하지 않는 새 계층 구조에서 인터페이스를 구현할 수있는 기능을 제공합니다.
Bill K

4

@deadnix post에 대해 언급했듯이 템플릿 패턴이 형식화한다는 사실에도 불구하고 부분 구현은 반 패턴입니다.

템플릿 패턴 의이 wikipedia 예제에 대한 깨끗한 솔루션 :

interface Game {
    void initialize(int playersCount);
    void makePlay(int player);
    boolean done();
    void finished();
    void printWinner();
}
class GameRunner {
    public void playOneGame(int playersCount, Game game) {
        game.initialize(playersCount);
        int j = 0;
        for (int i = 0; !game.finished(); i++)
             game.makePlay(i % playersCount);
        game.printWinner();
    }
} 
class Monopoly implements Game {
     //... implementation
}

이 솔루션은 상속 대신 구성을 사용하기 때문에 더 좋습니다 . 템플릿 패턴은 독점 규칙의 구현과 게임 실행 방법의 구현 사이의 종속성을 소개합니다. 그러나 이들은 완전히 다른 두 가지 책임이며이를 결합 할만한 이유가 없습니다.


+1. 참고로 "템플릿 패턴이 형식화한다는 사실에도 불구하고 부분 구현은 반 패턴입니다." Wikipedia에 대한 설명은 패턴을 명확하게 정의하고 코드 예제 만 "잘못된"입니다 (위에서 설명한 것처럼 상속이 필요하지 않고 더 간단한 대안이 존재한다는 점에서 상속을 사용한다는 점에서). 다시 말해, 패턴 자체가 사람들을 비난하는 것이 아니라 사람들이 구현하는 경향이 있다고 생각합니다.
Joh

2

아니요. 제안 된 대안조차도 추상 클래스 사용을 포함합니다. 또한 언어를 지정하지 않았으므로 계속 진행하여 일반 코드가 취성 상속보다 더 나은 옵션이라고 말할 것입니다. 추상 클래스는 인터페이스보다 중요한 이점이 있습니다.


-1. 나는 "일반 코드 대 상속"이 그 질문과 어떤 관련이 있는지 이해하지 못한다. "추상 클래스가 인터페이스보다 중요한 이점을 갖는 이유"를 설명하거나 정당화해야합니다.
Joh

1

추상 클래스는 인터페이스가 아닙니다. 인스턴스화 할 수없는 클래스입니다.

완전히 추상적 인 수업이 필요하십니까? 대신 인터페이스를 작성하십시오. 구현이 추상 클래스가 필요하십니까? 인터페이스를 만들고 클래스를 만듭니다. 클래스를 상속하고 인터페이스를 구현하십시오. 또 다른 이점은 일부 클래스는 부모 클래스가 필요하지 않지만 인터페이스 만 구현한다는 것입니다.

그러나 당신은 추상이 아닌 쓸모없는 클래스를 갖게 될 것입니다. 기본 클래스의 기능 허점을 채우기 위해 추상 메소드가 필요합니다.

예를 들어이 클래스가 주어지면

public abstract class Frobber {
    public abstract void Frob();

    public abstract boolean IsFrobbingNeeded { get; }

    public void FrobUntilFinished() {
        while (IsFrobbingNeeded) {
            Frob();
        }
    }
}

어떻게 당신은 어느 쪽이 클래스에서이 기본 기능 구현하는 것이 Frob()도를 IsFrobbingNeeded?


1
인터페이스도 인스턴스화 할 수 없습니다.
Sjoerd

@Sjoerd : 그러나 공유 구현을 가진 기본 클래스가 필요합니다. 그것은 인터페이스가 될 수 없습니다.
구성자

1

저는 추상 클래스가 필수적인 역할을하는 서블릿 프레임 워크를 만든 사람입니다. 50 %의 경우 메서드를 재정의해야 할 때 세미 추상 메서드가 필요하며 컴파일러에서 해당 메서드가 재정의되지 않았다는 경고를보고 싶습니다. 주석 추가 문제를 해결합니다. 귀하의 질문으로 돌아가서, 추상 클래스와 인터페이스의 두 가지 사용 사례가 있으며, 지금까지 아무도 사용되지 않습니다.


0

인터페이스가 더 이상 사용되지 않는다고 생각하지만 전략 패턴에 의해 사용되지 않을 수 있습니다.

추상 클래스의 주요 용도는 구현의 일부를 연기하는 것입니다. "클래스 구현의이 부분은 다를 수 있습니다".

불행히도 추상 클래스는 클라이언트가 상속을 통해 이것을 수행하도록 강요합니다 . 전략 패턴을 사용하면 상속없이 동일한 결과를 얻을 수 있습니다. 클라이언트는 항상 자신의 클래스를 정의하는 대신 클래스의 인스턴스를 만들 수 있으며 클래스의 "구현"(동작)은 동적으로 달라질 수 있습니다. 이 전략 패턴에는 디자인 타임뿐만 아니라 런타임에 동작을 변경할 수있는 부가적인 장점이 있으며 관련 유형 사이의 연결이 훨씬 약합니다.


0

나는 일반적으로 ABC보다 순수한 인터페이스와 관련된 유지 관리 문제, 심지어 다중 상속에 사용되는 ABC조차도 훨씬 더 많이 직면했습니다. YMMV-dunno, 아마도 우리 팀은 그것들을 부적절하게 사용했을 것입니다.

즉, 실제 비유를 사용하면 기능과 상태가 완전히없는 순수한 인터페이스에 얼마나 많이 사용됩니까? USB를 예로 사용하면 상당히 안정적인 인터페이스입니다 (현재 USB 3.2에 있다고 생각하지만 이전 버전과의 호환성도 유지했습니다).

그러나 이것은 상태 비 저장 인터페이스가 아닙니다. 기능이 없습니다. 순수한 인터페이스보다 추상 기본 클래스와 비슷합니다. 실제로는 매우 구체적인 기능 및 상태 요구 사항을 가진 구체적인 클래스에 더 가까우며 유일한 추상화는 포트에 연결되는 유일한 요소가 대체 가능한 부분입니다.

그렇지 않으면 표준화 된 폼 팩터와 기능 요구 사항이 훨씬 느슨한 컴퓨터의 "구멍"일뿐입니다. 모든 제조업체는 해당 구멍이 무언가를 수행하도록 자체 하드웨어를 만들 때까지 자체적으로 아무것도하지 않습니다. 그것은 훨씬 더 약한 표준이되며 "구멍"과 그 기능에 대한 명세에 지나지 않지만 그 방법에 대한 중앙 규정은 없습니다. 한편 모든 하드웨어 제조업체가 기능과 상태를 "홀"에 연결하는 고유 한 방법을 찾은 후에는 200 가지 방법으로 끝날 수 있습니다.

그리고 그 시점에서 우리는 다른 제조업체와는 다른 문제를 일으키는 특정 제조업체를 보유 할 수 있습니다. 사양을 업데이트해야하는 경우 사양을 업데이트하고 테스트해야하는 완전히 다른 방법으로 200 개의 서로 다른 구체적인 USB 포트 구현이있을 수 있습니다. 일부 제조업체는 자체적으로 공유하는 사실상의 표준 구현 (해당 인터페이스를 구현하는 아날로그 기본 클래스)을 개발할 수 있지만 전부는 아닙니다. 일부 버전은 다른 버전보다 느릴 수 있습니다. 일부는 처리량이 더 좋지만 지연 시간이 더 길거나 그 반대 일 수 있습니다. 일부는 다른 것보다 더 많은 배터리 전력을 사용할 수 있습니다. 일부는 USB 포트와 작동해야하는 모든 하드웨어에서 작동하지 않거나 작동하지 않을 수 있습니다. 일부는 사용자에게 방사능 중독을 일으키는 경향이있는 작동을 위해 원자로를 부착해야 할 수도 있습니다.

그리고 그것이 제가 개인적으로 순수한 인터페이스로 찾은 것입니다. CPU 케이스에 대해 마더 보드의 폼 팩터를 모델링하는 것과 같이 의미가있는 경우도 있습니다. 폼 팩터 유추는 실제로 "홀"과 마찬가지로 거의 무 상태이며 기능이 없습니다. 그러나 나는 종종 팀이 그것을 모든 경우에서 우수하다고 생각하는 것은 큰 실수라고 생각합니다.

반대로, 팀이 너무 거대하지 않으면 실제로 하나의 중앙 표준이 아닌 200 개의 경쟁 USB 구현에 해당하는 유사체를 갖는 것이 바람직하지 않은 경우 인터페이스보다 ABC가 더 많은 사례를 인터페이스보다 더 잘 해결할 것이라고 생각합니다. 유지하십시오. 내가 근무했던 전 팀에서는 ABC와 다중 상속을 허용하기 위해 코딩 표준을 완화하기 위해 열심히 노력해야했으며 주로 위에서 설명한 이러한 유지 관리 문제에 대응했습니다.

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