Когда использовать интерфейс вместо абстрактного класса и наоборот?
Это может быть общий вопрос ООП. Я хотел сделать общее сравнение между интерфейсом и абстрактным классом на основе их использования.
Когда нужно использовать интерфейс, а когда - абстрактный класс?
26 ответов
Я написал статью об этом:
Абстрактные классы и интерфейсы
Подводя итог:
Когда мы говорим об абстрактных классах, мы определяем характеристики типа объекта; указав, что такое объект.
Когда мы говорим об интерфейсе и определяем возможности, которые мы обещаем предоставить, мы говорим об установлении договора о том, что может сделать объект.
Абстрактный класс может иметь общее состояние или функциональность. Интерфейс - это только обещание предоставить состояние или функциональность. Хороший абстрактный класс сократит объем кода, который необходимо переписать, потому что его функциональность или состояние могут быть разделены. Интерфейс не имеет определенной информации для обмена
Лично мне почти никогда не нужно писать абстрактные классы.
В большинстве случаев я вижу, что абстрактные классы используются (неправильно), потому что автор абстрактного класса использует шаблон "Шаблонный метод".
Проблема с "Методом шаблона" заключается в том, что он почти всегда несколько повторяется - "производный" класс знает не только о "абстрактном" методе своего базового класса, который он реализует, но и о открытых методах базового класса., хотя в большинстве случаев это не нужно называть их.
(Чрезмерно упрощенный) пример:
abstract class QuickSorter
{
public void Sort(object[] items)
{
// implementation code that somewhere along the way calls:
bool less = compare(x,y);
// ... more implementation code
}
abstract bool compare(object lhs, object rhs);
}
Итак, здесь автор этого класса написал общий алгоритм и намеревается, чтобы люди использовали его, "специализируя" его, предоставляя свои собственные "крючки" - в данном случае, метод "сравнения".
Таким образом, предполагаемое использование примерно так:
class NameSorter : QuickSorter
{
public bool compare(object lhs, object rhs)
{
// etc.
}
}
Проблема в том, что вы неправильно связали воедино две концепции:
- Способ сравнения двух предметов (какой предмет должен идти первым)
- Метод сортировки элементов (например, быстрая сортировка или сортировка слиянием и т. Д.)
В приведенном выше коде теоретически автор метода "сравнение" может повторно вызвать метод "Сортировка" суперкласса... хотя на практике они никогда не захотят или не будут этого делать.
Цена, которую вы платите за эту ненужную связь, состоит в том, что трудно изменить суперкласс, а в большинстве языков OO невозможно изменить его во время выполнения.
Альтернативный метод - использовать шаблон проектирования "Стратегия":
interface IComparator
{
bool compare(object lhs, object rhs);
}
class QuickSorter
{
private readonly IComparator comparator;
public QuickSorter(IComparator comparator)
{
this.comparator = comparator;
}
public void Sort(object[] items)
{
// usual code but call comparator.Compare();
}
}
class NameComparator : IComparator
{
bool compare(object lhs, object rhs)
{
// same code as before;
}
}
Итак, обратите внимание: все, что у нас есть, это интерфейсы и конкретные реализации этих интерфейсов. На практике вам больше ничего не нужно для создания ОО высокого уровня.
Чтобы "скрыть" тот факт, что мы реализовали "сортировку имен" с помощью класса "QuickSort" и "NameComparator", мы все равно могли бы написать метод фабрики:
ISorter CreateNameSorter()
{
return new QuickSorter(new NameComparator());
}
Каждый раз, когда у вас есть абстрактный класс, вы можете сделать это... даже если между базовым и производным классом существуют естественные реентерабельные отношения, обычно стоит сделать их явными.
Одна заключительная мысль: все, что мы сделали выше, это "составление" функции "NameSorting" с помощью функции "QuickSort" и функции "NameComparison"... на функциональном языке программирования этот стиль программирования становится еще более естественным, с меньшим количеством кода.
Если вы смотрите на Java в качестве языка ООП,
" Интерфейс не обеспечивает реализацию метода " больше не действует при запуске Java 8. Теперь Java предоставляет реализацию в интерфейсе для методов по умолчанию.
Проще говоря, я хотел бы использовать
интерфейс: для реализации контракта несколькими несвязанными объектами. Он обеспечивает возможность " HAS A ".
абстрактный класс: для реализации одинакового или разного поведения среди нескольких связанных объектов. Устанавливает отношение "есть".
Сайт Oracle предоставляет ключевые различия между interface
а также abstract
учебный класс.
Рассмотрите возможность использования абстрактных классов, если:
- Вы хотите поделиться кодом между несколькими тесно связанными классами.
- Вы ожидаете, что классы, которые расширяют ваш абстрактный класс, имеют много общих методов или полей или требуют модификаторов доступа, отличных от public (например, protected и private).
- Вы хотите объявить нестатические или не финальные поля.
Рассмотрите возможность использования интерфейсов, если:
- Вы ожидаете, что несвязанные классы будут реализовывать ваш интерфейс. Например, многие несвязанные объекты могут реализовать
Serializable
интерфейс. - Вы хотите указать поведение определенного типа данных, но не беспокоитесь о том, кто реализует его поведение.
- Вы хотите воспользоваться множественным наследованием типа.
Пример:
Абстрактный класс (это отношение)
Читатель - это абстрактный класс.
BufferedReader является Reader
FileReader является Reader
FileReader
а также BufferedReader
используются для общего назначения: чтение данных, и они связаны через Reader
учебный класс.
Интерфейс (есть возможность)
Сериализуемый это интерфейс.
Предположим, что у вас есть два класса в вашем приложении, которые реализуют Serializable
интерфейс
Employee implements Serializable
Game implements Serializable
Здесь вы не можете установить какие-либо отношения через Serializable
интерфейс между Employee
а также Game
, которые предназначены для разных целей. Оба способны сериализировать состояние, и сравнение на этом заканчивается.
Посмотрите на эти сообщения:
Как мне объяснить разницу между интерфейсом и абстрактным классом?
Мои два цента:
Интерфейс в основном определяет контракт, которого должен придерживаться любой реализующий класс (реализовать элементы интерфейса). Он не содержит никакого кода.
С другой стороны, абстрактный класс может содержать код, и могут быть некоторые методы, помеченные как абстрактные, которые должен реализовать наследующий класс.
Редкие ситуации, в которых я использовал абстрактные классы, - это когда у меня есть некоторая функциональность по умолчанию, которую наследующий класс может не заинтересовать в переопределении, скажем, абстрактного базового класса, от которого наследуются некоторые специализированные классы.
Пример (очень элементарный!): Рассмотрим базовый класс Customer с абстрактными методами, такими как CalculatePayment()
, CalculateRewardPoints()
и некоторые неабстрактные методы, такие как GetName()
, SavePaymentDetails()
,
Специализированные классы, такие как RegularCustomer
а также GoldCustomer
унаследует от Customer
базовый класс и реализовать свой собственный CalculatePayment()
а также CalculateRewardPoints()
метод логики, но повторно использовать GetName()
а также SavePaymentDetails()
методы.
Вы можете добавить больше функциональных возможностей к абстрактному классу (т.е. не абстрактным методам), не затрагивая дочерние классы, которые использовали более старую версию. Принимая во внимание, что добавление методов к интерфейсу повлияет на все классы, реализующие его, поскольку теперь им потребуется реализовать вновь добавленные члены интерфейса.
Абстрактный класс со всеми абстрактными членами будет похож на интерфейс.
Хорошо, только что сам "проглотил" это - вот с точки зрения непрофессионала (не стесняйтесь поправлять меня, если я ошибаюсь) - я знаю, что эта тема оооооооооочень, но кто-то другой может наткнуться на нее однажды...
Абстрактные классы позволяют создавать чертежи и дополнительно КОНСТРУКТИВНО (реализовывать) свойства и методы, которыми должны обладать ВСЕ его потомки.
Интерфейс, с другой стороны, позволяет вам только объявить, что вы хотите, чтобы свойства и / или методы с заданным именем существовали во всех классах, которые его реализуют, но не указывает, как вы должны это реализовать. Кроме того, класс может реализовывать МНОГО интерфейсов, но может расширять только ОДИН абстрактный класс. Интерфейс - это скорее высокоуровневый архитектурный инструмент (который становится понятнее, если вы начнете понимать шаблоны проектирования) - аннотация работает в обоих лагерях и может выполнять некоторые грязные задачи.
Зачем использовать один над другим? Первое допускает более конкретное определение потомков, второе допускает больший полиморфизм. Этот последний момент важен для конечного пользователя / кодера, который может использовать эту информацию для реализации API(интерфейса) в различных комбинациях / формах, чтобы удовлетворить их потребности.
Я думаю, что это был для меня момент "лампочки" - подумайте об интерфейсах меньше с точки зрения автора и больше с точки зрения любого кодера, который придет позже в цепочке, который добавляет реализацию в проект или расширяет API.
Когда делать то, что очень просто, если у вас в голове есть ясная концепция.
Абстрактные классы могут быть получены, тогда как интерфейсы могут быть реализованы. Между ними есть какая-то разница. Когда вы наследуете абстрактный класс, отношения между производным классом и базовым классом являются отношениями "это". например, собака - это животное, овца - это животное, что означает, что производный класс наследует некоторые свойства от базового класса.
Принимая во внимание, что для реализации интерфейсов это соотношение "может быть". например, собака может быть шпионской собакой. Собака может быть цирковой собакой. Собака может быть гоночной собакой. Это означает, что вы реализуете определенные методы для приобретения чего-либо.
Я надеюсь, что я ясно.
Когда предпочитать абстрактный класс интерфейсу?
- Если планируется обновление базового класса в течение жизни программы / проекта, лучше всего разрешить, чтобы базовый класс был абстрактным классом.
- Если кто-то пытается создать основу для объектов, тесно связанных в иерархии, очень полезно использовать абстрактный класс
Когда предпочитать интерфейс над абстрактным классом?
- Если не иметь дело с массивным иерархическим типом фреймворка, интерфейсы были бы отличным выбором
- Поскольку множественное наследование не поддерживается с абстрактными классами (проблема с бриллиантами), интерфейсы могут сэкономить время
1. Если вы создаете что-то, что предоставляет общие функциональные возможности несвязанным классам, используйте интерфейс.
2. Если вы создаете что-то для объектов, тесно связанных в иерархии, используйте абстрактный класс.
Классы могут наследовать только от одного базового класса, поэтому, если вы хотите использовать абстрактные классы для обеспечения полиморфизма для группы классов, все они должны наследовать от этого класса. Абстрактные классы могут также предоставлять члены, которые уже были реализованы. Следовательно, вы можете обеспечить определенное количество идентичных функциональных возможностей с помощью абстрактного класса, но не можете с помощью интерфейса.
Вот несколько рекомендаций, которые помогут вам решить, использовать ли интерфейс или абстрактный класс для полиморфизма ваших компонентов.
- Если вы планируете создать несколько версий вашего компонента, создайте абстрактный класс. Абстрактные классы предоставляют простой и легкий способ создания версий ваших компонентов. При обновлении базового класса все наследующие классы автоматически обновляются с изменением. Интерфейсы, с другой стороны, не могут быть изменены после создания таким образом. Если требуется новая версия интерфейса, вы должны создать совершенно новый интерфейс.
- Если создаваемая вами функциональность будет полезна для широкого круга разнородных объектов, используйте интерфейс. Абстрактные классы следует использовать в первую очередь для объектов, которые тесно связаны, тогда как интерфейсы лучше всего подходят для предоставления общих функций несвязанным классам.
- Если вы разрабатываете небольшие, лаконичные кусочки функциональности, используйте интерфейсы. Если вы разрабатываете большие функциональные блоки, используйте абстрактный класс.
- Если вы хотите обеспечить общую, реализованную функциональность среди всех реализаций вашего компонента, используйте абстрактный класс. Абстрактные классы позволяют вам частично реализовать ваш класс, тогда как интерфейсы не содержат реализации для каких-либо членов.
Скопировано из:
http://msdn.microsoft.com/en-us/library/scsyfw1d%28v=vs.71%29.aspx
Я написал статью о том, когда использовать абстрактный класс, а когда использовать интерфейс. Между ними существует гораздо больше различий, кроме "один IS-A... и один CAN-DO...". Для меня это консервированные ответы. Я упоминаю несколько причин, когда использовать любой из них. Надеюсь, поможет.
Я думаю, что самый краткий способ выразить это следующим образом:
Общие свойства => абстрактный класс.
Общая функциональность => интерфейс.
И, если выразиться менее кратко...
Пример абстрактного класса:
public abstract class BaseAnimal
{
public int NumberOfLegs { get; set; }
protected BaseAnimal(int numberOfLegs)
{
NumberOfLegs = numberOfLegs;
}
}
public class Dog : BaseAnimal
{
public Dog() : base(4) { }
}
public class Human : BaseAnimal
{
public Human() : base(2) { }
}
Так как у животных есть общее свойство - в данном случае количество ног - имеет смысл создать абстрактный класс, содержащий это общее свойство. Это также позволяет нам писать общий код, который работает с этим свойством. Например:
public static int CountAllLegs(List<BaseAnimal> animals)
{
int legCount = 0;
foreach (BaseAnimal animal in animals)
{
legCount += animal.NumberOfLegs;
}
return legCount;
}
Пример интерфейса:
public interface IMakeSound
{
void MakeSound();
}
public class Car : IMakeSound
{
public void MakeSound() => Console.WriteLine("Vroom!");
}
public class Vuvuzela : IMakeSound
{
public void MakeSound() => Console.WriteLine("VZZZZZZZZZZZZZ!");
}
Обратите внимание, что Vuvuzelas и Cars - это совершенно разные вещи, но они имеют общую функциональность: создание звука. Таким образом, интерфейс имеет смысл здесь. Кроме того, это позволит программистам группировать вещи, которые издают звуки, под общим интерфейсом - IMakeSound
в этом случае. С этим дизайном, вы могли бы написать следующий код:
List<IMakeSound> soundMakers = new List<ImakeSound>();
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Vuvuzela());
foreach (IMakeSound soundMaker in soundMakers)
{
soundMaker.MakeSound();
}
Можете ли вы сказать, что это будет выводить?
Наконец, вы можете объединить два.
Комбинированный пример:
public interface IMakeSound
{
void MakeSound();
}
public abstract class BaseAnimal : IMakeSound
{
public int NumberOfLegs { get; set; }
protected BaseAnimal(int numberOfLegs)
{
NumberOfLegs = numberOfLegs;
}
public abstract void MakeSound();
}
public class Cat : BaseAnimal
{
public Cat() : base(4) { }
public override void MakeSound() => Console.WriteLine("Meow!");
}
public class Human : BaseAnimal
{
public Human() : base(2) { }
public override void MakeSound() => Console.WriteLine("Hello, world!");
}
Здесь мы требуем всего BaseAnimal
издает звук, но мы еще не знаем его реализацию. В таком случае мы можем абстрагировать реализацию интерфейса и делегировать его реализацию его подклассам.
И еще один момент. Помните, как в примере с абстрактным классом мы могли работать с общими свойствами различных объектов, а в примере интерфейса мы могли вызывать общую функциональность различных объектов? В этом последнем примере мы могли бы сделать оба.
Рассмотрите возможность использования абстрактных классов, если любое из этих утверждений применимо к вашей ситуации:
- Вы хотите поделиться кодом между несколькими тесно связанными классами.
- Вы ожидаете, что классы, которые расширяют ваш абстрактный класс, имеют много общих методов или полей или требуют модификаторов доступа, кроме public (например, protected и private).
- Вы хотите объявить нестатические или не финальные поля. Это позволяет вам определять методы, которые могут обращаться и изменять состояние объекта, к которому они принадлежат.
Рассмотрите возможность использования интерфейсов, если любое из этих утверждений применимо к вашей ситуации:
- Вы ожидаете, что несвязанные классы будут реализовывать ваш интерфейс. Например, интерфейсы Comparable и Cloneable реализованы многими несвязанными классами.
- Вы хотите указать поведение определенного типа данных, но не беспокоитесь о том, кто реализует его поведение.
- Вы хотите воспользоваться несколькими наследованиями.
Используйте абстрактный класс, если вы хотите предоставить некоторые базовые реализации.
Ответы различаются в зависимости от языка. Например, в Java класс может реализовывать (наследовать) несколько интерфейсов, но наследовать только от одного абстрактного класса. Таким образом, интерфейсы дают вам больше гибкости. Но это не так в C++.
Для меня я бы пошел с интерфейсами во многих случаях. Но я предпочитаю абстрактные классы в некоторых случаях.
Классы в ОО вообще относятся к реализации. Я использую абстрактные классы, когда хочу навязать некоторые детали реализации дочерним элементам, иначе я использую интерфейсы.
Конечно, абстрактные классы полезны не только для принудительной реализации, но и для обмена некоторыми конкретными деталями между многими связанными классами.
Чисто на основе наследования вы будете использовать реферат, в котором вы определяете явно наследуемые, абстрактные отношения (например, animal->cat) и / или требуете наследования виртуальных или непубличных свойств, особенно общего состояния (которое интерфейсы не могут поддерживать).).
Вы должны попытаться отдать предпочтение композиции (посредством внедрения зависимостей), а не наследованию, и заметьте, что интерфейсы, являющиеся контрактами, поддерживают модульное тестирование, разделение проблем и (в зависимости от языка) множественное наследование так, как не могут абстрагироваться.
В Java вы можете наследовать от одного (абстрактного) класса, чтобы "обеспечить" функциональность, и вы можете реализовать много интерфейсов для "обеспечения" функциональности
Это может быть очень сложный вызов...
Один указатель, который я могу дать: объект может реализовать много интерфейсов, в то время как объект может наследовать только один базовый класс (в современном языке OO, таком как C#, я знаю, что C++ имеет множественное наследование - но разве это не осуждается?)
Одно интересное место, где интерфейсы работают лучше, чем абстрактные классы, это когда вам нужно добавить дополнительную функциональность в группу (связанных или не связанных) объектов. Если вы не можете дать им базовый абстрактный класс (например, они sealed
или у вас уже есть родитель), вы можете вместо этого дать им фиктивный (пустой) интерфейс, а затем просто написать методы расширения для этого интерфейса.
Основное правило большого пальца: для "Существительных" используйте абстрактный класс, а для "Глаголов" используйте интерфейс
Например: car
абстрактный класс и drive
Мы можем сделать это интерфейсом.
Краткий ответ: абстрактный класс позволяет создавать функциональные возможности, которые подклассы могут реализовать или переопределить. Интерфейс позволяет вам только определять функциональность, но не реализовывать ее. И хотя класс может расширять только один абстрактный класс, он может использовать несколько интерфейсов.
Если у нас есть реализация, которая будет одинаковой для всех производных классов, то лучше использовать абстрактный класс вместо интерфейса. когда у нас есть интерфейс, мы можем переместить нашу реализацию в любой класс, реализующий интерфейс. В абстрактном классе он позволяет избежать дублирования кода и совместно использовать реализацию для всех производных классов. Интерфейсы позволяют разрабатывать слабосвязанные системы, что помогает лучше тестировать.
Оба являются контрактом для определения класса:
Вывод 1: Оба намерения - обобщение объекта
При определении абстрактных классов они также могут иметь реализацию по умолчанию .
Вывод 2: отличие заключается в дизайне поведенческого обобщения
При использовании абстрактных классов классы могут наследоваться только от одного абстрактного класса.
Вывод 3: абстрактный класс имеет ограничение в использовании. Это означает ограничение в поведенческом обобщении .
Окончательный вывод — когда использовать какой: Различие находится на уровне поведенческого обобщения .
При разработке поведенческих классов, если функциональность просто концептуально ограничена среди определенных классов или, другими словами, является общей для определенного класса, используйте абстрактный класс. но если функциональность более общая, чем определенные классы , или мы можем/хотим добавить функциональность к другим классам, используйте интерфейс как контракт.
Транспортное средство содержит автомобиль, танк, самолет, тележку и т. д.
Абстрактный класс Транспортное средство может иметь подклассы, такие как автомобиль, танк, самолет, тележка и т. д.
public abstract Vehicle {...}
public Car extends Vehicle {...}
public Tank extends Vehicle {...}
Итак, что такое движимое? почти все! Затем
камень, торт, машина, планета, галактика, даже ты подвижен!
большинство из них также являются наблюдаемыми!
И что такое съедобный? Все по игре Tasty Planet... Тогда
камень, машина, планета, галактика, даже время!
public interface Movable {...}
public interface Observable {...}
public interface Eatable {...}
public class Stone implements Movable, Eatable, Observable {...}
public class Time implements Eatable, Observable {...}
public class Stupidity implements Observable {...}
Окончательно!
public class ChocolateCar extends Vehicle implements Eatable {...}
Абстрактный класс может иметь реализации.
Интерфейс не имеет реализаций, он просто определяет вид контракта.
Также могут быть некоторые языковые различия: например, C# не имеет множественного наследования, но в классе может быть реализовано несколько интерфейсов.