IT TIP

상속을 사용하는 이유는 무엇입니까?

itqueen 2020. 12. 4. 21:40
반응형

상속을 사용하는 이유는 무엇입니까?


나는 그 질문이 이전에 논의 되었다는 것을 알고 있지만, 상속이 적어도 때로는 구성보다 바람직하다는 가정하에 항상 보입니다. 나는 이해를 얻기 위해 그 가정에 도전하고 싶다.

내 질문은 이것이다 : 때문에 당신이 고전 상속 할 수 있음은 객체 조성 아무것도 성취 할 수 이후 매우 자주 학대 고전 상속 [1] 및 이후 개체 조성물은 위임 객체의 런타임을 변경하는 당신에게 유연성을 제공, 왜 당신에게 것 이제까지 사용 고전적 상속?

위임을위한 편리한 구문을 제공하지 않는 Java 및 C ++와 같은 일부 언어에서 상속을 권장하는 이유를 이해할 수 있습니다. 이러한 언어에서는 명확하게 부정확하지 않을 때마다 상속을 사용하여 많은 타이핑을 절약 할 수 있습니다. 그러나 Objective C 및 Ruby와 같은 다른 언어는 고전적인 상속 위임을위한 매우 편리한 구문을 모두 제공합니다 . Go 프로그래밍 언어는 내가 아는 한 고전적 상속이 가치보다 더 문제가되고 코드 재사용을위한 위임 만 지원한다고 결정한 유일한 언어입니다.

내 질문을 설명하는 또 다른 방법은 다음과 같습니다. 클래식 상속이 특정 모델을 구현하는 데 부정확하지 않다는 것을 알고 있더라도 구성 대신 사용하기에 충분한 이유입니까?

[1] 많은 사람들이 클래스가 인터페이스를 구현하도록하는 대신 고전적 상속을 사용하여 다형성을 달성합니다. 상속의 목적은 다형성이 아니라 코드 재사용입니다. 또한 어떤 사람들 은 종종 문제가 될 수 있는 "is-a"관계에 대한 직관적 이해를 모델링하기 위해 상속을 사용 합니다.

최신 정보

상속에 대해 말할 때 정확히 무엇을 의미하는지 명확히하고 싶습니다.

클래스가 부분적으로 또는 완전히 구현 된 기본 클래스에서 상속하는 상속의 종류에 대해 이야기하고 있습니다. 나는 없는 기록을 위해 나는에 대해 주장하고 있지 않다 인터페이스를 구현 같은 일 금액 순수 추상 기본 클래스에서 상속에 대해 이야기.

업데이트 2

상속이 C ++에서 다형성을 달성하는 유일한 방법이라는 것을 이해합니다. 이 경우 왜 그것을 사용해야하는지 분명합니다. 그래서 제 질문은 다형성을 달성하는 뚜렷한 방법을 제공하는 Java 또는 Ruby와 같은 언어로 제한됩니다 (각각 인터페이스 및 덕 타이핑).


명시 적으로 재정의하지 않은 모든 것을 동일한 인터페이스 ( "기본"개체)를 구현하는 다른 개체에 위임하는 경우 기본적으로 구성 위에 상속을 그린 것입니다.하지만 (대부분의 언어에서) 훨씬 더 자세한 정보 및 상용구. 상속 대신 합성을 사용하는 목적은 위임하려는 동작 만 위임 할 수 있도록하는 것입니다.

명시 적으로 재정의하지 않는 한 객체가 기본 클래스의 모든 동작을 사용하도록하려면 상속이 가장 간단하고, 가장 간단하고, 가장 간단한 표현 방법입니다.


상속의 목적은 다형성이 아니라 코드 재사용입니다.

이것은 당신의 근본적인 실수입니다. 거의 정반대입니다. (공개) 상속 주요 목적은 해당 클래스 간의 관계를 모델링하는 것입니다. 다형성은 그것의 큰 부분입니다.

올바르게 사용하면 상속은 기존 코드를 재사용하는 것이 아닙니다. 오히려 기존 코드 에서 사용 하는 것 입니다. 즉, 기존 기본 클래스와 함께 작동 할 수있는 기존 코드가있는 경우 기존 기본 클래스에서 새 클래스를 파생하면 다른 코드가 이제 새 파생 클래스에서도 자동으로 작동 할 수 있습니다.

코드 재사용을 위해 상속을 사용할 수 있지만, 그렇게하는 경우 일반적으로 공용 상속이 아닌 개인 상속 이어야합니다 . 사용중인 언어가 위임을 잘 지원한다면 개인 상속을 사용할 이유가 거의 없을 가능성이 매우 높습니다. OTOH, 개인 상속은 위임 (일반적으로)이 지원하지 않는 몇 가지 사항을 지원합니다. 특히이 경우 다형성이 확실히 부차적 인 관심 사라 할지라도 여전히 문제가 될 있습니다. 즉, 개인 상속을 사용하면 거의 원하는 기본 클래스에서 시작할 수 있으며 (허용한다고 가정 할 때) 옳지 않은 부분.

위임을 사용하는 유일한 선택은 기존 클래스를 그대로 사용하는 것입니다. 원하는대로 작동하지 않는 경우 유일한 선택은 해당 기능을 완전히 무시하고 처음부터 다시 구현하는 것입니다. 어떤 경우에는 손실이 없지만 다른 경우에는 상당히 상당합니다. 기본 클래스의 다른 부분이 다형성 함수를 사용하는 경우 private 상속을 사용하면 다형성 함수 재정의 할 수 있고 다른 부분은 재정의 된 함수를 사용합니다. 위임을 사용하면 새 기능을 쉽게 연결할 수 없으므로 기존 기본 클래스의 다른 부분에서 재정의 한 것을 사용합니다.


상속을 사용하는 주된 이유 는 구성의 한 형태 아니라 다형성 동작을 얻을 수 있기 때문입니다. 다형성이 필요하지 않다면 적어도 C ++에서는 상속을 사용해서는 안됩니다.


다형성이 상속의 큰 장점이라는 것을 누구나 알고 있습니다. 상속에서 찾은 또 다른 이점은 실제 세계의 복제본을 만드는 데 도움이된다는 것입니다. 예를 들어 급여 시스템에서 우리는 슈퍼 클래스 Employee로 이러한 모든 클래스를 상속하면 관리자 개발자, 사무실 소년 등을 처리합니다. 이 모든 수업이 기본적으로 직원이라는 사실을 실제 상황에서 더 이해하기 쉽게 만듭니다. 그리고 한 가지 더 클래스는 속성도 포함하는 메서드를 포함합니다. 따라서 사회 보장 번호 연령 등과 같은 Employee 클래스의 직원에 대한 일반적인 속성을 포함하면 더 큰 코드 재사용과 개념적 명확성과 물론 다형성을 제공 할 것입니다. 그러나 상속을 사용하는 동안 우리는 기본 설계 원칙을 염두에 두어야합니다. "


다음과 같은 경우 상속이 선호됩니다.

  1. 당신은 (당신이 쓰기 위임하는 방법을 많이 필요합니다 대표단)이 확장 클래스의 전체 API를 노출해야 하고 말하는 "모든 알 수없는 방법을 위임 '하는 간단한 방법을 제공하지 않는 언어입니다.
  2. "친구"라는 개념이없는 언어에 대한 보호 된 필드 / 방법에 액세스해야합니다.
  3. 언어가 다중 상속을 허용하면 위임의 이점이 다소 감소합니다.
  4. 언어가 런타임에 클래스 또는 인스턴스에서 동적으로 상속 할 수있는 경우 일반적으로 위임이 필요하지 않습니다. 노출되는 메서드 (및 노출 방법)를 동시에 제어 할 수 있다면 전혀 필요하지 않습니다.

내 결론 : 위임은 프로그래밍 언어의 버그에 대한 해결 방법입니다.


나는 항상 까다로울 수 있으므로 상속을 사용하기 전에 두 번 생각합니다. 그것은 단순히 가장 우아한 코드를 생성하는 많은 경우가 있다고 말합니다.


인터페이스는 객체가 할 수있는 일만 정의하고 방법은 정의하지 않습니다. 간단히 말해서 인터페이스는 계약일뿐입니다. 인터페이스를 구현하는 모든 개체는 계약의 자체 구현을 정의해야합니다. 실제 세계에서 이것은 당신에게separation of concern. 미리 알지 못하는 다양한 객체를 처리해야하는 응용 프로그램을 작성한다고 상상해보십시오. 여전히 처리해야합니다. 아는 것은 객체가 수행해야하는 모든 작업입니다. 따라서 인터페이스를 정의하고 계약의 모든 작업을 언급합니다. 이제 해당 인터페이스에 대해 애플리케이션을 작성합니다. 나중에 코드 나 응용 프로그램을 활용하려는 사람은 시스템에서 작동하도록 개체에 인터페이스를 구현해야합니다. 인터페이스는 개체가 계약에 정의 된 각 작업이 수행되는 방식을 정의하도록 강제합니다. 이렇게하면 누구나 인터페이스를 구현하는 객체를 작성할 수 있습니다.이를 통해 시스템에 완벽하게 적응할 수 있고 수행해야 할 작업은 수행 방법을 정의해야하는 객체뿐입니다.

실제 개발에서이 방법은 일반적으로 Programming to Interface and not to Implementation.

인터페이스는 계약 또는 서명일 뿐이며 구현에 대해 아무것도 모릅니다.

인터페이스에 대한 코딩이란 클라이언트 코드가 항상 팩토리에서 제공하는 Interface 객체를 보유한다는 것을 의미합니다. 팩토리에서 반환 된 모든 인스턴스는 팩토리 후보 클래스가 구현해야하는 인터페이스 유형입니다. 이렇게하면 클라이언트 프로그램이 구현에 대해 걱정하지 않고 인터페이스 서명이 모든 작업을 수행 할 수 있는지 결정합니다. 이것은 런타임에 프로그램의 동작을 변경하는 데 사용할 수 있습니다. 또한 유지 관리 관점에서 훨씬 더 나은 프로그램을 작성하는 데 도움이됩니다.

여기에 기본적인 예가 있습니다.

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

[STAThread]
static void Main()
{
    //This is your client code.
    ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
    speaker.Speak();
    Console.ReadLine();
}

public interface ISpeaker
{
    void Speak();
}

public class EnglishSpeaker : ISpeaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : ISpeaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak German.");
    }

    #endregion
}

public class SpanishSpeaker : ISpeaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    #endregion
}

대체 텍스트 http://ruchitsurati.net/myfiles/interface.png


What about the template method pattern? Let's say you have a base class with tons of points for customizable policies, but a strategy pattern doesn't make sense for at least one of the following reasons:

  1. The customizable policies need to know about the base class, can only be used with the base class and don't make sense in any other context. Using strategy instead is do-able but a PITA because both the base class and the policy class need to have references to each other.

  2. The policies are coupled to each other in that it wouldn't make sense to freely mix-and-match them. They only make sense in a very limited subset of all possible combinations.


You wrote:

[1] Many people use classical inheritance to achieve polymorphism instead of letting their classes implement an interface. The purpose of inheritance is code reuse, not polymorphism. Furthermore, some people use inheritance to model their intuitive understanding of an "is-a" relationship which can often be problematic.

In most languages, the line between 'implementing an interface' and 'deriving a class from another' is very thin. In fact, in languages like C++, if you're deriving a class B from a class A, and A is a class which consists of only pure virtual methods, you are implementing an interface.

Inheritance is about interface reuse, not implementation reuse. It is not about code reuse, as you wrote above.

Inheritance, as you correctly point out, is meant to model an IS-A relationship (the fact that many people get this wrong has nothing to do with inheritance per se). You can also say 'BEHAVES-LIKE-A'. However, just because something has an IS-A relationship to something else doesn't meant that it uses the same (or even similiar) code to fulfill this relationship.

Compare this C++ example which implements different ways to output data; two classes use (public) inheritance so that they can be access polymorphically:

struct Output {
  virtual bool readyToWrite() const = 0;
  virtual void write(const char *data, size_t len) = 0;
};

struct NetworkOutput : public Output {
  NetworkOutput(const char *host, unsigned short port);

  bool readyToWrite();
  void write(const char *data, size_t len);
};

struct FileOutput : public Output {
  FileOutput(const char *fileName);

  bool readyToWrite();
  void write(const char *data, size_t len);
};

Now imagine if this was Java. 'Output' was no struct, but an 'interface'. It might be called 'Writeable'. Instead of 'public Output' you would say 'implements Writable'. What's the difference as far as the design is concerned?

None.


Classical inheritance's main usefulness is if you have a number of related classes that will have identical logic for methods that operate on instance variables/properties.

There are really 3 ways to handle that:

  1. Inheritance.
  2. Duplicate the code (code smell "Duplicated code").
  3. Move the logic to yet another class (code smells "Lazy Class," "Middle Man," "Message Chains," and/or "Inappropriate Intimacy").

Now, there can be misuse of inheritance. For example, Java has the classes InputStream and OutputStream. Subclasses of these are used to read/write files, sockets, arrays, strings, and several are used to wrap other input/output streams. Based on what they do, these should have been interfaces rather than classes.


One of the most useful ways I see to use inheritance is in GUI objects.


When you asked:

Even if you know that classical inheritance is not incorrect to implement a certain model, is that reason enough to use it instead of composition?

The answer is no. If the model is incorrect (using inheritance), than it's wrong to use no matter what.

Here are some problems with inheritance that I've seen:

  1. Always having to test the run time type of derived class pointers to see if they can be cast up (or down too).
  2. This 'testing' can be achieved in various ways. You may have some sort of virtual method that returns a class identifier. Or failing that you may have to implement RTTI (Run time type identification) (At least in c/c++) which can give you a performance hit.
  3. class types that fail to get 'cast' up can be potentially problematic.
  4. There are many ways to cast your class type up and down the inheritance tree.

Something totally not OOP but yet, composition usually means an extra cache-miss. It does depend but having the data closer is a plus.

Generally, I refuse to go into some religious fights, using your own judgment and style is the best you can get.

참고URL : https://stackoverflow.com/questions/3351666/why-use-inheritance-at-all

반응형