![]() |
![]() |
![]()
Post
#1
|
|
Grupa: Zarejestrowani Postów: 78 Pomógł: 5 Dołączył: 15.04.2006 Ostrzeżenie: (10%) ![]() ![]() |
Mam taki kod:
dostaje blad: Cytat Catchable fatal error: Object of class naglowki could not be converted to string in C:\xampplite\htdocs\gra\index.php on line 33 Probowalem na rozne sposoby, ale nie mam pojecia dlaczego taki blad (IMG:http://forum.php.pl/style_emoticons/default/sad.gif) |
|
|
![]() |
![]()
Post
#2
|
|
Grupa: Zarejestrowani Postów: 149 Pomógł: 12 Dołączył: 3.03.2008 Skąd: łódzkie Ostrzeżenie: (0%) ![]() ![]() |
Zapodam cały rozdział, bo ciekawy i najlepiej odda o co chodzi z tymi getterami i setterami.
Metody zwracające i ustawiające są złe Jak już wspomniałem, fundamentalną regułą rządzącą systemami obiektowymi jest ukrywanie przez obiekty ich szczegółów implementacyjnych. Stosowanie tej zasady umożliwia modyfikowanie implementacji obiektów bez konieczności wprowadzania zmian w kodzie, który te obiekty wykorzystuje. Oznacza to, że powinieneś unikać stosowania funkcji zwracających i ustawiających, które z jednej strony niczego nie ułatwiają, a jednocześnie zapewniają dostęp do szczegółów implementacyjnych (pól) w systemach obiektowych. Warto zwrócić uwagę na fakt, iż ani przykład systemu terminali ATM, ani przykład modelu ruchu ulicznego nie wykorzystywał metod zwracających i ustawiających pola obiektów. Nie twierdzę przy tym, że Twoje funkcje nigdy nie powinny zwracać wartości, lub że funkcji get i set nigdy nie należy stosować. Obiekty czasami po prostu muszą przekazywać swoje dane, aby cały system mógł funkcjonować prawidłowo. Tak czy inaczej, funkcje get i set często są wykorzystywane w sposób niewłaściwy, a więc w roli środków zapewniających dostęp do pól, które w przeciwnym razie byłyby prywatne, zatem ich używanie może prowadzić do poważnych utrudnień. To, co rozumiem przez właściwe zastosowania tych metod omówię na końcu tego podrozdziału. Obecność metod zwracających i ustawiających (nazywanych często akcesorami i mutatorami, choć zdarza się, że słowo akcesor jest używane w odniesieniu do obu tych metod) zwykle oznacza brak jasności oraz niewłaściwe podejście do rozwiązywanego problemu. Programiści często umieszczają te metody w definicjach klas, ponieważ najzwyczajniej w świecie chcą uniknąć myślenia o sposobie komunikowania się obiektów klas w czasie wykonywania systemu. Użycie metod zwracających pozwala odwlec moment analizy technik komunikacji do czasu właściwego kodowania. Takie podejście jest przejawem czystego lenistwa; z pewnością nie jest to oznaka „programowania dla elastyczności”. Przeanalizujmy banalny przykład, który dobrze pokazuje, dlaczego należy unikać metod zwracających. W Twoim programie może występować tysiąc wywołań metody getX(), z których każde zakłada, że wartość zwracana przez tę metodę należy do określonego typu. Wartość zwracana przez metodę getX() może być składowana np. w zmiennej lokalnej, zatem typ tej zmiennej musi odpowiadać typowi zwracanej wartości. Jeśli będziesz musiał zmienić implementację obiektu w taki sposób, że zmieni się typ zmiennej X, popadniesz w poważne tarapaty. Jeśli dotychczasowym typem zmiennej X był int, a nowym typem jest long, po wprowadzeniu odpowiedniej zmiany otrzymasz tysiąc błędów kompilacji. Jeśli rozwiążesz ten problem w sposób niewłaściwy, a więc zastosujesz operację rzutowania typu zwracanej wartości na typ int, kod będzie co prawda kompilowany, ale z pewnością nie będzie działał prawidłowo (zwracana wartość będzie obcinana). Aby uniknąć negatywnych skutków tej zmiany, będziesz musiał zmodyfikować kod otaczający każde z tysiąca wywołań metody getX(). Zdecydowanie nie chciałbym się znaleźć w podobnej sytuacji. Przyjrzyjmy się teraz klasie Money. Ponieważ klasa ta była początkowo pisana wyłącznie z myślą o obsłudze dolarów amerykańskich, zdefiniowano w niej metodę getValue(), która zwraca liczbę zmiennoprzecinkową typu double, oraz setValue(), która ustawia nową wartość. Pierwszy problem polega na tym, że takie rozwiązanie umożliwia zupełnie nonsensowne działania na reprezentowanych kwotach pieniężnych, co ilustruje poniższy fragment kodu: Kod Money a, b, c; // … a.setValue( b.getValue() * c.getValue() ); Co właściwie oznacza pomnożenie dwóch dolarów przez pięć dolarów? Drugi problem jest jeszcze poważniejszy: może zaistnieć konieczność wprowadzenia do aplikacji rozwiązań międzynarodowych, a więc umożliwiających między innymi obsługę wielu różnych walut. Taka zmiana będzie wymagała dodania pola nazwanego currency, które będzie miało wewnętrznie przypisywane takie wartości jak US_DOLLAR, YEN, LEU, ZLOTY czy HRYVNA. Stosunkowo drobna zmiana — wielkie problemy. Co w tej sytuacji będzie zwracała metoda getValue()? Metoda ta nie może po prostu zwracać wartości typu double, ponieważ sama wartość niewiele Ci mówi. Musisz przecież wiedzieć, z jaką walutą masz do czynienia. Nie jest też możliwe normalizowanie zwracanej wartości, np. do dolarów, ponieważ przeliczniki stosowane na rynku walutowym zmieniają się co minutę. Warto się też zastanowić, co należałoby zrobić z otrzymaną wartością. Nie można ich przecież tak po prostu wyświetlić, ponieważ wymagałoby to opatrzenia ich symbolem odpowiedniej waluty. Możesz oczywiście do istniejącej metody getValue() dołączyć nową metodę getCurrency(), ale od tej chwili cały kod wykorzystujący tę wartość będzie musiał dodatkowo pobierać informację o walucie i lokalnie normalizować uzyskiwane wartości do standardowej waluty. Powielenie tego rozwiązania w tysiącu miejsc istniejącego kodu źródłowego wymagałoby mnóstwa pracy. Musiałbyś też znaleźć wszystkie okna swojego systemu, w których wyświetlane są kwoty pieniężne — logika odpowiednich elementów graficznych musiałaby zostać przebudowana do postaci obsługującej różne waluty. Ta „prosta” zmiana błyskawicznie staje się źródłem ogromnych komplikacji. Oto kolejny przykład: przeanalizuj wszystkie problemy związane z polami System.in, System.out oraz System.err po wprowadzeniu do Javy klas Reader i Writer. Wszystkie trzy pola były publiczne, co samo w sobie było przekleństwem tego rozwiązania. Samo zastosowanie odpowiednich otoczek (np. metody System.getOut(), która zwracała pole System.out) oczywiście nie rozwiązywało problemu — pola System.out i System.err musiały być (opartymi na formacie Unicode) obiektami klasy Writer, nie (opartymi na formatowaniu bajtowym) obiektami klasy PrintStream. To samo dotyczyło pola System.in i klasy Reader. Zmiana zadeklarowanych typów obiektów zawierających pola System.out nie jest rozwiązaniem wystarczającym. Obiekty klasy Writer są wykorzystywane w nieco inny sposób niż strumienie wyjściowe. Oba mechanizmy różnią się przecież semantyką i udostępniają różne metody. W efekcie musisz więc zmienić (lub przynajmniej sprawdzić) cały kod otaczający, wykorzystujący pole System.out do wyświetlania na konsoli danych wyjściowych. Jeśli Twój program np. stosował formatowanie Unicode dla danych tekstowych wyświetlanych za pośrednictwem pola System. out, będziesz musiał użyć aż dwóch wywołań metody write() do zapisania na konsoli pojedynczego znaku. Co więcej, będziesz zmuszony do wprowadzenia do swojego programu dodatkowego kodu wyciągającego bardziej i mniej znaczące bajty znaków i wyświetlającego je osobno. Cały ten kod będzie jednak trzeba usunąć w wersji opartej na klasie Writer. Opisany problem jest efektem siły przyzwyczajenia. Kiedy programiści proceduralni zaczynają korzystać z Javy, próbują budować kod, który będzie choć trochę przypominał ich dotychczasowe dokonania. Języki proceduralne nie oferują możliwości 50 Wzorce projektowe. Analiza kodu sposobem na ich poznanie wykorzystywania klas, ale zawierają zwykle rozwiązania podobne do struktur języka C (czyli w praktyce klasy bez metod i z samymi polami publicznymi). Dla takich programistów naśladowanie struktur języka C jest zupełnie naturalne — próbują budować definicje klas pozbawionych metod i udostępniających wyłącznie publiczne pola. Kiedy taki programista proceduralny usłyszy gdzieś, że należy stosować pola prywatne, odpowiednio zmienia ich deklaracje i tworzy publiczne metody get i set. Takie rozwiązanie nie jest jednak niczym więcej niż niepotrzebnym komplikowaniem publicznego dostępu do danych. Programiści stosujący tego typu działania z pewnością nie tworzą systemów obiektowych. Programiści proceduralni będą argumentowali, że publiczne akcesory otaczające pola prywatne są o tyle „lepsze” od dostępnych bezpośrednio pól publicznych, że dają znacznie większą kontrolę nad operacjami modyfikowania wartości pól. Programista obiektowy stwierdzi natomiast, że każdy dostęp — kontrolowany czy nie — jest źródłem potencjalnych problemów konserwacyjnych. Dostęp kontrolowany może być co prawda lepszy od dostępu swobodnego, ale nie zmienia to faktu, że takie rozwiązanie jest z wielu względów niewłaściwe. Argument o wyższości akcesorów nad bezpośrednim dostępem w ogóle nie uwzględnia najważniejszej słabości obu rozwiązań — zdecydowana większość klas i tak nie potrzebuje metod akcesorów (ani mutatorów). Oznacza to, że dobrze zaprojektowany system przesyłania komunikatów (o sposobach jego projektowania za chwilę) w większości przypadków pozwala całkowicie wyeliminować metody get i set oraz w konsekwencji tworzyć klasy, których konserwacja będzie dużo łatwiejsza. Cd. w następnym poście. Nie twierdzę, że zwracanie wartości jest złe, lub że powinieneś wyeliminować ze swojego programu wszystkie metody get — to po prostu niemożliwe. Minimalizowanie udziału tego typu funkcji w definicjach klas spowoduje jednak znaczne ułatwienia w konserwacji kodu. Z czysto praktycznej perspektywy, częste stosowanie metod get i set sprawia, że kod jest nie tylko bardziej skomplikowany, ale także mniej elastyczny. Przeanalizuj typową „wszechmogącą” klasę proceduralną, która z innych obiektów zbiera informacje niezbędne do wykonania jakiegoś zadania. Implementacja tej klasy pełna jest wywołań zewnętrznych metod get. Co jednak powinieneś zrobić, kiedy okaże się, że odpowiednie zadania są już wykonywane przez jeden z obiektów udostępniających swoje dane tą drogą? Czy można przenieść kod wykonujący właściwe zadania z jednej, „wszechmogącej” klasy w miejsca, w których składowane są niezbędne dane? Wywołania akcesorów przestają być potrzebne, a cały kod jest dużo prostszy. Stosowanie metod get i set powoduje też, że program staje się nieelastyczny (nie można w jego ramach łatwo implementować nowych wymagań biznesowych) i wyjątkowo trudny w konserwacji. Prawdopodobnie najważniejszą zasadą systemów obiektowych jest abstrakcja danych, a więc ścisłe ukrywanie implementacji mechanizmów obsługi komunikatów przed innymi obiektami. To tylko jeden z powodów, dla których wszystkie Twoje zmienne klasowe (pola klasy niebędące stałymi) powinny być prywatne (deklarowane ze słowem kluczowym private). Jeśli stworzysz publiczną zmienną klasową, nie będziesz mógł zmieniać tego pola wraz z ewolucją całej klasy, ponieważ w ten sposób uniemożliwiłbyś prawidłowe funkcjonowanie kodu zewnętrznego, który z tego pola korzysta. Z pewnością nie masz ochoty na przeszukiwanie tysiąca zastosowań jakiejś klasy tylko dlatego, że wprowadziłeś drobną zmianę w jej definicji. Bezmyślne stosowanie metod zwracających i ustawiających jest niebezpieczne z tych samych względów, które decydują o zagrożeniach związanych z używaniem pól publicznych — także metody get i set zapewniają zewnętrzny dostęp do szczegółów implementacyjnych. Co będzie, jeśli zostaniesz zmuszony do zmiany typu udostępnianego w ten sposób pola? Przecież będziesz wówczas musiał zmienić także typ zwracany przez metodę akcesora. Jeśli wartość ta jest używana w wielu miejscach, będziesz musiał zmienić odpowiednie fragmenty w całym kodzie. Ja wolałbym jednak ograniczyć zakres koniecznych zmian do definicji pojedynczej klasy. Nie chcę, by prosta modyfikacja przenosiła się na cały program. Na podstawie reguły ukrywania szczegółów implementacji można stworzyć wiarygodny test systemów obiektowych. Czy możesz dokonywać istotnych zmian w definicji pojedynczej klasy (włącznie z wyrzuceniem całych fragmentów kodu i wstawieniem w ich miejsce zupełnie nowej implementacji) bez konieczności modyfikowania kodu wykorzystującego obiekty tej klasy? Tak głęboka modularyzacja oprogramowania bardzo ułatwia konserwację oprogramowania i pełni zasadniczą funkcję w ocenie jego obiektowości. Nieprzestrzeganie zasady ukrywania szczegółów implementacji bardzo ogranicza możliwość stosowania pozostałych mechanizmów obiektowych. Ponieważ metody akcesorów naruszają regułę hermetyzacji, można bez trudu wykazać, że systemy, w których często lub niewłaściwie stosuje się tego typu rozwiązania po prostu nie są systemami obiektowymi. Co więcej, jeśli skrupulatnie przeprowadzisz proces projektowania (w przeciwieństwie do kodowania ad hoc), szybko stwierdzisz, że Twój program nie musi zawierać niemal żadnych metod akcesorów. Proces ten jest więc bardzo ważny. Zapewne zauważyłeś, że w przedstawionym przykładzie modelowania ruchu ulicznego w ogóle nie stosowano metod zwracających i ustawiających. Obiekty klasy Car nie udostępniają metody getSpeed(), także obiekty klasy Road nie definiują metody getAverageSpeed(). Nie potrzebujemy metod getLocation() czy setLocation() w obiektach klasy Car, ponieważ składujemy informacje o lokalizacji pojazdów w obiektach klasy Road reprezentujących odcinki dróg, na których te pojazdy aktualnie przebywają. Nie potrzebujemy też metody setAverageSpeed() w obiektach klasy Road, ponieważ obiekty te same obliczają średnią prędkość pojazdów. Brak metod zwracających i ustawiających nie oznacza, że jakieś potrzebne dane nie mogą być przekazywane pomiędzy poszczególnymi modułami tego systemu — przykładowo, obiekt Road przekazuje informacje o lokalizacji do obiektów klasy Car. Tak czy inaczej, podczas projektowania systemów obiektowych należy możliwie głęboko minimalizować przepływy danych. Doskonałym papierkiem lakmusowym wskazującym na „słuszność” zastosowanych mechanizmów jest następująca reguła: Nie proś obiektu o informacje, których potrzebujesz do wykonania jakiejś czynności; zamiast tego proś obiekt zawierający te informacje o wykonanie tych czynności za Ciebie. Przykładowo, nie powinieneś stosować przedstawionego wcześniej wyrażenia: Kod Money a, b, c; // … a.setValue( b.getValue() * c.getValue() ); Zamiast tego zażądaj od obiektu klasy Money wykonania odpowiednich działań za Ciebie: Kod Money a, b, c; // … a.increaseBy( b ); Nie mówisz już: „daj mi ten atrybut, abym mógł go wyświetlić”. Teraz żądasz czegoś zupełnie innego: „daj mi możliwą do wyświetlenia wizualizację tego atrybutu” lub „wyświetl swoją zawartość”. Kolejnym wyznacznikiem realizacji tej reguły jest szczegółowość operacji. Operacje obszerne sprowadzają się do jednorazowego żądania od obiektów wykonania dużych zadań. Szczegółowe operacje żądają od obiektów realizacji stosunkowo niewielkich zadań. Ogólnie, wolę obszerne metody, ponieważ ich stosowanie upraszcza kod i w wielu przypadkach eliminuje konieczność stosowania metod zwracających i ustawiających. Metody akcesorów i mutatorów można eliminować dopiero na etapie budowy modelu systemu, ponieważ bez dobrze przemyślanego modelu dynamicznego wiarygodne przewidywanie przyszłych mechanizmów komunikacji obiektów klas jest po prostu niemożliwe. Oznacza to, że w przypadku braku tego modelu musisz zapewniać jak najwięcej technik udostępniania danych, ponieważ nie jesteś w stanie przewidzieć, które z tych technik faktycznie będą konieczne. Taka strategia projektowania przez zgadywanie jest w najlepszym przypadku nieefektywna, ponieważ w praktyce oznacza stratę czasu na pisanie niepotrzebnych metod (lub dodawanie zbędnych mechanizmów do implementowanych klas). Jeśli postępujesz w myśl zasady, że w pierwszej kolejności należy budować model statyczny, musisz być przygotowany na to, że stracisz mnóstwo czasu na tworzenie nieprzydatnych lub zbyt elastycznych metod. Co więcej, jeśli niewłaściwy model statyczny wymusi zbyt duże nakłady na tworzenie niepotrzebnych rozwiązań, cały projekt może się zakończyć niepowodzeniem; a jeśli mimo to zdecydujesz się na jego kontynuowanie, koszt konserwacji kodu będzie przewyższał koszt jego ponownego napisania. Wróćmy teraz do przykładu modelowania ruchu ulicznego, w którym użyłem modelu statycznego do analizy relacji odkrytych w fazie planowania systemu przesyłania komunikatów. Nie projektowałem modelu statycznego, by później próbować na jego podstawie budować model dynamiczny (z uwzględnieniem ograniczeń narzuconych przez przygotowany wcześniej model statyczny). Uważne projektując i skupiając się na tym, co chcesz osiągnąć (nie na tym, jak to zrobić), możesz wyeliminować ze swojego programu znaczną część metod zwracających i ustawiających. Treść znajduje się w pierwszym rozdziale książki, a ten jest dostępny jako przykładowy na stronie Heliona (IMG:http://forum.php.pl/style_emoticons/default/smile.gif) |
|
|
![]() ![]() |
![]() |
Aktualny czas: 9.10.2025 - 00:45 |