Что такое интерфейс
В бытовом смысле, например, автомобильный интерфейс означает руль, педали, спидометр и тахометр, кнопки, то есть, приборы, через которые водитель взаимодействует с автомобилем. Слово "Интерфейс" и переводится как внешний вид для взаимодействия. Благодаря тому, что руль и педали на всех автомобилях работают одинаково, то есть, разные автомобили имеют одинаковый интерфейс, водителю не нужно переучиваться, чтобы взаимодействовать с другим автомобилем.
Также и в программировании: интерфейс - набор публичных методов, которые видны тем, кто взаимодействует с классом.
Например, для класса
интерфейс будет
Потому что там 3 публичных метода с вот таким описанием.
Зачем нужны интерфейсы
Для чего это нужно? Мы можем заменить один класс на другой и тот код, который с ним работает, не придётся переписывать, если интерфейс останется тем же самым. Например, мы пишем большую программу, которая работает с базой данных. Если всю работу с базой данных мы вынесем в отдельный класс, то мы сможем сменить одну базу данных на другую, просто заменив этот класс и не трогая весь остальной код. А чтобы ничего не сломалось, мы вынесем все публичные методы этого класса в интерфейс и во всём остальном коде будем использовать только интерфейс.
Переменная databaseHelper имеет тип IDatabaseHelper, то есть, мы имеем доступ только к тем методам, которые есть в интерфейсе. Если какой-то метод класса PostgresHelper отсутствует в интерфейсе IDatabaseHelper, то мы не сможем его использовать, потому что по синтаксису переменная имеет тип IDatabaseHelper, а не PostgresHelper. Таким образом, мы будем работать только с теми методами, которые есть в интерфейсе. А если нам понадобится поменять PostgresHelper на какой-нибудь MsSqlHelper, то мы просто унаследуем его от этого же интерфейса и автоматически будем обязаны реализовать все методы, которые там есть:
Если мы забудем хотя бы один метод, будет такая ошибка компиляции.
Более того, все использования переменной databaseHelper останутся как были, потому что она как была типа IDatabaseHelper, так и останется. Не нужно будет менять типы таких переменных или вызовы методов. Надо будет поменять только место её создания:
Принцип абстракции в ООП
Как мы только что увидели, чтобы использовать функционал "подключения" к базе данных PostgresHelper или MsSqlHelper, мы не должны знать, какой код написан внутри них. Нам достаточно знать только публичные методы: например, что у них есть метод ExecuteNonQuery(), что он получает на вход строку и что ничего не возвращает в ответ. Таким образом, мы приходим к четвёртому принципу объектно-ориентированного программирования - принципу абстракции: используя интерфейсы, мы абстрагируемся от реализации методов. Нам достаточно знать только как вызывать публичные методы. То есть, нам достаточно знать только интерфейс. Это также подобно принципу "чёрного ящика": мы знаем, какие у него есть рычажки снаружи, а что внутри - нам неважно. У того же автомобиля, чтобы его водить, достаточно уметь использовать руль и педали и совсем не нужно знать детали его устройства. Точно так же и в программировании.
Ещё про интерфейсы
Интерфейс описывает публичные методы класса, поэтому все методы интерфейса будут public. Интерфейс не содержит никакой логики, а только внешность - как называются публичные методы, какие аргументы они принимают на вход и какой тип данных возвращают. Поэтому все методы интерфейса будут abstract. Поскольку в интерфейсе все методы public abstract, то эти ключевые слова не пишутся.
Классы могут наследоваться от интерфейсов (говорят - "реализовывать интерфейсы") точно так же, как от обычных классов. При этом класс должен либо реализовать все публичные методы интерфейса, либо оставить какие-то методы нереализованными (то есть, с ключевым словом abstract) и сам стать абстрактным.
Поскольку свойства в C# - это, по сути, два метода (геттер и сеттер), то свойства применительно к интерфейсам рассматриваются как методы.
Для примера выделим из класса BaseAnimal ("базовое животное") два интерфейса: INamedObject ("объект, имеющий имя") и IMovableObject ("объект, который можно двигать"):
Один класс может реализовать несколько интерфейсов:
Проблема множественного наследования
А вот наследоваться можно только от одного класса. Это вызвано тем, что в разных классах могут быть свои реализации метода. Например, в Class1 метод Method1() печатает "Class1", а в Class2 тот же самый метод печатает "Class2":
Тогда было бы непонятно, какую именно реализацию метода должен использовать класс-наследник ChildClass, который наследуется и от Class1, и от Class2 одновременно:
В C++ было, что выбирается метод родителя, стоящего ближе к началу, но это очень криво и неочевидно. Поэтому в C#, Java и многих других си-подобных языках множественное наследование запрещено.
Но если наследоваться от интерфейсов (точнее, реализовывать интерфейсы), проблемы не возникнет, ведь интерфейсы не содержат никакой логики. Соответственно, если ChildClass реализует и Interface1, и Interface2, то оба интерфейса всего лишь говорят ему: у тебя должен быть метод Method1(), но не дают его реализаций, которые могли бы конфликтовать между собой. Метод остаётся нереализованным, так что реализовывать его обязан сам ChildClass или его наследник:
Далее
Пишем статические поля и методы. Паттерн Singleton
Оглавление