Наследование и полиморфизм: простота использования и чистота

StackOverflow https://stackoverflow.com/questions/11854

Вопрос

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

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

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

Это было полезно?

Решение

Этого можно добиться с помощью множественного наследования.В вашем конкретном случае (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;
    }
};

Теперь и Person, и Car «is-a» Damage, то есть реализуют интерфейс Damage.Использование чисто виртуальных классов (чтобы они напоминали интерфейсы) является ключевым моментом и должно использоваться часто.Это изолирует будущие изменения от изменения всей системы.Прочтите о принципе открытости-закрытости для получения дополнительной информации.

Другие советы

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

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

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

public class Person : IDamageable

public class House : IDamageable

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@Кевин

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

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

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

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

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

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

Видеть это:

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

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

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

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

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

@Андрей

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top