Правильно ли наследовать от встроенных классов? - PullRequest
6 голосов
/ 14 ноября 2008

Я хочу определенным образом проанализировать файл Apache access.log с программой на python, и, хотя я совершенно новичок в объектно-ориентированном программировании, я хочу начать делать это сейчас.

Я собираюсь создать класс ApacheAccessLog , и единственное, что я могу себе сейчас представить, это то, что он будет делать, - это метод readline . Правильно ли в этом случае наследовать от встроенного класса file , поэтому класс будет вести себя так же, как экземпляр самого класса file , или нет? Каков наилучший способ сделать это?

Ответы [ 6 ]

15 голосов
/ 14 ноября 2008

В этом случае я бы использовал делегирование вместо наследование . Это означает, что ваш класс должен содержать объект файла в качестве атрибута и вызывать для него метод readline. Вы можете передать файловый объект в конструктор класса logger.

Для этого есть как минимум две причины:

  1. Делегирование уменьшает связность, например, вместо файловых объектов вы можете использовать любой другой объект, который реализует метод readline ( ввод с утки здесь пригодится).
  2. При наследовании из файла общедоступный интерфейс вашего класса становится излишне широким. Он включает в себя все методы, определенные в файле, даже если эти методы не имеют смысла в случае журнала Apache.
6 голосов
/ 21 ноября 2008

Я работаю в Java, но уверен, что в Python будут применяться те же принципы. Как правило, вы не должны никогда наследовать от класса, реализацию которого вы не понимаете и не контролируете, если этот класс не был разработан специально для наследования. Если он был разработан таким образом, он должен четко описать это в своей документации.

Причина этого в том, что наследование потенциально может связать вас с деталями реализации класса, от которого вы наследуете.

Использовать пример из книги Джоша Блоха «Эффективная Java»

Если бы мы расширили класс ArrayList, чтобы иметь возможность подсчитать количество элементов, которые были добавлены к нему в течение срока его службы (не обязательно того, что он содержит в настоящее время), у нас может возникнуть желание написать как то так.

public class CountingList extends ArrayList {
    int counter = 0;

    public void add(Object o) {
        counter++;
        super.add(0);
    }

    public void addAll(Collection c) {
        count += c.size();
        super.addAll(c);
    }

    // Etc.
}

Теперь это расширение выглядит так, будто оно будет точно подсчитывать количество элементов, которые были добавлены в список, но на самом деле оно может и не быть. Если ArrayList реализовал addAll путем итерации по предоставленному Collection и вызова его метода интерфейса addAll для каждого элемента, то мы будем считать каждый элемент, добавленный с помощью метода addAll, дважды. Теперь поведение нашего класса зависит от деталей реализации ArrayList.

Это, конечно, в дополнение к недостатку невозможности использовать другие реализации List с нашим классом CountingList. Плюс недостатки наследования от конкретного класса, которые обсуждались выше.

Насколько я понимаю, Python использует аналогичный (если не идентичный) механизм отправки методов для Java и поэтому будет подвергаться тем же ограничениям. Если бы кто-то мог привести пример на Python, я уверен, что он был бы еще более полезным.

1 голос
/ 14 ноября 2008

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

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

В качестве примера того, почему следует избегать наследования, если оно вам не нужно, вы можете взглянуть на класс java.util.Stack . Поскольку он расширяет Vector, он наследует всех методов в Vector. Большинство из этих методов нарушают контракт, подразумеваемый Stack, например, LIFO. Было бы намного лучше реализовать Stack, используя Vector внутри, только выставляя методы Stack в качестве API. Тогда было бы легко изменить реализацию на ArrayList или что-то еще позже, что сейчас невозможно из-за наследования.

1 голос
/ 14 ноября 2008

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

1 голос
/ 14 ноября 2008

Вполне приемлемо наследовать от встроенного класса. В этом случае я бы сказал, что вы правы на деньги.
Файл журнала «является», который сообщает вам, что наследование в порядке ..

Общее правило.
Собака "является" русским животным, поэтому наследуется от животного.
Владелец "имеет" n животных, поэтому не наследует от животных.

0 голосов
/ 24 ноября 2008

Вы, похоже, нашли свой ответ, что в этом случае делегирование - лучшая стратегия. Тем не менее, я хотел бы добавить, что, за исключением делегирования, нет ничего плохого в расширении встроенного класса, особенно если вашей альтернативой, в зависимости от языка, является «исправление обезьян» (см. http://en.wikipedia.org/wiki/Monkey_patch)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...