Zaczynam projekt grupy szkolnej w Javie, używając Swinga. Jest to prosty GUI w aplikacji komputerowej Database.
Profesor dał nam kod zeszłorocznego projektu, abyśmy mogli zobaczyć, jak on to robi. Moje pierwsze wrażenie jest takie, że kod jest o wiele bardziej skomplikowany, niż powinien być, ale wyobrażam sobie, że programiści często myślą to, patrząc na kod, którego nie napisali.
Mam nadzieję znaleźć powody, dla których jego system jest dobry lub zły. (Zapytałem profesora, a on powiedział, że później zobaczę, dlaczego jest lepiej, co mnie nie satysfakcjonuje).
Zasadniczo, aby uniknąć sprzężenia jego trwałych obiektów, modeli (logiki biznesowej) i widoków, wszystko odbywa się za pomocą łańcuchów. Trwałymi obiektami, które są przechowywane w bazie danych, są tablice skrótów ciągów, a modele i widoki „subskrybują się”, zapewniając klucze łańcuchów dla „zdarzeń”, które subskrybują.
Po uruchomieniu zdarzenia widok lub model wysyła ciąg znaków do wszystkich swoich subskrybentów, którzy decydują, co zrobić dla tego zdarzenia. Na przykład w jednej z metod detektora akcji widoków (uważam, że ustawia to właściwość bicycleMakeField na obiekcie trwałym):
else if(evt.getSource() == bicycleMakeField)
{
myRegistry.updateSubscribers("BicycleMake", bicycleMakeField.getText());
}
To wywołanie ostatecznie trafia do tej metody w modelu pojazdu:
public void stateChangeRequest(String key, Object value) {
... bunch of else ifs ...
else
if (key.equals("BicycleMake") == true)
{
... do stuff ...
Profesor mówi, że ten sposób robienia rzeczy jest bardziej rozszerzalny i łatwiejszy w utrzymaniu niż widok wywołujący metodę na obiekcie logiki biznesowej. Mówi, że nie ma sprzężenia między poglądami a modelami, ponieważ nie znają się nawzajem.
Myślę, że jest to gorszy rodzaj sprzężenia, ponieważ widok i model muszą używać tych samych ciągów, aby działać. Jeśli usuniesz widok lub model lub popełnisz literówkę w ciągu, nie wystąpią błędy kompilacji. Dzięki temu kod jest o wiele dłuższy niż myślę, że powinien.
Chcę z nim o tym porozmawiać, ale wykorzystuje swoje doświadczenie branżowe, aby odeprzeć wszelkie argumenty, które ja, niedoświadczony student, mogę wysunąć. Czy jego podejście nie ma żadnej przewagi?
Aby to wyjaśnić, chcę porównać powyższe podejście, mając widok oczywiście połączony z modelem. Na przykład możesz przekazać obiekt modelu pojazdu do widoku, a następnie zmienić „markę” pojazdu, wykonaj następujące czynności:
vehicle.make = bicycleMakeField.getText();
Zmniejszyłoby to 15 wierszy kodu, które są obecnie WYŁĄCZNIE używane do ustawienia marki pojazdu w jednym miejscu, do jednego czytelnego wiersza kodu. (A ponieważ tego rodzaju operacje są wykonywane setki razy w całej aplikacji, myślę, że byłoby to duże zwycięstwo zarówno pod względem czytelności, jak i bezpieczeństwa.)
Aktualizacja
Mój lider zespołu i ja zrestrukturyzowaliśmy ramy w taki sposób, w jaki chcieliśmy to zrobić, używając pisania statycznego, poinformowaliśmy profesora i ostatecznie daliśmy mu wersję demonstracyjną. Jest wystarczająco hojny, aby pozwolić nam korzystać z naszego frameworka, dopóki nie poprosimy go o pomoc, i jeśli uda nam się przyspieszyć resztę naszego zespołu - co wydaje nam się sprawiedliwe.
Odpowiedzi:
Podejście zaproponowane przez profesora najlepiej opisać jako ściśle wpisane i jest błędne na prawie każdym poziomie.
Sprzężenie najlepiej redukuje się przez inwersję zależności , która robi to poprzez wysyłkę polimorficzną, a nie przez szereg przypadków.
źródło
Twój profesor robi to źle . Próbuje całkowicie oddzielić widok i model, zmuszając komunikację do podróżowania sznurkiem w obu kierunkach (widok nie zależy od modelu, a model nie zależy od widoku) , co całkowicie przeczy celowi projektowania obiektowego i MVC. Wygląda na to, że zamiast pisać klasy i projektować architekturę opartą na OO, pisze odmienne moduły, które po prostu reagują na wiadomości tekstowe.
Aby być nieco uczciwym wobec profesora, próbuje stworzyć w Javie coś, co wygląda bardzo podobnie do bardzo popularnego wywoływanego interfejsu .NET
INotifyPropertyChanged
, który powiadamia subskrybentów o zdarzeniach zmiany poprzez przekazywanie ciągów określających, która właściwość uległa zmianie. Przynajmniej w .NET interfejs ten służy przede wszystkim do komunikacji z modelu danych w celu przeglądania obiektów i elementów GUI (wiązanie).Jeśli zostanie to właściwie wykonane , przyjęcie tego podejścia może przynieść pewne korzyści¹:
events
ale w Javie trzeba tworzyć klasy lub obiekty i pisać kod subskrypcji / anulowania subskrypcji dla każdego zdarzenia.Krótko mówiąc (i aby odpowiedzieć na twoje pytanie), można to zrobić, ale podejście profesora jest winne za to, że nie pozwala się na przynajmniej jednokierunkową zależność między widokiem a modelem. To po prostu niepraktyczne, zbyt akademickie i nie jest dobrym przykładem tego, jak korzystać z dostępnych narzędzi.
¹ Wyszukaj w Google,
INotifyPropertyChanged
aby znaleźć milion sposobów, w jaki ludzie starają się unikać używania magicznego ciągu podczas jego wdrażania.źródło
enum
s (lubpublic static final String
s) powinny być używane zamiast magicznych strun.Twój profesor nie rozumie OO . Na przykład mając konkretną klasę pojazdu?
I żeby ta klasa zrozumiała markę roweru?
Pojazd powinien być abstrakcyjny i powinna istnieć konkretna podklasa o nazwie Bike. Będę grał według jego zasad, aby uzyskać dobrą ocenę, a potem zapomnę o jego nauczaniu.
źródło
Myślę, że masz rację, magiczne sznurki są złe. Ktoś z mojego zespołu musiał kilka dni temu debugować problem spowodowany przez łańcuchy magiczne (ale napisali to w swoim własnym kodzie, więc trzymałem gębę na kłódkę). I stało się ... w przemyśle !!
Zaletą tego podejścia jest to, że kodowanie może być szybsze, szczególnie w przypadku małych projektów demonstracyjnych. Wady polegają na tym, że jest on podatny na literówki i im częściej ciąg znaków jest używany, tym większa szansa, że kod literówki coś popsuje. A jeśli kiedykolwiek chcesz zmienić magiczny ciąg, refaktoryzacja ciągów jest trudniejsza niż zmiennych refaktoryzujących, ponieważ narzędzia refaktoryzujące mogą również dotykać ciągów zawierających magiczny ciąg (jako substring), ale same nie są magicznym ciągiem i nie należy ich dotykać. Ponadto może być konieczne prowadzenie słownika lub indeksu magicznych ciągów, aby nowi programiści nie zaczęli wymyślać nowych ciągów magicznych w tym samym celu i nie tracili czasu na wyszukiwanie istniejących ciągów magicznych.
W tym kontekście wygląda na to, że Enum może być lepszy niż ciągi magiczne lub nawet globalne stałe ciągów. Ponadto, IDE często daje ci jakąś pomoc w kodzie (autouzupełnianie, refaktoryzacja, znajdź wszystkie odniesienia itp.), Gdy używasz ciągów ciągłych lub Enums. IDE nie oferuje dużej pomocy, jeśli używasz magicznych ciągów.
źródło
Używanie „doświadczenia branżowego” jest słabym argumentem. Twój profesor musi wymyślić lepszy argument :-).
Jak powiedzieli inni, używanie magicznych strun jest z pewnością marne, używanie enum jest uważane za znacznie bezpieczniejsze. Wyliczenie ma znaczenie i zakres , magiczne ciągi nie. Poza tym sposób, w jaki twój profesor oddziela obawy, jest uważany za starszą i delikatniejszą technikę niż stosowana obecnie.
Widzę, że @backtodos to omówił, kiedy odpowiadałem na to pytanie, więc po prostu dodam swoją opinię, że używając Inversion of Control (którego forma jest zależna od Dependency Injection), już niedługo natkniesz się na środowisko Spring w branży. ..) jest uważany za bardziej nowoczesny sposób radzenia sobie z tego rodzaju oddzielaniem.
źródło
Powiedzmy sobie jasno; jest nieuchronnie pewne sprzężenie. Fakt, że istnieje taki subskrybowalny temat, jest punktem sprzęgającym, podobnie jak natura reszty wiadomości (takiej jak „pojedynczy ciąg”). Biorąc to pod uwagę, powstaje pytanie, czy sprzężenie jest większe, gdy wykonuje się je za pomocą łańcuchów lub typów. Odpowiedź zależy od tego, czy martwisz się sprzężeniem rozproszonym w czasie lub przestrzeni: czy wiadomości zostaną zachowane w pliku przed odczytaniem później, czy też zostaną wysłane do innego procesu (szczególnie jeśli to nie jest napisane w tym samym języku), użycie ciągów może znacznie uprościć sprawę. Minusem jest to, że nie ma sprawdzania typu; błędy zostaną wykryte dopiero w czasie wykonywania (a czasem nawet wtedy).
Strategią ograniczania zagrożeń / kompromisów w Javie może być użycie interfejsu maszynowego, ale ten interfejs maszynowy znajduje się w osobnym pakiecie. Powinien składać się tylko z Javy
interface
i podstawowych typów wartości (np. Wyliczenia, wyjątki). W tym pakiecie nie powinno być żadnych implementacji interfejsów. Następnie wszyscy stosują się do tego, a jeśli konieczne jest kompleksowe przesyłanie wiadomości, określona implementacja delegata Java może w razie potrzeby używać magicznych ciągów. Ułatwia także migrację kodu do bardziej profesjonalnego środowiska, takiego jak JEE lub OSGi, i znacznie pomaga w drobiazgach, takich jak testowanie…źródło
Twój profesor proponuje użycie „magicznych sznurków”. Chociaż „luźno łączy” klasy między sobą, umożliwiając łatwiejsze zmiany, jest anty-wzorcem; ciągi powinny być bardzo, BARDZO rzadko używane do przechowywania lub kontrolowania instrukcji kodu, a kiedy to absolutnie musi się zdarzyć, wartość ciągów, których używasz do sterowania logiką, powinna być centralnie i stale definiowana, więc JEDEN autorytet w zakresie oczekiwanej wartości dowolny konkretny ciąg.
Powód jest bardzo prosty; ciągi nie są sprawdzane przez kompilator pod kątem składni lub zgodności z innymi wartościami (w najlepszym wypadku sprawdzane są pod kątem prawidłowej formy dotyczącej znaków zmiany znaczenia i innego formatowania specyficznego dla języka w ciągu). Możesz umieścić w łańcuchu wszystko, co chcesz. Jako taki, możesz uzyskać beznadziejnie zepsuty algorytm / program do kompilacji, a błąd pojawia się dopiero po uruchomieniu. Rozważ następujący kod (C #):
... cały ten kod się kompiluje, za pierwszym razem, ale błąd jest oczywisty przy drugim spojrzeniu. Istnieje wiele przykładów tego rodzaju programowania i wszystkie one są obarczone tego rodzaju błędem, NAWET JEŚLI zdefiniujesz ciągi jako stałe (ponieważ stałe w .NET są zapisywane w manifeście każdej biblioteki, w której są używane, co musi następnie wszystkie zostaną ponownie skompilowane po zmianie zdefiniowanej wartości, aby wartość mogła zmienić się „globalnie”). Ponieważ błąd nie jest wychwytywany w czasie kompilacji, musi zostać wychwycony podczas testowania w czasie wykonywania, a jest to gwarantowane tylko przy 100% pokryciu kodu w testach jednostkowych z odpowiednim ćwiczeniem kodu, co zwykle można znaleźć tylko w najbardziej absolutnie bezpiecznym przypadku awarii , systemy czasu rzeczywistego.
źródło
W rzeczywistości klasy te są nadal ściśle powiązane. Tyle że teraz są ściśle powiązane w taki sposób, że kompilator nie może powiedzieć Ci, kiedy coś się zepsuło!
Jeśli ktoś zmieni „BicycleMake” na „BicyclesMake”, to nikt nie dowie się, że wszystko jest zepsute do czasu uruchomienia.
Błędy czasu wykonania są znacznie bardziej kosztowne do naprawienia niż błędy czasu kompilacji - to tylko wszelkiego rodzaju zło.
źródło