development

구체적인 클래스에서 상속하는 좋은 예가 있습니까?

big-blog 2020. 10. 27. 22:46
반응형

구체적인 클래스에서 상속하는 좋은 예가 있습니까?


배경:

Java 프로그래머로서 저는 인터페이스에서 광범위하게 상속 (구현)하고 때로는 추상 기본 클래스를 디자인합니다. 그러나 나는 구체적인 (비 추상적) 클래스를 하위 클래스로 분류 할 필요성을 실제로 느끼지 못했습니다. (제가 수행 한 경우 나중에 위임 과 같은 다른 솔루션 이 더 낫다는 것이 밝혀졌습니다).

이제 저는 구체적인 클래스에서 상속하는 것이 적절한 상황이 거의 없다고 느끼기 시작했습니다. 한 가지로, Liskov 대체 원칙 (LSP)은 사소하지 않은 클래스를 충족하기가 거의 불가능 해 보입니다. 여기에있는 다른 많은 질문들비슷한 의견을 반영하는 것 같습니다.

그래서 내 질문 :

어떤 상황에서 (있는 경우) 구체적 클래스에서 상속하는 것이 실제로 합리적입니까? 제약 조건을 감안할 때 이것이 최고의 디자인이라고 느끼는 다른 구체적인 클래스에서 상속 된 클래스의 구체적이고 실제적인 예를 제공 할 수 있습니까? 특히 LSP를 충족하는 예제 (또는 LSP를 만족하는 것이 중요하지 않은 예제)에 관심이 있습니다.

주로 Java 배경 지식이 있지만 모든 언어의 예제에 관심이 있습니다.


종종 인터페이스에 대한 골격 구현 이 있습니다 I. 추상 메서드없이 (예 : 후크를 통해) 확장 성을 제공 할 수있는 경우 인스턴스화 할 수 있으므로 비추 상 골격 클래스를 사용하는 것이 좋습니다.

예를 들어 , 에서 상속 할 필요없이 장식 또는 간단한 코드 재사용을 활성화 하는 것과 같이 구현 하는 구체적인 클래스의 다른 객체로 전달할 수 있는 전달 래퍼 클래스 가 있습니다 . 이러한 예제는 Effective Java item 16, 상속보다 구성을 선호합니다. (저작권 때문에 여기에 게시하고 싶지 않지만 실제로는 모든 메서드 호출을 래핑 된 구현으로 전달 하는 것입니다).CICCI


다음이 적절할 수있는 좋은 예라고 생각합니다.

public class LinkedHashMap<K,V>
    extends HashMap<K,V>

또 다른 좋은 예는 예외 상속입니다.

public class IllegalFormatPrecisionException extends IllegalFormatException
public class IllegalFormatException extends IllegalArgumentException
public class IllegalArgumentException extends RuntimeException
public class RuntimeException extends Exception
public class Exception extends Throwable

제가 생각할 수있는 매우 일반적인 경우 중 하나는 양식, 텍스트 상자, 콤보 상자 등과 같은 기본 UI 컨트롤에서 파생되는 것입니다. 이들은 완전하고 구체적이며 독립적으로 사용할 수 있습니다. 그러나 대부분은 매우 기본적이며 때로는 기본 동작이 원하는 것이 아닙니다. 예를 들어, 완전히 동적 인 UI 레이어를 생성하지 않는 한, 거의 누구도 완전한 형태의 인스턴스를 사용하지 않을 것입니다.

예를 들어, 최근에 상대적으로 완성 된 소프트웨어에서 (주로 개발에 집중하기 위해 시간이 부족했다는 의미) ComboBox에 "지연 로딩"기능을 추가해야한다는 것을 알았습니다. 첫 번째 창이로드되는 데 50 년 (컴퓨터 연도 기준)이 걸립니다. 또한 다른 ComboBox에 표시된 항목에 따라 하나의 ComboBox에서 사용 가능한 옵션을 자동으로 필터링 할 수있는 기능이 필요했고 마지막으로 다른 편집 가능한 컨트롤에서 하나의 ComboBox 값을 "미러링"하고 하나의 컨트롤에서 변경을 수행하는 방법이 필요했습니다. 기타도 마찬가지입니다. 그래서 기본 ComboBox를 확장하여 이러한 추가 기능을 제공하고 LazyComboBox와 MirroringComboBox라는 두 가지 새로운 유형을 만들었습니다. 둘 다 완전히 서비스 가능하고 구체적인 ComboBox 컨트롤을 기반으로합니다. 일부 동작을 무시하고 몇 가지 다른 동작을 추가합니다. 그것들은 매우 느슨하게 결합되어 있지 않으므로 너무 단단하지는 않지만 추가 된 기능은 충분히 일반적이므로 필요한 경우 이러한 클래스 중 하나를 처음부터 다시 작성하여 동일한 작업을 수행 할 수 있습니다.


일반적으로 구체적으로 클래스에서 파생되는 유일한 시간은 프레임 워크에있을 때입니다. 사소한 예 에서 파생 Applet되거나 JApplet되는 것.


이것은 제가 착수하고있는 현재 구현의 예입니다.

OAuth 2 환경에서는 문서가 아직 초안 단계에 있으므로 사양이 계속 변경됩니다 (작성 당시 버전 21).

따라서 AccessToken다양한 액세스 토큰을 수용하기 위해 구체적인 클래스 를 확장해야했습니다 .

이전 초안에서는 token_type필드가 설정 되지 않았 으므로 실제 액세스 토큰은 다음과 같습니다.

public class AccessToken extends OAuthToken {

    /**
     * 
     */
    private static final long serialVersionUID = -4419729971477912556L;
    private String accessToken;
    private String refreshToken;
    private Map<String, String> additionalParameters;

    //Getters and setters are here
}

액세스가 수익을 토큰과 함께 이제 token_type, 내가 가진

public class TokenTypedAccessToken extends AccessToken {

    private String tokenType;
    //Getter and setter are here...
}

따라서 두 가지를 모두 반환 할 수 있으며 최종 사용자는 현명하지 않습니다. :-)

요약 : 구체적인 클래스의 구조를 변경하지 않고 구체적인 클래스와 동일한 기능을 가진 사용자 지정 클래스를 원한다면 구체적인 클래스를 확장하는 것이 좋습니다.


주로 Java 배경 지식이 있지만 모든 언어의 예제에 관심이 있습니다.

많은 프레임 워크와 마찬가지로 ASP.NET은 상속을 많이 사용하여 클래스간에 동작을 공유합니다. 예를 들어 HtmlInputPassword 에는 다음과 같은 상속 계층 구조가 있습니다.

System.Object
  System.Web.UI.Control
    System.Web.UI.HtmlControls.HtmlControl          // abstract
      System.Web.UI.HtmlControls.HtmlInputControl   // abstract
        System.Web.UI.HtmlControls.HtmlInputText
          System.Web.UI.HtmlControls.HtmlInputPassword

여기서 파생되는 구체적인 클래스의 예를 볼 수 있습니다.

당신이 프레임 워크를 구축하고 있고 당신이 그렇게하기를 원한다고 확신한다면, 당신은 멋지고 큰 상속 계층 구조를 원할 수도 있습니다.


다른 사용 사례는 기본 동작을 재정의하는 것입니다.

파싱을 위해 표준 Jaxb 파서를 사용하는 클래스가 있다고 가정 해 보겠습니다.

public class Util{

    public void mainOperaiton(){..}
    protected MyDataStructure parse(){
        //standard Jaxb code 
    }
} 

이제 구문 분석 작업에 다른 바인딩 (Say XMLBean)을 사용하고 싶다고 가정 해 보겠습니다.

public class MyUtil extends Util{

    protected MyDataStructure parse(){
      //XmlBean code code 
    }
}

이제 슈퍼 클래스의 코드 재사용과 함께 새 바인딩을 사용할 수 있습니다.


장식 패턴 , 너무 일반적으로하지 않고 클래스에 추가 동작을 추가하는 편리한 방법은 구체적인 클래스의 상속을 많이 사용한다. 이미 여기에서 언급되었지만 "포워딩 래퍼 클래스"라는 과학적 이름으로 다소간 언급되었습니다.


많은 답변이 있지만 내 자신의 $ 0.02를 추가합니다.

나는 가끔 concreate 클래스를 재정의하지만 특정 상황에서. 프레임 워크 클래스가 확장되도록 설계 될 때 적어도 하나는 이미 언급되었습니다. 몇 가지 예를 들어 2 개의 추가 항목이 떠 오릅니다.

1) 구체적인 클래스의 동작을 조정하고 싶다면. 때로는 구체적인 클래스가 작동하는 방식을 변경하거나 특정 메서드가 호출 될 때를 알고 싶어서 무언가를 트리거 할 수 있습니다. 종종 구체적인 클래스는 하위 클래스가 메서드를 재정의하는 유일한 용도 인 후크 메서드를 정의합니다.

예 : MBeanExporterJMX 빈을 등록 해제 할 수 있어야하기 때문에 오버로딩했습니다 .

public class MBeanRegistrationSupport {
    // the concrete class has a hook defined
    protected void onRegister(ObjectName objectName) {
    }

우리 반:

public class UnregisterableMBeanExporter extends MBeanExporter {
    @Override
    protected void onUnregister(ObjectName name) {
        // always a good idea
        super.onRegister(name);
        objectMap.remove(name);
    }

또 다른 좋은 예가 있습니다. 메서드를 재정의 LinkedHashMap하도록 설계되었습니다 removeEldestEntry.

private static class LimitedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
    @Override
    protected boolean removeEldestEntry(Entry<K, V> eldest) {
        return size() > 1000;
    }

2) 클래스가 기능에 대한 일부 조정을 제외하고 구체적인 클래스와 상당한 양의 겹침을 공유하는 경우.

Example: My ORMLite project handles persisting Long object fields and long primitive fields. Both have almost the identical definition. LongObjectType provides all of the methods that describe how the database deals with long fields.

public class LongObjectType {
    // a whole bunch of methods

while LongType overrides LongObjectType and only tweaks a single method to say that handles primitives.

public class LongType extends LongObjectType {
    ...
    @Override
    public boolean isPrimitive() {
        return true;
    }
}

Hope this helps.


  1. Inheriting concrete class is only option if you want to extend side-library functionality.

  2. For example of real life usage you can look at hierarchy of DataInputStream, that implements DataInput interface for FilterInputStream.


I'm beginning to feel that there is almost no situation where inheriting from a concrete class is appropriate.

This is one 'almost'. Try writing an without extending Applet or JApplet.

Here is an e.g. from the applet info. page.

/* <!-- Defines the applet element used by the appletviewer. -->
<applet code='HelloWorld' width='200' height='100'></applet> */
import javax.swing.*;

/** An 'Hello World' Swing based applet.

To compile and launch:
prompt> javac HelloWorld.java
prompt> appletviewer HelloWorld.java  */
public class HelloWorld extends JApplet {

    public void init() {
        // Swing operations need to be performed on the EDT.
        // The Runnable/invokeLater() ensures that happens.
        Runnable r = new Runnable() {
            public void run() {
                // the crux of this simple applet
                getContentPane().add( new JLabel("Hello World!") );
            }
        };
        SwingUtilities.invokeLater(r);
    }
}

Another good example would be data storage types. To give a precise example: a red-black tree is a more specific binary tree, but retrieving data and other information like size can be handled identical. Of course, a good library should have that already implemented but sometimes you have to add specific data types for your problem.

I am currently developing an application which calculates matrices for the users. The user can provide settings to influence the calculation. There are several types of matrices that can be calculated, but there is a clear similarity, especially in the configurability: matrix A can use all the settings of matrix B but has additional parameters which can be used. In that case, I inherited from the ConfigObjectB for my ConfigObjectA and it works pretty good.


In general, it is better to inherit from an abstract class than from a concrete class. A concrete class must provide a definition for its data representation, and some subclasses will need a different representation. Since an abstract class does not have to provide a data representation, future subclasses can use any representation without fear of conflicting with the one that they inherited. Even i never found a situation where i felt concrete inheritence is neccessary. But there could be some situations for concrete inheritence specially when you are providing backward compatibility to your software. In that case u might have specialized a class A but you want it to be concrete as your older application might be using it.


Your concerns are also echoed in the classic principle "favor composition over inheritance", for the reasons you stated. I can't remember the last time I inherited from a concrete class. Any common code that needs to be reused by child classes almost always needs to declare abstract interfaces for those classes. In this order I try to prefer the following strategies:

  1. Composition (no inheritance)
  2. Interface
  3. Abstract Class

Inheriting from a concrete class really isn't ever a good idea.

[EDIT] I'll qualify this statement by saying I don't see a good use case for it when you have control over the architecture. Of course when using an API that expects it, whaddaya gonna do? But I don't understand the design choices made by those APIs. The calling class should always be able to declare and use an abstraction according to the Dependency Inversion Principle. If a child class has additional interfaces to be consumed you'd either have to violate DIP or do some ugly casting to get at those interfaces.


from the gdata project:

com.google.gdata.client.Service is designed to act as a base class that can be customized for specific types of GData services.

Service javadoc:

The Service class represents a client connection to a GData service. It encapsulates all protocol-level interactions with the GData server and acts as a helper class for higher level entities (feeds, entries, etc) that invoke operations on the server and process their results.

This class provides the base level common functionality required to access any GData service. It is also designed to act as a base class that can be customized for specific types of GData services. Examples of supported customizations include:

Authentication - implementing a custom authentication mechanism for services that require authentication and use something other than HTTP basic or digest authentication.

Extensions - define expected extensions for feed, entry, and other types associated with a the service.

Formats - define additional custom resource representations that might be consumed or produced by the service and client side parsers and generators to handle them.


I find the java collection classes as a very good example. So you have an AbstractCollection with childs like AbstractList, AbstractSet, AbstractQueue... I think this hierarchy has been well designed.. and just to ensure there's no explosion there's the Collections class with all its inner static classes.


You do that for instance in GUI libraries. It makes not much sense to inherit from a mere Component and delegate to a Panel. It is likely much easyer to inherit from the Panel directly.


Just a general thought. Abstract classes are missing something. It makes sense if this, what is missing, is different in each derived class. But you may have a case where you don't want to modify a class but just want to add something. To avoid duplication of code you would inherit. And if you need both classes it would be inheritance from a concrete class.

So my answer would be: In all cases where you really only want to add something. Maybe this just doesn't happen very often.

참고URL : https://stackoverflow.com/questions/7496010/any-good-examples-of-inheriting-from-a-concrete-class

반응형