Google, кажется, думает так. Их недавняя запись в пространство OpenID и непосредственное последующее ветвление протокола, имеют две вещи сказать о проблеме:
В общем, ковариация позволяет выражать больше информации в интерфейсе производного класса, чем это верно в интерфейсе базового класса. Поведение производного класса более специфично, чем поведение базового класса, а ковариация выражает (один из аспектов) различия.
Это полезно, когда у вас есть связанные иерархии губбинов, в ситуациях, когда некоторые клиенты захотят использовать интерфейс базового класса, но другие клиенты будут использовать интерфейс производного класса. Без константной корректности:
class URI { /* stuff */ };
class HttpAddress : public URI {
bool hasQueryParam(string);
string &getQueryParam(string);
};
class Resource {
virtual URI &getIdentifier();
};
class WebPage : public Resource {
virtual HttpAddress &getIdentifier();
};
Клиенты, которые знают, что у них есть веб-страница (возможно, браузеры), знают, что имеет смысл смотреть на параметры запроса. Клиенты, использующие базовый класс Resource, не знают об этом. Они всегда будут связывать возвращенный HttpAddress &
с URI &
переменной или временной.
Если они подозревают, но не знают, что их объект Resource имеет HttpAddress, тогда они могут dynamic_cast
. Но ковариация превосходит «просто знание» и приведение типов по той же причине, по которой статическая типизация вообще полезна.
Есть альтернативы - прикрепите функцию getQueryParam
к URI
но заставьте hasQueryParam
вернуть false для всего (загромождает интерфейс URI). Оставьте WebPage :: getIdentifier
определенным так, чтобы он возвращал URL &
, фактически возвращая HttpIdentifier &
, а вызывающие абоненты выполняли бессмысленное dynamic_cast
(загромождают вызывающий код и документация WebPage, где вы говорите, что «возвращаемый URL гарантированно будет динамически преобразован в HttpAddress»). Добавьте функцию getHttpIdentifier
в WebPage
(загромождает интерфейс WebPage
). Или просто используйте ковариацию для того, для чего она предназначена, что выражается в том, что WebPage
не имеет FtpAddress
или MailtoAddress
, у нее есть HttpAddress
.
Наконец, есть, конечно, разумный аргумент в пользу того, что у вас не должно быть иерархий куббинов, не говоря уже о связанных иерархиях куббинов. Но эти классы так же легко могут быть интерфейсами с чисто виртуальными методами, поэтому я не думаю, что это влияет на валидность использования ковариации.
что выражает тот факт, что веб-страница
не имеет FtpAddress
или MailtoAddress
, у нее есть HttpAddress
.
Наконец, есть, конечно, разумный аргумент в пользу того, что у вас не должно быть иерархий куббинов, не говоря уже о связанных иерархиях куббинов. Но эти классы так же легко могут быть интерфейсами с чисто виртуальными методами, поэтому я не думаю, что это влияет на валидность использования ковариации.
что выражает тот факт, что веб-страница
не имеет FtpAddress
или MailtoAddress
, у нее есть HttpAddress
.
Наконец, есть, конечно, разумный аргумент в пользу того, что у вас не должно быть иерархий куббинов, не говоря уже о связанных иерархиях куббинов. Но эти классы так же легко могут быть интерфейсами с чисто виртуальными методами, поэтому я не думаю, что это влияет на валидность использования ковариации.
Я думаю, что ковариация может быть полезна при объявлении фабричных методов, которые возвращают определенный класс, а не его базовый класс. Эта статья достаточно хорошо объясняет этот сценарий и включает следующий пример кода:
class product
{
...
};
class factory
{
public:
virtual product *create() const = 0;
...
};
class concrete_product : public product
{
...
};
class concrete_factory : public factory
{
public:
virtual concrete_product *create() const
{
return new concrete_product;
}
...
};
Другой пример - конкретная фабрика, которая будет возвращать указатели на конкретные классы вместо абстрактного (я использовал его для внутреннего использования на фабрике, когда фабрика должна была создавать составные объекты) .
Это становится полезным в сценарии, когда вы хотите использовать бетонный завод для создания бетонных изделий. Вам всегда нужен наиболее специализированный интерфейс, который является достаточно общим ...
Код , использующий бетонный завод, может безопасно предполагать, что продукты являются бетонными продуктами, поэтому он может безопасно использовать расширения, предоставляемые бетонным класс продукта по отношению к абстрактному классу. Это действительно можно рассматривать как синтаксический сахар, но в любом случае это мило.
Каноническим примером является метод .clone ()
/ .copy ()
. Таким образом, вы всегда можете выполнить
obj = obj->copy();
независимо от типа объекта obj.
Изменить: этот метод клонирования должен быть определен в базовом классе Object (как на самом деле в Java). Таким образом, если бы клон не был ковариантным, вам пришлось бы либо выполнить приведение, либо ограничиться методами корневого базового класса (у которого было бы очень мало методов по сравнению с классом исходного объекта копии).
Я часто использую ковариацию при работе с существующим кодом, чтобы избавиться от static_cast. Обычно ситуация похожа на это:
class IPart {};
class IThing {
public:
virtual IPart* part() = 0;
};
class AFooPart : public IPart {
public:
void doThis();
};
class AFooThing : public IThing {
virtual AFooPart* part() {...}
};
class ABarPart : public IPart {
public:
void doThat();
};
class ABarThing : public IThing {
virtual ABarPart* part() {...}
};
Это позволяет мне использовать
AFooThing* pFooThing = ...;
pFooThing->Part()->doThis();
и
ABarThing pBarThing = ...;
pBarThing->Part()->doThat();
вместо
static_cast< AFooPart >(pFooThing->Part())->doThis();
и
static_cast< ABarPart >(pBarThing->Part())->doThat();
. Теперь, встречая такой код, можно спорить о первоначальном дизайне и о том, есть ли лучший один - но по моему опыту, часто существуют ограничения, такие как приоритеты, затраты / выгода и т. д., которые мешают обширному усовершенствованию дизайна и допускают только небольшие шаги, такие как этот.