Programowanie Obiektowe – Wykład 7 dr Piotr Jastrzębski Wydział Matematyki i Informatyki http://wmii.uwm.edu.pl/~piojas/ piojas@matman.umw.edu.pl
Tematyka wykładu Zasady dobrego programowania Zasady programowania obiektowego S.O.L.I.D. Obsługa wyjątków
Filary programowania obiektowego Abstrakcja Hermetyzacja Dziedziczenie Polimorfizm
Zasady dobrego oprogramowania Keep it Simple Stupid (KISS) - Buzi (Bez Udziwnień Zapisu, Idioto) Don’t Repeat Yourself (DRY) Tell Don’t Ask You Aren't Gonna Need It (YAGNI) Separation of Concerns
Keep it Simple Stupid (KISS) - Buzi (Bez Udziwnień Zapisu, Idioto) kod ma być prosty i zrozumiały unikanie skomplikowanych zapisów nie oznacza upraszczania za wszelką cenę
Don’t Repeat Yourself (DRY) unikanie powtórzeń w kodzie – u nas to np. polimorficzność, stałe oddzielenie powtarzającej się części od metody zalety: unikanie błędów, poprawki możemy dodać w jednym miejscu
Tell Don’t Ask związane z hermetyzacją i podziałem obowiązków należy mówić obiektom jakie akcje mają wykonywać nie należy uzależnia wykonania akcji od stanu w jakim znajdują się obiekty
public class TDA_ZłyPrzykład { public void ObliczCene(Pojazd pojazd, Kalkulator kalkulator, int znizka) var cenaNetto = znizka > 5 ? pojazd.cenaKoncowa - 5000 : pojazd.cenaKoncowa - 3000; if (cenaNetto > kalkulator.cenaPodstawowa) kalkulator.DoliczAkcesoria(); } else // bez akcesoriów public class Kalkulator internal int cenaPodstawowa; public void DoliczAkcesoria() public class Pojazd internal int cenaKoncowa;
public class TDA_DobryPrzyklad { public void ObliczCene(Pojazd pojazd, Kalkulator kalkulator, int znizka) kalkulator.ObliczCeneNetto(pojazd, znizka); } public class Kalkulator internal int cenaPodstawowa; public void ObliczCeneNetto(Pojazd pojazd, int znizka) var cenaNetto = znizka > 5 ? pojazd.cenaKoncowa - 10000 : pojazd.cenaKoncowa - 20000; if (cenaNetto > this.cenaPodstawowa) this.DodajAkcesoria(); else // bez akcesoriów private void DodajAkcesoria() { } public class Pojazd internal int cenaKoncowa;
You Aren't Gonna Need It (YAGNI) tworzenie tego co jest potrzebne i niezbędne usuwanie dodatków, które mogą się przydać np. usunięcie zbędnych usingów, zwolnienie zasobów, usuwanie nie używanych zmiennych, metod Cytat: Antoine de Saint-Exupéry Perfekcję osiąga się wtedy, gdy nie można już nic odjąć, a nie dodać.
Separation of Concerns elementy składowe (np. klasy i metody) powinny być rozłączne i mieć oddzielne zastosowanie te elementy nie powinny współdzielić odpowiedzialności Cytat: Albert Einstein Wszystko trzeba robić tak prosto, jak to tylko jest możliwe, ale nie prościej.
Zasady programowania obiektowego S.O.L.I.D. Single Responsibility Principle (SRP) – zasada jednej odpowiedzialności Open-Closed Principle (OCP) – zasada otwarte- zamknięte Liskov Substitution Principle (LSP) – Zasada podstawienia Liskov Interface Segregation Principle (ISP) – Zasada segregacji interfejsów Dependency Inversion Principle (DIP) – Zasada odwrócenia zależności
Zasada jednej odpowiedzialności każdy obiekt powinien mieć tylko jeden cel i odpowiedzialność nie powinien istnieć więcej niż jeden powód do modyfikacji klasy jeśli jest kilka odpowiedzialności, powinniśmy podzielić klasę podobne do Separation of Concerns
Przykład public interface ISilnik { void ZmniejszObroty(); void ZwiejszObroty(); void AktualneObroty(); }
Przykład class Silnik : ISilnik { public void AktualneObroty() throw new NotImplementedException(); } public void ZmniejszObroty() public void ZwiejszObroty()
Przykład – źle! public interface ISilnik { void ZmniejszObroty(); void ZwiejszObroty(); void AktualneObroty(); //biegi void BiegWDol(); void BiegWGore(); }
Przykład – dobrze! public interface ISkrzynia { void BiegWDol(); void BiegWGore(); } class Skrzynia : ISkrzynia public void BiegWDol() throw new NotImplementedException(); public void BiegWGore()
Zasada otwarte-zamknięte klasa powinna być otwarta na rozbudowę ,ale zamknięta do jej własnej modyfikacji możemy dodawać nowe pola i metody, ale bez zmiany w wewnętrzną strukturę zmiana istniejącej struktury może mieć wpływ na inne elementy hermetyzacja, dziedziczenie, polimorfizm, delegaty unikamy instrukcji warunkowych
Przykład enum DietType { Lose, Keep, Put } class Person internal DietType Diet; internal string Name;
Przykład – źle! public void ServeAMeal(List<Person> persons) { foreach (var person in persons) if(person.Diet == DietType.Lose) Console.WriteLine("Serwuj mniejsza porcje"); else if(person.Diet == DietType.Keep) Console.WriteLine("Serwuj standardowa porcje"); else if(person.Diet == DietType.Put) Console.WriteLine("Serwuj powiekszona porcje"); }
Przykład – dobrze! public void ServeAMeal(List<Person> persons, Dictionary<DietType, string> meals) { foreach (var person in persons) serve(person, meals); } private void serve(Person person, Dictionary<DietType, string> meals) string meal; if (meals.TryGetValue(person.Diet, out meal)) Console.WriteLine(meal);
Zasada podstawienia Liskov powinniśmy być w stanie używać klasy pochodnej w miejsce klasy nadrzędnej i ona zachowuje się w taki sam sposób, bez modyfikacji klasa pochodna nie ma wpływu na zachowanie klasy nadrzędnej w hierarchii klas powinno dać się traktować obiekt klas pochodneh jak obiekt klas bazowej dziedziczenie ma być stosowane tylko gdy chcemy skorzystać z polimorfizmu, a nie tylko gdy chcemy wyciągnąć wspólne cechy
Przykład – źle! class Frezarka { public void Operacja() Console.WriteLine("Frezarka robi coś"); } class Obrabiarka : Frezarka Console.WriteLine("Obrabiarka robi coś");
Przykład – źle! Obrabiarka obr = new Obrabiarka(); obr.Operacja(); //Wynik: "Obrabiarka robi coś" Frezarka frez = obr; frez.Operacja(); //Wynik: "Frezarka robi coś"
Przykład – dobrze! class Frezarka { public virtual void Operacja() Console.WriteLine("Frezarka robi coś"); } class Obrabiarka : Frezarka public override void Operacja() Console.WriteLine("Obrabiarka robi coś");
Przykład – dobrze! Obrabiarka obr = new Obrabiarka(); obr.Operacja(); //Wynik: "Obrabiarka robi coś" Frezarka frez = obr; frez.Operacja(); //Wynik: "Obrabiarka robi coś"
Jeszcze lepiej? Refactoring interface IMaszyna { void Operacja(); } class Frezarka : IMaszyna public virtual void Operacja() Console.WriteLine("Frezarka robi coś"); class Obrabiarka : Frezarka public override void Operacja() Console.WriteLine("Obrabiarka robi coś");
Zasada segregacji interfejsów dzielenie interfejsów na mniejsze grupy, tak by klasa dziedzicząca po nich, nie miała do dyspozycji niepotrzebnych metod nie powinno być jednego dużego interfejsu
Zasada odwrócenia zależności Moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych. Obie grupy modułów powinny zależeć od abstrakcji. moduły wysokopoziomowe – logika biznesowa moduły niskopoziomowe – komunikacją z bazą danych, ftp, API, algorytmy do liczenia Abstrakcje nie powinny zależeć od szczegółowych rozwiązań. To szczegółowe rozwiązania powinny zależeć od abstrakcji.
Przykład – źle! class B { public void ZrobCos() { } } class A public void Metoda() B b = new B(); b.ZrobCos();
Przykład – dobrze! interface IB { void ZrobCos() { } } class B : IB public void ZrobCos() { } class A public void Metoda() IB b = new B(); b.ZrobCos();
Wyjątki i obsługa błędów Wyjątek - mechanizm kontroli przepływu występujący w językach programowania i służący do obsługi zdarzeń wyjątkowych
Blok try catch try { // kod } catch // kod w razie wystąpienia wyjątku
try { //kod } catch (Exception e) Console.WriteLine(e.Message);
Właściwości klasy System.Exception Data Dodatkowe informacje na temat źródła wystąpienia wyjątku. HelpLink Umożliwia odczytanie lub ustawienie linka (np. do pomocy) związanego z błędem. Message Komunikat błędu. Source Umożliwia odczytanie lub przypisanie nazwy aplikacji lub obiektu, w którym wystąpił błąd. TargetSite Umożliwia odczytanie metody, w której wystąpił błąd.
Celowe wywołanie wyjątku try { throw new IndexOutOfRangeException(); } catch (Exception e) Console.WriteLine(e.Message);
„Najpopularniejsze” wyjątki StackOverflowException - nieskończona rekurencja OutOfMemoryExcetpion PathTooLongException DirectoryNotFoundException UnauthorizedAccessException FileNotFoundException ArgumentOutOfRangeException ArgumentNullException NullReferenceExcetpion IndexOutOfRangeException DivideByZeroException NotFiniteNumberException OverflowException NotImplementedException
Własna klasa na wyjątki – dziedziczy po System.Exception public class NaszWyjątekException : System.Exception { public NaszWyjątekException (string Message) : base(Message) this.Source = „Nasz własny wyjątek"; this.HelpLink = "http://google.pl/"; }
Połykanie kodu – źle! try { // warunek do sprawdzenia } catch(Exception e} { // brak jakiegokolwiek kodu }
Wyrzucanie ponowne – źle! try { // warunek do sprawdzenia } catch(Exception e) { // instrukcje do wykonania. throw e;// źle }
Wyrzucanie ponowne – dobrze! try { // warunek do sprawdzenia } catch(Exception e) { // instrukcje do wykonania. throw;// dobrze! }