Какие-нибудь хорошие примеры наследования от конкретного класса?

Фон:

Как программист на Java, я широко наследую (а скорее: реализую) от интерфейсов, а иногда создаю абстрактные базовые классы. Однако я никогда не чувствовал необходимости подкласса конкретного (не абстрактного) класса (в тех случаях, когда я это делал, позже выяснилось, что другое решение, такое как delegation было бы лучше).

Итак, теперь я начинаю чувствовать, что практически нет ситуации, когда унаследовать от конкретного класса. Во-первых, для нетривиальных классов почти невозможно удовлетворить принцип подстановки "nofollow noreferrer" > . также многие другие questions, похоже, отражают аналогичное мнение.

Итак, мой вопрос:

В какой ситуации (если есть) действительно ли имеет смысл наследовать от конкретного класса? Можете ли вы дать конкретный, реальный пример класса, который наследуется от другого конкретного класса, где вы чувствуете, что это лучший дизайн с учетом ограничений? Меня особенно интересуют примеры, которые удовлетворяют LSP (или примеры, где выполнение LSP кажется несущественным).

У меня в основном есть Java-фона, но меня интересуют примеры с любого языка.

Ответ 1

У вас часто есть скелетные реализации для интерфейса I. Если вы можете предложить расширяемость без абстрактных методов (например, с помощью перехватов), предпочтительнее иметь неабстрактный скелетный класс, потому что вы можете его создать.

Примером может служить классы оболочки пересылки, чтобы иметь возможность пересылать другой объект конкретного класса C, реализующий I, например. включение украшения или простое повторное использование кода C без наследования на уровне C. Вы можете найти такой пример в Эффективный Java, пункт 16, одобрить композицию над наследованием. (Я не хочу публиковать его здесь из-за авторских прав, но на самом деле просто перенаправляет все вызовы методов I на завернутую реализацию).

Ответ 2

Я думаю, что следующий пример является хорошим примером, когда это может быть уместно:

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

Ответ 3

Один очень распространенный случай, который я могу придумать, - это выработать основные элементы пользовательского интерфейса, такие как формы, текстовые поля, выпадающие списки и т.д. Они полны, конкретны и хорошо способны стоять самостоятельно; однако большинство из них также очень простые, и иногда их поведение по умолчанию не то, что вы хотите. Практически никто, например, не использовал бы экземпляр неподтвержденной формы, если только они не создавали полностью динамический слой пользовательского интерфейса.

Например, в части программного обеспечения я написал, что недавно достиг относительной зрелости (то есть у меня не хватило времени, чтобы сосредоточиться в первую очередь на ее развитии:)), я обнаружил, что мне нужно добавить "ленивую загрузку" в ComboBoxes, поэтому для первого окна для загрузки не потребуется 50 лет (в компьютерные годы). Я также нуждался в возможности автоматически фильтровать доступные параметры в одном ComboBox на основе того, что было показано в другом, и, наконец, мне нужен способ "зеркалировать" одно значение ComboBox в другом редактируемом элементе управления и внести изменения в один элемент управления другой также. Итак, я расширил базовый ComboBox, чтобы предоставить ему дополнительные функции, и создал два новых типа: LazyComboBox, а затем MirroringComboBox. Оба они основаны на полностью исправном конкретном элементе управления ComboBox, просто переопределяя некоторые виды поведения и добавляя пару других. Они не очень слабо связаны и, следовательно, не слишком SOLID, но добавленная функциональность достаточно универсальна, и если бы мне пришлось это сделать, я мог бы переписать любой из этих классов с нуля, чтобы выполнить ту же работу, возможно, лучше.

Ответ 4

Вообще говоря, единственное время, которое я получаю от конкретных классов, - это когда они находятся в рамках. Вывод из Applet или JApplet является тривиальным примером.

Ответ 5

Это пример текущей реализации, которую я предпринимаю.

В среде 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...
}

Итак, я могу вернуть оба, и конечный пользователь не станет более мудрым.: -)

В резюме: Если вы хотите, чтобы пользовательский класс имел те же функциональные возможности вашего конкретного класса, не изменяя структуру конкретного класса, я предлагаю расширить конкретный класс.

Ответ 6

Другим вариантом использования будет переопределение поведения по умолчанию:

Допустим, что существует класс, который использует стандартный парсер 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 
    }
}

Теперь я могу использовать новое связывание с повторным использованием кода суперкласса.

Ответ 7

У меня в основном есть 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

в котором можно увидеть примеры конкретных классов, получаемых из.

Если вы создаете фреймворк - и вы уверены, что хотите это сделать, вы вполне можете найти себе желаемую хорошую иерархию наследования.

Ответ 8

шаблон декоратора, удобный способ добавления дополнительного поведения в класс, не делая его слишком общим, сильно использует наследование конкретных классов, Здесь уже упоминалось, но под некоторым научным названием "класс оболочки пересылки".

Ответ 9

Множество ответов, но я бы добавил свои собственные $0,02.

Я редко переопределяю классы, но при определенных обстоятельствах. По крайней мере 1 уже упоминалось, когда рамки классов предназначены для расширения. Приводятся еще два дополнительных примера:

1) Если я хочу настроить поведение конкретного класса. Иногда я хочу изменить, как работает конкретный класс, или я хочу знать, когда вызван какой-то метод, поэтому я могу что-то вызвать. Часто конкретные классы определяют метод крюка, единственное использование которого для подклассов переопределяет метод.

Пример: мы переопределили MBeanExporter, потому что нам нужно иметь возможность отменить регистрацию JMX bean:

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) Если класс разделяет значительное количество перекрытий с конкретным классом, за исключением некоторых настроек для функциональности.

Пример: My ORMLite проект обрабатывает сохраняющиеся поля объектов Long и Long примитивные поля. Оба имеют почти идентичное определение. LongObjectType содержит все методы, описывающие, как база данных имеет дело с полями Long.

public class LongObjectType {
    // a whole bunch of methods

while LongType переопределяет LongObjectType и только модифицирует один метод, чтобы сказать, что обрабатывает примитивы.

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

Надеюсь, что это поможет.

Ответ 10

  • Наследование конкретного класса - это только опция, если вы хотите расширить функции боковой библиотеки.

  • Например, использование реальной жизни вы можете посмотреть на иерархию DataInputStream, которая реализует интерфейс DataInput для FilterInputStream.

Ответ 11

Я начинаю чувствовать, что практически нет ситуации, когда унаследовать от конкретного класса целесообразно.

Это один "почти". Попробуйте написать applet без расширения Applet или JApplet.

Вот, например, из апплета. страница.

/* <!-- 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);
    }
}

Ответ 12

Другим хорошим примером могут быть типы хранения данных. Чтобы дать точный пример: красно-черное дерево является более конкретным двоичным деревом, но получение данных и другой информации, такой как размер, можно обрабатывать одинаково. Конечно, хорошая библиотека должна иметь уже реализованную, но иногда вам приходится добавлять определенные типы данных для вашей проблемы.

В настоящее время я разрабатываю приложение, которое вычисляет матрицы для пользователей. Пользователь может настроить параметры, влияющие на расчет. Существует несколько типов матриц, которые можно вычислить, но есть четкое сходство, особенно в конфигурации: матрица A может использовать все настройки матрицы B, но имеет дополнительные параметры, которые могут быть использованы. В этом случае я унаследовал от ConfigObjectB для моего ConfigObjectA, и он работает очень хорошо.

Ответ 13

В общем, лучше наследовать от абстрактного класса, чем от конкретного класса. Конкретный класс должен дать определение для представления своих данных, а некоторые подклассы нуждаются в другом представлении. Поскольку абстрактный класс не должен предоставлять представление данных, будущие подклассы могут использовать любое представление, не опасаясь конфликта с тем, которое они унаследовали. Даже я никогда не находил ситуации, когда я чувствовал, что необходимо конкретное наследование. Но конкретные ситуации для конкретного наследования могут возникнуть специально, когда вы обеспечиваете обратную совместимость с вашим программным обеспечением. В этом случае u может специализироваться на class A, но вы хотите, чтобы он был конкретным, поскольку ваше более старое приложение могло его использовать.

Ответ 14

Ваши опасения также отражены в классическом принципе " одобрить композицию над наследованием" по причинам, которые вы указали. Я не могу вспомнить последний раз, когда я унаследовал от конкретного класса. Любой общий код, который нужно повторно использовать дочерними классами, почти всегда должен объявлять абстрактные интерфейсы для этих классов. В этом порядке я предпочитаю следующие стратегии:

  • Состав (без наследования)
  • Интерфейс
  • Абстрактный класс

Наследование от конкретного класса действительно не всегда является хорошей идеей.

[EDIT] Я буду квалифицировать это утверждение, сказав, что не вижу в этом хорошего варианта использования, когда вы контролируете архитектуру. Конечно, при использовании API, который это ожидает, whaddaya будет делать? Но я не понимаю выбор дизайна, сделанный этими API. Вызывающий класс должен всегда иметь возможность объявлять и использовать абстракцию в соответствии с Принципом инверсии зависимостей. Если у дочернего класса есть дополнительные интерфейсы для использования, вам придется либо нарушить DIP, либо сделать уродливое литье, чтобы получить доступ к этим интерфейсам.

Ответ 15

из проекта gdata:

com.google.gdata.client.Service предназначен для использования в качестве базового класса, который может быть настроен для определенных типов Услуги GData.

Сервис javadoc:

Класс Service представляет собой клиентское соединение с службой GData. Он инкапсулирует все взаимодействия на уровне протокола с сервером GData и выступает в качестве вспомогательного класса для объектов более высокого уровня (фиды, записи и т.д.), Которые вызывают операции на сервере и обрабатывают их результаты.

Этот класс обеспечивает общую функциональность базового уровня, необходимую для доступа к любой службе GData. Он также предназначен для использования в качестве базового класса, который может быть настроен для определенных типов служб GData. Примеры поддерживаемых настроек:

Аутентификация - реализация настраиваемого механизма проверки подлинности для служб, требующих проверки подлинности, и использования чего-то другого, кроме базовой или дайджест-аутентификации HTTP.

Расширения - определить ожидаемые расширения для каналов, записей и других типов, связанных с сервисом.

Форматы - определяют дополнительные пользовательские представления ресурсов, которые могут быть использованы или созданы парсерами и генераторами на стороне клиента и на стороне клиента для их обработки.

Ответ 16

Я считаю, что классы коллекции java являются очень хорошим примером. Таким образом, у вас есть AbstractCollection с дочерними элементами, такими как AbstractList, AbstractSet, AbstractQueue... Я думаю, что эта иерархия хорошо разработана.. и просто для того, чтобы там не было никакого взрыва там класса Collections со всеми его внутренними статическими классами.

Ответ 17

Вы делаете это, например, в библиотеках графического интерфейса. Не имеет смысла наследовать от простого компонента и делегировать его группе. Скорее всего, гораздо проще наследовать от Panel напрямую.

Ответ 18

Просто общая мысль. Абстрактные классы чего-то не хватает. Имеет смысл, если это, что отсутствует, отличается в каждом производном классе. Но у вас может быть случай, когда вы не хотите изменять класс, но просто хотите что-то добавить. Во избежание дублирования кода вы наследуете. И если вам нужны оба класса, это будет наследование от конкретного класса.

Итак, я бы ответил: во всех случаях, когда вы действительно хотите что-то добавить. Возможно, это происходит нечасто.