Разработка интерфейсов классов в C++

Понимание Интерфейсов в классах

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

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

В то же время, только потому, что добавление методов для открытого интерфейса (вероятно) ничего не испортит, это не означает, что вы должны начинать с крошечного интерфейса. Прежде всего, если кто-нибудь решит унаследоваться от вашего класса, а в последствии вы выберете функцию с тем же именем, то возникнет путаница. Во-первых, если вы не объявите функцию виртуальной, то объект подкласса выберет функцию в зависимости от статического типа указателя. Это может создать проблемы. Кроме того, если вы объявите ее виртуальной, то у вас, возможно, возникнет проблема с тем, что она может предоставлять другую функциональность, отличную от оригинальной реализации этой функции. И, наконец, вы просто не можете добавлять чисто виртуальные функции классу, который уже используется, потому что никто из унаследовавших от него не будет реализовывать эту функцию.

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

Наследование и дизайн класса

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

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

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

class TrafficWatch
{
        public:
        // интерфейс  некоторого класса, который реализует обработку информации в сети
        void addPacket (const Packet&; network_packet);

        int getAveragePacketSize ();

        int getMaxPacket ();

        virtual bool isOverloaded ();
};<span style="text-align: justify;"> </span>

В этом классе некоторые методы не будут меняться от реализации к реализации; добавление пакета будет всегда обрабатываться одинаково, и средний размер пакета также не будет меняться. С другой стороны, у каждого может быть разное представление о перегруженности сети. Это будет меняться от ситуации к ситуации, и мы хотим, чтобы каждый решал сам — для некоторых трафик выше 10 Мбит/с может быть перегруженной сетью, а для других 100 Мбит/сек — нормальная ситуация.

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

Чтобы разработать связь ISA, убедитесь, что это для данный класс включает все необходимые функции, и не включает те, которые на самом деле не нужны в подклассах. Одним из примеров, имеющих дополнительные функции является класс Птица Bird, который реализует функцию полета fly. Проблема в том, что не все птицы могут летать — например, пингвины и страусы эму. Это говорит о том, что более разумным выбором дизайна может быть два подкласса птиц, один для птиц, которые могут летать, и один для нелетающих птиц. Конечно, два подкласса птиц могут быть излишними, в зависимости от сложности вашей иерархии классов. Если вы знаете, что никто никогда не будет использовать класс для нелетающих птиц, то это не так уж плохо. Конечно, вы не всегда будете знать, для чего кто-то будет использовать ваш класс, и гораздо проще подумать, прежде чем приступить к реализации всей иерархии класса, чем возвращаться назад и менять, когда люди используют его.

Практика

К сожалению, для данной темы пока нет подходящих задач. Если у вас есть таковые на примете, отправте их по адресу: admin@cppstudio.com. Мы их опубликуем!

Автор: Marienko L.
Дата: 05.10.2012
Поделиться:

Комментарии

  1. Андрей Аршинов

    Если честно, то не совсем понятно : зачем нужны интерфейсы, что это такое?

    • Viktor Dolgalyov

      Интерфейс — это функции, которые доступны не из объекта.

      Нужно это для обеспечения инкапсуляции.

      Пример из учебника:

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

  2. Muthabor

    что значит знак амперсанта в
    void addPacket (const Packet&; network_packet);

    и почему здесь используется точка с запятой, не просто запятая?

  3. Богдан

    тут что-то вообще не ясное, предлагаю чу чуть углубить и более разъяснить. и тему не пропустили?

Оставить комментарий

Вы должны войти, чтобы оставить комментарий.