Чем отличается абстрактный класс от интерфейса

Интерфейсы vs. классы

Обсуждая с различными людьми — в большинстве своём опытными разработчиками — классический труд «Приёмы объектно-ориентированного проектирования. Паттерны проектирования» Гаммы, Хелма и др., я с изумлением встретил полное непонимание одного из базовых подходов ООП — различия классов и интерфейсов.

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

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

Поэтому я попытался систематизировать своё понимание вопроса в этой заметке.

Главное отличие класса от интерфейса — в том, что класс состоит из интерфейса и реализации.

Любой класс всегда неявно объявляет свой интерфейс — то, что доступно при использовании класса извне. Если у нас есть класс Ключ и у него публичный метод Открыть, который вызывает приватные методы Вставить, Повернуть и Вынуть, то интерфейс класса Ключ состоит из метода Открыть.

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

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

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

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

Нам придётся самое меньшее перегружать в Магнитной Карточке реализацию метода Открыть, используя уже последовательность Вставить, Провести и Вынуть.

Это уже плохо, потому что мы не знаем детали реализации класса Ключ — вдруг мы упустили какое-то очень важное изменение данных, которое должно было быть сделано — и было сделано в методе Ключ:: Открыть? Нам придётся лезть во внутреннюю реализацию Ключа и смотреть, что и как — даже если у нас есть такая техническая возможность (open source навсегда и так далее), это грубое нарушение инкапсуляции, которое ни к чему хорошему не приведёт.

Именно так и пишут Гамма и др.: наследование является нарушением инкапсуляции.

Можете попробовать самостоятельно поразмышлять над такими вопросами: — Что делать с тем фактом, что Ключ вставляется просто в скважину, а Магнитная Карточка — обязательно сверху (не посередине и не снизу)? — Что делать, когда нам понадобиться сделать Бесконтактную Карточку, которую надо не вставлять, а подносить?

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

Мы должны опираться на интерфейсы, а не классы.

Объявим интерфейс Ключ, содержащий метод Открыть. Объявим класс Поворотный Ключ, реализующий интерфейс Ключ при помощи своих методов Вставить, Повернуть и Вынуть.

Объявим класс Магнитная Карточка, тоже реализующий интерфейс Ключ, но уже по-своему — и без каких-либо неприятных пересечений с реализацией Поворотного Ключа. Этого помогло нам достичь отделение интерфейса от реализации. Всегда помните об общем принципе: нам нужно использовать не класс, а интерфейс.

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

Вам может показаться странным, но это именно то, что отличает человека от животного — использование интерфейсов вместо классов.

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

Когда мы мыслим классами — уподобляемся животным. Люди мыслят (и программируют) интерфейсами.

Использование интерфейсов даёт большие возможности. Например, класс может реализовывать несколько интерфейсов: класс Ключ-от-Домофона может содержать интерфейсы Ключ и Брелок.

Что касается наследования классов, которое, как вы помните, нарушает инкапсуляцию — часто вместо наследования лучше использовать делегирование и композицию.

Не забыли Бесконтактную Карточку? Так хочется сделать её родственной Магнитной Карточке! (Например, чтобы знать, что их обе можно положить в Отделение-для-Карточек в Бумажнике.

) Однако у них, кажется, нет ничего общего: интерфейс Ключ их роднит в той же мере, что и Поворотный Ключ с Ключом-от-Домофона.

Решение? Делаем класс (а может, и интерфейс — подумайте, что здесь подойдёт лучше) Карточка, реализующий интерфейс Ключ за счёт делегирования Магнитной Карточке либо же Бесконтактной Карточке. А чтобы узнать, что такое делегирование и композиция, а так же при чём тут абстрактная фабрика — обратитесь к книгам, посвящённым паттернам проектирования.

У использования интерфейсов вместо классов есть ещё много преимуществ. Вы сами сможете увидеть их на практике. Оставайтесь людьми!

P.S. К моему удивлению, я не смог найти на «Хабрахабре» ни блога, посвящённого ООП, ни блога, посвящённого программированию в целом.

Если они есть — укажите, пожалуйста, а пока за неимением лучших вариантов размещаю в персональном блоге.

Источник: https://habr.com/post/30444/

oop — В чем разница между интерфейсом и абстрактным классом? — Qaru

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

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

Резюме

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

Альтернативное резюме

  • Интерфейс предназначен для определения общедоступных API
  • Абстрактный класс предназначен для внутреннего использования и для определения SPI

О важности скрытия деталей реализации

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

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

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

Все разработчики Java должны были настроить свой код для соответствия деталям реализации: избегать случайного доступа, добавлять кеш для ускорения доступа или просто переопределять ArrayList самостоятельно, хотя это было бы несовместимо со всем другим кодом, который действительно работает с List. Это было бы ужасно…

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

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

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

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

Например, AbstractCollection предоставляет базовые реализации для isEmpty на основе размера 0, contains как повторение и сравнение, addAll как повторяющееся add и т.д.

Это позволяет реализациям сосредоточиться на ключевых частях, которые различают их: как фактически хранить и извлекать данные.

API по сравнению с SPI

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

Он называется Application Programming Интерфейс, а не Классы прикладного программирования.

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

Абстрактные классы — это сильные помощники для использования при реализации интерфейса, предполагая некоторый уровень детализации реализации. Альтернативно, абстрактные классы используются для определения SPI, интерфейсов поставщика услуг.

Разница между API и SPI является тонкой, но важной: для API основное внимание уделяется тем, кто использует, а для SPI основное внимание уделяется тому, кто реализует.

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

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

Если вместо этого используются абстрактные классы, новые методы могут быть либо определены в терминах существующих абстрактных методов, либо как пустые throw not implemented exception заглушки, которые, по крайней мере, позволят более старую версию реализации службы все еще компилироваться и выполняться.

Заметка о Java 8 и методах по умолчанию

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

Читайте также:  Чем отличается майка от футболки

Какой из них использовать?

  • Должна ли существо быть публично использована другими частями кода или другим внешним кодом? Добавьте к нему интерфейс, чтобы скрыть детали реализации из публичного абстрактного контракта, что является общим поведением вещи.
  • Есть ли что-то, что должно иметь несколько реализаций с большим количеством общего кода? Сделайте как интерфейс, так и абстрактную, неполную реализацию.
  • Будет ли только одна реализация, и никто ее не будет использовать? Просто сделайте это конкретным классом.
    • “ever” — это долгое время, вы можете играть в нее безопасно и по-прежнему добавлять интерфейс поверх нее.

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

Другими словами, не объявляйте свои переменные как ArrayList theList = new ArrayList(), если у вас на самом деле не очень сильная зависимость от того, что он является массивом, и ни один другой тип списка не сократит его для вас.

Вместо этого используйте List theList = new ArrayList или даже Collection theCollection = new ArrayList, если на самом деле это список, а не какой-либо другой тип коллекции.

Источник: http://qaru.site/questions/441/what-is-the-difference-between-an-interface-and-abstract-class

Абстрактные классы и методы в PHP. В чем их отличие от интерфейсов?

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

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

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

Определение абстрактного класса

Создание абстрактного класса в PHP происходит аналогично обычному, с указанием ключевого слова abstract перед class. Выражение в общем виде можно записать как «abstract class ИмяАбстрактногоКласса {/* определение методов и свойств */}».

//Определение абстрактного класса “Электрический генератор” abstract class ElectricityGenerator { //свойство накопленной энергии protected $energyCount = 0; //метод генерации энергии function generateEnergy() { $this->energyCount += 10; echo “Сгенерировано {$this->energyCount}Кл энергии”; } }

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

Работа с абстрактными методами

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

Правило формирования абстрактного метода:

  • Он может иметь модификаторы доступа public или protected. Использование private запрещено.
  • Абстрактные методы не должны иметь реализации. В их определение входит только модификатор доступа, имя и список параметров. Сразу за круглыми скобками параметров находится точка с запятой.
  • В самом начале определения метода, перед модификатором доступа должно находиться ключевое слово abstract.

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

//Абстрактный класс “Банкомат” abstract class CashMachine { //Абстрактный метод входа в систему abstract public function login($pinCode); //Обычный метод успешного входа public function greeting() { echo 'Вы успешно вошли в систему!'; } }

Наследование абстрактного класса

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

//Наследование абстрактного класса из //прошлого примера. class ShopTerminal extends CashMachine { //Реализация абстрактного метода public function login($pinCode) { if ($pinCode === 1111) { $this->greeting(); } else { echo 'Введен неправильный PIN!'; } } } //Создаем экземпляр магазинного терминала $shopTerminal = new ShopTerminal(); $shopTerminal->login(1111); /* * Результат: * Вы успешно вошли в систему! */ $shopTerminal->login(4567); /* * Результат: * Введен неправильный PIN! */

Источник: https://coder-booster.ru/learning/php-beginners/abstract-classes-and-methods

Ооп в php: абстрактные классы и интерфейсы

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

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

Для того чтобы убедиться в наличии нужного нам метода у объекта можно проверять его тип при помощи оператора instanceof или уточнить тип аргумента метода.

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

Давным-давно, когда абстрактных классов и интерфейсов в php не было, это можно было сделать примерно вот так:

class Animal { function say() { die (“Этот метод должен иметь реализацию в наследниках класса ” . __CLASS__); }
}

class Animal { function say() { die (“Этот метод должен иметь реализацию в наследниках класса ” . __CLASS__); } }

Каждый наследуемый от Animal класс вынужден будет содержать реализацию метода say, иначе при его вызове произойдёт аварийное завершение выполнения сценария. Естественно, такой подход устарел и имеет множество недостатков. В подобных случаях необходимо использовать абстрактные классы или интерфейсы.

Абстрактные классы

Абстрактные классы могут содержать описание абстрактных методов. Для таких методов указывается лишь заголовок с ключевым словом abstract и всеми прочими атрибутами, указываемыми при объявлении метода. Абстрактные методы не имеют тела или реализации, они лишь описывают, что должен уметь делать объект, а как он это будет делать — проблема наследников абстрактного класса.

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

Объявление абстрактного класса начинается с ключевого слова abstract. Давайте сделаем наш класс Animal и его метод say абстрактными.

abstract class Animal { abstract public function say();
}

abstract class Animal { abstract public function say(); }

Теперь класс, унаследованный от класса Animal, обязан будет содержать реализацию метода say или должен быть объявлен абстрактным, в противном случае ещё до начала выполнения скрипта произойдёт ошибка.

/* * Fatal error: Class Cat contains 1 abstract method * and must therefore be declared abstract or implement * the remaining methods (Animal::say) */
class Cat extends Animal { public function __construct($name) { $this->name = $name; }
}

/* * Fatal error: Class Cat contains 1 abstract method * and must therefore be declared abstract or implement * the remaining methods (Animal::say) */ class Cat extends Animal { public function __construct($name) { $this->name = $name; } }

В абстрактном классе можно объявлять так же и обычные методы и поля, которые могут быть унаследованы. Несмотря на то, что абстрактный класс не может иметь экземпляров, он может иметь конструктор, который могут использовать для инициализации его полей потомки. Добавим поле, хранящие кличку животного, гетер для её получения и конструктор в класс Animal.

abstract class Animal { private $name;
public function __construct($name) { $this->name = $name; }
abstract public function say();
public function getName() { return $this->name; }
}

abstract class Animal { private $name; public function __construct($name) { $this->name = $name; } abstract public function say(); public function getName() { return $this->name; } }

Давайте унаследуем от нашего абстрактного класса Animal ещё и класс Dog, описывающий собаку, и добавим в него, и в класс Cat реализации метода Say. Обратите внимание, что в конструкторах этих классов вызываются конструктор абстрактного класса-предка.

class Cat extends Animal {
public function __construct($name) { parent::__construct($name); }
public function say() { echo “meow-meow”; }
}

class Dog extends Animal {
public function __construct($name) { parent::__construct($name); } public function say() { echo “woof-woof!”; }
}

class Cat extends Animal { public function __construct($name) { parent::__construct($name); } public function say() { echo “meow-meow”; } } class Dog extends Animal { public function __construct($name) { parent::__construct($name); } public function say() { echo “woof-woof!”; } }

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

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

Интерфейсы

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

Имя интерфейса, так же как и имя абстрактного класса, можно использовать для уточнения типа параметра метода или указывать в качестве правого операнда оператора instanceof. Для объявления интерфейса используется ключевое слово interface.

Давайте переместим абстрактный метод say из класса Animal в интерфейс.

interface СanSay { public function say();
}

interface СanSay { public function say(); }

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

Пусть наш класс Animal, который уже не содержит метод say, будет имплементировать интерфейс CanSay. Классы Dog и Cat при этом останутся его наследниками класса Animal.

abstract class Animal implements CanSay { private $name;
public function __construct($name) { $this->name = $name; }
public function getName() { return $this->name; }
}

abstract class Animal implements CanSay { private $name; public function __construct($name) { $this->name = $name; } public function getName() { return $this->name; } }

Теперь для того, чтобы проверить, что объект может говорить, нужно удостовериться что класс, которому он принадлежит, реализует интерфейс CanSay. Сделать это можно указав в качестве типа нужного аргумента метода имя интерфейса, или использовав оператор instanceof.

Перенос абстрактного метода say в отдельный класс сделал наш код более расширяемым.

Теперь можно реализовать интерфейс CanSay и в других классах, которые не являются наследниками класса Animal, и тот функционал, которому было нужно, чтобы объекты, которыми он оперирует, умели говорить (реализовывали интерфейс CanSay), не изменится.

Стоит так же отметить ещё одно отличие интерфейсов от абстрактных классов — один класс может реализовывать сколь угодно много интерфейсов. Для этого их имена просто нужно перечислить через запятую после ключевого слова implements. Унаследовать же несколько абстрактных классов нельзя.

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

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

Читайте также:  Чем отличается растительное масло от подсолнечного

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

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

Источник: https://true-coder.ru/oop-v-php/oop-v-php-abstraktnye-klassy-i-interfejsy.html

C#. Абстрактные классы или интерфейсы

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

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

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

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

И вот тут начинается трудный выбор, что использовать в качестве «базы» абстрактный класс или интерфейс? И наверное, однозначного ответа, из серии «поступайте всегда так» всё-таки и нет! Но всё же, больше плюсов на стороне интерфейсов. Почему, давайте разберемся на практике, вспомним пример из статьи, где я рассказывал про абстрактные классы:

//Базовый класс, описывающий оружие (теперь абстрактный) abstract class Weapon { //Атака оружием public abstract void Attack(); } //Класс-наследник оружия, описывает нож class Knife : Weapon { //Атака ножом (теперь переопределенный метод, добавили override) public override void Attack() { Console.WriteLine(“Knife Attack!”); } } //Класс-наследник оружия, описывает ружьё class Gun : Weapon { //Атака ружьем (теперь переопределенный метод, добавили override) public override void Attack() { Console.WriteLine(“Gun Attack!”); } }

Вот в этом примере, лучше использовать вместо абстрактного класса «Weapon» интерфейс (как собственно, мы и делали в уроке № 24 базового курса).

Почему, ну смотрите, класс «Weapon» не содержит ни какой конкретики, все его методы (а точнее один метод) являются абстрактными, т.е.

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

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

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

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

Например, мы можем прийти к выводу, что у каждого оружия обязательно должно быть описание (его мы будем хранить в поле базового класса), ну и нужна возможность получить доступ к этому описанию (для этого, мы сделаем свойство). Тогда, базовый класс «Weapon» выглядел бы примерно так:

//Базовый класс, описывающий оружие (теперь абстрактный) abstract class Weapon { //Атака оружием public abstract void Attack(); //Открытое, не абстрактное свойство public string Description { get { return description; } set { description = value; } } //Закрытое, не абстрактное свойство private string description = “Без описания”; }

И таким образом, все классы наследники «Weapon» получат поле «description» и свойство «Description» по наследству. Т.е. нам не нужно будет дублировать эти сущности в каждом классе наследнике.

С одной стороны, это очень удобно, но вот только в определенный момент, мы может понять, что у какого-то наследника не должно быть поля «description» или свойство «Description» должно работать как-то иначе! И это будет неприятным сюрпризом…

Так вот, подводя итоги, я бы посоветовал использовать классы вместо интерфейсов в качестве «базы» для других, только в тех случаях, когда Вы понимаете, что интерфейсом в этом конкретном случае точно не обойтись!

Источник: http://plssite.ru/it_etc/csharp_abstract_classes_or_interfaces_article.html

Интерфейсы vs. Абстрактные классы

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

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

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

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

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

Интерфейс vs. Абстрактный класс — это компромисс между свободой клиента интерфейса/класса и свободой разработчика.

  • Интерфейс «гибче» с точки зрения клиентов: любой класс может реализовать любой интерфейс. Но интерфейс «жёстче» с точки зрения его разработчика: его сложнее изменять (нарушится работа всех клиентов), нельзя наложить ограничения на конструктор клиентов, нельзя использовать код повторно.
  • Абстрактный класс «жёстче» с точки зрения клиентов: клиент будет вынужден отказаться от текущего базового класса. Но абстрактный класс «гибче» с точки зрения его разработчика: он позволяет использовать код повторно, ограничить конструктор наследников, позволяет вносить изменения (легко добавить виртуальный метод, не сломав существующих клиентов), четче задавать «контракт» с наследниками с помощью Шаблонных Методов.

Разница для библиотек

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

Квалина и Абрамс в своей книге “Framework Design Guidelines” дают такой совет:

«В общем случае, классы являются более предпочтительной конструкцией для моделирования абстракций.

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

После выпуска интерфейса, набор его членов навеки является фиксированным. Любые изменения интерфейса сломают существующие типы, реализующие этот интерфейс.»

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

Различия для моделирования

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

Иногда бывает сложным понять, что должно быть «главным», а что — «второстепенным». Что важнее при моделировании кондиционеров? Иерархия кондиционеров с «вкраплением» интерфейсов бытовой техники, или иерархия бытовой техники с аспектами IAirConditioner?

На этот вопрос нет однозначного ответа, и иногда приходится перепрыгивать с одной «проекции» на другую.

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

«Второстепенность» интерфейсов не значит, что они играют второстепенную роль в моделировании. Интерфейсы моделируют семейство типов, и не нужны, если семейство типов отсутствует.

Именно поэтому Марк Сииманн (автор замечательной книги “Dependency Injection in .NET”) вводит специальный принцип: “Reuse Abstraction Principle”. Принцип говорит, что пока у интерфейса не появится как минимум 3 реализации (моки — не всчет!), разработчик не может сказать, чем же является эта «абстракция», и какое такое «общее поведение» она моделирует.

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

Также Марк критикует подход слепого выделения интерфейса для класса. Такие интерфейсы называются “Header Interfaces” (по аналогии с заголовочными файлами в С++).

Их проблема в том, что у них всегда лишь одна полноценная реализация + реализация в тестах.

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

Подведем итог:

  • Интерфейсы и абстрактные классы моделируют семейства типов.
  • Интерфейсы накладывают минимум ограничений на клиентов, максимум ограничений на разработчика интерфейсов.
  • С абстрактными классами все наоборот: они позволяют использовать повторно код и предоставляют еще и «защищенный интерфейс»: протокол с наследниками.
  • Классы формируют основную иерархию, интерфейсы определяют «дополнительное» поведение.

Источник: http://sergeyteplyakov.blogspot.com/2014/12/interfaces-vs-abstract-classes.html

Абстрактные классы и интерфейсы

Поиск Лекций

Концептуально понятия класса и интерфейса отличаются в ООП. Интерфейс — это декларация методов (событий) объекта без конкретизации реакций на эти события. С++ не декларирует интерфейс явно, как это делает, например, Java. Но, в качестве альтернативы явному описанию интерфейса, в С++ введены так называемые абстрактные классы.

Абстрактными становятся классы в которых определена одна или более функций-заглушек — виртуальных функций с оператором присвоения значения 0 в их определении, например:

virtual void rotate(int) = 0; // pure virtual method

virtual void rotate(int) = 0 { /* code of pure method */ }; // pure virtual method

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

Пример абстрактного класса:

class shape { // abstract class

point center;

. . .

public:

where() { return center; }

move(point p) { center = p; draw(); }

virtual void rotate(int) = 0; // pure virtual function

virtual void draw() = 0; // pure virtual function

virtual void hilite() = 0; // pure virtual function

. . .

};

shape x; // ERROR: attempt to create an object of an abstract class

shape* sptr; // pointer to abstract class is OK

shape f(); // ERROR: abstract class cannot be a return type

int g(shape s); // ERROR: abstract class cannot be a function argument type

shape& h(shape&); // reference to abstract class as return

// value or function argument is OK

Представим, что класс D является непосредственно производным от абстрактного класса B. Тогда каждая виртуальная функция-заглушка класса B, не имеющая переопределения в классе D, становится автоматически функцией заглушкой и для класса D. Из чего следует, что и класс D будет абстрактным.

Пример абстрактного производного класса:

// abstract class circle derived from abstract class shape

class circle : public shape {

int radius; // private

public:

void rotate(int) { } // virtual function defined: no action

// to rotate a circle

void draw(); // circle::draw must be defined somewhere

Читайте также:  Чем отличается муниципальный район от городского округа

}

34.35. Шаблоны функций. Назначение, особенности использования. Примеры. (УМК);

Шаблоны классов. Назначение, особенности использования. Примеры. (УМК);

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

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

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

Шаблоны, в некотором смысле, напоминают макросы, однако они более разнообразны и надежны. Тем не менее, достаточно часто шаблоны реализуются как обычные С-макросы.

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

Нужно отметить, что подавляющее большинство компонентов библиотек С++ базируется на шаблонах.

Шаблоны функций.

Определение шаблона функции имеет вид:

template<\p>

тип_возвращаемого_значения имя_функции (список параметров){ /* реализация */ }

Элементы списка_параметров_типов (разделяемые запятыми) определяют типы переменных или констант, передаваемых при вызове функции. Каждый элемент данного списка предваряется ключевым словом class, которое в данном контексте ссылается не на конкретный тип данных class, а на любой тип данных, фактически передаваемый при вызове функции, любой!

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

Пример определения шаблона функции:

template T1 add (T1 a,T1 b){

T1 result;

result=a+b;

return result;

}

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

Пример явной инсталляции функции:

float add (float a, float b);

Во втором случае инсталляция осуществляется в момент вызова функции с конкретными аргументами. Напрмер:

int a = 10, b = 20, c;

c = add (a, b);

Необходимо только, чтобы для типа-параметра был определен оператор +.

Шаблоны классов.

Определение шаблона функции имеет вид:

template class имя_класса { /* тело класса */ }

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

Пример определения шаблона класса:

template class Vector {

T *data;

int size;

public:

Vector(int);

~Vector( ) { delete[ ] data; }

T& operator[ ] (int i) { return data[i]; }

};

// Note the syntax for out-of-line definitions.

template Vector::Vector(int n) {

data = new T[n];

size = n;

};

void main() {

Vector x(5); // Generate a vector to store five integers

for (int i = 0; i < 5; ++i)

x[i] = i; // Initialize the vector.

}

В списке_параметров_типов собственно параметры типов указываются с ключевым словом class, а параметры констант — с какими-либо другими ключевыми словами, применяемыми для идентификации типа.

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

Шаблоны функций, которые являются членами класса, нельзя описывать как virtual. Т.о.

реализация функции-члена шаблона класса, которая находится вне определения шаблона класса, должна дополнительно включать следующие два элемента:

Определение должно начинаться с ключевого слова template, за которым следует такой же список_параметров_типов, какой указан в определении шаблона класса.

За именем_класса, предшествующим оператору разрешения видимости, должен следовать список_имен_параметров шаблона.

Пример определение функции-члена шаблона класса:

template class demo {

T var1;

S var2;

public:

T func(void);

};

template T demo::func(void) {

return var1;

}

36.Обработка исключений. Пример. (УМК);

С++ обеспечивает встроенный механизм обработки ошибок, называемый обработкой исключительных ситуаций. Благодаря обработке исключительных ситуаций можно упростить управление и реакцию на ошибки времени исполнения. Обработка исключительных ситуаций в С++ строится с помощью трех ключевых слов: try, catch, throw.

Операторы программы, во время выполнения которых вы хотите обеспечить обработку исключительных ситуаций, располагаются в блоке try. Если исключительная ситуация (т.е. ошибка) имеет место внутри блока try, искусственно она генерируется (с помощью throw).

Перехватывается и обрабатывается исключительная ситуация с помощью ключевого слова catch. Любой оператор, который генерирует исключительную ситуацию, должен выполняться внутри блока try. (Функции, которые вызываются внутри блока try также могут генерировать исключительную ситуацию).

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

try { // блок try

}

catch (type1 arg1) { // блок catch

}

catch (type2 arg2) { // блок catch

}

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

Когда исключительная ситуация возникает, она перехватывается соответствующим ей оператором catсh, который ее обрабатывает. С блоком try может быть связано более одного оператора catch. То, какой конкретно оператор catch используется, зависит от типа исключительной ситуации. Т.е.

, если тип данных, указанный в операторе catch, соответствует типу исключительной ситуации, то выполняется данный оператор catch. А все другие операторы блока try пропускаются. Если исключительная ситуация перехвачена, то аргумент arg получает ее значение.

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

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

throw; или throw выражение;

Выражение throw устанавливает исключение. Самый внутренний блок try, в котором устанавливается исключение, используется для выбора оператора catch, который обрабатывает исключение. Выражение throw без аргумента повторно устанавливает текущее исключение. Обычно оно используется для дальнейшей обработки исключения вторым обработчиком, вызываемый из первого.

Рассмотрим пример работы исключительной ситуации.

void main(){

try{ throw 10; }

catch(int i) { cout

Источник: https://poisk-ru.ru/s27244t4.html

Отличия абстрактного класса от интерфейса (abstract class and interface)

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

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

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

  • Конкретная фиксированная операция, которая должна быть абсолютно стабильно для всех типов кнопок.

  • Конкретная операция с поведением по умолчанию (т.е. операция, чье поведение подходит для многих типов кнопок, но могут быть кнопки с другим поведением).

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

Другими словами, тип Кнопки может содержать невиртуальные методы (non-virtual methods), виртуальные методы (virtual methods) и абстрактные методы (abstract methods).

Наличие разных типов методов является очень важным инструментом моделирования и позволяет весьма точно выражать намерения проектировщика.

Например, мы можем добавить невиртуальную операцию «Нажатия на кнопку», которая будет делегировать часть своей работы виртуальному (или абстрактному методу) «Обработать нажатие», но при этом всегда выполнять определенную часть работы (прозорливый читатель увидит в этом описании паттерн «Шаблонный метод»).

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

Но что, если наш тип «Кнопка меню» будет отнаследован от двух типов с одной и той же виртуальной операцией? Как переопределить лишь одну, а оставить другую? А как быть клиенту нового типа и различить, какую операцию вызвать? А что если у двух базовых типов есть поле с одним именем? А что если у одного базового типа метод «Нажать кнопку» реализован, а у другого — лишь описан в виде декларации?

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

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

Теперь будет легко провести разницу между тремя понятиями — интферфейса, абстрактного базового класса и конкретного базового класса.

  • Интерфейс — описывает некоторое семейство типов и содержит лишь декларации операций (да, я осознанно пишу слово «декларация», а не использую слово «контракт», которое в ООП имеет вполне определенное значение).

  • Абстрактный базовый класс описывает некоторое семейство типов, но помимо декларации операций может содержать реализации по умолчанию (виртуальные методы) и фиксированные операции (невиртуальные методы).

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

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

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

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

Так, например, наследование классов моделирует отношение «Является» («Кнопка меню» ЯВЛЯЕТСЯ «Кнопкой»), а базовые классы обычно содержат определенный функционал, тесно связанный с функционалом производного класса.

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

Интерфейсы же, по своей природе обладают меньшей связностью (low coupling), поскольку не обладают конкретным поведением, которое может осложнить жизнь класса-наследника.

Интерфейсы также могут моделировать отношение «Является» («Кнопка меню» ЯВЛЯЕТСЯ «IКнопкой»), но могут определять и менее жесткое отношение «Может выполнять роль» (CAN DO).

Например, интерфейс IEquatable из BCL определяет «дополнительное» поведение, которое говорит о возможности типов сравнивать значения объектов.

Источник: http://library.kiwix.org/ru.stackoverflow.com_ru_all_2018-03/A/answer/235464.html