Интерфейсы и структуры
15.1. Пользовательские и стандартные интерфейсы. Структуры
Интерфейсы
В объектно-ориентированном программировании иногда требуется определить, что класс должен делать, а не как он будет это делать. Такой подход может быть реализован с помощью абстрактного класса, при этом в абстрактном классе часть методов может быть реализована, часть нет. Кроме этого в С# предусмотрена возможность полностью отделить структуру класса от его реализации. Это делается с помощью интерфейса.
Интерфейс - это "крайний случай" абстрактного класса, в котором не предусмотрена ни одна реализация члена класса. Таким образом, интерфейс описывает функциональность классов, но не определяет способа ее реализации. Каждый класс, наследуя интерфейс, может реализовать его элементы по-своему. Так достигается полиморфизм - объекты разных классов по-разному реагируют на вызовы одного и того же метода.
Синтаксис интерфейса:
[атрибуты] [спецификаторы] interface имя_интерфейса : [предки] { //объявление функциональных членов интерфейса без реализации ... }
Для интерфейса могут быть указаны спецификаторы new, public, internal и private. Спецификатор new применяется для вложенных интерфейсов и имеет такой же смысл, как и соответствующий спецификатор метода класса. По умолчанию интерфейс доступен только из сборки, в которой он описан ( internal ).
Все функциональные члены интерфейса по умолчанию являются открытыми ( public ) и абстрактными ( abstract ), поэтому при описании метода указывается только тип возвращаемого им значения и сигнатуры.
В качестве функциональных членов в интерфейсе можно объявлять сигнатуры методов, свойств, индексаторов и событий (для Windows-приложений). Интерфейсы не могут содержать члены данных, конструкторы, деструкторы или операторные методы (методы, переопределяющие операции). Ни один член интерфейса не может быть объявлен статическим.
Напомним, что класс может наследовать один базовый класс и несколько интерфейсов. Класс, наследующий интерфейс, должен реализовать его в полном объеме. Т.к. функциональные члены, объявленные внутри интерфейса, являются открытыми, то их реализация также должна быть открытой. Кроме того, сигнатура функционального члена в реализации должна в точности совпадать с сигнатурой, заданной в определении интерфейса.
В качестве примера рассмотрим интерфейс IDemo и его реализацию для классов DemoPoint и DemoLine из предыдущей лекции:
//определение интерфейса interface IDemo { void Show(); //объявление метода double Dlina(); //объявление метода int X {get;} //объявление свойства, доступного только для чтения int this [int i]{get;set;} //объявление индексатора, доступного для чтения-записи } //класс DemoPoint наследует интерфейс IDemo class DemoPoint:IDemo { protected int x; protected int y; public DemoPoint ( int x, int y) { this.x=x; this.y=y; } public void Show() //реализация метода, объявленного в интерфейсе { Console.WriteLine("точка на плоскости: ({0}, {1})",x, y); } public double Dlina() //реализация метода, объявленного в интерфейсе { return Math.Sqrt(x*x+y*y); } public int X //реализация свойства, объявленного в интерфейсе { get { return x; } } public int this [int i] //реализация индексатора, объявленного в интерфейсе { get { if (i==0) return x; else if (i==1) return y; else throw new Exception ("недопустимое значение индекса"); } set { if (i==0) x=value; else if (i==1) y=value; else throw new Exception ("недопустимое значение индекса"); } } } //класс DemoShape наследует класс DemoPoint и интерфейс IDemo class DemoShape : DemoPoint, IDemo { protected int z; public DemoShape(int x, int y, int z):base(x, y) { this.z=z; } // реализация метода, объявленного в интерфейсе, с сокрытием одноименного метода из //базового класса public new void Show() { Console.WriteLine("точка в пространстве: ({0}, {1}, {2})", x, y, z); } // реализация метода, объявленного в интерфейсе, с сокрытием одноименного метода из //базового класса public new double Dlina() { return Math.Sqrt(x*x+y*y+z*z); } // реализация индексатора, объявленного в интерфейсе, с сокрытием одноименного // индексатора из базового класса public new int this [int i] { get { if (i==0) return x; else if (i==1) return y; else if (i==2) return z; else throw new Exception ("недопустимое значение индекса"); } set { if (i==0) x=value; else if (i==1) y=value; else if (i==2) z=value; else throw new Exception ("недопустимое значение индекса"); } } } class Program { static void Main() { //создание массива интерфейсных ссылок IDemo []a =new IDemo[4]; //заполнение массива a[0]=new DemoPoint(0,1); a[1]=new DemoPoint(-3, 0); a[2]=new DemoShape(3,4,0); a[3]= new DemoShape(0,5, 6); //просмотр массива foreach (IDemo x in a) { x.Show(); Console.WriteLine("Dlina={0:f2}",x.Dlina()); Console.WriteLine("x="+x.X); x[1]+=x[0]; Console.Write("новые координаты - "); x.Show(); Console.WriteLine(); } } }
Обратите внимание, что в интерфейсе IDemo было объявлено свойство Х, доступное только для чтения. Наследуя интерфейс IDemo, класс DemoPoint определил реализацию данного свойства. Класс DemoShape, наследуя класс DemoPoint и IDemo, не предложил собственной реализации свойства Х, поэтому при обращении к объекту типа DemoShape вызывается реализация данного свойства, определенная в базовом классе.
- Добавьте в интерфейс IDemo свойство Y, которое позволит обращаться для чтения к значению поля у. Реализуйте работу с данным свойством в классах DemoPoint и DemoShape.
- Добавьте свойство Z для обращения к полю z класса DemoShape. Подумайте, куда именно нужно добавить определение данного свойства и почему.