Зачем нужны интерфейсы c
Интерфейс представляет некое описание типа, набор компонентов, который должен иметь тип данных. И, собственно, мы не можем создавать объекты интерфейса напрямую с помощью конструктора, как например, в классах:
IMovable m = new IMovable(); // ! Ошибка, так сделать нельзя interface IMovable
В конечном счете интерфейс предназначен для реализации в классах и структурах. Например, реализуем выше определенный интерфейс IMovable:
// применение интерфейса в классе class Person : IMovable < public void Move() < Console.WriteLine("Человек идет"); >> // применение интерфейса в структуре struct Car : IMovable < public void Move() < Console.WriteLine("Машина едет"); >>
При применении интерфейса, как и при наследовании после имени класса или структуры указывается двоеточие и затем идут названия применяемых интерфейсов. При этом класс должен реализовать все методы и свойства применяемых интерфейсов, если эти методы и свойства не имеют реализации по умолчанию.
Если методы и свойства интерфейса не имеют модификатора доступа, то по умолчанию они являются публичными, при реализации этих методов и свойств в классе и структуре к ним можно применять только модификатор public .
Применение интерфейса в программе:
Person person = new Person(); Car car = new Car(); DoAction(person); DoAction(car); void DoAction(IMovable movable) => movable.Move(); interface IMovable < void Move(); >class Person : IMovable < public void Move() =>Console.WriteLine("Человек идет"); > struct Car : IMovable < public void Move() =>Console.WriteLine("Машина едет"); >
В данной программе определен метод DoAction() , который в качестве параметра принимает объект интерфейса IMovable. На момент написания кода мы можем не знать, что это будет за объект — какой-то класс или структура. Единственное, в чем мы можем быть уверены, что этот объект обязательно реализует метод Move и мы можем вызвать этот метод.
Иными словами, интерфейс — это контракт, что какой-то определенный тип обязательно реализует некоторый функционал.
Консольный вывод данной программы:
Человек идет Машина едет
Реализация интерфейсов по умолчанию
Начиная с версии C# 8.0 интерфейсы поддерживают реализацию методов и свойств по умолчанию. Зачем это нужно? Допустим, у нас есть куча классов, которые реализуют некоторый интерфейс. Если мы добавим в этот интерфейс новый метод, то мы будем обязаны реализовать этот метод во всех классах, применяющих данный интерфейс. Иначе подобные классы просто не будут компилироваться. Теперь вместо реализации метода во всех классах нам достаточно определить его реализацию по умолчанию в интерфейсе. Если класс не реализует метод, будет применяться реализация по умолчанию.
IMovable tom = new Person(); Car tesla = new Car(); tom.Move(); // Walking tesla.Move(); // Driving interface IMovable < void Move() =>Console.WriteLine("Walking"); > class Person : IMovable < >class Car : IMovable < public void Move() =>Console.WriteLine("Driving"); >
В данном случае интерфейс IMovable определяет реализацию по умолчанию для метода Move . Класс Person не реализует этот метод, поэтому он применяет реализацию по умолчанию в отличие от класса Car , который определяет свою реализацию для метода Move.
Стоит отметить, что хотя для объекта класса Person мы можем вызвать метод Move — ведь класс Person применяет интерфейс IMovable , тем не менее мы не можем написать так:
Person tom = new Person(); tom.Move(); // Ошибка - метод Move не определен в классе Person
Множественная реализация интерфейсов
Интерфейсы имеют еще одну важную функцию: в C# не поддерживается множественное наследование, то есть мы можем унаследовать класс только от одного класса, в отличие, скажем, от языка С++, где множественное наследование можно использовать. Интерфейсы позволяют частично обойти это ограничение, поскольку в C# классы и структуры могут реализовать сразу несколько интерфейсов. Все реализуемые интерфейсы указываются через запятую:
class myClass: myInterface1, myInterface2, myInterface3, .
Рассмотрим на примере:
Message hello = new Message("Hello World"); hello.Print(); // Hello World interface IMessage < string Text < get; set; >> interface IPrintable < void Print(); >class Message : IMessage, IPrintable < public string Text < get; set; >public Message(string text) => Text = text; public void Print()=> Console.WriteLine(Text); >
В данном случае определены два интерфейса. Интерфейс IMessage определяет свойство Text, которое представляет текст сообщения. А интерфейс IPrintable определяет метод Print.
Класс Message реализует оба интерфейса и затем применяется в программе.
Интерфейсы в преобразованиях типов
Все сказанное в отношении преобразования типов характерно и для интерфейсов. Поскольку класс Message реализует интерфейс IMessage, то переменная типа IMessage может хранить ссылку на объект типа Message:
// Все объекты Message являются объектами IMessage IMessage hello = new Message("Hello METANIT.COM"); Console.WriteLine(hello.Text); // Hello METANIT.COM // Не все объекты IMessage являются объектами Message, необходимо явное приведение // Message someMessage = hello; // ! Ошибка // Интерфейс IMessage не имеет свойства Print, необходимо явное приведение // hello.Print(); // ! Ошибка // если hello представляет класс Message, выполняем преобразование if (hello is Message someMessage) someMessage.Print();
Преобразование от класса к его интерфейсу, как и преобразование от производного типа к базовому, выполняется автоматически. Так как любой объект Message реализует интерфейс IMessage.
Обратное преобразование — от интерфейса к реализующему его классу будет аналогично преобразованию от базового класса к производному. Так как не каждый объект IMessage является объектом Message (ведь интерфейс IMessage могут реализовать и другие классы), то для подобного преобразования необходима операция приведения типов. И если мы хотим обратиться к методам класса Message, которые не определены в интерфейсе IMessage, но являются частью класса Message, то нам надо явным образом выполнить преобразование типов:
if (hello is Message someMessage) someMessage.Print();
interface (справочник по C#)
Интерфейс определяет контракт. record Любой class или struct реализующий этот контракт должен предоставлять реализацию элементов, определенных в интерфейсе. Интерфейс может определить реализацию по умолчанию для членов. Он также может определять члены static , чтобы обеспечить единую реализацию для общих функциональных возможностей. Начиная с C# 11 интерфейс может определить static abstract или static virtual члены, чтобы объявить, что тип реализации должен предоставлять объявленные члены. Как правило, методы объявляют, static virtual что реализация должна определять набор перегруженных операторов.
В следующем примере класс ImplementationClass должен реализовать метод с именем SampleMethod , не имеющий параметров и возвращающий значение void .
Дополнительные сведения и примеры см. в разделе Интерфейсы.
Пример интерфейса
interface ISampleInterface < void SampleMethod(); >class ImplementationClass : ISampleInterface < // Explicit interface member implementation: void ISampleInterface.SampleMethod() < // Method implementation. >static void Main() < // Declare an interface instance. ISampleInterface obj = new ImplementationClass(); // Call the member. obj.SampleMethod(); >>
Интерфейс может быть членом пространства имен или класса. Объявление интерфейса может содержать объявления (сигнатуры без реализации) следующих членов.
Члены интерфейса по умолчанию
Эти предыдущие объявления элементов обычно не содержат текст. Элемент интерфейса может объявить текст. Органы-члены в интерфейсе — это реализация по умолчанию. Члены с телом позволяют интерфейсу предоставлять реализацию по умолчанию для классов и структур, которые не предоставляют реализацию с переопределением. Интерфейс может включать:
- Константы
- Операторы
- Статический конструктор
- Вложенные типы
- Статические поля, методы, свойства, индексаторы и события
- Объявления членов с помощью явного синтаксиса реализации интерфейса.
- Явные модификаторы доступа (доступ по умолчанию — public ).
Статические абстрактные и виртуальные члены
Начиная с C# 11 интерфейс может объявлять static abstract и static virtual члены для всех типов элементов, кроме полей. Интерфейсы могут объявлять, что реализация типов должна определять операторы или другие статические члены. Эта функция позволяет универсальным алгоритмам указывать поведение, подобное числу. Примеры можно увидеть в числовых типах во время выполнения .NET, например System.Numerics.INumber . Эти интерфейсы определяют общие математические операторы, реализованные многими числовыми типами. Компилятор должен разрешать вызовы static virtual и static abstract методы во время компиляции. Методы static virtual , static abstract объявленные в интерфейсах, не имеют механизма диспетчеризации среды выполнения, аналогичного virtual методам или abstract объявленным в классах. Вместо этого компилятор использует сведения о типе, доступные во время компиляции. static virtual Поэтому методы почти исключительно объявляются в универсальных интерфейсах. Кроме того, большинство интерфейсов, объявляющих или методы, static virtual объявляют, что один из параметров типа должен реализовать объявленный интерфейс. static abstract Например, интерфейс объявляет, INumber что T должен реализовываться INumber . Компилятор использует аргумент типа для разрешения вызовов методов и операторов, объявленных в объявлении интерфейса. Например, int тип реализует INumber . Когда параметр T типа обозначает аргумент int типа, вызываются члены, static объявленные в int ней. Кроме того, если double аргумент типа является аргументом типа, вызываются члены, static объявленные в типе double .
Диспетчеризация static abstract методов и static virtual методов, объявленных в интерфейсах, разрешается с помощью типа времени компиляции выражения. Если тип среды выполнения выражения является производным от другого типа времени компиляции, будет вызываться статические методы базового типа (время компиляции).
Эту функцию можно попробовать, работая с руководством по статическим абстрактным членам в интерфейсах.
Наследование интерфейса
Интерфейсы не могут содержать состояние экземпляра. Хотя статические поля теперь разрешены, поля экземпляров не допускаются в интерфейсах. Автоматические свойства экземпляра не поддерживаются в интерфейсах, так как они неявно объявляют скрытое поле. Это правило оказывает незначительное воздействие на объявления свойств. В объявлении интерфейса следующий код не объявляет автоматически реализованное свойство, как это делается в class или struct . Вместо этого он объявляет свойство, которое не имеет реализации по умолчанию, но должно быть реализовано в любом типе, реализующем интерфейс.
public interface INamed < public string Name >
Интерфейс может наследовать от одного или нескольких базовых интерфейсов. Когда интерфейс переопределяет метод, реализованный в базовом интерфейсе, он должен использовать синтаксис явной реализации интерфейса.
Если список базовых типов содержит базовый класс и интерфейсы, базовый класс должен стоять первым в списке.
Класс, реализующий интерфейс, может явно реализовывать члены этого интерфейса. Явным образом реализованный член нельзя получить через экземпляр класса, но только через экземпляр интерфейса. Кроме того, обращение к членам интерфейса по умолчанию можно осуществлять только через экземпляр интерфейса.
Дополнительные сведения о явной реализации интерфейса см. в статье Явная реализация интерфейса.
Пример реализации интерфейса
В следующем примере показана реализация интерфейса. В этом примере интерфейс содержит объявление свойства, а класс содержит реализацию. Любой экземпляр класса, который реализует IPoint , имеет целочисленные свойства x и y .
interface IPoint < // Property signatures: int X < get; set; >int Y < get; set; >double Distance < get; >> class Point : IPoint < // Constructor: public Point(int x, int y) < X = x; Y = y; >// Property implementation: public int X < get; set; >public int Y < get; set; >// Property implementation public double Distance => Math.Sqrt(X * X + Y * Y); > class MainClass < static void PrintPoint(IPoint p) < Console.WriteLine("x=, y=", p.X, p.Y); > static void Main() < IPoint p = new Point(2, 3); Console.Write("My Point: "); PrintPoint(p); >> // Output: My Point: x=2, y=3
Спецификация языка C#
См. также
- Ключевые слова в C#
- Ссылочные типы
- Интерфейсы
- Использование свойств
- Использование индексаторов
Совместная работа с нами на GitHub
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
Интерфейсы в C#
У тех, кто только начинает осваивать C# часто возникает вопрос что такое интерфейс и зачем он нужен.
Сначала о том, что можно найти по первой же ссылке в поисковике. В большинстве статей смысл интерфейса разъясняется как «договор» о том, что должен содержать класс, какие свойства и методы. Например у нас есть интерфейс:
public interface IForecast < int GetForecast(int value); int Size < get; set; >>
Соответственно, если какой-то класс реализует данный интерфейс, то он должен содержать реализацию метода int GetForecast(int value) и свойство Size.
Практическая ценность такого договора минимальная. Если выкинуть интерфейс из проекта, то все будет замечательно работать и без него. Разве что вы захотите полностью переписать логику класса и тогда интерфейс поможет вам не забыть про необходимые методы.
Подводя итог этой части, если какой-то интерфейс реализуется только в одном единственном классе, то не тратьте время на него. Он просто не нужен.
Дополнение
Сразу после публикации на меня обрушили море критики, сначала я пытался отвечать, но потом понял, что смысла нет. Все критики упирают на то, что я написал в предыдущем абзаце и пытаются приводить сложные примеры проектов где много классов, всякие ссылки туда-сюда, поддержка командой и прочее. Я наверно был не прав, что так коротко подвел итог вступления. Но суть в том, что интерфейс нужен далеко не всегда. Если у вас ваш личный (или просто не очень большой) проект, который не планируется масштабировать, на который ни кто не ссылается и, самое главное, который успешно работает, то введение в проект интерфейсов ничего не изменит. И не надо придумывать истории, что где-то кто-то однажды реализовал интерфейс и обрел бессмертие. Если бы интерфейс был нужен везде, он был бы неотъемлемой частью класса.
Это статья о том, как можно использовать интерфейс не для связки между проектами, а внутри одного проекта. По этому поводу критики пока еще не было.
Конец дополнения
К счастью возможности интерфейса намного интереснее. Интерфейс может задать общий признак для разнородных объектов, а это открывает огромные возможности по части гибкости кода.
Например, предположим у нас есть два класса:
class First
class Second
Это два совсем разных класса, но пусть у них будет что-то общее, т.е. мы хотим, чтобы один метод работал с ними обоими без дополнительной логики. В таком случае мы можем реализовать общий интерфейс, т.е. объявить эти классы как
class First : IForecast
class Second : IForecast
Теперь мы можем сделать общий метод для них:
void AnyMethod(IForecast anyClass)
Как видно, переменная value получит значение функции GetForecast от того класса, который будет передан в качестве параметра без дополнительных действий по приведению типов и т.п.
Другой пример, мы не знаем заранее какой класс нам потребуется в ходе вычислений, однако нам надо объявить экземпляр этого класса до начала работы. В такой случае можно объявить экземпляр класса, реализующего интерфейс:
. IForecast any; … А потом: if(. ) any = new First(); else any = new Second();
Можно пойти еще дальше и объявить массив (или list) таких объектов:
var array = new IForecast[2]; array[0] = new First(); array[1] = new Second();
А потом можно например вычислить сумму всех свойств Size:
var summ = 0; foreach (var forecast in array)
У объявления через интерфейс есть один недостаток: вам будут доступны только методы и свойства, объявленные в интерфейсе и самом классе. Если ваш класс унаследован от базового, то для доступа к его методам придется делать приведение типа:
public class BaseClass < public int GetValue(int value) < return value * 2; >> public class Second: BaseClass, IForecast IForecast frc; frc = new Second(); var frcBase = (BaseClass) frc; var result = frcBase.GetValue(45);
Еще один фокус с интерфейсам можно провернуть используя возможность реализовать в одном классе два интерфейса с одинаковыми по сигнатуре, но разными по содержанию методами. Я не знаю какая от этого может быть практическая польза кроме уже упомянутой, но если вдруг вам не хватает фантазии придумывать имена функций, то можете вынести их в интерфейсы.
Например добавим в наш проект еще один интерфейс:
public interface IForecast2
И создадим новый класс, наследующий оба интерфейса:
public class Third: IForecast, IForecast2 < int IForecast.GetForecast(int value) < return value + Size; >public int Size < get; set; >int IForecast2.GetForecast(int value) < return 2 * value + Size; >>
Это называется явной реализацией интерфейса. Теперь можно построить такую конструкцию:
var third = new Third ; var v1 = ((IForecast) third).GetForecast(100); var v2 = ((IForecast2)third).GetForecast(100); Console.WriteLine(v1+v2);
Интересно, что при реализации таких интерфейсов методам не могут быть назначены модификаторы доступа (“public” или что-то еще), однако они вполне доступны как публичные через приведение типа.
Как видно, это работает, но выглядит слишком громоздко. Чтобы как-то улучшить код, можно написать несколько иначе:
var third = new Third ; var bad = (IForecast) third; var good = (IForecast2) third; var v1 = bad.GetForecast(100); var v2 = good.GetForecast(100); Console.WriteLine(v1+v2)
bad и good будут ссылаться на third, но иметь в виду разные интерфейсы. Привидение можно сделать один раз, а его результат потом использовать многократно. Возможно, что в некоторых случаях это сделает код более читаемым.
Еще одно применение интерфейса — быстрое изготовление заглушек для функций.
Например, вы в команде строите большой проект и ваш класс зависит от класса, который пишет коллега. Но еще не написал. Мудрый начальник подготовил интерфейсы всех основных классов еще на первом этапе разработки проекта и вы теперь можете не ждать коллегу, а реализовать класс-заглушку для своего класса. Т.е. временный класс не будет содержать логики, а будет только делать вид что работает: принимать вызовы и возвращать адекватные значения. Для этого надо только реализовать тот же интерфейс, что получил ваш коллега.
Работающий проект со всеми примерами из этой статьи можно посмотреть здесь: ссылка
Зачем придумали интерфейсы?
В интернете много статей на тему интерфейсов, что это такое и как их реализовывать. Но я не нашел внятного ответа кто и зачем их придумал? Я только начинаю изучать C# и вообще не вижу смысла в интерфейсах. Классы удобны например в ситуации когда нам нужно в игре создать много врагов, нам не надо каждому врагу прописовать параметры, можно все задать в классе. Вот пример с метанина:
interface IMovable
Реализация в классе:
// применение интерфейса в классе class Person : IMovable < public void Move() < Console.WriteLine("Человек идет"); >> // применение интерфейса в структуре struct Car : IMovable < public void Move() < Console.WriteLine("Машина едет"); >>
Но если я просто удалю интерфейс и сделаю вот так
class Person < public void Move() < Console.WriteLine("Человек идет"); >>
Код по прежнему будет работать.
Вот и вопрос зачем все это нужно?
Я просто не могу представить себе ситуацию где бы было необходимо использовать интерфейсы.
В C#8 добавили реализацию метода по умолчанию в интерфейсе тем самым сделав из интерфейса недо-класс.
Смотрю видео на ютубе и там все используют интерфейсы но не говорят зачем, просто как будто так и надо.
Не спроста же все их используют? Но без понимания зачем все это нужно у меня просто не получается найти смысл их применения и понять как работает чужой код когда в нем есть интерфейсы.
Единственный смысл в них вижу в том что не нужно писать документацию к классам, просто наследуем от интерфейса а дальше пусть уже другой программист лезет в код и смотрит чего там и как.
Ни в коем случае не хочу сказать что я тут самый умный а все остальные ошибались, просто хочу разобраться почему придумали интерфейсы если и без них все хорошо работает?
Извините что так сумбурно написал, сам на эмоциях, неделю уже читаю статьи по теме и ни как не могу осилить.
Отслеживать
4,972 1 1 золотой знак 9 9 серебряных знаков 27 27 бронзовых знаков
задан 17 янв 2020 в 23:07
User12351259599491 User12351259599491
311 4 4 серебряных знака 17 17 бронзовых знаков
+1 за желание разобраться.
– user176262
17 янв 2020 в 23:11
Дополню небольшим примером из видео о том как сделать парсер сайта: Человек создает интерфейс с разными полями в частности там есть поле string BaseUrl < get; set; >и в классе он пишет реализацию public string BaseUrl < get; set; >= «https://habrahabr.ru»; У меня просто в этот момент происходит крик в голове «Зачем ты создал этот интерфейс если все равно в классе ты прописал тоже самое. » Это же просто лишний код, который будет путать людей если они не очень хорошо знают c#.
17 янв 2020 в 23:45
На этот вопрос отвечали много раз, используйте поиск. Если вы захотите составить список всех объектов, которые могут двигаться: машин, людей, галактик. какой у него будет тип?
17 янв 2020 в 23:46
см. видео 1 и 2
18 янв 2020 в 0:06
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
Есть как минимум 4 причины для использования интерфейсов. Их может и больше, но я текст пишу из головы, потому остальные причины, если такие есть, загуглите или додумаете сами.
Давайте начнем с того, как строятся большие и сложные системы. Основной механизм построения чего то большого — это разделение этого большого на малые части, определения того, как эти части взаимодействуют, и после этого программирование каждой из частей.
Ключевой момент здесь в том, что вы не можете сразу продумать все эти мелкие части и сазу написать все классы для них.
Давайте возьмем типичный пример. Вы пишете свою игру и вам надо предусмотреть сохранение вашей игры. Вы точно знаете, что именно вам надо сохранить, но вы ещё не знаете, куда вы будете сохранять, в файл или в БД или передавать данные на сервер. Что делать в такой ситуации?
В такой ситуации вам поможет интерфейс. Например, у вас есть класс, который вам надо сохранить.
public class MyGameState < //. my game data >
Напишем для него интерфейс сохранения
public interface IGameStore
Что тут произошло? Вашего сохранения ещё в природе нет, кода для него нет, какой это будет класс, какая у класса-хранилища будет иерария наследования, ничего не известно, но вы уже определили, как ваша игра будет взаимодействовать с этим, ещё не существующим классом. Вы можете этот класс написать позже, или его может написать любой другой программист и все будет работать, так как контракт вашего будушего класса уже известен — вы уже определили с помощью интерфейса все нужные для класса методы.
Другими словами, интерфейс помогает определять взаимодействие между модулями вашей программы, даже если эти модули ещё не существуют.
Но тут вы можете возразить мне и сказать — что вы с таким же успехом можете определить класс для сохранения, например
public absract class GameStore
Да, это верно, вы можете определить абстрактный класс. В таком случае, для реализации сохранения, вам надо будет унаследоваться от абстрактрого класса и реализовать нужные методы. Но давайте подумаем, какие ограничения это накладывает на наш класс сохранения? А вот какие — наш класс сохранения должен будет быть унаследован именно от указанного абстрактного класса и больше ни от кого. Вы можете возразить — мол, в случае интерфейса будет то же самое — ведь класс сохранения должен его реализовать. Но вот и нет — в C# реализация интерфейса никак не ограничивает класс, так как класс может реализовать сколько угодно интерфейсов. А вот наследование от абстрактного класса — уже дело другое, ведь вы не можете насловаться от 2 классов. Отсюда вторая причина использования интерфйсов — Использование интерфйсов позволяет обойти ограничения множественного наследования.
Окей, ну, допустим, мы решили, что мы будем сохранять нашу игру, но для сохранения нашей игры, нам обязательно надо знать имя текушего игрока — то есть его никнейм. Это требование леко выразить, добавив в наш абстрактный класс конструктор, который принимает имя игрока, например (код может не копилироваться, я его практически на телефоне пишу, но идея должна быть ясна)
public absract class GameStore < protected string PlayerNamepublic GameStore(string playerName) < PlayerName = playerName; >public abstract void SaveGame(MyGameState state); public abstract MyGameState Load(); >
Теперь посмотрите что произошло. Мы не только заставляем классы для сохранения игры наследоваться от нашего абстрактного класса, но ещё и накладываем на них определенные огреничения. Интересны ли эти ограничения остальной части игры? Думаю, нет. В таком случае, зачем остальной части игры знать об этих огреничениях? Нет никаких причин для этого. Таким образом, Используя интерфейс, вы декларируете желаемое поведение. Используя класс — вы декларируете детали реализации. То есть, когда у вас был интерфейс в игре, вы декларировали, что «не важно что тут будет за объект, не важно как он будет написан, но мне надо чтобы он мог сохранять и загружать состояние игры» — то есть вам было важно, что объект может делать, при этом не важно, как он это делает. В этом суть инкапсуляции. Когда же вы используете класс, особенно если класс содержит какие либо посторонние детали, вы уже покажываете, что вам важно не только что объект может делать, но и также кем объект является и какие у объекта есть детали реализации.
А теперь давайте посмотрим на это с точки зрения игры. Игре, на самом деле, не интересно знать, как и куда будет происходить сохранение. Игре не интересны детали сохранения. Игре не интересен даже факт — сущестует ли уже написанный класс сохранения. Все, что игре надо — это получить объект, который содержит нужные игре методы. Всё. При таком подходе связь между игрой и реальным классом сохранения лежит только через интерфейс, что является связью гораздо более слабой. Сам класс может быть написан как угодно, содержать какие угодно огранчения или детали реализации, эти ограничения и детали могут меняться и быть переписаны любое количество раз и все будет работать, пока класс сохранения реализует интерфейс. Таким образом, связь классов через интерфейс является более слабой связью, чем связь классов через абстрактный класс или напрямую, откуда следует выводы:
- Если вы хотите держать свои модули слабо связанными (а вы должны хотеть), используйте для связи интерфейсы
- При использовании интерфейов для связи, на них ложится бремя совместимости — интерфейсы должны меняться как можно реже
- Так как интерфейсы должны меняться как можно реже, написание интерфейса становится задачай более ответственной, чем написание класса. Класс, закрытый интерфейсом, всегда можно переписать. Интерфейс же, используемый во многих компонентах, переписать возможно не всегда, при этом есть большой риск потери обратной совместимости с уже написанным до этого кодом.