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





Иногда стоит отказаться от идеала в пользу реалистичного. Если из-за того, что «делать правильно» без реальной выгоды возникнет серьезная проблема, то я сделаю это неправильно. С учетом сказанного, я часто думаю, что стоит потратить время на то, чтобы сделать это правильно, потому что ненужное множественное наследование увеличивает сложность, а может делает систему менее удобной в обслуживании. Вам действительно нужно решить, что лучше для ваших обстоятельств.
Один из вариантов - реализовать в этих объектах интерфейс Damageable, а не наследовать от DamageCounter. Таким образом, у человека счетчик урона имеет, но является повреждается. (Я часто нахожу, что интерфейсы имеют больше смысла как прилагательные, чем как существительные.) Тогда вы можете иметь последовательный интерфейс повреждений для объектов Damageable и не раскрывать, что счетчик повреждений является базовой реализацией (если вам не нужно).
Если вы хотите пойти по шаблонному маршруту (при условии, что C++ или аналогичный), вы можете сделать это с помощью миксинов, но это может очень быстро стать уродливым, если все сделано плохо.
Обычно, когда мы говорим о «есть» или «есть», мы говорим о наследовании и композиции.
Гм ... счетчик повреждений будет просто атрибутом одного из ваших производных классов и не будет обсуждаться в терминах «Человек - счетчик повреждений» в отношении вашего вопроса.
Видеть это:
http://www.artima.com/designtechniques/compoinh.html
Что может помочь вам на этом пути.
@ Дерек: Судя по формулировке, я предположил, что был - это базовый класс, перечитав вопрос, теперь я вроде как понимаю, к чему он клонит.
Этот вопрос действительно сбивает с толку: /
Ваш вопрос, выделенный жирным шрифтом, является открытым и имеет ответ «в зависимости от обстоятельств», но ваш пример на самом деле не дает много информации о контексте, из которого вы задаете вопрос. Эти строки меня сбивают с толку;
sets of data that should all be processed in a similar way
Каким образом? Обрабатываются ли наборы функцией? Другой класс? Через виртуальную функцию данных?
In particular, different objects would ideally act the same, which would be very easily achieved with polymorphism
Идеал «действовать одинаково» и полиморфизм абсолютно не связаны. Как полиморфизм упрощает достижение?
@Kevin
Normally when we talk about 'is a' vs 'has a' we're talking about Inheritance vs Composition.
Um...damage counter would just be attribute of one of your derived classes and wouldn't really be discussed in terms of 'A person is a damage counter' with respect to your question.
Наличие счетчика урона в качестве атрибута не позволяет ему объединять различные объекты со счетчиками урона в коллекцию. Например, у человека и автомобиля могут быть счетчики повреждений, но у вас не может быть vector<Person|Car>, vector<with::getDamage()> или чего-либо подобного на большинстве языков. Если у вас есть общий базовый класс Object, вы можете использовать их таким образом, но тогда вы не сможете получить общий доступ к методу getDamage().
В этом была суть его вопроса, как я его читал. «Должен ли я нарушать is-a и has-a ради того, чтобы относиться к определенным объектам так, как если бы они были одинаковыми, даже если это не так?»
«Делать это правильно» будет иметь преимущества в долгосрочной перспективе, хотя бы потому, что кому-то, обслуживающему систему позже, будет легче понять, было ли это сделано правильно с самого начала.
В зависимости от языка у вас может быть возможность множественного наследования, но обычно простые интерфейсы имеют наибольший смысл. Под «простым» я подразумеваю создание интерфейса, который не должен быть излишним. Лучше иметь много простых интерфейсов и несколько монолитных. Конечно, всегда есть компромисс, и слишком много интерфейсов, вероятно, приведет к тому, что об одном из них «забудут» ...
@Андрей
The ideal of "acting the same" and polymorphism are absolutely unrelated. How does polymorphism make it easy to achieve?
Все они имеют, например, одну общую функцию. Назовем его addDamage(). Если вы хотите сделать что-то вроде этого:
foreach (obj in mylist)
obj.addDamage(1)
Тогда вам понадобится либо динамический язык, либо они должны быть расширены от общего родительского класса (или интерфейса). например.:
class Person : DamageCounter {}
class Car : DamageCounter {}
foreach (DamageCounter d in mylist)
d.addDamage(1)
Затем вы можете относиться к Person и Car одинаково при определенных очень полезных обстоятельствах.
Я думаю, вам следует реализовать интерфейсы, чтобы иметь возможность обеспечивать свои отношения имеет (я делаю это на C#):
public interface IDamageable
{
void AddDamage(int i);
int DamageCount {get;}
}
Вы можете реализовать это в своих объектах:
public class Person : IDamageable
public class House : IDamageable
И вы были бы уверены, что свойство DamageCount и имеет метод, позволяющий добавлять повреждения, не подразумевая, что человек и дом связаны друг с другом какой-то иерархией.
Этого можно добиться с помощью множественного наследования. В вашем конкретном случае (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. Использование чистых виртуальных классов (чтобы они были похожи на интерфейсы) является ключевым и должно использоваться часто. Он изолирует будущие изменения от изменения всей системы. Прочтите принцип открытого-закрытого для получения дополнительной информации.
Я согласен с Джоном, но при условии, что вам все еще нужен отдельный класс счетчика урона, вы можете:
class IDamageable {
virtual DamageCounter* damage_counter() = 0;
};
class DamageCounter {
...
};
Затем каждый повреждаемый класс должен предоставить свою собственную функцию-член damage_counter (). Обратной стороной является то, что он создает виртуальную таблицу для каждого повреждаемого класса. Вместо этого вы можете использовать:
class Damageable {
public:
DamageCounter damage_counter() { return damage_counter_; }
private:
DamageCounter damage_counter_;
};
Но многие люди являются Не круто с множественным наследованием, когда у нескольких родителей есть переменные-члены.
Полиморфизм не требует наследования. Полиморфизм - это то, что вы получаете, когда несколько объектов реализуют одну и ту же подпись (метод) сообщения.