Rozumiem pojęcie obiektu i jako programista Java czuję, że paradygmat OO przychodzi mi w praktyce dość naturalnie.
Jednak ostatnio pomyślałem:
Poczekaj chwilę, jakie są praktyczne korzyści z używania obiektu w porównaniu z użyciem klasy statycznej (z odpowiednimi praktykami enkapsulacji i OO)?
Mógłbym wymyślić dwie zalety używania obiektu (oba są znaczące i potężne):
Polimorfizm: pozwala dynamicznie i elastycznie zamieniać funkcjonalność w czasie wykonywania. Pozwala również łatwo dodawać nowe funkcje „części” i alternatywy do systemu. Na przykład, jeśli istnieje
Car
klasa zaprojektowana do pracy zEngine
obiektami i chcesz dodać nowy silnik do systemu, z którego może korzystać samochód, możesz utworzyć nowąEngine
podklasę i po prostu przekazać obiekt tej klasy doCar
obiektu, bez konieczności zmienić cokolwiek na tematCar
. Możesz to zrobić podczas działania.Będąc w stanie „przekazywać funkcjonalność”: możesz dynamicznie przekazywać obiekt wokół systemu.
Ale czy są jakieś przewagi obiektów nad klasami statycznymi?
Często, gdy dodam nowe „części” do systemu, robię to, tworząc nową klasę i tworząc z niej obiekty.
Ale ostatnio, kiedy zatrzymałem się i pomyślałem o tym, zdałem sobie sprawę, że klasa statyczna zrobiłaby to samo co obiekt, w wielu miejscach, w których normalnie używam obiektu.
Na przykład pracuję nad dodaniem mechanizmu zapisywania / ładowania plików do mojej aplikacji.
W przypadku obiektu linia wywołująca kodu będzie wyglądać następująco: Thing thing = fileLoader.load(file);
W przypadku klasy statycznej wyglądałoby to tak: Thing thing = FileLoader.load(file);
Co za różnica?
Dość często nie potrafię wymyślić żadnego powodu, aby utworzyć obiekt, gdy zwykła klasa statyczna działałaby tak samo. Ale w systemach OO klasy statyczne są dość rzadkie. Więc coś mi brakuje.
Czy są jeszcze jakieś zalety innych obiektów niż te, które wymieniłem? Proszę wytłumacz.
EDYCJA: aby wyjaśnić. Uważam, że obiekty są bardzo przydatne podczas wymiany funkcji lub przekazywania danych. Na przykład napisałem aplikację, która tworzy melodie. MelodyGenerator
miał kilka podklas, które inaczej tworzą melodie, a obiekty tych klas były wymienne (wzorzec strategii).
Melodie też były przedmiotami, ponieważ warto je przekazywać. Podobnie jak Akordy i Łuski.
Ale co z „statycznymi” częściami systemu - które nie zostaną przekazane? Na przykład - mechanizm „zapisz plik”. Dlaczego powinienem zaimplementować go w obiekcie, a nie w klasie statycznej?
źródło
Thing
?FileLoader
na czytający z gniazda? A może próbny test? Lub taki, który otwiera plik zip?System.Math
w .NET jest przykładem czegoś, co ma o wiele większy sens jako klasa statyczna: nigdy nie będziesz musiał go wymieniać ani wyśmiewać i żadna z operacji nie mogłaby logicznie stać się częścią instancji. Naprawdę nie sądzę, że twój przykład „oszczędzania” pasuje do tego rachunku.Odpowiedzi:
lol brzmisz jak zespół, nad którym kiedyś pracowałem;)
Java (i prawdopodobnie C #) z pewnością obsługuje ten styl programowania. Pracuję z ludźmi, których pierwszym instynktem jest: „Mogę zrobić to metodą statyczną!” Ale z czasem pojawiają się pewne subtelne koszty.
1) Java jest językiem obiektowym. I to doprowadza funkcjonalnych facetów do szaleństwa, ale tak naprawdę dobrze sobie radzi. Ideą OO jest połączenie funkcjonalności ze stanem w celu uzyskania małych jednostek danych i funkcjonalności, które zachowują ich semantykę poprzez ukrywanie stanu i ujawnianie tylko funkcji, które mają sens w tym kontekście.
Przechodząc do klasy tylko metodami statycznymi, łamiesz część „stanu” równania. Ale państwo wciąż musi gdzieś mieszkać. Z czasem widziałem, że klasa ze wszystkimi metodami statycznymi zaczyna mieć coraz bardziej skomplikowane listy parametrów, ponieważ stan przechodzi z klasy do wywołań funkcji.
Po utworzeniu klasy ze wszystkimi metodami statycznymi, przejrzyj i po prostu sprawdź, ile z tych metod ma jeden wspólny parametr. Jest wskazówką, że ten parametr powinien być albo klasą zawierającą te funkcje, albo parametr ten powinien być atrybutem instancji.
2) Zasady OO są dość dobrze zrozumiane. Po chwili możesz spojrzeć na projekt klasy i sprawdzić, czy spełnia on kryteria takie jak SOLID. Po wielu testach jednostek ćwiczeniowych dobrze rozumiesz, co sprawia, że klasa jest „odpowiedniej wielkości” i „spójna”. Ale nie ma dobrych reguł dla klasy ze wszystkimi metodami statycznymi i nie ma żadnego prawdziwego powodu, dla którego nie należy po prostu pakować w nią wszystkiego. Klasa jest otwarta w twoim edytorze, więc do cholery? Po prostu dodaj tam swoją nową metodę. Po pewnym czasie twoja aplikacja zamienia się w szereg konkurujących „Boskich obiektów”, z których każdy próbuje zdominować świat. Ponownie, przekształcenie ich w mniejsze jednostki jest wysoce subiektywne i trudno stwierdzić, czy masz rację.
3) Interfejsy są jedną z najpotężniejszych funkcji Java. Dziedziczenie klas okazało się problematyczne, ale programowanie za pomocą interfejsów pozostaje jedną z najpotężniejszych sztuczek języka. (ditto C #) Klasy całkowicie statyczne nie mogą wejść w ten model.
4) Zatrzaskuje drzwi przed ważnymi technikami OO, z których nie możesz skorzystać. Możesz więc pracować przez lata z młotkiem w skrzynce z narzędziami, nie zdając sobie sprawy, o ile łatwiej byłoby, gdybyś miał także śrubokręt.
4.5) Tworzy najtrudniejsze, najbardziej niezniszczalne zależności czas kompilacji. Tak więc, na przykład, jeśli masz,
FileSystem.saveFile()
nie ma możliwości, aby to zmienić, poza udawaniem JVM w czasie wykonywania. Co oznacza, że każda klasa, która odwołuje się do twojej klasy funkcji statycznej, ma twardą zależność od czasu kompilacji od tej konkretnej implementacji, co sprawia, że rozszerzenie jest prawie niemożliwe i ogromnie komplikuje testowanie. Możesz przetestować klasę statyczną w izolacji, ale bardzo trudno jest przetestować klasy, które odnoszą się do tej klasy w izolacji.5) Doprowadzisz swoich pracowników do szaleństwa. Większość profesjonalistów, z którymi współpracuję, poważnie podchodzi do swojego kodu i zwraca uwagę na co najmniej pewien poziom zasad projektowania. Odkładając na bok rdzeń języka, zmuszą go do wyciągania włosów, ponieważ będą ciągle poprawiać kod.
Kiedy jestem w języku, zawsze staram się dobrze go używać. Na przykład, kiedy jestem w Javie, używam dobrego projektu OO, ponieważ wtedy naprawdę wykorzystuję język do tego, co robi. Kiedy jestem w Pythonie, mieszam funkcje na poziomie modułu z klasami okazjonalnymi - mogłem pisać klasy tylko w Pythonie, ale myślę, że nie użyłbym języka do tego, w czym jest dobry.
Inną taktyką jest złe używanie języka, a oni narzekają na wszystkie problemy, które powoduje. Ale dotyczy to praktycznie każdej technologii.
Kluczową funkcją Java jest zarządzanie złożonością w małych, testowalnych jednostkach, które łączą się ze sobą, dzięki czemu są łatwe do zrozumienia. Java kładzie nacisk na jasne definicje interfejsu niezależnie od implementacji - co jest ogromną korzyścią. Dlatego właśnie (i inne podobne języki OO) są tak szeroko stosowane. Mimo całej gadatliwości i rytualizmu, kiedy skończę z dużą aplikacją Java, zawsze mam wrażenie, że pomysły są bardziej przejrzyste w kodzie niż moje projekty w bardziej dynamicznym języku.
Ale to trudne. Widziałem, jak ludzie dostają błąd „całkowicie statyczny”, i ciężko jest im z tego zrezygnować. Ale widziałem, jak odczuwają wielką ulgę, gdy się z tym pogodzą.
źródło
Zapytałeś:
Przed tym pytaniem wymieniono polimorfizm i przekazano go jako dwie zalety korzystania z obiektów. Chcę powiedzieć, że takie są cechy paradygmatu OO. Enkapsulacja to kolejna cecha paradygmatu OO.
Nie są to jednak korzyści. Korzyści to:
Powiedziałeś:
Myślę, że masz rację. U podstaw programowania leży wyłącznie transformacja danych i tworzenie efektów ubocznych opartych na danych. Czasami przekształcanie danych wymaga danych pomocniczych. Innym razem tak nie jest.
Gdy mamy do czynienia z pierwszą kategorią transformacji, dane pomocnicze należy albo przekazać jako dane wejściowe, albo gdzieś przechowywać. Obiekt to lepsze podejście niż klasy statyczne dla takich przekształceń. Obiekt może przechowywać dane pomocnicze i wykorzystywać je we właściwym czasie.
W przypadku drugiej kategorii transformacji klasa statyczna jest tak dobra jak obiekt, jeśli nie lepsza. Funkcje matematyczne są klasycznymi przykładami tej kategorii. Większość standardowych funkcji biblioteki C również należy do tej kategorii.
Zapytałeś:
Jeśli
FileLoader
nie trzeba nigdy przechowywać żadnych danych, wybrałbym drugie podejście. Jeśli istnieje niewielka szansa, że do wykonania operacji będą potrzebne dane pomocnicze, pierwsze podejście jest bezpieczniejsze.Zapytałeś:
Wymieniłem zalety (zalety) stosowania paradygmatu OO. Mam nadzieję, że są zrozumiałe. Jeśli nie, chętnie to rozwinę.
Zapytałeś:
Jest to przykład, w którym klasa statyczna po prostu nie działa. Istnieje wiele sposobów zapisywania danych aplikacji w pliku:
Jedynym sposobem na zapewnienie takich opcji jest utworzenie interfejsu i obiektów, które implementują interfejs.
Podsumowując
Zastosuj odpowiednie podejście do danego problemu. Lepiej nie być religijnym w stosunku do jednego podejścia do drugiego.
źródło
Melody
,Chord
,Improvisations
,SoundSample
i inne), to będzie ekonomiczny uprościć wdrażanie poprzez abstrakcje.Czasami zależy to od języka i kontekstu. Na przykład
PHP
skrypty używane do obsługi żądań zawsze mają jedno żądanie obsługi i jedną odpowiedź do wygenerowania przez cały okres istnienia skryptu, więc statyczne metody działania na żądanie i wygenerowania odpowiedzi mogą być odpowiednie. Ale na serwerze, na którym napisanoNode.js
, może być wiele różnych żądań i odpowiedzi jednocześnie. Pierwsze pytanie brzmi: czy jesteś pewien, że klasa statyczna naprawdę odpowiada obiektowi singletonowemu?Po drugie, nawet jeśli masz singletony, używanie obiektów pozwala wykorzystać polimorfizm przy użyciu technik takich jak Dependency_injection i Factory_method_pattern . Jest to często używane w różnych wzorcach Inversion_of_control i przydatne do tworzenia próbnych obiektów do testowania, rejestrowania itp.
Wspomniałeś o zaletach powyżej, więc oto jedna, której nie zrobiłeś: dziedziczenie. Wiele języków nie ma możliwości zastąpienia metod statycznych w taki sam sposób, w jaki można zastąpić metody instancji. Ogólnie rzecz biorąc, znacznie trudniej jest dziedziczyć i zastępować metody statyczne.
źródło
Oprócz postu Roba Y.
Tak długo, jak funkcjonalność twojego
load(File file)
może być wyraźnie oddzielona od wszystkich innych funkcji, których używasz, możesz używać statycznej metody / klasy. Eksternalizujesz stan (co nie jest złe), a także nie dostajesz redundancji, ponieważ możesz na przykład częściowo zastosować lub curry swoją funkcję, abyś nie musiał się powtarzać. (to w rzeczywistości jest takie samo lub podobne do używaniafactory
wzorca)Jednak gdy tylko dwie z tych funkcji zaczną mieć wspólne zastosowanie, chcesz mieć możliwość ich grupowania. Wyobraź sobie, że masz nie tylko
load
funkcję, ale takżehasValidSyntax
funkcję. Co zrobisz?Widzisz dwa odniesienia
myfile
tutaj? Zaczynasz się powtarzać, ponieważ twój stan zewnętrzny musi być przekazywany dla każdego połączenia. Rob Y opisał sposób internalizacji stanu (tutajfile
), abyś mógł to zrobić w następujący sposób:źródło
hasValidSyntax()
iload()
nie może ponownie użyć stanu (wynik częściowo przeanalizowanego pliku).Głównym problemem podczas korzystania z klas statycznych jest to, że zmuszają cię do ukrycia zależności i zmuszają cię do uzależnienia od implementacji. W poniższej sygnaturze konstruktora, jakie są twoje zależności:
Sądząc po podpisie, osoba potrzebuje tylko imienia, prawda? Cóż, nie jeśli implementacja jest:
Więc osoba nie jest tylko instancją. W rzeczywistości pobieramy dane z bazy danych, co daje nam ścieżkę do pliku, który następnie musi zostać przeanalizowany, a następnie rzeczywiste dane, które chcemy, muszą zostać usunięte. Ten przykład jest wyraźnie przesadzony, ale dowodzi tego. Ale czekaj, może być o wiele więcej! Piszę następujący test:
To powinno jednak minąć, prawda? Mam na myśli, że zamknęliśmy wszystkie inne rzeczy, prawda? Właściwie to wysadza w powietrze wyjątek. Dlaczego? Och, tak, ukryte zależności, o których nie wiedzieliśmy, mają stan globalny. Do
DBConnection
potrzeb zainicjalizowany i połączone. DoFileLoader
potrzeb inicjalizowane naFileFormat
przedmiot (npXMLFileFormat
lubCSVFileFormat
). Nigdy o nich nie słyszałeś? O to właśnie chodzi. Twój kod (i kompilator) nie może ci powiedzieć, że potrzebujesz tych rzeczy, ponieważ wywołania statyczne ukrywają te zależności. Czy powiedziałem test? Miałem na myśli, że nowy młodszy programista właśnie dostarczył coś takiego w swoim najnowszym wydaniu. W końcu skompilowane = działa, prawda?Ponadto powiedz, że korzystasz z systemu, na którym nie działa instancja MySQL. Lub powiedzmy, że korzystasz z systemu Windows, ale twoja
DBConnection
klasa wychodzi tylko na serwer MySQL w systemie Linux (ze ścieżkami systemu Linux). Lub powiedzmy, że jesteś w systemie, w którym ścieżka zwrócona przezDBConnection
nie jest dla ciebie do odczytu / zapisu. Oznacza to, że próba uruchomienia lub przetestowania tego systemu w którymkolwiek z tych warunków zakończy się niepowodzeniem, nie z powodu błędu kodu, ale z powodu błędu projektu, który ogranicza elastyczność kodu i wiąże cię z implementacją.Powiedzmy, że chcemy rejestrować każde wywołanie bazy danych dla jednej konkretnej
Person
instancji, która przechodzi pewną, kłopotliwą ścieżkę. MoglibyśmyDBConnection
logować się , ale to wszystko rejestruje , wprowadzając dużo bałaganu i utrudniając rozróżnienie konkretnej ścieżki kodu, którą chcemy śledzić. Jeśli jednak używamy wstrzykiwania zależności zDBConnection
instancją, moglibyśmy po prostu zaimplementować interfejs w dekoratorze (lub rozszerzyć klasę, ponieważ mamy obie opcje dostępne dla obiektu). Dzięki klasie statycznej nie możemy wstrzyknąć zależności, nie możemy zaimplementować interfejsu, nie możemy zawinąć go w dekorator i nie możemy rozszerzyć klasy. Możemy to nazwać tylko bezpośrednio, gdzieś głęboko ukryte w naszym kodzie. W ten sposób jesteśmy zmuszeni mieć ukrytą zależność od implementacji.Czy to zawsze jest złe? Niekoniecznie, ale może być lepiej odwrócić swój punkt widzenia i powiedzieć: „Czy istnieje dobry powód, aby to nie był przypadek?” zamiast „Czy jest dobry powód, aby to była instancja?” Jeśli naprawdę możesz powiedzieć, że Twój kod będzie równie niezachwiany (i bezstanowy), jak
Math.abs()
zarówno pod względem jego implementacji, jak i sposobu, w jaki zostanie użyty, możesz rozważyć ustawienie go jako statycznego. Posiadanie instancji w stosunku do klas statycznych daje jednak świat elastyczności, który nie zawsze jest łatwy do odzyskania po fakcie. Może także dać ci większą jasność co do prawdziwej natury zależności twojego kodu.źródło
new
utworzyć kilka obiektów w konstruktorze (co nie jest ogólnie wskazane), ale mogę je również wstrzyknąć. W przypadku klas statycznych nie ma możliwości ich wstrzyknięcia (zresztą i tak nie jest to łatwe w Javie, i to byłaby zupełnie inna dyskusja dla języków, które na to pozwalają), więc nie ma wyboru. Dlatego w mojej odpowiedzi podkreślam pomysł bycia zmuszonym do ukrywania zależności tak mocno.Class
, wtedy upewniamy się, że jest to odpowiednia klasa) lub piszemy na klawiaturze (upewniamy się, że odpowiada na właściwą metodę). Jeśli chcesz to zrobić, powinieneś ponownie ocenić swój wybór języka. Jeśli chcesz zrobić to pierwsze, instancje są w porządku i są wbudowane.Tylko moje dwa centy.
Na twoje pytanie:
Możesz zadać sobie pytanie, czy mechanizm części systemu, która odtwarza dźwięki, czy części systemu, która zapisuje dane do pliku, ZMIENI SIĘ . Jeśli Twoja odpowiedź brzmi „tak”, oznacza to, że powinieneś je streścić za pomocą
abstract class
/interface
. Możesz zapytać, skąd mogę wiedzieć o przyszłości? Zdecydowanie nie możemy. Tak więc, jeśli rzecz jest bezstanowa, możesz użyć „klasy statycznej”, na przykład wjava.lang.Math
przypadku podejścia obiektowego.źródło