Читайте также: |
|
При розробці класів може виникнути ситуація, коли всі похідні класи повинні мати деякий метод. Однак на рівні базового класу недостатньо інформації для змістовного означення цього методу. Наприклад у нашому базовому класі Shape таким є віртуальний метод Draw. У таких випадках є можливість оголосити у класі методи без реалізації.
Віртуальні методи, які оголошуються в класі без реалізації називаються абстрактними. У мові C# для оголошення у базовому класі абстрактного методу перед методом слід замінити ключове слово virtual на abstract. Якщо у класі визначено хоча б один абстрактний метод, то такий клас також називається абстрактним. У мові C# визначенню абстрактного класу повинен передувати модифікатор abstract. Наприклад:
public abstract class Shape { ... //абстрактний метод зображеня фігури public abstract void Draw(); } |
Абстрактні методи та класи мають наступні особливості:
− абстрактний метод є неявним віртуальним методом;
− оголошувати абстрактні методи дозволяється тільки в абстрактних класах;
− оскільки абстрактний метод не містить фактичної реалізації, то оголошення такого методу просто закінчується «;»;
− абстрактними можуть бути також властивості, індексатори та події;
− неможливо оголосити конструктор абстрактним методом;
− неможливо створити об’єкт абстрактного класу;
− абстрактні класи використовуються для породження інших класів − вони використовуються у якості базових;
− не абстрактний клас, який є похідним від абстрактного повинен містити фактичну реалізацію усіх успадкованих абстрактних методів;
− абстрактний клас не може бути запечатаним або статичним.
На діаграмі класів імена абстрактних класів та заголовки абстрактних методів форматуються курсивом (мал. 8).
Мал. 8. Діаграма абстрактного класу «Геометрична фігура».
Інтерфейси
Абстрактний клас є зручним механізмом для розширення функціональності похідних класів. Абстрактний клас надає поліморфний інтерфейс (відкриті поліморфні члени) у вигляді абстрактних членів для всіх своїх похідних класів ієрархії. Однак цей поліморфний інтерфейс має ряд суттєвих обмежень.
По-перше, він підтримується тільки у похідних класах. Однак, досить часто потрібно розробляти такі ієрархії класів, які не мають спільних базових класів та водночас для яких є характерною схожа функціональність.
По-друге, у кожному похідному класі обов’язково має підтримуватися відповідний набір абстрактних членів та надаватися їх реалізація. Іншими словами: може виникнути ситуація, у якій певна функціональність має сенс не для всієї ієрархії, а тільки для її окремих класів. Наприклад не має сенсу визначати метод площі для похідного класу «Точка», та є сенс для − «Опуклий многокутник», «Круг», тощо.
У таких випадках зручним є використання типу інтерфейс − дуже схожого типу до абстрактних класів. Абстрактні класи, у яких оголошено тільки абстрактні методи (зокрема, властивості, індексатори та події) називаються інтерфейсами.
Визначення. Загальне правило визначення таких класів є наступним:
interface ім’я_інтерфейсу [:список_інтерфейсів] { // оголошення списку абстрактних членів } |
Наприклад для окремих класів ієрархії класів геометричних фігур є сенс у визначенні адитивних числових характеристик − площі та об’єму. Таку функціональність описує наступний інтерфейс:
interface IAdditiveSpecifications { double Area(); double Volume(); } |
При визначенні інтерфейсів слід пам’ятати наступне:
− інтерфейси описують групу логічно-зв’язаних функціональних можливостей;
− інтерфейси − повністю абстрактні класи;
− інтерфейси можуть містити методи, індексатори, події та властивості;
− інтерфейси не можуть містити константи, поля, оператори, конструктори, деструктори та вкладені класи;
− інтерфейси не можуть містити статичних членів;
− члени інтерфейсів є автоматично відкритими і не можуть супроводжуватися ніякими модифікаторами доступу;
− можна визначати ієрархії інтерфейсів;
− інтерфейс може бути похідним від декількох базових інтерфейсів.
Підтримка інтерфейсу. Кажуть, що клас (структура) підтримує інтерфейс, якщо він містить реалізацію усіх оголошень інтерфейсу. Отже, інтерфейси можуть підтримувати як класи так і структури. Також можлива підтримка класом чи структурою декількох інтерфейсів.
Для розширення функціональних можливостей деякого класу (структури) за рахунок забезпечення у ньому підтримки інтерфейсів, необхідно у визначенні цього класу (структури) після двокрапки через кому вказати імена відповідних інтерфейсів. У випадку забезпечення підтримки інтерфейсу у деякому похідному класі, у цьому списку першим після двокрапки має бути вказане ім’я базового класу, а вже потім список інтерфейсів.
Реалізація підтримки інтерфейсу передбачає обов’язкову реалізацію всіх членів інтерфейсу. Кожен член інтерфейсу може бути реалізований двома способами: неявним та явним.
Неявна реалізація окремого члену інтерфейсу у класі нічим не відрізняється від його реалізації у випадку якби він був власним членом цього класу. Неявна реалізація вимагає обов’язкову відкриту (публічну) реалізацію відповідних членів. Наприклад, у нашому випадку неявна реалізація методу визначення площі буде виглядати наступним чином:
public class ConvexPolygon: Shape, IAdditiveSpecifications { ... //реалізація методів інтерфейсу IAdditiveSpecifications - аддитивних числових //характеристик фігур //неявна реалізація методу визначення площі public double Area() { double s = 0; for (byte i = 1; i < n - 1; i++) s += CompGeometry.WedgeProduct(vertices[0], vertices[i], vertices[0], vertices[i+1]); return s / 2; } } |
Як було зазначено вище, окремий клас або структура може підтримувати довільну кількість інтерфейсів. Через це завжди існує ймовірність реалізації інтерфейсів зі членами, що мають ідентичні імена, і, отже, виникає необхідність в усуненні конфліктів на рівні імен. Для таких випадків у мові C# передбачений синтаксис явної реалізація інтерфейсів.
У загальному випадку явна реалізація члена інтерфейсу здійснюється у такий спосіб:
тип_значення ім’я_інтерфейсу.ім’я_методу (параметри) { //тіло методу } |
Зверніть увагу, що в цьому синтаксисі вказувати модифікатор доступу не потрібно, оскільки члени, реалізовані явно, є неявно приватними − автоматично вважаються приватними.
Наприклад, у нашому випадку явна реалізація методу визначення об’єму фігури для класу ConvexPolygon буде виглядати наступним чином:
public class ConvexPolygon: Shape, IAdditiveSpecifications { ... //явна реалізація методу визначення об’єму double IAdditiveSpecifications.Volume() { return 0; } } |
На діаграмі класів підтримка класом інтерфейсів позначається за аналогією до того, як це показано на мал. 8 для нашого випадку.
Мал. 9. Графічне позначення підтримки інтерфейсу адитивних числових характеристик класом «Опуклий многокутник».
Доступ до функціональних можливостей інтерфейсу здійснюється через посилання на цей інтерфейс. Оскільки, інтерфейси не мають власних конструкторів, то єдиним засобом для створення посилання (об’єкта-змінної) на той чи інший інтерфейс є операція явного приведення типів для об’єктів класів, які підтримують даний інтерфейс. Наприклад:
ConvexPolygon p = new ConvexPolygon(); IAdditiveSpecifications r = (IAdditiveSpecifications)p; Console.WriteLine("Площа фiгури: {0}", r.Area()); Console.WriteLine("Oб\'єм фiгури: {0}", r.Volume()); |
У випадку неявної реалізації члена інтерфейсу є можливість доступу до нього на рівні об’єктів. Наприклад:
ConvexPolygon p = new ConvexPolygon(); Console.WriteLine("Площа фiгури: {0}", p.Area()); |
Слід відмітити, що у наведених прикладах доступ до методів інтерфейсу IAdditiveSpecifications є коректним, оскільки у обох ситуаціях нам точно відомо, що клас ConvexPolygon підтримує цей інтерфейс. Однак у деяких випадках виникає необхідність у визначенні факту підтримки класом того чи іншого інтерфейсу динамічно − на етапі виконання програми. Наприклад, нехай задано масив, який містить об’єкти класів сумісних із класом Shape. Ставиться задача знаходження значення найбільшої площі об’єктів масиву. Зрозуміло, що у заданому масиві інтерфейс IAdditiveSpecifications буде підтримуватися тільки частиною його елементів. Одним зі способів динамічного визначення факту підтримки конкретного інтерфейсу є застосування операторів is та as. Наприклад:
Shape[] s = { new Point(1, 1), new ConvexPolygon() }; double AreaMax = double.MinValue; for (int i = 0; i < s.Length; i++) { if (s[i] is IAdditiveSpecifications) //або (s[i] as IAdditiveSpecifications)!= null if (AreaMax < ((IAdditiveSpecifications)s[i]).Area()) AreaMax = ((IAdditiveSpecifications)s[i]).Area(); } if (AreaMax!= double.MinValue) Console.WriteLine("Найбiльша площа: {0}", AreaMax); else Console.WriteLine("Жодна iз фiгур не характеризується площею."); |
Приклади реалізації підтримки стандартних інтерфейсів бібліотек середовища.NET. Окрім можливості визначення власних інтерфейсів, у бібліотеках середовища.NET пропонується величезний набір стандартних (які поставляються із платформою) інтерфейсів.
Порівняння об’єктів. Так інтерфейс IСomparable простору імен System забезпечує поведінку можливості порівняння об’єктів класу та, як правило, реалізовується у класі з метою визначення порядку на множині об’єктів класу та використовується для сортування об’єктів класу. Формально його визначення виглядає так:
[ComVisibleAttribute(true)] public interface IComparable { int CompareTo(object o); } |
Отже, підтримка даного інтерфейсу класом вимагає реалізації одного методу CompareTo(object o). Цей метод порівнює поточний екземпляр класу з іншим об’єктом цього ж класу та повертає ціле число у відповідності до наступної таблиці:
Менше нуля | У порядку поточний екземпляр класу передує заданому у методі об’єкту. |
Нуль | У порядку поточний екземпляр знаходиться на тій же позиції, що й заданий у методі об’єкт. |
Більше нуля | У порядку поточний екземпляр класу слідує після заданого у методі об’єкту. |
Підтримка інтерфейсу IСomparable може виявитися дуже зручним засобом для забезпечення можливості сортування масивів об’єктів цього класу. Справа в тому, що базовим класом для всіх масивів у мові C# є клас Array із простору імен System, а, отже, методи цього класу можна застосовувати до будь-якого похідного від нього класу, зокрема і до масиву визначеного користувачем. Серед загальнодоступних методів класу Array є статичний метод Sort, одне із перевантажень якого Sort(Array array) сортує одновимірний масив array за зростанням. Цей метод автоматично викликає реалізацію (як явну так і неявну) інтерфейсу IСomparable.
Наприклад. Реалізуємо підтримку інтерфейсу IСomparable у класі ConvexPolygon. Слід мати на увазі, що при реалізації методу CompareTo програміст самостійно визначає що саме береться за основу в операції впорядкування. У нашому випадку будемо впорядковувати многокутники за величиною їхніх площ.
public class ConvexPolygon:Shape,IAdditiveSpecifications,IComparable { ... //явна реалізація інтерфейсу IComparable int IComparable.CompareTo(object ob) { ConvexPolygon tmp = ob as ConvexPolygon; if (tmp!= null) if (this.Area() < tmp.Area()) return -1; else if (this.Area() > tmp.Area()) return 1; else return 0; else throw new ArgumentException("Цей аргумент не cумісний із класом ConvexPolygon!"); } } |
У цьому випадку відсортувати масив об’єктів класу ConvexPolygon за зростанням можна так:
Point p1 = new Point(0, 0); Point p2 = new Point(0, 3); Point p3 = new Point(4, 3); Point p4 = new Point(4, 0); ConvexPolygon[] mas = { new ConvexPolygon(p1,p2,p3,p4), new ConvexPolygon(), new ConvexPolygon(p1,p2,p3) }; if (mas[0] is IComparable) Array.Sort(mas); else Console.WriteLine("Клас ConvexPolygon не пiдтримує iнтерфейс IComparable!"); |
Відсортувати ж масив за спаданням можна обернувши відсортований за зростанням масив за допомогою загальнодоступного статичного методу Reverse класу Array для обернення масиву. Наприклад:
Array.Reverse(mas); |
Сортування за різними критеріями. У багатьох алгоритмах потрібно виконувати сортування об’єктів за різними критеріями. У C# для цього використовується інтерфейс IComparer.
Інтерфейс IComparer визначений у просторі імен System.Collections. Він містить один метод Compare, який повертає результат порівняння двох об’єктів, які передаються йому в якості параметрів:
[ComVisibleAttribute(true)] public interface IComparer { int Compare(object ob1, object ob2); } |
Принцип застосування цього інтерфейсу полягає в тому, що для кожного критерію (наприклад за прощею, за кількістю вершин, тощо) сортування об’єктів описується невеликий допоміжний клас, який реалізує цей інтерфейс. Цей допоміжний клас критерію, як правило оголошується вкладеним в основний клас. Об’єкт цього класу передається у стандартний метод сортування масивів Sort() класу Array в якості другого аргументу (існує декілька перевантажень цього методу).
Наведемо приклад сортування масиву об’єктів класу ConvexPolygon за площею (допоміжний клас SortByArea) та за кількістю вершин (допоміжний клас SortByVerticesCount). Класи критеріїв сортування реалізовані як вкладені, оскільки вони потрібні тільки об’єктам класу ConvexPolygon.
public class ConvexPolygon:Shape,IAdditiveSpecifications,IComparable { ... // клас критерію сортування за площею public class SortByArea: IComparer { int IComparer.Compare(object ob1, object ob2) { ConvexPolygon cp1 = (ConvexPolygon)ob1; ConvexPolygon cp2 = (ConvexPolygon)ob2; return ((IComparable)cp1).CompareTo(cp2); } } // клас критерію сортування за кількістю вершин public class SortByVerticesCount: IComparer { int IComparer.Compare(object ob1, object ob2) { ConvexPolygon cp1 = (ConvexPolygon)ob1; ConvexPolygon cp2 = (ConvexPolygon)ob2; if (cp1.n > cp2.n) return 1; if (cp1.n < cp2.n) return -1; return 0; } } } |
У цьому випадку відсортувати масив об’єктів класу ConvexPolygon за зростанням за різними критеріями можна так:
Array.Sort(mas, new ConvexPolygon.SortByArea()); Array.Sort(mas, new ConvexPolygon.SortByVerticesCount()); |
Перебір об’єктів та ітератори. Для перебору елементів у спеціальним чином організованій групі даних застосовується оператор foreach. Прикладом такої групи є масив. Зручність цього виду циклу полягає в тому, що не потрібно визначати кількість елементів у групі та виконувати їх перебір за індексом: просто вказується необхідність на перебір всіх елементів групи. Загальний синтаксис цього оператора наступний:
foreach(тип ім’я_змінної in ім’я_групи_даних) тіло циклу |
Ім’я змінної задає локальну по відношенню до циклу змінну, яка буде по черзі приймати всі значення із заданої іменем групи даних. Тип змінної повинен відповідати типу елементів групи даних. Наприклад, нехай задано масив:
int[] a = {24, 50, -18, 3, -7} |
Вивід цього масиву на консоль з допомогою оператора foreach виглядає наступним чином:
foreach(int x in a) Console.WriteLine(x); |
У C# є можливість застосування оператора foreach до класу, визначеного користувачем. Для цього потрібно у класі реалізувати інтерфейс IEnumerable. Вся справа в тому, що об’єкт, який задається іменем групи даних, має належати до класу сумісного із типом IEnumerable.
Інтерфейс IEnumerable (переліковний) визначає всього один метод − GetEnumerator(), який повертає об’єкт типу IEnumerator (перелічувач) та який можна використовувати для перегляду елементів об’єкту класу як групи даних. Інтерфейс IEnumerator визначає три методи:
§ властивість Current, яка повертає поточний елемент об’єкта як групи даних;
§ метод MoveNext, що переводить перелічувач на наступний елемент об’єкта як групи даних;
§ метод Reset, який встановлює перелічував на початок перегляду.
Отже, якщо потрібно, щоб для перебору елементів класу міг застосовуватися цикл foreach, необхідно реалізувати всі ці методи. Однак, у другій версії платформи були введені засоби, які спрощують виконання перебору у об’єкті − ітератори.
Ітератор представляє собою блок коду, який задає послідовність перебору елементів об’єкта. На кожному проході циклу foreach виконується один крок ітератора, який закінчується видачею наступного значення. Видача значення виконується за допомогою ключового слова yield. Розглянемо приклад реалізації підтримки перебору вершин опуклого многокутника.
public class ConvexPolygon:Shape,IAdditiveSpecifications,IComparable,IEnumerable { ... public IEnumerator GetEnumerator() { // опис ітератора for (int i = 0; i < n; i++) yield return vertices[i]; } } |
Тепер вивід вершин опуклого многокутника може бути реалізований, наприклад, так:
Point p1 = new Point(0, 0); Point p2 = new Point(0, 3); Point p3 = new Point(4, 3); Point p4 = new Point(4, 0); foreach (Point p in new ConvexPolygon(p1,p2,p3,p4)) p.Draw(); |
Все, що потрібно зробити у версії 2.0 для підтримки перебору, − вказати, що клас підтримує інтерфейс IEnumerable, та описати ітератор. Блок ітератора синтаксично є звичним блоком коду та може бути описаний в тілі методу, операції або блоці get властивості, якщо відповідне значення, що повертається, має тип IEnumerable або IEnumerator. В тілі блоку літератора можуть зустрічатися дві конструкції:
§ yield return формує значення, яке видається на черговій ітерації;
§ yield break сигналізує про завершення ітерації.
Слід пам’ятати, що ключове слово yield є ключовим для компілятора тільки в цих двох конструкціях.
Код блока ітератора виконується не так, як звичайні блоки. Компілятор формує службовий об’єкт-перелічувач, при виклику метода MoveNext якого виконується код блока ітератора, який видає чергове значення за допомогою ключового слова yield. Наступний виклик метода MoveNext об’єкта-перелічувача поновлює виконання блока ітератора з того моменту, на якому він був призупинений в попередній раз.
Ще однією перевагою використання ітераторів є те, що для одного і того ж класу можна задати різний порядок перебору елементів. Наприклад:
public class ConvexPolygon:Shape,IAdditiveSpecifications,IComparable,IEnumerable { ... public IEnumerable OnlyOnAxes() { for (int i = 0; i < n; i++) if(vertices[i].x==0 || vertices[i].y == 0) yield return vertices[i]; } } |
Тепер вивід тільки тих вершин опуклого многокутника, які лежать на осях координат, може бути реалізований, наприклад, так:
ConvexPolygon cp = new ConvexPolygon(new Point(1,0), new Point(-3,0), new Point(-1,2)); foreach (Point p in cp.OnlyOnAxes()) p.Draw(); |
Тема 4
Узагальнення
Дата добавления: 2015-10-26; просмотров: 329 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
Агрегація та композиція | | | Недолік обмеження операцій |