Когда ковариантность C++ является лучшим решением?

Google, кажется, думает так. Их недавняя запись в пространство OpenID и непосредственное последующее ветвление протокола, имеют две вещи сказать о проблеме:

  1. OpenID полезен, особенно когда он связывается с сайтами с очень большим количеством пользователей для использования с сайтами, которые могли поддерживать большое количество пользователей и вероятно не потянут их. Почему перестраивают колесо в системе аутентификации, когда кто-то как Google или те уже, которые работают над OID, оно покрыло?
  2. OpenID. грязен, поскольку у людей уже есть большое количество отличающихся имен пользователей со многими участвующими поставщиками. Однако у многих пользователей была прекрасная возможность, когда Gmail пришел для получения их собственного имени как их адреса электронной почты, и иметь довольно бесконечное число учетных записей они могут создать. Google, кажется, думает, что их система учетной записи отдельно достаточна для всех, Открывают пользователей ID. Я, вероятно, согласился бы с этим.
29
задан Community 23 May 2017 в 11:46
поделиться

6 ответов

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

Это полезно, когда у вас есть связанные иерархии губбинов, в ситуациях, когда некоторые клиенты захотят использовать интерфейс базового класса, но другие клиенты будут использовать интерфейс производного класса. Без константной корректности:

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 .

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

12
ответ дан 28 November 2019 в 01:38
поделиться

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

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;
    }
    ...
};
4
ответ дан 28 November 2019 в 01:38
поделиться

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

0
ответ дан 28 November 2019 в 01:38
поделиться

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

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

0
ответ дан 28 November 2019 в 01:38
поделиться

Каноническим примером является метод .clone () / .copy () . Таким образом, вы всегда можете выполнить

 obj = obj->copy();

независимо от типа объекта obj.

Изменить: этот метод клонирования должен быть определен в базовом классе Object (как на самом деле в Java). Таким образом, если бы клон не был ковариантным, вам пришлось бы либо выполнить приведение, либо ограничиться методами корневого базового класса (у которого было бы очень мало методов по сравнению с классом исходного объекта копии).

31
ответ дан 28 November 2019 в 01:38
поделиться

Я часто использую ковариацию при работе с существующим кодом, чтобы избавиться от 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();

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

2
ответ дан 28 November 2019 в 01:38
поделиться
Другие вопросы по тегам:

Похожие вопросы: