Programowanie zaawansowane

Slides:



Advertisements
Podobne prezentacje
C++ wykład 2 ( ) Klasy i obiekty.
Advertisements

C++ wykład 4 ( ) Przeciążanie operatorów.
Język C/C++ Funkcje.
Deklaracje i definicje klas w C++ Składowe, pola, metody Konstruktory
Programowanie obiektowe
Programowanie obiektowe
Wzorce.
Prowadzący: mgr inż. Elżbieta Majka
Generics w .NET 2.0 Łukasz Rzeszot.
PROGRAMOWANIE STRUKTURALNE
SIECI KOMPUTEROWE (SieKom) PIOTR MAJCHER WYŻSZA SZKOŁA ZARZĄDZANIA I MARKETINGU W SOCHACZEWIE Zarządzanie.
Serwery Aplikacji ASP .NET Web Objects Arkadiusz Popa.
Struktury.
Tablice.
Dziedziczenie i jego rodzaje
C++ wykład 2 ( ) Klasy i obiekty.
Zasady zaliczenia Warunki uzyskania zaliczenia:
Wstęp do programowania obiektowego
Projektowanie i programowanie obiektowe II - Wykład IV
Projektowanie i programowanie obiektowe II - Wykład II
Pakiety w Javie Łukasz Smyczyński (132834). Czym są pakiety? Klasy w Javie są grupowane w pewne zbiory zwane pakietami. Pakiety są więc pewnym podzbiorem.
Podstawy programowania
Źródła: podręcznikopracował: A. Jędryczkowski.
Java – coś na temat Klas Piotr Rosik
Dziedziczenie Maciek Mięczakowski
Inicjalizacja i sprzątanie
Programowanie obiektowe Wykład 3 dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/21 Dariusz Wardowski.
Programowanie obiektowe Wykład 6 dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/14 Dariusz Wardowski.
Programowanie obiektowe – język C++
Programowanie obiektowe 2013/2014
Zawansowane techniki programistyczne
ZWIĄZKI MIĘDZY KLASAMI KLASY ABSTRAKCYJNE OGRANICZENIA INTERFEJSY SZABLONY safa Michał Telus.
Prasek Aneta, Skiba Katarzyna. Funkcje stałe const to takie funkcje, które nie mogą modyfikować stanu obiektu. Oznacza to, że funkcja stała nie może zmieniać.
Kurs języka C++ – wykład 9 ( )
PL/SQL – dalsza wędrówka
Programowanie w języku C++
Projektowanie stron WWW
Programowanie strukturalne i obiektowe C++
Model obiektowy bazy danych
Proces tworzenia oprogramowania Proces tworzenia oprogramowania jest zbiorem czynności i związanych z nimi wyników, które prowadzą do powstania produktu.
Kurs języka C++ – wykład 4 ( )
Programowanie zaawansowane Zaawansowane konstrukcje języka C#
Waldemar Bartyna Pytania egzaminacyjne 1.
Formatowanie dokumentów
Projektowanie obiektowe. Przykład: Punktem wyjścia w obiektowym tworzeniu systemu informacyjnego jest zawsze pewien model biznesowy. Przykład: Diagram.
Waldemar Bartyna 1 Programowanie zaawansowane LINQ to XML.
Platforma .Net.
Łukasz Bieszczad Mateusz Gałązka Karol Włodarek
Programowanie Zaawansowane
Wykład 4 Dr Aneta Polewko-Klim Dr Aneta Polewko-Klim
Zestaw pytań nr. 3 Typy generyczne Wyjątki OPRACOWALI: JAKUB GRYCZEWSKIKINGA ROSA DANIEL KAPTEJNYWOJCIECH ŁĘCZYCKI
Struktura systemu operacyjnego
Partnerstwo dla Przyszłości 1 Lekcja 28 Dziedziczenie i rodzaje dziedziczenia.
PHP jest językiem skryptowym służącym do rozszerzania możliwości stron internetowych. Jego składnia jest bardzo podobna do popularnych języków programowania.
Waldemar Bartyna Pytania egzaminacyjne 1.
T ESTY JEDNOSTKOWE W C# Alicja Majka, A GENDA Wprowadzenie do środowiska Czym są testy jednostkowe i po co je stosować? XUnit, NUnit Pokrycie.
Testy jednostkowe. „Test jednostkowy (unit test) to fragment kodu, który sprawdza inny fragment kodu”
Wykład 4 Dr Aneta Polewko-Klim Dr Aneta Polewko-Klim
Programowanie Obiektowe – Wykład 6
Typy wyliczeniowe, kolekcje
Klasy, pola, obiekty, metody. Modyfikatory dostępu, hermetyzacja
(według:
Delegaty Delegat to obiekt „wiedzący”, jak wywołać metodę.
Programowanie Obiektowe – Wykład 2
PGO Interfejsy Michail Mokkas.
Język C++ Typy Łukasz Sztangret Katedra Informatyki Stosowanej i Modelowania Prezentacja przygotowana w oparciu o materiały Danuty Szeligi i Pawła Jerzego.
PGO Dziedziczenie Michail Mokkas.
PGO Przeciążanie metod i konstruktorów
Zapis prezentacji:

Programowanie zaawansowane Interfejsy

Bez dziedziczenia

Dziedziczenie

Dziedziczenie

Bez polimorfizmu

Bez polimorfizmu

Informacje dla polimorfizmu

Polimorfizm

Interfejsy Definicja interfejsu Implementacja standardowych interfejsów: Enumeracja, Klonowanie, Porównywanie. Wykorzystanie interfejsów jako mechanizmu wywołań zwrotnych

Definicja interfejsu

Definicja interfejsu Interfejs jest nazwanym zbiorem abstrakcyjnych składowych. Dobór składowych definiowanych przez interfejs zależy od zachowania, które dany interfejs modeluje. Interfejs wyraża pewne zachowanie, które wybrana klasa lub struktura musi zaimplementować poprzez implementację wszystkich abstrakcyjnych składowych tego interfejsu. Klasa (lub struktura) może implementować wiele interfejsów, tym samym wspierając wiele zachowań.

Przykładowy interfejs (IDbConnection) Biblioteka klas bazowych platformy .NET zawiera setki predefiniowanych interfejsów, implementowanych przez różne klasy i struktury. Na przykład, ADO.NET posiada definicje wielu dostawców danych, którzy umożliwiają połączenie się z różnymi systemami zarządzania bazami danych (SqlConnection, OracleConnection, OdbcConnection). Pomimo tego, że każdy z tych obiektów połączeniowych ma własną unikalną nazwę, należy do innej przestrzeni nazw, jak również może być częścią różnych pakietów, wszystkie te obiekty implementują wspólny interfejs: IDbConnection.

Przykładowy interfejs (IDbConnection) c. d.

Przykładowy interfejs (IDbConnection) c. d. 2 Dzięki temu, że każdy z obiektów połączeniowych implementuje interfejs IDbConnection (wspiera takie zachowanie), mamy gwarancję, że każdy z tych obiektów posiada metody pozwalające na otwieranie i zamykania połączenia (Open(), Close()), tworzenie poleceń (CreateCommand()) itd.. Interfejs ten (jak również jego składowe), dostawcy danych i obiekty połączeniowe będą dokładnie omówione na wykładzie poświęconym ADO.NET.

Kilka zasad Konwencja przyjęta w .NET wymaga, aby nazwy interfejsów poprzedzane były dużą literą I (i jak interfejs). Wszystkie składowe interfejsu są zawsze abstrakcyjne, co oznacza, że Każda z klas implementujących dany interfejs, musi te wszystkie składowe zaimplementować. Każda z klas wspierających dany interfejs, może te składowe zaimplementować w dowolny, odpowiedni dla jej specyfiki działania, sposób.

Przykładowy interfejs (IDropTarget) W przestrzeni nazw System.Windows.Forms zdefiniowana jest klasa Control, będącą klasą bazową dla wielu elementów interfejsu graficznego w projektach Windows Forms takich jak: DataGridView, Label, StatusBar, itd. . Klasa Control implementuje interfejs IDropTarget, który definiuje podstawową funkcjonalność typu przeciągnij i upuść ( ang. drag-and-drop).

Przykładowy interfejs (IDropTarget) c. d. Znając ten interfejs i wiedząc, że implementuje go klasa System.Windows.Forms.Control, możemy słusznie zakładać, że każda klasa, która rozszerza tą klasę (po niej dziedziczy) posiada metody OnDragDrop(), OnDragEnter(), OnDragLeave(), OnDragOver().

Interfejs a klasa abstrakcyjna Klasa oznaczona jako abstrakcyjna może definiować dowolną liczbę abstrakcyjnych składowych, tworząc w ten sposób interfejs polimorficzny dla klas pochodnych. W przypadku, gdy klasa definiuje zbiór abstrakcyjnych składowych, może również zdefiniować dowolną liczbę konstruktorów, pól danych, nieabstrakcyjnych składowych (posiadających implementację), itd. Interfejsy, z drugiej strony, zawierają tylko abstrakcyjne składowe.

Ograniczenia interfejsu polimorficznego Tylko klasy pochodne musza wspierać składowe zdefiniowane przez abstrakcyjną klasę bazową. W dużych systemach, nie jest niczym nadzwyczajnym projektowanie wielu hierarchii klas, które nie mają wspólnej klasy nadrzędnej (nie licząc oczywiście klasy System.Object) . W oparciu tylko o mechanizm klas abstrakcyjnych, nie ma sposobu na skonfigurowanie typów w różnych hierarchiach tak, aby wspierały ten sam interfejs polimorficzny.

Prosty przykład Tylko typy pochodne klasy ClonableType muszą implementować metodę Clone(). Inne klasy, niedziedziczące po klasie ClonableType, nie są objęte jej interfejsem polimorficznym.

Rozwiązaniem są interfejsy Interfejs, po zdefiniowaniu, może być implementowany przez: dowolną klasę lub strukturę, w dowolnej hierarchii, w dowolnej przestrzeni nazw, w dowolnym pakiecie, definiowany w dowolnym jeżyku programowania platformy .NET. Interfejsy są wysoce polimorficzne

Przykład możliwości interfejsu Przykładem interfejsu może być interfejs IClonable zdefiniowany w przestrzeni nazw System. Interfejs ten definiuje jedną metodę o nazwie Clone(). W .NET Framework 3.5, wiele typów, pozornie w żaden sposób nie powiązanych (System.Array, System.Data.SqlClient.SqlConnection, System.OperatingSystem, System.String), implementuje właśnie interfejs IClonable.

Przykład możliwości interfejsu c. d. Pomimo, że wymienione poprzednio klasy nie mają wspólnej klasy nadrzędnej (oprócz System.Object), mogą one brać udział w polimorfizmie za pośrednictwem interfejsu IClonable. Mając metodę, która posiada parametr typu IClonable, moglibyśmy do niej przekazać dowolny obiekt implementujący ten interfejs.

Przykład możliwości interfejsu c. d. 2 Po uruchomieniu programu zobaczymy wypisane nazwy wszystkich obiektów, pobrane za pomocą dziedziczonej po klasie System.Object metody GetType().

Drugi problem z klasami abstrakcyjnymi Każdy z typów pochodnych musi dostarczyć implementację wszystkich abstrakcyjnych składowych klasy bazowej. Dla wyjaśnienia załóżmy, że do naszej abstrakcyjnej klasy Shape dodaliśmy metodę abstrakcyjną GetNumberOfPoints(), która zwraca liczbę wierzchołków.

Ponownie, rozwiązaniem są interfejsy Jedyną klasą, która ma wierzchołki jest klasa Hexagon. Mimo, że w przypadku pozostałych klas, pobieranie liczby wierzchołków nie ma sensu, wszystkie klasy pochodne muszą być zgodne z interfejsem polimorficznym klasy bazowej. Rozwiązaniem problemu jest zdefiniowanie interfejsu wyrażającego zachowanie „posiadania wierzchołków”. Taki interfejs może być implementowany przez odpowiednie klasy, bez jakichkolwiek zmian w pozostałych klasach.

Definiowanie nowego interfejsu Na poziomie syntaktycznym, interfejs definiujemy poprzez słowo kluczowe interface. W odróżnieniu od pozostałych typów w .NET, interfejsy nie posiadają klas bazowych (nawet klasy bazowej System.Object). Przy składowych interfejsu nie specyfikujemy modyfikatorów dostępu. Wszystkie składowe interfejsów są domyślnie publiczne i abstrakcyjne.

Definiowanie nowego interfejsu c. d. Ponieważ wszystkie składowe interfejsu są abstrakcyjne, nie posiadają żadnej implementacji. Taką deklarację metody nazywamy jej prototypem. Za implementowanie składowych interfejsu odpowiedzialne są klasy i struktury wspierające dany interfejs.

Przykład źle zdefiniowanego interfejsu

Składowe interfejsu Interfejsy mogą definiować prototypy metod, właściwości, zdarzeń i indeksatorów. Dla przykładu, nasz interfejs możemy zdefiniować za pomocą właściwości tylko do odczytu, zamiast tradycyjnej metody.

Użyteczność interfejsu Interfejsy, same w sobie, są bezużyteczne; są przecież tylko nazwanym zbiorem abstrakcyjnych składowych. Nie nożna stworzyć obiektu interfejsu. Znaczenie i użyteczność interfejsów rozpoczyna się od momentu ich zaimplementowania przez wybrane klasy i struktury. W naszym przykładzie, niektóre klasy (posiadające wierzchołki) będą implementować interfejs IPointy, pozostałe (takie jak Circle) nie będą tego robić.

Implementowanie interfejsów Listę implementowanych interfejsów podajmy po przecinkach w definicji typu Klasa bazowa musi być pierwsza pozycją po dwukropku. Gdy klasa dziedziczy po System.Object, po dwukropku podajemy od razu listę interfejsów. W przypadku struktur (które domyślnie dziedziczą po klasie System.ValueType), listę implementowanych interfejsów umieszczamy zaraz po definicji struktury.

Implementowanie interfejsów c. d.

Wszystko albo nic Implementowanie interfejsu rządzie się prostą regułą wszystko albo nic. Typ implementujący wybrany interfejs musi zaimplementować każdą ze zdefiniowanych w tym interfejsie składowych. I tak, w przypadku wspomnianego wcześniej interfejsu IDbConnection, wspierające go klasy muszą zaimplementować dziesięć jego składowych.

Klasa Triangle

Nowa wersja klasy Hexagon

Zmieniona hierarchia klas

Wywołanie składowych interfejsu Najprostszym sposobem skorzystania z nowej funkcjonalności klasy wspierającej dany interfejs jest bezpośrednie wywołanie odpowiedniej metody. Taki sposób sprawdza się oczywiście w przypadku, gdy wiemy, jakie interfejsy wspiera dana klasa. Co zrobić w sytuacji, gdy tej wiedzy nie posiadamy? (Mamy np. do czynienia z tablicą obiektów typu Shape.) Przeanalizujemy trzy możliwe rozwiązania.

Użycie odpowiedniego bloku try/catch Jeżeli dana klasa nie wspiera interfejsu, zostanie zgłoszony wyjątek InvalidCastException.

Użycie słowa kluczowego as Jeżeli dany obiekt wspiera interfejs IPointy, zostanie zwrócona referencja do tego interfejsu. W przeciwnym wypadku zostanie zwrócony null.

Użycie słowa kluczowego is Jeżeli dany obiekt wspiera interfejs IPointy, zostanie zwrócona wartość true, w przeciwnym razie wartość false.

Interfejsy jako parametry Ponieważ interfejsy to jedne z typów platformy .NET, możemy definiować metody, które przyjmuję interfejsy jako parametry. Dla przykładu, zdefiniujmy jeszcze jeden interfejs, który będzie modelował możliwość rysowanie obiektów danej klasy w trój-wymiarze.

Interfejsy jako parametry c. d. Interfejs ten będzie wspierany przez klasy Circle i Hexagon.

Interfejsy jako parametry c. d. 2 Definiujemy metodę, która rysuje obiekty wspierające interfejs IDraw3D. Przekazanie parametru nie wspierającego tego interfejsu spowoduje błąd kompilatora.

Interfejsy jako wartości zwracane Interfejsy mogą być również wykorzystane jako typ wartości zwracanej przez metodę. Dla przykładu, zdefiniujmy metodę, która bierze dowolny obiekt System.Object, sprawdza jego kompatybilność z interfejsem IPointy i zwraca referencję do uzyskanego interfejsu (jeżeli jest on wspierany).

Interfejsy jako wartości zwracane c. d. Zdefiniowaną metodę można przetestować w następujący sposób:

Tablice interfejsów Ten sam interfejs może być implementowany przez wiele typów, nie należących nawet do tych samych hierarchii klas (nie mających wspólnej klasy bazowej, poza klasą System.Object). Fakt ten daje możliwość tworzenie wielu zaawansowanych konstrukcji programistycznych. Załóżmy, że zdefiniowaliśmy dodatkowo trzy klasy wspierające interfejs IPointy, niepołączone żadną hierarchią klas.

Tablice interfejsów c. d. Możemy teraz zdefiniować tablicę „spiczastych” obiektów (implementujących interfejs IPointy. Wszystkie elementy takie j tablicy mogą być traktowane jako kompatybilne z typem IPointy, pomimo ich dużej różnorodności i przynależności do różnych hierarchii klas.

Korzystajmy z pomocy środowiska! Implementacja interfejsu wymaga sporo pisania. W końcu trzeba zaimplementować każdą składową interfejsu w każdym wspierającym ją typie. Środowisko Visual Studio (jak również Express Edition) ułatwia nam to zadanie poprzez „sprytne znaczniki”, które pojawiają się po najechaniu kursorem na nazwę interfejsu. Po wybraniu opcji „Implement interfece nazwa”, środowisko wstawi szkielety wszystkich składowych danego interfejsu.

Konflikty nazw Klasa lub struktura może implementować dowolną liczbę interfejsów. Pociąga to za sobą możliwość pojawiania się konfliktów nazw składowych kilku interfejsów. Załóżmy, że mamy zdefiniowane następujące trzy interfejsy: Każdy z nich definiuje składową o tej samej nazwie Draw()

Konflikty nazw c. d. W przypadku zdefiniowania jednej klasy wspierającej te trzy interfejsy, kompilator zezwoliłby na poniższą implementację tych interfejsów (czyli przy pomocy jednej metody o nazwie Draw()). Mimo, że ten sposób nie powoduje błędów, nie pozwala nam jednak na korzystanie z zalet wykorzystania interfejsów.

Konflikty nazw c. d. 2 Bez względu na to, jaki interfejs uzyskamy z obiektu, wykonana zostaje zawsze ta sama metoda. A oczywiście, np. drukowanie i zapis do pamięci są realizowane w różny sposób.

Jawna implementacja interfejsu Konflikt nazw można rozwiązać stosując jawną implementację nazw. Nazwy metod poprzedzamy nazwami odpowiednich interfejsów.

Jawna implementacja interfejsu c. d. Jak można zauważyć, przy implementowanych metodach nie podaliśmy modyfikatorów dostępu. W przypadku jawnej implementacji interfejsu, składowe automatycznie stają się prywatne (i nie możemy tego zmienić). Jedynym sposobem odwołania się do tak zdefiniowanej metody jest rzutowanie obiektu na odpowiedni interfejs. Przykład znajduje się na następnym slajdzie.

Jawna implementacja interfejsu c. d. 2 Jawną implementację interfejsu możemy również stosować wtedy, gdy chcemy „ukryć” pewną funkcjonalność definiowanej klasy. Z funkcjonalność tej możemy korzystać poprzez rzutowanie danego obiektu na odpowiedni (nam znany) interfejs.

Definiowanie hierarchii interfejsów Podobnie, jak w przypadku klasy, interfejsu również można definiować poprzez hierarchie. Interfejs, rozszerzając inny interfejs, dziedziczy deklarację jego abstrakcyjnych składowych. W odróżnieniu od klas, interfejsy nie dziedziczą żadnej (domyślnej) implementacji. Interfejs rozszerza po prostu wskazany interfejs o dodatkowe abstrakcyjne składowe.

Definiowanie hierarchii interfejsów c. d. Hierarchie interfejsów są szczególnie użyteczne w przypadku, gdy chcemy rozszerzyć funkcjonalność istniejącego interfejsu, ale bez dokonywanie zmian w istniejącym kodzie. Przykładowa hierarchia interfejsów może wyglądać w następujący sposób:

Definiowanie hierarchii interfejsów c. d. 2 Jeżeli pewien typ chce wspierać interfejs, musi on zaimplementować składowe wszystkich interfejsów rozszerzanych przez dany interfejs.

Definiowanie hierarchii interfejsów c. d. 3 Po powołaniu obiektu klasy wspierającej interfejs dziedziczący po innych interfejsach, możemy: wywołać na poziomie obiektu składowe każdego z interfejsów w łańcuchu dziedziczenia, uzyskać jawny dostęp do każdego z tych interfejsów poprzez rzutowanie.

Rozszerzanie interfejsów W odróżnieniu od klas, w przypadku interfejsów możliwe jest wskazanie wielu rozszerzanych interfejsów..

Rozszerzanie interfejsów c.d. Jeżeli chcemy zdefiniować klasę wspierającą IShape, to ile metod musimy zaimplementować?

Rozszerzanie interfejsów c.d. 2 Prawidłowa odpowiedz: To zależy. Jeżeli chcemy stworzyć wspólną implementację metody Draw(), wystarczy dostarczyć tylko trzy metody.

Rozszerzanie interfejsów c.d. 3 Jeżeli chcemy dostarczyć różne implementacje metody Draw() dla każdego z interfejsów (co oczywiście jest bardziej sensowne), zaimplementujemy cztery składowe.

Podsumowanie Interfejsy stanowią fundamentalny aspekt platformy .NET Framework. Praca z interfejsami jest składową tworzenia aplikacji niezależnie od jej zastosowania (sieciowe, GUI, biblioteki do obsługi baz danych, itp. ). Interfejsy w odróżnieniu od klas bazowych stosujemy: gdy w naszej hierarchii klas tylko ich część ma wspierać pewne wspólne zachowanie, gdy chcemy zamodelować wspólne zachowanie dla klas zdefiniowanych w różnych hierarchiach klas (nie mających wspólnego obiektu bazowego poza klasą System.Object).

Implementacja standardowych interfejsów IEnumerable, IEnumerator

Tworzenie typów wyliczeniowych Interfejsy IEnumerable i IEnumerator wykorzystywane są w konstrukcji języka C# znanej jako pętla foreach. Pozwala ona nam przechodzić po kolejnych elementach typów tablicowych. W rzeczywistości każdy typ posiadający zaimplementowaną metodę GetEnumerator() może być używany w konstrukcji foreach.

Tworzenie typów wyliczeniowych c. d. Aby zademonstrować sposób dostosowania typu do potrzeb pętli foreach, zmodyfikujemy klasę Car dodając nowe właściwości.

Tworzenie typów wyliczeniowych c. d. 2 Oraz stworzymy nową klasę reprezentującą garaż przechowujący listę samochodów za pomocą obiektu klasy System.Array

Tworzenie typów wyliczeniowych c. d. 3 Chcemy osiągnąć możliwość przejścia po wszystkich samochodach w garażu za pomocą konstrukcji foreach. Niestety, kompilator poinformuje nas, że klasa Garage nie posiada metody GetEnumerator().

Interfejs IEnumerable i IEnumerator Metoda GetEnumerator() jest sformalizowana w interfejsie IEnumerable w przestrzeni nazw System.Collections. Typ, który implementuje ten interfejs, potrafi zwracać kolejne elementy zawarte w obiekcie tej klasy (np. w pętli foreach). Metoda GetEnumeator() zwraca referencję do kolejnego interfejsu: System.Collections.IEnumerator. Interfejs ten zapewnia infrastrukturę pozwalającą na przechodzenie po wewnętrznych obiektach zawartych w kontenerze kompatybilnym z interfejsem IEnumerable.

Interfejs IEnumerable i IEnumerator c. d. Możemy zapewnić własną implementację składowych GetEnumerator(), MoveNext(), Current, i Reset(). Prostszym rozwiązaniem, możliwym do zastosowania w większości przypadków, jest delegacja żądania do wewnętrznego typu tablicowego (typy te oczywiście wspierają interfejs IEnumerable).

Interfejs IEnumerable i IEnumerator c. d. 2 Dzięki wsparciu dla interfejsu IEnumerable, obiekty danej klasy mogą być używane w pętli foreach. Można również korzystać „ręcznie” z typu IEnumerator, o ile metoda GetEnumerator() została zdefiniowana jako publiczna. Jeżeli chcemy ukryć tą nową funkcjonalność klasy na poziomie obiektu, stosujemy jawną implementację interfejsu.

Definiowanie iteratora Od wersji platformy .NET 2.0 mamy jeszcze jeden sposób przystosowania klasy do jej używania w pętli foreach; poprzez zdefiniowanie iteratora. Iterator to składowa klasy, która specyfikuje, w jaki sposób powinny być zwracane wewnętrzne elementy klasy na żądanie pętli foreach. Metoda ta musi nazywać się GetEnumerator() i zwracać wartość typu IEnumerator, ale klasa nie musi wspierać żadnego interfejsu.

Słowo kluczowe yield return Słowo kluczowe yield return jest wykorzystane do specyfikowania wartości, które mają zostać zwrócone do wywołującej pętli foreach. Gdy program dotrze do instrukcji yield return, bieżące miejsce jest zapamiętywane. Po kolejnym odwołaniu się do iteratora, jego wykonanie będzie kontynuowane od zapamiętanego miejsca. Metoda iteratora nie musi wykorzystywać wewnętrznej pętli foreach. Można ja również zdefiniować w poniżej przedstawiony sposób.

Definiowanie nazwanego iteratora Słowo kluczowe yield return może być używane w dowolnej metodzie. Takie metody, określane mianem nazwanych iteratorów, mogą przyjmować dowolną liczbę argumentów. Należy pamiętać, że nazwany iterator zwraca typ IEnumerable, a nie IEnumerator.

Definiowanie nazwanego iteratora c. d. Nazwane iteratory pozwalają nam na posiadanie kilku sposobów zwracania wewnętrznych elementów.

Implementacja standardowych interfejsów IClonable

Klonowanie obiektów Klonowanie wykorzystuje się w celu utworzenia identycznej kopii danego obiektu. Późniejsza zmiana obiektu oryginalnego nie powinna wpływać w żaden sposób na stan wewnętrzny utworzonych kopii (klonów). Klasy, których obiekty można klonować implementują interfejs IClonable definiujący pojedyncza metodę Clone().

Płytkie klonowanie W przypadku klas definiujących składowe nie będące referencjami do innych typów (tylko bezpośrednio wartościami) wystarczy zastosowanie płytkiego klonowania (kopiowania wartości składowa po składowej).

Głębokie klonowanie W przypadku płytkiego klonowania, metodę Clone() możemy prosto zaimplementować za pomocą metody odziedziczonej po System.Object: MemberwiseClone(). W przypadku, gdy klasa posiada referencje do innych typów musimy zastosować głębokie klonowanie. Dodajmy do naszego przykłady dodatkowy typ.

Głębokie klonowanie c. d.

Głębokie klonowanie c. d. 2 Przykładowy kod sprawdzający rezultat płytkiego planowania w przypadku istnienia w klasie referencji do innych typów.

Głębokie klonowanie c. d. 3 Poprzednia implementacja metody Clone() nie dała oczekiwanego rezultatu, ponieważ nie uwzględniała kopiowania obiektów wskazywanych przez referencję w definicji klonowanego obiektu. Aby uwzględnić te referencje, możemy postąpić w następujący sposób:

Implementacja standardowych interfejsów IComparable

Porównywanie obiektów jednej klasy Interfejs System.IComparable definiuje zachowanie pozwalające na sortowanie obiektów wspierających ten interfejs według wybranego klucza. Klasa System.Array definiuje statyczną metodę Sort(). Realizuje ona sortowanie tablicy obiektów implementujących interfejs IComparable.

Porównywanie obiektów jednej klasy c. d. Dodajmy do naszej klasy nowe pole i właściwość, według której będziemy sortować obiekty tej klasy. Stwórzmy tablice przykładowych obiektów tej klasy

Implementacja interfejsu IComparable Klasa wspierająca interfejs IComparable musi zaimplementować metodę CompareTo(). Od implementacji tej metody zależy sposób porównywania obiektów tej klasy.

Implementacja interfejsu IComparable c. d. Wartości zwracane przez metodę CompareTo(): Liczba mniejsza od zera – ta instancja obiektu jest przed wskazanym obiektem w porządku sortowania, Zero – ta instancje jest równa wskazanemu obiektowi, Liczba większa od zera – ta instancja obiektu jest po wskazanym obiekcie w porządku sortowania, Możemy uprościć porównanie wykorzystując fakt, że typ int również implementuj interfejs IComparable.

Implementacja interfejsu IComparable c. d. 2 Możemy teraz przetestować rezultat zaimplementowania interfejsu IComparable w następujący sposób:

Implementacja interfejsu IComparer Możliwe jest również obsłużenie w jednej klasie kilku sposobów sortowania jej obiektów. Służy do tego interfejs IComparer zdefiniowany w przestrzeni nazw System.Collactions. W przeciwieństwie do interfejsu ICoparable, interfejs IComparer nie jest typowo implementowany w klasie, której obiekty porównujemy. Interfejs ten implementują odpowiednie klasy pomocnicze, jedna dla każdego sposobu sortowania.

Implementacja interfejsu IComparer c. d. Do tej pory, w typie Car mamy zdefiniowane sortowanie w oparciu o identyfikator samochodu. Zdefiniujemy teraz klasę pomocnicza (implementującą interfejs IComparer), która będzie porównywać obiekty w oparciu o ich „imię”.

Implementacja interfejsu IComparer c. d. 2 Teraz, kod użytkownika może skorzystać z nowego sposobu sortowania. Klasa System.Array posiada metodę Sort(), której argumentami są: tablica do sortowania oraz interfejs IComparer.

Implementacja interfejsu IComparer c. d. 3 Zalecanym sposobem postępowania fest dodanie do klasy właściwości tylko do odczytu, która będzie zwracać odpowiednią klasę pomocniczą realizującą zadany sposób sortowania. W ten sposób, użytkownik może implementować sortowanie w oparciu o odpowiednio nazwane właściwości klasy, a nie osobną klasę pomocniczą, o której musi wcześniej wiedzieć.

Wykorzystanie interfejsów mechanizmu komunikacji Interfejsy Wykorzystanie interfejsów jako mechanizmu komunikacji

Interfejsy wywołań zwrotnych Interfejsy wykorzystywane są nie tylko do umożliwiania polimorfizmu w przypadku klas należących do różnych hierarchii, przestrzeni nazw i pakietów. Interfejsy mogą również być stosowane w roli mechanizmu wywołań zwrotnych. W C#, delegaty są dedykowanymi rozwiązaniami do komunikacji między obiektami. Mechanizm wywołań zwrotnych zrealizowany za pomocą interfejsów omówimy na przykładzie znanej klasy Car. Rozpoczniemy od stworzenia interfejsu.

Interfejsy wywołań zwrotnych c. d. Interfejsy często nie są implementowane przez klasy biorące udział w komunikacji, ale przez klasę pomocniczą (ang. sink). Klasa zgłaszająca zdarzenia będzie wywoływać metody klasy pomocniczej w przypadku zajścia odpowiednich okoliczności.

Interfejsy wywołań zwrotnych c. d. 2 Kolejnym krokiem jest zdefiniowanie metod pozwalających na przekazanie (i wycofanie) referencji do interfejsu do klasy, która ma nas informować o zdarzeniach. Dodatkowo wprowadzamy możliwość rejestracji wielu interfejsów wywołań zwrotnych, co umożliwia informowanie wielu zainteresowanych (multicast).

Interfejsy wywołań zwrotnych c. d. 3 W klasie Car dodajemy przechodzenie po liście zarejestrowanych interfejsów i zgłaszanie (wywoływanie) odpowiednich zdarzeń.

Interfejsy wywołań zwrotnych c. d. 4 Posiadając taką infrastrukturę, możemy zaimplementować metodę pozwalającą na odbieranie informacji o zdarzeniach od obiektów klasy Car.

Pytania egzaminacyjne (Zestaw06) Co to jest interfejs? Czym interfejs różni się od interfejsu polimorficznego? W jaki sposób implementujemy interfejsy? Do czego służą słowa kluczowe as i is? Czym się różni jawna i niejawna implementacja interfejsów? Co trzeba zrobić, aby po zawartości naszej klasy można było iterować za pomocą pętli foreach Czym się różnie płytkie i głębokie klonowanie? Co trzeba zrobić, aby kolekcję naszych obiektów można było posortować?

Dziękuję za uwagę