Шаг 160.
Язык программирования C#. Начала.
Абстрактные классы и интерфейсы. Интерфейсные переменные

    На этом шаге мы рассмотрим назначение, создание и использование таких переменных.

    Хотя интерфейс напоминает класс, но на основе интерфейса объект создать нельзя (хочется верить, что не нужно объяснять, почему этого нельзя сделать). Но существует такое понятие, как интерфейсная переменная.

    Интерфейсная переменная - это переменная, типом которой указано название интерфейса. Особенность и "сила" интерфейсной переменной в том, что интерфейсная переменная может ссылаться на объект любого класса, реализующего данный интерфейс (указанный типом интерфейсной переменной). То есть если имеется класс, который реализует некоторый интерфейс, и мы создали объект такого класса, то ссылку на данный объект можно записать не только в объектную переменную, типом которой указан интерфейс. Правда здесь имеется важное ограничение: через интерфейсную переменную можно получить доступ только к тем методам, свойствам и индексаторам (через индексирование объекта), которые объявлены в интерфейсе.


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

    Рассмотрим пример, в котором используется указанная особенность интерфейсных переменных. Проанализируем программу, представленную ниже.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace pr160_1
{
    // Интерфейс: 
    interface MyInterface {
        // Объявление метода: 
        char getChar(int n);
        // Объявление индексатора: 
        char this[int k] { 
            get;
        }
    }

    // Первый класс, реализующий интерфейс: 
    class Alpha: MyInterface {
        // Закрытое символьное поле: 
        private char symb;
        // Конструктор с символьньм аргументом: 
        public Alpha(char s) {
            // Полю присваивается значение: 
            symb = s;
        }
        // Описание метода: 
        public char getChar(int n) {
            // Результат: 
            return (char)(symb + n);
        }
        // Описание индексатора: 
        public char this[int k] {
            // Аксессор для считывания значения: 
            get {
                // Результат: 
                return getChar(k);
            }
        }
    }

    // Второй класс, реализующий интерфейс: 
    class Bravo: MyInterface {
        // Закрытое текстовое поле: 
        private string text;
        // Конструктор с текстовым аргументом: 
        public Bravo(string t) {
            // Полю присваивается значение: 
            text = t;
        }
        // Описание метода: 
        public char getChar(int k) { 
            return text[k % text.Length];
        }
        // Описание индексатора: 
        public char this[int k] {
            // Аксессор для считывания значения: 
            get {
                // Результат: 
                return getChar(k);
            }
        }
    }

    // Класс с главным методом: 
    class Program
    {
        // Главный метод:
        static void Main()
        {
            // Целочисленная переменная: 
            int m = 5;
            // Интерфейсная переменная:
            MyInterface R;
            // Создается объект класса Alpha и ссылка на него 
            // записывается в интерфейсную переменную:
            R = new Alpha('F');
            // Вызов метода через интерфейсную переменную:
            Console.WriteLine("Символы от \'{0}\' до \'{1}\':", 
                 R.getChar(-m), R.getChar(m)); 
            // Индексирование объекта через 
            // интерфейсную переменную: 
            for(int i = -m; i <= m; i++) {
                Console.Write("|" + R[i]);
            }
            Console.WriteLine("|");
            // Создается объект класса Bravo и ссылка на него 
            // записывается в интерфейсную переменную:
            R = new Bravo("bravo");
            // Вызов метода через интерфейсную переменную:
            Console.WriteLine("Символы от \'{0}\' до \'{1}\':", 
                 R.getChar(0), R.getChar(2 * m + 1)); 
            // Индексирование объекта через 
            // интерфейсную переменную: 
            for(int i = 0; i <= 2 * m + 1; i++) {
                Console.Write("|" + R[i]);
            }
            Console.WriteLine("|");
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

    Результат выполнения программы следующий:


Рис.1. Результат выполнения программы

    В программе описан интерфейс MyInterface. В этом интерфейсе объявляется метод getChar() с целочисленным аргументом и символьным результатом, а также объявлен индексатор с целочисленным индексом и символьным значением. Интерфейс MyInterface реализуется в классах Alpha и Bravo. В классе Alpha имеется закрытое символьное поле symb и конструктор с одним аргументом (определяет значение символьного поля). Метод getChar() описан в классе так, что результатом возвращается значение выражения (char)(symb+n). Это значение вычисляется следующим образом: к коду символа из поля symb прибавляется целочисленное значение аргумента метода n, и полученное целое число преобразуется к символьному типу. Индексатор определен так, что при заданном индексе k результатом возвращается значение выражения getChar(k). То есть при индексировании объекта с индексом k и вызове метода getChar() с аргументом k по определению получаем один и тот же результат. Стоит сразу отметить, что в классе Bravo индексатор определен так же - на основе метода getChar(). Этот класс тоже реализует интерфейс MyInterface. В классе есть закрытое текстовое поле text и конструктор с текстовым аргументом, определяющим значение текстового поля. Метод getChar() определен таким образом, что при аргументе k в качестве результата возвращается значение выражения text[k%text.Length]. Это символ из текстового поля text с индексом k (с учетом циклической перестановки индекса, если он выходит за верхнюю допустимую границу).


Значением выражения k%text.Length является значение k, если значение k меньше значения выражения text.Length (количество символов в тексте). Вообще же значение выражения k%text.Length - это остаток от деления значения k на значение text.Length.

    Как отмечалось выше, для индексатора с индексом k результатом возвращается значение getChar(k).


Метод getChar() в классе Alpha определен так, что объект этого класса можно индексировать, помимо прочего, и отрицательными индексами. В классе Bravo метод getChar() определен таким образом, что индексировать объект класса можно только неотрицательными индексами.

    В главном методе командой

  MyInterface R;
объявляется интерфейсная переменная R типа MyInterface. Командой
  R = new Alpha('F');
создается объект класса Alpha, и ссылка на него записывается в интерфейсную переменную R. Так можно делать, поскольку класс Alpha реализует интерфейс MyInterface. При значении -5 целочисленной переменной m выполняется цикл, в котором индексная переменная i пробегает значения от -m до m, отображаются значение, выражения R[i] с проиндексированным объектом. Получаем последовательность символов, начиная с символа за 5 позиций до символа 'F' (аргумент конструктора класса Alpha при создании объекта) до символа через 5 позиций после 'F'. Первый и последний символы в этой последовательности можно вычислить с помощью выражений R.getChar(-m) и R.getChar(m), в которых через интерфейсную переменную вызывается метод объекта, на который переменная ссылается.

    Похожие операции выполняются с объектом класса Bravo. Этот объект создается командой

  R = new Bravo("bravo");    , 
а ссылка на него записывается в интерфейсную переменную R. Как и в предыдущем случае, когда переменная R ссылалась на объект класса Alpha, мы можем вызывать через переменную метод getChar() (команды R.getChar(0) и R.getChar(2*m+1)), а также можем индексировать объект, на который ссылается переменная (команда вида R[i]). При этом необходимо учитывать, что перебор символов выполняется в тексте "bravo" (аргумент, переданный конструктору класса Bravo при создании объекта) с использованием принципа циклической перестановки индекса, если он выходит за верхнюю допустимую границу.


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

    На следующем шаге мы рассмотрим явную реализацию членов интерфейса.




Предыдущий шаг Содержание Следующий шаг