Урок № 24. Интерфейсы и контракты


Интерфейсы и контакты

Доброго времени суток! В этом уроке мы поговорим об интерфейсах в C#. Я доступными словами постараюсь рассказать, что же это такое, зачем они нужны, когда и как их использовать в своих программа. Давайте вспомним примеры из двух предыдущих уроков, когда мы создали иерархию классов, представляющих воображаемое оружие:

//Базовый класс, описывающий оружие
class Weapon
{
    //Атака оружием
    public virtual void Attack()
    {
        Console.WriteLine("Weapon Attack!");
    }
}

//Класс-наследник оружия, описывает нож
class Knife : Weapon
{
    //Атака ножом
    public override void Attack()
    {
        Console.WriteLine("Knife Attack!");
    }
}

//Класс-наследник оружия, описывает ружьё
class Gun : Weapon
{
    //Атака ружьём
    public override void Attack()
    {
        Console.WriteLine("Gun Attack!");
     }
}

Как видно, на вершине иерархии находится класс «Weapon», описывающий некое базовое, безликое оружие, а вот его наследники, классы «Knife» и «Gun» уже обладают определенной конкретикой. И какая же получается ситуация, на практике, мы может создать объект класса «Weapon» и «атаковать» с помощью него, т.е. вызвать метод «Attack», но подумайте сами, какая же будет «атака», если класс описывает некое безликое оружие? Не понятно какая будет «атака». И по сути, класс «Weapon» нужен нам не для использования его объектов, а для использования его в качестве базового класса для других, уже наделенных конкретикой классов. И самое ценное в нем, это – набор его виртуальных методов (в данном случае, одного метода). Т.е. нам важно, чтобы все классы наследники, обладали общим «поведением», обязательно имели необходимый набор открытых методов. Вот для этой цели, в C# принято использовать интерфейсы, т.е. интерфейс используется вместо базового класса (сейчас я покажу это на практике, и Вы поймете, что к чему).

//Интерфейс, представляющий абстрактное оружие
interface IWeapon //Именовать интерфейсы принято начиная с букву I
{
    //Атака
    void Attack(); //Объявлен метод, но тела метода нет
}

//Класс-наследник оружия, описывает нож
class Knife : IWeapon
{
    //Атака ножом
    public void Attack()
    {
        Console.WriteLine("Knife Attack!");
    }
}

//Класс-наследник оружия, описывает ружьё
class Gun : IWeapon
{
    //Атака ружьём
    public void Attack()
    {
        Console.WriteLine("Gun Attack!");
    }
}

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

interface [Название_интерфейса]
{
    //Методы, свойства, но без реализации, 
    //и никаких полей
}

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

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

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

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

Можно сказать, что класс наследник, подписывается на некий контракт, который представлен интерфейсом. Например, класс «Knife» обязан реализовать метод «Attack», подписавшись на интерфейс «IWeapon», но после этого его объекты будут считаться полноправным оружием.

Давайте теперь модернизируем метод «Main» из предыдущего урока, с учетом использования интерфейса «IWeapon» вместо класса «Weapon».

static void Main(string[] args)
{
    //Создаем два объекта, по одному для каждого класса
    Knife knife = new Knife();
    Gun gun = new Gun();

    //А вот так, cделать уже нельзя!
    /*
     * IWeapon weapon = new IWeapon();
     */

    //Временная ссылка базового типа (на текущее оружие)
    IWeapon currentWeapon = knife; //По умолчанию на нож

    //Сообщение пользователю
    string message = "Используемые клавиши: \nEnter - Атака \n1 - Нож\n2 - Ружьё\nEsc - Выход";

    //Вывод сообщения
    Console.WriteLine(message);
   
    //Для хранения введенного пользователем кода клавиши
    ConsoleKeyInfo currentKey = new ConsoleKeyInfo();

    //Пока пользователь не нажал на клавишу Esc
    while (currentKey.Key != ConsoleKey.Escape)
    {
        //Получить код нажатой клавиши
        currentKey = Console.ReadKey();

        //В зависимости от кода
        switch (currentKey.Key)
        {
            //Пользователь нажал клавишу "1"
            case ConsoleKey.D1:
                //Сменить оружие
                currentWeapon = knife;
                Console.WriteLine(".Текущее оружие: нож");
            break;

            //Пользователь нажал клавишу "2"
            case ConsoleKey.D2:
                //Сменить оружие
                currentWeapon = gun;
                Console.WriteLine(".Текущее оружие: ружьё");
            break;

            //Пользователь нажал на клавишу Enter
            case ConsoleKey.Enter:
                //Огонь!
                currentWeapon.Attack();
            break;
        }
    }
}

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

Перейти к следующему уроку