Урок 21. Интерфейсы C#


Двадцать первый урок учебника по объектно-ориентированному программированию на языке C# завершает исследование полиморфизма в языке программирования C#. Эта статья описывает использование интерфейсов для определения необходимых членов в классах, которые их реализуют.

Что такое интерфейс?

Ранее в учебнике по объектно-ориентированному программированию на языке C# термин interface использовался для описания открытого интерфейса класса. Открытый интерфейс содержит методы, свойства, индексаторы и другие элементы, которые могут быть доступны другим классам. В этой статье рассматривается второе, связанное с этим определение интерфейса.

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

Многие классы могут реализовать один интерфейс. Это увеличивает полиморфизм, позволяя классам вести себя по-разному в зависимости от их использования. Например, класс Customer, класс Employee и бизнес-класс могут все реализовать интерфейс, допускающий запись. Если этот интерфейс содержит свойства, описывающие адрес, то для отправки письма любому из этих типов сущностей можно использовать метод, принимающий записываемый объект в качестве параметра. Примечание: префикс "I" для интерфейса является признанным соглашением об именовании.

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

Когда выбрать интерфейс или абстрактный класс

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

Создание интерфейсов

Чтобы продемонстрировать использование интерфейсов, мы создадим несколько и применим их к группе классов. В этой статье мы определим интерфейсы и классы, которые представляют хищников и хищных животных. Каждый класс animal будет реализовывать по крайней мере один из интерфейсов IPredator и IPrey.

Для начала создайте новое консольное приложение с именем "InterfacesDemo".

Объявление интерфейса

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

интерфейс-имя интерфейса {}
Первый интерфейс, который мы создадим, будет реализован всеми животными, которые являются добычей хищников. Чтобы использовать Соглашение для именования интерфейса, этот интерфейс будет называться "IPrey". Чтобы создать интерфейс, добавьте новый файл интерфейса в проект с именем "IPrey". Если предпочитаемая среда разработки не содержит шаблона для интерфейсов, просто добавьте файл класса и измените объявление класса в соответствии с приведенным ниже:

interface IPrey
{
}
Создание свойства интерфейса

Когда свойство добавляется к интерфейсу, оно действует только как заполнитель, не содержащий собственной функциональности. Функциональность добавляется отдельными классами, реализующими интерфейс. Поэтому объявления свойств интерфейса сопоставимы с абстрактными объявлениями свойств и используют аналогичный синтаксис метода доступа get и set. Однако, поскольку все члены интерфейсов являются открытыми по определению, ключевое слово "public" опущено.

В следующем коде показаны примеры свойств интерфейса "только для чтения-записи", "только для чтения" и " только для записи:

int ReadWriteProperty { get; set; }
int ReadOnlyProperty { get; }
int WriteOnlyProperty { set; }
Интерфейс IPrey определяет свойство для удержания убегающей скорости хищных животных. Это важная ценность, когда животное должно убежать от хищника. Чтобы создать свойство, добавьте следующий код в интерфейс IPrey:

int FleeSpeed { get; set; }
Создание метода интерфейса

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

Интерфейс IPrey будет включать в себя метод, вызываемый, когда животное пытается убежать от хищника. Этот метод будет называться "Flee" и не потребует никаких параметров. Чтобы создать его, добавьте в интерфейс следующий код:

void Flee();
Создание интерфейса IPredator

Интерфейс IPrey теперь завершен. Мы также создадим второй интерфейс для представления хищных животных. Этот интерфейс будет определять свойство и метод. Свойство будет содержать скорость атаки всех животных, реализующих интерфейс IPredator.

Метод интерфейса, "атака" будет вызван, когда хищник хочет атаковать другое существо. Этот метод будет принимать один параметр, содержащий хищное животное, на которое будет совершено нападение. Так что любое животное-жертва может быть атаковано, параметр будет иметь тип IPrey. Это использование полиморфизма означает, что объект любого класса, реализующий IPrey, будет иметь допустимое значение параметра.

Чтобы создать второй интерфейс, создайте новый файл интерфейса с именем IPredator и добавьте следующий код:

interface IPredator
{
    int AttackSpeed { get; set; }
 
    void Attack(IPrey prey);
}
Реализация интерфейса

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

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

В нашем примере мы создадим класс для кошек. Класс будет реализовывать интерфейс IPredator. Чтобы создать класс, добавьте новый файл класса в проект и назовите его "Cat". Измените определение класса, чтобы использовать интерфейс следующим образом:

class Cat : IPredator
{
}
Поскольку класс Cat не реализует все члены интерфейса IPredator, код в настоящее время не является допустимым. Попытки компиляции программы приведут к двум ошибкам, выделяющим отсутствующий метод и свойство.

Реализация элементов интерфейса

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

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

Чтобы реализовать элементы интерфейса и создать метод Cat-specific "Purr", измените код класса следующим образом:

class Cat : IPredator
{
    private int _attackSpeed;
 
    public int AttackSpeed
    {
        get { return _attackSpeed; }
        set { _attackSpeed = value; }
    }
 
    public void Attack(IPrey prey)
    {
        if (_attackSpeed > prey.FleeSpeed)
            Console.WriteLine("Caught prey");
        else
            Console.WriteLine("Prey escaped");
    }
 
    public void Purr()
    {
        Console.WriteLine("Cat purred");
    }
}
Обратите внимание на тип параметра метода атаки. Объект prey может быть любого класса, который реализует IPrey. Поскольку свойство FleeSpeed является частью интерфейса IPrey, значение может быть считано и сравнено со скоростью атаки кошки, чтобы определить, ловит ли кошка свою добычу.

Подобный класс может быть создан продемонстрировать интерфейс IPrey. Этот класс будет представлять собой рыбу. Добавьте в проект новый файл класса с именем "Fish" и скопируйте его в следующем коде:

class Fish : IPrey
{
    private int _fleeSpeed;
 
    public int FleeSpeed
    {
        get { return _fleeSpeed; }
        set { _fleeSpeed = value; }
    }
 
    public void Flee()
    {
        Console.WriteLine("Fish fleeing");
    }
}
Тестирование классов

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

static void Main(string[] args)
{
    Cat tabby = new Cat();
    tabby.AttackSpeed = 10;
 
    Fish bubbles = new Fish();
    bubbles.FleeSpeed = 12;
 
    tabby.Purr();
    tabby.Attack(bubbles);
}
 
/* 
 
Cat purred
Prey escaped
 
*/
Используя методы полиморфизма, можно объявить переменную, используя интерфейс в качестве ее типа. Хотя экземпляр интерфейса не может быть создан, такая переменная может быть назначена объекту любого типа, который реализует интерфейс. Это означает, что предыдущий пример можно переписать, как показано ниже. Однако, поскольку интерфейс для хищников не содержит метода "мурлыканье", он становится недоступным и должен быть удален.

static void Main(string[] args)
{
    IPredator tabby = new Cat();
    tabby.AttackSpeed = 10;
 
    IPrey bubbles = new Fish();
    bubbles.FleeSpeed = 12;
 
    tabby.Attack(bubbles);
}
Реализация нескольких интерфейсов

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

Чтобы объявить класс, реализующий более одного интерфейса, список интерфейсов добавляется к объявлению класса. Список разделяется запятыми и предваряется двоеточием (:). Если класс также наследуется от базового класса, список интерфейсов должен отображаться после имени родительского класса.

В нашем примере мы объявили класс для fish и реализовали интерфейс IPrey. Конечно, рыба тоже может быть хищником, поэтому мы можем разумно реализовать интерфейс IPredator для этого класса. Чтобы добавить интерфейс к классу Fish, измените объявление для этого класса следующим образом:

class Fish : IPrey, IPredator
Теперь, когда класс Fish должен реализовать интерфейс IPredator, соответствующие члены должны быть объявлены до компиляции класса. Добавьте в класс следующее свойство и метод:

private int _attackSpeed;
 
public int AttackSpeed
{
    get { return _attackSpeed; }
    set { _attackSpeed = value; }
}
 
public void Attack(IPrey prey)
{
    if (_attackSpeed > prey.FleeSpeed)
        Console.WriteLine("Caught prey");
    else
        Console.WriteLine("Prey escaped");
}
Теперь мы можем изменить Основной метод программы, чтобы проверить использование рыбы как хищник и добыча. Обратите внимание на использование полиморфизма класса Fish, поскольку он действует как объект IPredator для атакующего, так и объект IPrey для целевого параметра prey.

static void Main(string[] args)
{
    IPredator shark = new Fish();
    shark.AttackSpeed = 30;
 
    IPrey ray = new Fish();
    ray.FleeSpeed = 15;
 
    shark.Attack(ray);
}
 
/* 
 
Caught prey
 
*/
Явная реализация интерфейса

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

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

int IPredator.AttackSpeed
{
    get { return _attackSpeed; }
    set { _attackSpeed = value; }
}
 
void IPredator.Attack(IPrey prey)
{
    if (_attackSpeed > prey.FleeSpeed)
        Console.WriteLine("Caught prey");
    else
        Console.WriteLine("Prey escaped");
}
Одно из ключевых преимуществ явной реализации проявляется, когда класс реализует несколько интерфейсов. Если несколько интерфейсов включают соответствующее объявление члена, это приведет к неоднозначности в классе, использующем неявную реализацию. Поскольку явный синтаксис именует унаследованный интерфейс, неоднозначность удаляется.

Явно реализованные члены становятся частично закрытыми. В Примере Cat, если создается объект Cat, элементы интерфейса недоступны. Свойство и метод становятся доступными только в том случае, если объект Cat отображается в переменной IPredator. Таким образом, следующий код является недопустимым:

Cat c = new Cat();
c.AttackSpeed = 30;
Наконец, поскольку члены являются частными в классе Cat, они не доступны для наследования подклассов. Это означает, что недопустимо пометить такой элемент как виртуальный или абстрактный.

Наследование интерфейса

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

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

Для демонстрации наследования интерфейса мы создадим новый интерфейс для всех животных. IPrey и IPredator будут настроены так, что они унаследуют от этого нового, "IAnimal" интерфейс. Для начала добавьте новый файл интерфейса с именем "IAnimal". Добавьте одно свойство к интерфейсу, чтобы удерживать имя животного.

interface IAnimal
{
    string Name { get; set; }
}
Чтобы указать, что интерфейсы IPrey и IPredator являются потомками IAnimal, измените объявления для этих двух интерфейсов следующим образом:

// IPrey.cs
 
interface IPrey : IAnimal
{
    ...
}
 
 
// IPredator.cs
 
interface IPredator : IAnimal
{
    ...
}
Теперь, когда интерфейсы наследуются от IAnimal, классы Cat и Fish также должны реализовать члены IAnimal, прежде чем программа может быть скомпилирована. Чтобы объявить свойство неявно, добавьте в оба класса следующий код:

private string _name;
 
public string Name
{
    get { return _name; }
    set { _name = value; }
}
Теперь интересен выбор типа данных для переменных животных. Классы Cat и Fish могут быть инстанцированы и назначены переменным IAnimal или IPredator. При использовании в качестве объекта IAnimal будет присутствовать только свойство Name. При использовании в качестве объекта IPredator члены из интерфейсов IAnimal и IPredator доступны, но другие члены класса невидимы.

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

Fish bubbles = new Fish();
IAnimal animal = bubbles;
IPredator predator = bubbles;
IPrey prey = bubbles;
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегистатьи IT, си шарп, ООП




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



Как улучшить настроение
Правка заливщика фото на facebook, или что общего с парсингом
Java: наибольшее число, оканчивающееся на 0