Урок № 25. Реализация нескольких интерфейсов


Реализация нескольких интерфейсов

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

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

/*
* Интерфейс, представляющий скрываемые предметы,
* т.е. предметы, которые можно скрыть (спрятать)
*/
interface IRetractable
{
    //Скрывает предмет
    void Hide();
    
    //Показывает предмет
    void Show();

    //Свойство, показывающее виден ли предмет (только для чтения)
    bool IsVisible
    {
        /*
         * Доступен, только метод доступа get,
         * метод set, реализовать будет нельзя
         */
         get;
    }
}

Данный интерфейс, содержит несколько методов и одно свойство (property), причем, свойство заявлено только с методом доступа get (т.е. только для чтения), который внутри интерфейса естественно не содержит реализации. А метода set вообще нет, это значит, что в классах, реализующих интерфейс «IRetractable» его тоже не должно быть.

Так по логике, нож можно спрятать в карман, а вот ружье уже нет, значит класс «Knife» из предыдущего урока должен реализовывать еще и интерфейс «IRetractable», а класс «Gun», соответственно – нет. На практике, это будет выглядеть следующим образом:

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

/*
* Интерфейс, представляющий скрываемые предметы,
* предметы, которые можно скрыть (спрятать)
*/

interface IRetractable
{
    //Скрывает предмет
    void Hide();
    
    //Показывает предмет
    void Show();

    //Свойство, показывающее виден ли предмет (только для чтения)
    bool IsVisible
    {
        /*
         * Доступен, только метод доступа get,
         * метод set, реализовать будет нельзя
         */
         get;
     }
}

//Класс, реализующий интерфейсы IWeapon и IRetractable, описывает нож
class Knife : IWeapon, IRetractable //Реализуемые интерфейсы перечисляются через запятую
{
    //Атака ножом
    public void Attack()
    {
        //Если нож не скрыт
        if (isVisible == true)
        {
            Console.WriteLine("Атака ножом!");
        }
        else
        {
            //Если нож скрыт, атаковать им нельзя
            Console.WriteLine("Сначала нужно достать нож...");
        }
    }
    
    //Скрыть нож
    public void Hide()
    {
        //Скрываем нож
        isVisible = false;
        Console.WriteLine(".Нож скрыт...");
    }

    //Достать нож
    public void Show()
    {
        //Показываем нож
        isVisible = true;
        Console.WriteLine(".Достали нож...");
    }

    /*
     * Уточнить, скрыт или виден нож,
     * возвращает true, если нож виден
     */
    public bool IsVisible
    {
        get
        {
            return isVisible;
        }
    }

    //Закрытое поле, показывающее, виден ли нож
    private bool isVisible = true;
}

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

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

Теперь нужно слегка доработать код метода «Main» из предыдущего урока. Добавим в оператор выбора switch..case, еще одну секцию выбора case, которая будет срабатывать при нажатии на клавишу «0». При нажатии этой клавиши, будет проверяться способность текущего оружия «прятаться», т.е. можно ли его прятать в карман, и если такая возможность есть, то скрывать оружие если оно еще не скрыто, ну или доставать его в противном случае. А проверять возможность оружия «скрываться» мы будем, проверяя реализуется ли классом выбранного оружия интерфейс «IRetractable». На практике, то будет выглядеть примерно так:

//Пользователь нажал клавишу "0"
case ConsoleKey.D0:
{
    //Скрыть/показать оружие
    
    /*
     * Если текущее оружие (точнее объект, на который сейчас ссылается
     * переменная currentWeapon, является объектом класса реализующего
     * интерфейс IRetractable)
     */
    if ((currentWeapon is IRetractable) == true)
    {
        //Это оружие можно скрыть...
        IRetractable tmpWeapon = (IRetractable)currentWeapon;

        //Если оружие не скрыто
        if (tmpWeapon.IsVisible == true)
        {
            //Скрыть его
            tmpWeapon.Hide();
        }
        else
        {
            //Иначе - показать оружие
            tmpWeapon.Show();
        }
    }
    else
    {
        //Данное оружие нельзя скрыть
        Console.WriteLine(".Текущее оружие скрыть нельзя!");
    }
}
break;

Обратите особое внимание на выделенные строки. Выражение «currentWeapon is IRetractable», в операторе if, вернет истину, если объект «currentWeapon» относится к классу, реализующему интерфейс «IRetractable», т.е. является скрываемым объектом. Разберем теперь следующий фрагмент кода:

//Это оружие можно скрыть...
IRetractable tmpWeapon = (IRetractable)currentWeapon;

Означает он примерно следующее: мы создаем переменную типа «IRetractable», по сути, ссылку и заставляем её ссылаться на объект «currentWeapon», но так как объект «currentWeapon» имеет другой тип (на момент выполнения мы точно не знаем какой, но знаем, что другой), нам нужно преобразовать его к типу «IRetractable», т.е. явно указать системе, чтобы она воспринимала его как объект типа «IRetractable». Делается это следующим выражением: «(IRetractable)currentWeapon», где в круглых скобках указывается целевой тип данных, а после указывается объект, который нужно преобразовать к этому типу. Тем самым мы получаем возможность работать с объектом, который «хранится» в «currentWeapon» как с объектом типа «IRetractable». Что дальше и делаем, т.е. вызываем методы и свойства интерфейса «IRetractable».

Обратите внимание, что перед тем как привести объект к какому-либо типу, нужно проверить, возможна ли такая операция, это мы делали в строке кода:

if ((currentWeapon is IRetractable) == true)

Что значит примерно следующее: «Если объект «currentWeapon» можно преобразовать к типу «IRetractable» то …»

Все остальное должно быть понятно. Ниже представлен полный код метода «Main», попробуйте собрать проект, и запустить приложение. Поэкспериментируйте…

Код метода «Main»:

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 - Ружьё\n0 - Скрыть или показать оружие\nEsc - Выход";

    //Вывод сообщения
    Console.WriteLine(message);

    //Для хранения введенного пользователем кода клавиши
    ConsoleKeyInfo currentKey = new ConsoleKeyInfo();

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

        //В зависимости от кода
        switch (currentKey.Key)
        {
            //Пользователь нажал клавишу "0"
            case ConsoleKey.D0:
            {
                //Скрыть/показать оружие
                
                /*
                 * Если текущее оружие (точнее объект, на который сейчас ссылается
                 * переменная currentWeapon, является объектом класса реализующего
                 * интерфейс IRetractable)
                 */
                if (currentWeapon is IRetractable)
                {
                    //Это оружие можно скрыть...
                    IRetractable tmpWeapon = (IRetractable)currentWeapon;
 
                    //Если оружие не скрыто
                    if (tmpWeapon.IsVisible == true)
                    {
                        //Скрыть его
                        tmpWeapon.Hide();
                    }
                    else
                    {
                        //Иначе - показать оружие
                        tmpWeapon.Show();
                    }
                }
                else
                {
                    //Данное оружие нельзя скрыть
                    Console.WriteLine(".Текущее оружие скрыть нельзя!");
                }
            }
            break;
            
            //Пользователь нажал клавишу "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;
        }
    }
}

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