Наследование и полиморфизм - простота использования против чистоты - PullRequest
5 голосов
/ 15 августа 2008

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

Единственное решение, которое я могу придумать, - это чтобы член класса возвращал правильный тип объекта при неявном приведении вместо того, чтобы полагаться на наследование. Было бы лучше отказаться от - это / имеет идеал в обмен на простоту программирования?

Edit: Чтобы быть более конкретным, я использую C ++, поэтому использование полиморфизма позволило бы различным объектам «действовать одинаково» в том смысле, что производные классы могут находиться в одном списке и работать с виртуальной функцией базового класса. Использование интерфейса (или имитация их через наследование) выглядит как решение, которое я хотел бы использовать.

Ответы [ 10 ]

5 голосов
/ 15 августа 2008

Я думаю, что вы должны реализовывать интерфейсы, чтобы обеспечить соблюдение ваших отношений (я делаю это в C #):

public interface IDamageable
{
    void AddDamage(int i);
    int DamageCount {get;}
}

Вы можете реализовать это в ваших объектах:

public class Person : IDamageable

public class House : IDamageable

И вы были бы уверены, что свойство DamageCount и имеет метод, позволяющий вам добавлять урон, не подразумевая, что человек и дом связаны друг с другом в некоторой иерархии.

3 голосов
/ 15 августа 2008

Это может быть выполнено с использованием множественного наследования. В вашем конкретном случае (C ++) вы можете использовать чисто виртуальные классы в качестве интерфейсов. Это позволяет вам иметь множественное наследование, не создавая проблем области / неоднозначности. Пример:

class Damage {
    virtual void addDamage(int d) = 0;
    virtual int getDamage() = 0;
};

class Person : public virtual Damage {
    void addDamage(int d) {
        // ...
        damage += d * 2;
    }

    int getDamage() {
        return damage;
    }
};

class Car : public virtual Damage {
    void addDamage(int d) {
        // ...
        damage += d;
    }

    int getDamage() {
        return damage;
    }
};

Теперь и Персона, и Автомобиль - это Урон, то есть они реализуют интерфейс Урон. Использование чисто виртуальных классов (так что они похожи на интерфейсы) является ключевым и должно использоваться часто. Он защищает будущие изменения от изменения всей системы. Прочтите Принцип Открыто-Закрыто для получения дополнительной информации.

1 голос
/ 16 сентября 2008

Я согласен с Джоном, но, если вам все еще нужен отдельный класс счетчика урона, вы можете сделать:

class IDamageable {
  virtual DamageCounter* damage_counter() = 0;
};
class DamageCounter {
  ...
};

В этом случае каждый повреждаемый класс должен предоставить свою собственную функцию-членmage_counter (). Недостатком этого является то, что он создает vtable для каждого повреждаемого класса. Вместо этого вы можете использовать:

class Damageable {
 public:
  DamageCounter damage_counter() { return damage_counter_; }
 private:
  DamageCounter damage_counter_;
};

Но многие люди Не круто с множественным наследованием, когда у нескольких родителей есть переменные-члены.

0 голосов
/ 19 января 2010

Полиморфизм не требует наследования . Полиморфизм - это то, что вы получаете, когда несколько объектов реализуют одну и ту же сигнатуру (метод).

0 голосов
/ 15 августа 2008

@ Andrew

Идеал "действовать одинаково" и полиморфизм абсолютно не связаны. Как полиморфизм позволяет легко достичь?

Все они имеют, например, одну общую функцию. Давайте назовем это addDamage(). Если вы хотите сделать что-то вроде этого:

foreach (obj in mylist)
    obj.addDamage(1)

Тогда вам нужен либо динамический язык, либо он нужен для расширения из общего родительского класса (или интерфейса). e.g.:

class Person : DamageCounter {}
class Car : DamageCounter {}

foreach (DamageCounter d in mylist)
    d.addDamage(1)

Затем вы можете обращаться с Person и Car одинаково при определенных очень полезных обстоятельствах.

0 голосов
/ 15 августа 2008

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

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

0 голосов
/ 15 августа 2008

@ Кевин

Обычно, когда мы говорим о том, что «против» есть «мы имеем в виду наследование против композиции».

Гм ... счетчик урона будет просто атрибутом одного из ваших производных классов и не будет обсуждаться с точки зрения «человек - счетчик урона» в отношении вашего вопроса.

Наличие счетчика урона в качестве атрибута не позволяет ему разнообразить объекты со счетчиками урона в коллекцию. Например, у человека и автомобиля могут быть счетчики повреждений, но у вас не может быть vector<Person|Car> или vector<with::getDamage()> или чего-либо подобного в большинстве языков. Если у вас есть общий базовый класс Object, вы можете переместить их таким образом, но тогда вы не сможете получить общий доступ к методу getDamage().

Это была суть его вопроса, когда я его читал. «Должен ли я нарушать is-a и has-a ради того, чтобы относиться к определенным объектам так, как будто они одинаковые, даже если это не так?»

0 голосов
/ 15 августа 2008

Этот вопрос действительно сбивает с толку: /

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

наборы данных, которые должны обрабатываться одинаково

Каким образом? Обрабатываются ли наборы функцией? Еще один класс? Через виртуальную функцию на данных?

В частности, разные объекты в идеале должны действовать одинаково, что было бы очень легко достичь с помощью полиморфизма

Идеал «действовать одинаково» и полиморфизм абсолютно не связаны. Как полиморфизм позволяет легко достичь?

0 голосов
/ 15 августа 2008

Обычно, когда мы говорим о том, что «против» есть «мы имеем в виду наследование против композиции».

Гм ... счетчик урона будет просто атрибутом одного из ваших производных классов и не будет обсуждаться с точки зрения «человек - счетчик урона» в отношении вашего вопроса.

Смотрите это:

http://www.artima.com/designtechniques/compoinh.html

Что может помочь вам в пути.

@ Дерек : Исходя из формулировки, я предположил, что был базовым выражением, перечитав вопрос, который я вроде теперь вижу, к чему он стремится ,

0 голосов
/ 15 августа 2008

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

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

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

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