Шаблон проектирования не виртуального интерфейса в С#/С++

При разработке интерфейса кто-то рекомендовал использовать шаблон не виртуального интерфейса. Может ли кто-нибудь кратко изложить, каковы преимущества этого шаблона?

Ответы

Ответ 1

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

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

В качестве простого примера рассмотрим старый добрый класс животных с несколькими типичными производными классами:

class Animal
{
public:
    virtual void speak() const = 0;
};

class Dog : public Animal
{
public:
    void speak() const { std::cout << "Woof!" << std::endl; }
};

class Cat : public Animal
{
public:
    void speak() const { std::cout << "Meow!" << std::endl; }
};

В этом используется обычный общедоступный виртуальный интерфейс, к которому мы привыкли, но у него есть несколько проблем:

  • Каждое полученное животное представляет собой повторяющийся код - единственная часть, которая изменяется, является строкой, но каждый производный класс нуждается в полном шаблоне std::cout << ... << std::endl;.
  • Базовый класс не может гарантировать, что делает speak(). Производный класс может забыть новую строку или записать ее в cerr или что-то в этом роде.

Чтобы исправить это, вы можете использовать не виртуальный интерфейс, который дополняется частной виртуальной функцией, которая допускает полиморфное поведение:

class Animal
{
public:
   void speak() const { std::cout << getSound() << std::endl; }
private:
   virtual std::string getSound() const = 0;
};

class Dog : public Animal
{
private:
   std::string getSound() const { return "Woof!"; }
};

class Cat : public Animal
{
private:
   std::string getSound() const { return "Meow!"; }
};

Теперь базовый класс может гарантировать, что он будет выписываться на std::cout и заканчивается новой строкой. Это также упрощает обслуживание, поскольку производным классам не нужно повторять этот код.

Herb Sutter написал хорошую статью о не виртуальных интерфейсах, которую я бы рекомендовал проверить.

Ответ 2

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

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