Инкапсуляция |
Интерфейсы. Контейнерные классы
Интерфейсы
Презентацию к данной лекции Вы можете скачать здесь.
Синтаксис интерфейса
Интерфейс является "крайним случаем" абстрактного класса. В нем задается набор абстрактных методов, свойств и индексаторов, которые должны быть реализованы в производных классах. Иными словами, интерфейс определяет поведение, которое поддерживается реализующими этот интерфейс классами. Основная идея использования интерфейса состоит в том, чтобы к объектам таких классов можно было обращаться одинаковым образом.
Каждый класс может определять элементы интерфейса по-своему. Так достигается полиморфизм: объекты разных классов по-разному реагируют на вызовы одного и того же метода.
Синтаксис интерфейса аналогичен синтаксису класса:
[ атрибуты ] [ спецификаторы ] interface имя_интерфейса [ : предки ] тело_интерфейса [ ; ]
Для интерфейса могут быть указаны спецификаторы new, public, protected, internal и private. Спецификатор new применяется для вложенных интерфейсов и имеет такой же смысл, как и соответствующий модификатор метода класса. Остальные спецификаторы управляют видимостью интерфейса. По умолчанию интерфейс доступен только из сборки, в которой он описан ( internal ).
Интерфейс может наследовать свойства нескольких интерфейсов, в этом случае предки перечисляются через запятую. Тело интерфейса составляют абстрактные методы, шаблоны свойств и индексаторов, а также события.
В качестве примера рассмотрим интерфейс IAction, определяющий базовое поведение персонажей компьютерной игры, встречавшихся в предыдущих главах. Допустим, что любой персонаж должен уметь выводить себя на экран, атаковать и красиво умирать:
interface IAction { void Draw(); int Attack(int a); void Die(); int Power { get; } }
В интерфейсе IAction заданы заголовки трех методов и шаблон свойства Power, доступного только для чтения. Если бы требовалось обеспечить возможность установки свойства, в шаблоне следовало указать ключевое слово set, например:
int Power { get; set; }
Отличия интерфейса от абстрактного класса:
- элементы интерфейса по умолчанию имеют спецификатор доступа public и не могут иметь спецификаторов, заданных явным образом;
- интерфейс не может содержать полей и обычных методов — все элементы интерфейса должны быть абстрактными;
- класс, в списке предков которого задается интерфейс, должен определять все его элементы, в то время как потомок абстрактного класса может не переопределять часть абстрактных методов предка (в этом случае производный класс также будет абстрактным);
- класс может иметь в списке предков несколько интерфейсов, при этом он должен определять все их методы.
Реализация интерфейса
В списке предков класса сначала указывается его базовый класс, если он есть, а затем через запятую интерфейсы, которые реализует этот класс. Например, реализация интерфейса IAction в классе Monster может выглядеть следующим образом:
using System; namespace ConsoleApplication1 { interface IAction { void Draw(); int Attack( int a ); void Die(); int Power { get; } } class Monster : IAction { public void Draw() { Console.WriteLine( "Здесь был " + name ); } public int Attack( int ammo_ ) { ammo -= ammo_; if ( ammo > 0 ) Console.WriteLine( "Ба-бах!" ); else ammo = 0; return ammo; } public void Die() { Console.WriteLine( "Monster " + name + " RIP" ); health = 0; } public int Power { get { return ammo * health; } } … }
Сигнатуры методов в интерфейсе и реализации должны полностью совпадать. Для реализуемых элементов интерфейса в классе следует указывать спецификатор public. К этим элементам можно обращаться как через объект класса, так и через объект типа соответствующего интерфейса:
Monster Vasia = new Monster( 50, 50, "Вася" ); // объект класса Monster Vasia.Draw(); // результат: Здесь был Вася IAction Actor = new Monster( 10, 10, "Маша" ); // объект типа интерфейса Actor.Draw(); // результат: Здесь был Маша
Существует второй способ реализации интерфейса в классе: явное указание имени интерфейса перед реализуемым элементом. Спецификаторы доступа при этом не указываются. К таким элементам можно обращаться в программе только через объект типа интерфейса, например:
class Monster : IAction { int IAction.Power { get { return ammo * health; } } void IAction.Draw() { Console.WriteLine( "Здесь был " + name ); } ... } ... IAction Actor = new Monster( 10, 10, "Маша" ); Actor.Draw(); // обращение через объект типа интерфейса // Monster Vasia = new Monster( 50, 50, "Вася" ); // Vasia.Draw();
Таким образом, при явном задании имени реализуемого интерфейса соответствующий метод не входит в интерфейс класса. Это позволяет упростить его в том случае, если какие-то элементы интерфейса не требуются конечному пользователю класса.
Работа с объектами через интерфейсы. Операции is и as
При работе с объектом через объект типа интерфейса бывает необходимо убедиться, что объект поддерживает данный интерфейс. Проверка выполняется с помощью бинарной операции is. Эта операция определяет, совместим ли текущий тип объекта, находящегося слева от ключевого слова is, с типом, заданным справа.
Результат операции равен true, если объект можно преобразовать к заданному типу, и false в противном случае. Операция обычно используется в следующем контексте:
{ // выполнить преобразование "объекта" к "типу" // выполнить действия с преобразованным объектом }
Допустим, мы оформили какие-то действия с объектами в виде метода с параметром типа object. Прежде чем использовать этот параметр внутри метода для обращения к методам, описанным в производных классах, требуется выполнить преобразование к производному классу. Для безопасного преобразования следует проверить, возможно ли оно, например так:
static void Act( object A ) { if ( A is IAction ) { IAction Actor = (IAction) A; Actor.Draw(); } }
В метод Act можно передавать любые объекты, но на экран будут выведены только те, которые поддерживают интерфейс IAction.
Недостатком использования операции is является то, что преобразование фактически выполняется дважды: при проверке и при собственно преобразовании. Более эффективной является другая операция — as. Она выполняет преобразование к заданному типу, а если это невозможно, формирует результат null, например:
static void Act( object A ) { IAction Actor = A as IAction; if ( Actor != null ) Actor.Draw(); }
Обе рассмотренные операции применяются как к интерфейсам, так и к классам.