obiektowka Flashcards
(98 cards)
virtual vs abstract
metody
metoda:
-virtual:
+daje możliwość odwołania się do metody bazowej
+może mieć implementację w klasie pochodnej
+może mieć ciało
-abstract:
+nie daje możliwości odwołania się do metody bazowej
+musi mieć implementacji w klasie pochodnej
+nie może mieć ciała
+tylko w klasie abstrakcyjnej
Klasa abstrakcyjna
Klasa abstrakcyjna nie może mieć swoich reprezentantów
pod postacią obiektów.
Klasa abstrakcyjna jest wykorzystywana wyłącznie w roli
klasy bazowej dla innych klas.
Klasa staje się abstrakcyjną, gdy zawiera choćby jedną
abstrakcyjną metodę lub właściwość.
Klasa potomna względem klasy abstrakcyjnej musi
implementować wszystkie jej abstrakcyjne metody
i właściwości.
Klasa abstrakcyjna może zawierać także pola
oraz nieabstrakcyjne metody i właściwości.
Klasa zamknięta (finalna)
Klasa zamknięta nie może być bazową
dla klas od niej pochodnych.
Klasy zamknięte zapobiegają
przypadkowemu dziedziczeniu.
Metody zamknięte (finalne) stosuje się wówczas,
gdy działanie metody jest sprawdzone, poprawne, stabilne
i nie wymaga dalszych zmian.
Interfejs
-Interfejs jest tworem abstrakcyjnym podobnym do klasy,
ale posiada on jedynie deklaracje składowych,
które od C#8 może dodatkowo implementować.
-Nie można utworzyć obiektu będącego instancją interfejsu.
-Interfejs może zawierać: zagnieżdżone typy (np. klasy),
stałe, metody, właściwości, zdarzenia, indeksery.
-Interfejs nie może zawierać pól, operatorów,
konstruktorów i destruktorów.
-Klasa może dziedziczyć tylko po jednej klasie, ale może
implementować wiele interfejsów lub dziedziczyć po nich.
-Klasa implementująca interfejs może być tak samo lub
bardziej dostępna niż interfejs.
-Interfejs rozszerzający nie może być bardziej dostępny od
któregokolwiek interfejsu bazowego.
-Interfejs może być interfejsem rozszerzającym dla wielu
innych interfejsów (gdy po nich dziedziczy).
-Interfejsy mogą być włączone w dowolne miejsce hierarchii
dziedziczenia, w przeciwieństwie do klas abstrakcyjnych.
interfejs vs klasa abstrakcyjna
abstrakcja:
-nie może mieć obiektów
-wszystkie abstrakcyjne muszą być implementowane
-określone miejsce w hierarchii
-jedna klasa abstrakcyjna
-może mieć: pola, operatory, konstruktory, destruktory
interfejs:
-też nie może mieć obiektów
-wszystkie bez funkcjonalności muszą być implementowane
-dowolne miejsce w hierarchii
-wiele interfejsów
-nie może mieć: pól, operatorów, konstruktorów, destruktorów
SOLID
S — zasada pojedynczej odpowiedzialności
(SRP, ang. Single Responsibility Principle)
O — zasada otwarte-zamknięte
(OCP, ang. Open-Closed Principle)
L — zasada podstawienia Liskov
(LSP, ang. Liskov Substitution Principle)
I — zasada separacji interfejsów
(ISP, ang. Interface Segregation Principle)
D — zasada odwrócenia zależności
(DIP, ang. Dependency Inversion Principle)
Zasada pojedynczej odpowiedzialności
(SRP, ang. Single Responsibility Principle) mówi,
że każda klasa (a zatem obiekt) powinna odpowiadać
za jak najmniejszy fragment logiki programu.
Stosowanie zasady SRP redukuje odpowiedzialność
każdej z klas i zwiększa prawdopodobieństwo
ponownego ich użycia.
Zasada otwarte-zamknięte (OCP, ang. Open-Closed
Principle) mówi, że klasy, moduły, funkcje itp. powinny być
otwarte na rozszerzenia, a zamknięte na modyfikacje.
Zgodnie z zasadą OCP powinna istnieć możliwość
uzyskania nowych funkcjonalności
poprzez rozszerzenie elementu
(np. przez dziedziczenie
lub tworzenie statycznych metod rozszerzających),
a nie zmianę już istniejącego.
Zasada podstawienia Liskov mówi, że: „funkcje, które
używają wskaźników lub referencji do klas bazowych,
muszą być w stanie używać również obiektów klas
dziedziczących po klasach bazowych, bez dokładnej
znajomości tych obiektów”…
…co w praktyce oznacza przede wszystkim to, że
klasa dziedzicząca po klasie bazowej nie powinna zmieniać
jej funkcjonalności, tylko rozszerzać możliwości.
Zasada separacji interfejsów (ISP, ang. Interface
Segregation Principle) mówi, że tworzone interfejsy muszą
być odpowiedzialne za jak najmniejszą funkcjonalność.
Wynika to z faktu, że nadmienienie rozbudowany interfejs
(zwany tłustym lub zanieczyszczonym) wymaga
implementacji w klasach potomnych metod,
które być może nie będą nigdy wykorzystane.
Zasada odwrócenia zależności (DIP, ang. Dependency
Inversion Principle) mówi, że kod warstw wyższego poziomu
(np. klas potomnych) nie powinien zależeć od kodu niższych
warstw (np. klas bazowych).
Zgodnie z DIP obie warstwy powinny być od siebie
niezależne, ale zależeć od pewnych abstrakcji.
Jednocześnie abstrakcje nie powinny zależeć od
szczegółów.
Kolejność wykonywania konstruktorów
1) statyczne i klas bazowych w kolejności dziedziczenia
2) konstruktory obiektowych pól klasy
3) konstruktory klasy
Metoda statyczna
Z czym związana?
jest zatem związana z typem
(nie instancją, czyli obiektem).
Klasa statyczna
Kiedy się stosuje?
Różnice między niestatyczną
Dziedziczenie
Klasy statyczne stosuje się, gdy nie ma potrzeby korzystania
z indywidualnego stanu klasy.
Klasa statyczna może zawierać metody i pola statyczne.
Klasa niestatyczna może zawierać metody i pola statyczne
oraz niestatyczne
Klasa statyczna nie może implementować interfejsów
i brać udziału w dziedziczeniu.
Do klas statycznych nie odnosi się this, base (dziedziczenie).
Konstruktor statyczny
kiedy używany?
jest używany wówczas, gdy należy
zainicjalizować statyczną składową niestatycznej klasy bez
konieczności tworzenia instancji klasy.
Konstruktor statyczny uruchamiany jest w chwili tworzenia
pierwszego obiektu niestatycznej klasy tylko 1. raz, po nim
uruchamiane są (wielokrotnie) konstruktory niestatyczne.
Operator konwersji niejawnej (np. double poleFigury = prostokąt1; )
Operator konwersji jawnej (np. double poleFigury = (double)prostokąt1; )
Operator jednoargumentowy (np. Liczba liczbaOdwrotna=-liczba; )
Operator dwuargumentowy (np. Liczba liczba=liczba1+liczba2; )
public static implicit operator typwyjściowy(typwejściowy arg1)
public static explicit operator typwyjściowy(typwejściowy arg1)
public static typwyniku operator typoperatora1arg(typargu1 arg1)
public static typwyniku operator typoperatora2arg(typargu1 arg1 ,typargu2 arg2)
Przeciążając operatory == i !=, należy również
i co robią?
nadpisać
metody Equals() i GetHashCode(), które są dziedziczone po
klasie System.Object. W przeciwnym przypadku kompilator
zgłosi ostrzeżenie.
TRZEBA JE PRZECIĄŻĄĆ PARAMI
Metoda Equals() działa jak operator == i sprawdza,
czy porównywane obiekty są równe.
dla typów referencyjnych porównuje referencje
Metoda GetHashCode()
zwraca swego rodzaju
identyfikator obiektu. Jest
on zawsze równy dla tych
samych reprezentacji
w pamięci, ale nie musi
być różny dla różnych
obiektów (powtarza się co
ok. 10 tyś. obiektów)
GUID
co to? rozmiar? jak użyć?
Zmienne GUID (globally unique identifier) są 128b (16B)
liczbami całkowitymi, które mogą być używane tam, gdzie
wymagane są niepowtarzalne identyfikatory (m.in.
w b.d., w których klucz główny musi być unikatowy, np. w
przypadku, gdy ocena może być dodana do dowolnego
zasobu aplikacji, więc jej identyfikator musi być unikatowy).
Guid Guid1 = Guid.NewGuid();
aby przeciążyć operatory true false
należy również przeciążyć operatory które zwracają te wartości
Indeksator jest
Indeksatory pozwalają
Indeksator musi zawierać
Indeksatory mogą być przekazywane jako argumenty metod
Dlaczego nie może być statyczny?
kolekcją podobną do tablicy
ale indeksy mogą być innych typów
niestatyczny
Nie można tworzyć statycznych indeksatorów,
ponieważ indeksatory są powiązane z obiektami.
wygodnie odwoływać się do tablicy
lub innej struktury danych zadeklarowanej w klasie.
co najmniej jedną deklarację
dostępu get lub set (podobnie jak właściwość).
Składnia indeksatora jest analogiczna do właściwości.
[modyfikatory] typ this[argumenty_indeksów]{
deklaracje_dostępu //skojarzenie wartości z indeksami
}
tylko przez wartość. Nie mogą być więc one przekazywane
przez referencję (ref) i wyjście (out).
Struktury
jaki typ?
kiedy stosować?
dziedziczenie czy mogą, co z interfejsem
przypisywanie wartości i struktur
co mogą zawierać?
modyfikator readonly
jak działa new?
Struktury są mniej rozbudowaną alternatywą dla klas.
Struktury są typem wartości (bezpośrednim), nie zaś typem
referencyjnym, zatem niejawnie dziedziczą po klasie
bazowej „System.ValueType” (choć ta dziedziczy po Object).
Struktury należy wykorzystywać dla prostych, niewielkich
(do 16 bajtów) i krótkotrwałych danych, które są tworzone na
stosie. Gwarantuje to wówczas wydajność większą
niż w przypadku stosowania obiektów klas.
Struktury nie obsługują dziedziczenia,
ale mogą implementować interfejsy.
Podczas przypisania zmiennych typu struktura wartości
są kopiowane - powstają dwie odrębne kopie
tych samych danych.
Struktury są podobne do klas: mogą zawierać stałe, pola,
metody, właściwości, indeksatory, zdarzenia, konstruktory,
przeciążone operatory.
Struktury nie mogą zawierać bezpośredniej inicjalizacji pól
typu: int i=10;.
W strukturze od C#10 można definiować konstruktor
bezparametrowy (tak jak każdy), ale musi inicjować wszystkie parametry
struktury. Nie można natomiast tworzyć finalizerów.
Modyfikator readonly stosuje się do tych składowych struktur
(np. metod), które nie modyfikują ich stanu.
Słowo new nie oznacza działań typowych dla klas i
obiektów: nie tworzy nowego obiektu na stercie, ale oznacza
wywołanie konstruktora bezparametrowego w celu
inicjalizacji wszystkich pól struktury
(kompletnego stanu struktury)
klasa vs struktura
Klasa to typ referencyjny zorientowany na przechowywanie
wartości bardziej złożonych i ukierunkowany na
odwzorowywanie pewnej hierarchii przy użyciu hermetyzacji,
dziedziczenia i mechanizmów używanych w konstrukcji
dynamicznych struktur danych.
Struktura to typ wartościowy zorientowany na
przechowywanie wartości, która jest nieco bardziej
rozbudowana od typów pierwotnych (int, double, etc.)
i nie zajmuje więcej niż 16 bajtów na stosie
Dwie zmienne typu klasa (obiekty) są traktowane jako równe
gdy są tego samego typu i odwołują się dokładnie do tego
samego obiektu – obszaru na stercie (sposób działania
porównania można zmienić).
Dwie zmienne typu struktura, rekord i rekord struktury są
traktowane jako równe gdy są tego samego typu, a wartości
ich pól są sobie równe.
rekord (klasy) vs rekord struktury
Rekord (record class) to typ referencyjny zorientowany na
przechowywanie wartości bardziej złożonych, ale raczej
niezmiennych - przeznaczonych głównie do odczytu.
Takimi danymi są np. dane statystyczne. W deklaracji można
pisać record lub record class.
Rekord struktury to typ wartościowy – niereferencyjny.
W deklaracji pisze się record struct. Rekord struktury,
w przeciwieństwie do rekordu, jest przeznaczony do
przechowywania danych, które mogą się zmieniać.
Tablice
Tablice to zbiory wielu zmiennych tego samego typu.
Tablice umożliwiają przeprowadzanie operacji
na grupach obiektów.
Typ elementów tablicy wskazywany jest
z chwilą jej deklaracji.
Przydział pamięci na stercie dla określonej liczby elementów
następuje w momencie tworzenia tablicy. Są typami
referencyjnymi.
Istnieją tablice jednowymiarowe, wielowymiarowe
oraz tablice o różnej liczbie elementów w wierszach
lub kolumnach.
W tablicach oprócz typów wbudowanych można
przechowywać również klasy i struktury itd.
Każda tablica dziedziczy po abstrakcyjnej klasie
System.Array, która dostarcza podstawowych mechanizmów
do działania na elementach tablicy.
Do deklaracji tablicy służy operator [].
Tablice są indeksowane od wartości „0” do „n-1”, gdzie n to
liczba elementów tablicy
Do tworzenia bardziej złożonych i wyspecjalizowanych
struktur danych oraz efektywnego zarządzania nimi służą
kolekcje (inny wykład).
int[,] t4 ; //deklaracja bez alokacji
int[,] t5= new int[3,2]; //alokacja
dwu wymiarowe tablice ^
int[][] <- tez dwuwymiarowa
Predykaty wyszukiwawcze
co to?
jak deklarować?
Wyszukiwanie w tablicach bazuje na predykatach
wyszukiwawczych - metodach definiujących kryteria wysz.
Predykaty wyszukiwawcze można deklarować w wygodny
sposób z wykorzystaniem wyrażeń lambda.
Wyrażenia lambda
co to?
co zawiera?
jak można przekazywać argumenty?
Wyrażenia lambda to maksymalnie
uproszczone metody anonimowe.
Wyrażenia lambda zawierają jedynie
listę parametrów i ciało metody.
Wyrażenia lambda nie mają nazwy
oraz nie definiują typu zwracanego.
Gdy wyrażenie lambda przyjmuje jeden argument,
nie trzeba otaczać go nawiasami ().
Gdy ciało funkcji zawiera tylko jedną instrukcję, nie trzeba
używać klamerek {} ani słowa kluczowego return.
Zmienne zdefiniowane w ciele wyrażenia lambda
są usuwane po zakończeniu działania metody.
Można przekazywać do wyrażeń lambda parametry
poprzez referencję i wyjście.
Nie trzeba podawać typów parametrów dla zmiennych
przekazanych przez wartość, natomiast wymagane jest
podanie typu parametru dla zmiennych przekazanych
przez referencję i wyjście.
Zwracane wartości muszą pasować do kontekstu użycia
(typu danego delegata).
Func<>
co to?
co posiada?
Jakie parametry?
przeciazanie
Typy parametrow
Func<> jest delegatem generycznym, zatem jest osadzony
na wskazanych typach parametrów wejściowych
i wyjściowych.
Ma zero lub więcej parametrów wejściowych i dokładnie
jeden parametr wyjściowy. Jest to parametr ostatni.
Func<> można definiować bezpośrednio w kodzie.
Func<> wymaga podania typu parametrów oraz typu
zwracanego wyniku (ostatni parametr).
Func<> dysponuje konstruktorem przeciążonym 17 razy, tj.
Func<out>, Func<in, out>, Func<in1, in2, out>, Func<in1,
in2, in3, out> itd.</out>
nie da się przeciążać funkcji zadeklarowanej za pomocą func
Action<>
co to?
co posiada?
Action<> jest delegatem generycznym, który bazuje
wyłącznie na parametrach wejściowych (musi posiadać co
najmniej 1)- nie posiada on parametrów wyjściowych.
Action<> w pozostałych kwestiach jest zbliżony do Func<>.
Testy jednostkowe (ang. unit test)
Testy jednostkowe (ang. unit test) to metoda testowania
tworzonego oprogramowania polegająca na przygotowaniu
i wykonaniu dedykowanych testów.
Test jednostkowy wywiąże się z wykonaniem testowanego
fragmentu kodu i porównaniem wyniku z ustalonym przez
autora testu wynikiem wzorcowym (np. czy metoda Dodaj()
dla argumentów 2 i 2 powinna zwrócić 4).
Test jednostkowy to (inaczej) kod wykonujący inny kod
w warunkach kontrolowanych, który służy do weryfikacji
poprawności tego drugiego.
Testy jednostkowe pozwalają automatyzować proces
tworzenia oprogramowania, są zatem podstawą wielu
podejść do tworzenia oprogramowania (np. ekstremalnego).
Autor testu dostarcza dane wejściowe oraz informację
o oczekiwanym rezultacie, celem testu jest porównanie
rezultatu oczekiwanego z rezultatem otrzymanym.