Наличие одного интерфейса с множеством виртуальных методов? Или иметь много интерфейсов только с одним виртуальным методом? - PullRequest
5 голосов
/ 19 ноября 2010

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

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

class Library
   {
   public:
      void addBook(IBook &book);
   };

class IBook
   {
   public:
      string getAuthor()    = 0;
      string getTitle()     = 0;
      string getISBNCode()  = 0;
      size_t getNofPages()  = 0;
      size_t getNofImages() = 0;
      double getPrice()     = 0;
      void   printBook()    = 0;
      void   convertToPdf() = 0;
   };

К сожалению, нет смысла применять все эти методы для всех видов книг.

  • В некоторых книгах нет изображений (поэтому я нене хочу реализовывать getNofImages ())
  • Некоторые книги не имеют ISBN-кода
  • Некоторые книги нельзя купить, поэтому у них нет цены
  • Некоторые книги не могут быть напечатаны
  • Некоторые книги не могут быть преобразованы в PDF

Поскольку у меня только 1 интерфейс, я вынужден реализовать все для всех книг ивернуть 0, вернуть "" или ничего не делать в реализации, если это не имеет значения.

Альтернативой может быть разделение этих интерфейсов на множество интерфейсов, например:

class IBook
   {
   public:
      string getAuthor()    = 0;
      string getTitle()     = 0;
      size_t getNofPages()  = 0;
   };

class IISBNGetter
   {
   public:
      string getISBNCode()  = 0;
   };

class IImagesGetter
   {
   public:
      size_t getNofImages() = 0;
   };

class IBuyable
   {
   public:
      double getPrice()     = 0;
   };

class IPrintable
   {
   public:
      void   printBook()    = 0;
   };

class IConvertible
   {
   public:
      void   convertToPdf() = 0;
   };

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

Добавление книги в библиотеку становится примерно таким:

bookid = myLibrary->addBook (myBook);
myLibrary->setISBNGetter  (bookid, myBook);
myLibrary->setImageGetter (bookid, myBook);
myLibrary->setBuyable     (bookid, myBook);

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

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

Разве нет лучшего способа организовать интерфейсы для получения чего-то подобного?

Я также думал об использовании лямбда-выражений, но за экранами это почти то же самое, что иметь много интерфейсов только с 1Метод.

Есть идеи?

Ответы [ 7 ]

8 голосов
/ 19 ноября 2010

У меня есть IBook для реализации каждого метода:

class IBook
   {
   public:
      virtual ~IBook() {}

      virtual string getAuthor() { return ""; } // or some other meaningful default value
      virtual string getTitle() { return ""; }
      virtual string getISBNCode() { return ""; }
      virtual size_t getNofPages() { return 0; }
      virtual size_t getNofImages() { return 0; }
      virtual double getPrice() { return .0; }
      virtual void   printBook() {}
      virtual void   convertToPdf() {}
   };

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

2 голосов
/ 19 ноября 2010

Я думаю, вы должны провести различие между , имеющим ISBN, и реализацией интерфейса, который запрашивает ISBN (в данном случае, IBook).

Нет никакой причины, по которой вы не можете сказать, как часть определения «книга», что книга - это нечто такое, что «можно найти, имеет ли она ISBN, и если да, то что».

Если вам не нравится возвращать пустые значения, чтобы указать «нет», это достаточно справедливо. В некоторых доменах пустая строка является допустимым значением, так что это даже невозможно. Вы могли бы иметь:

bool hasISBNcode();
string getISBNcode();

или

std::pair<bool, string> getISBNcode();

или аналогичный.

В противном случае вы обнаружите dynamic_cast повсюду, прежде чем вызывать какую-либо из функций IBook, и вы также найдете 2 ^ n различных конкретных классов для разных типов книг. Или, в своем примере кода, вы задействуете библиотеку в том, имеет ли книга ISBN (что мне кажется неправильным - это не имеет ничего общего с библиотекой, это свойство одной книги). Ни с одним из них не особенно интересно работать, и они здесь не кажутся необходимыми.

Если такие вещи кажутся необходимыми, возможно, вы могли бы использовать стратегии. Определите ConcreteBook как способный искать для ISBN, использующего некоторый вспомогательный объект, без того, чтобы класс книги не знал, как выполняется поиск. Затем подключите различные объекты, чтобы это выглядело, в зависимости от того, есть ли у конкретной книги такой объект или нет. Кажется, это немного излишне, потому что, вероятно, где-то просто столбец, который можно обнулять в базе данных.

2 голосов
/ 19 ноября 2010

Полагаю, вам не нужно доходить до крайностей, а выбирать средний путь.Наличие одного интерфейса не очень хорошо, оно нарушает Принцип разделения интерфейсов (ISP) , с другой стороны, наличие такого количества интерфейсов также испортит ваш код.Я бы оставил один основной IBook и подумал над остальным.Например, IPrintable и IConvertible (для преобразования PDF) могут идти в одном интерфейсе.Вероятно, IBuyable и IISBNGetter также будут вместе.

2 голосов
/ 19 ноября 2010

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

Одной из распространенных реализаций этого промежуточного класса будетвыдавать какое-то исключение «MethodNotImplemented» в каждом методе, чтобы пользователь класса мог отлавливать их в каждом конкретном случае.

Видеть, что исключения немного дороже для такого случая, когда вызовнесуществующие методы не были бы «исключительными», также существует подход, при котором эти методы являются пустыми или возвращают значения по умолчанию, как опубликовала Симона.

1 голос
/ 19 ноября 2010

Существуют две незначительные (очень незначительные) проблемы:

  • Правильная абстракция иерархии логических частей.
  • Возможность нулевых значений.

Другие предоставили решения для каждой проблемы.А именно, что касается первого, не переходите на интерфейс "все", не переходите на единый метод для интерфейса, но попробуйте смоделировать иерархию, которая существует.Что касается последнего, boost::optional, возможно, дополненного отдельными методами запроса на существование элемента данных.

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

Что касается стиля (еще один аспект ясности), что все это getSin Javaism материал?

x = 2*getSin(v)/computeCos(v)

Не делаетсмысл в C ++, просто напишите sin.: -)

Приветствия и hth.,

1 голос
/ 19 ноября 2010

Вы можете иметь для каждой книги контейнер указателей на одноэлементные объекты базового интерфейса, например, std::map<std::string, IBase>.Затем вам потребуется интерфейс по имени, получить указатель на него (или ноль) и просто вызвать на нем какой-нибудь doDefault () (или вывести указатель на IDerived, если необходимо).Каждая интерфейсная функция должна иметь указатель на Book в качестве первого (или даже единственного) параметра: doDefault(const Book*).

1 голос
/ 19 ноября 2010

Другое решение - сохранить интерфейс, но использовать boost :: необязательный для возвращаемых значений, которые могут быть пустыми.

class IBook
   {
   public:
      virtual ~Ibook(){}

      virtual string getAuthor()    = 0;
      virtual string getTitle()     = 0;
      virtual string getISBNCode()  = 0;
      virtual size_t getNofPages()  = 0;
      virtual size_t getNofImages() = 0;
      virtual boost::optional< double > getPrice()     = 0;   // some have no price
      virtual void   printBook()    = 0;
      virtual void   convertToPdf() = 0;
   };
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...