Не виртуальный шаблон проектирования интерфейса в C#/C++

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

2 ответа

Решение

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

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

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

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; }
};

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

  1. Каждое производное животное повторяет код - единственная часть, которая изменяется, это строка, но каждый производный класс нуждается в std::cout << ... << std::endl; стандартный код.
  2. Базовый класс не может дать гарантии о том, что 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 и заканчивается новой строкой. Это также облегчает обслуживание, поскольку производные классы не должны повторять этот код.

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

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

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

Другие вопросы по тегам