Załóżmy, że mam niestandardowy obiekt, Student :
public class Student{
public int _id;
public String name;
public int age;
public float score;
}
Oraz klasa Window , która służy do wyświetlania informacji o Uczniu :
public class Window{
public void showInfo(Student student);
}
Wygląda całkiem normalnie, ale stwierdziłem, że Window nie jest dość łatwy do przetestowania indywidualnie, ponieważ do wywołania funkcji potrzebny jest prawdziwy obiekt Studenta . Próbuję więc zmodyfikować showInfo, aby nie akceptował bezpośrednio obiektu Studenta :
public void showInfo(int _id, String name, int age, float score);
aby łatwiej było testować Window indywidualnie:
showInfo(123, "abc", 45, 6.7);
Ale odkryłem, że zmodyfikowana wersja ma inne problemy:
Zmodyfikuj ucznia (np .: dodaj nowe właściwości) wymaga zmodyfikowania podpisu metody showInfo
Gdyby Student miał wiele właściwości, sygnatura metody Studenta byłaby bardzo długa.
Tak więc, używając niestandardowych obiektów jako parametru lub przyjmując każdą właściwość w obiektach jako parametr, która z nich jest łatwiejsza do utrzymania?
showInfo
wymaga prawdziwego Stringa, prawdziwego float i dwóch prawdziwych int. W jaki sposób dostarczanie prawdziwegoString
obiektu jest lepsze niż dostarczanie prawdziwegoStudent
obiektu?int
parametry. W witrynie połączeń nie ma weryfikacji, czy faktycznie przekazujesz je w odpowiedniej kolejności. Co jeśli zamieniszid
iage
, lubfirstName
ilastName
? Wprowadzasz potencjalny punkt awarii, który może być bardzo trudny do wykrycia, dopóki nie pojawi się na twojej twarzy, i dodajesz go na każdej stronie połączeń .showForm(bool, bool, bool, bool, int)
metoda - Kocham tych ...Odpowiedzi:
Używanie obiektu niestandardowego do grupowania powiązanych parametrów jest w rzeczywistości zalecanym wzorcem. W ramach refaktoryzacji nazywa się to Obiektem parametrów .
Twój problem leży gdzie indziej. Po pierwsze, ogólne nie
Window
powinny wiedzieć nic o uczniu. Zamiast tego powinieneś mieć coś,StudentWindow
co wie tylko o wyświetlaniuStudents
. Po drugie, absolutnie nie ma problemu z tworzeniemStudent
instancji do testowania,StudentWindow
o ileStudent
nie zawiera ona złożonej logiki, która drastycznie skomplikowałaby testowanieStudentWindow
. Jeśli ma taką logikęStudent
, należy preferować tworzenie interfejsu i kpiny.źródło
Student
Ugrupowanie ma sens i jest prawdopodobne, aby pojawić się w innych obszarach aplikacji.Student
. Byłby to Zachowaj cały obiekta.b.c
jeśli twoja metoda bierzea
. Jeśli twoja metoda dojdzie do punktu, w którym musisz mieć mniej więcej 4 parametry lub 2 poziomy głębokości przystąpienia do nieruchomości, prawdopodobnie należy ją rozważyć. Należy również pamiętać, że jest to wskazówka - podobnie jak wszystkie inne wytyczne, wymaga uznania użytkownika. Nie podążaj za nim na ślepo.Mówisz, że to
Ale możesz po prostu utworzyć obiekt studencki, aby przejść do okna:
Dzwonienie nie wydaje się bardziej skomplikowane.
źródło
Student
odnosi się do aUniversity
, który odnosi się do wieluFaculty
s iCampus
s, zProfessor
si iBuilding
s, z których żadenshowInfo
tak naprawdę nie korzysta, ale nie zdefiniowałeś żadnego interfejsu, który pozwalałby testom na „poznanie” tego i dostarczenie tylko odpowiedniego ucznia dane, bez budowania całej organizacji. PrzykłademStudent
jest zwykły obiekt danych i, jak mówisz, testy powinny być z nim zadowolone.W kategoriach laika:
Edytować:
Jak stwierdza @ Tom.Bowen89 , testowanie metody showInfo nie jest dużo bardziej skomplikowane:
źródło
źródło
Steve McConnell w Code Complete zajął się tym problemem, omawiając zalety i wady przekazywania obiektów do metod zamiast korzystania z właściwości.
Wybacz mi, jeśli pomylę niektóre szczegóły, pracuję z pamięci, ponieważ minął ponad rok, odkąd miałem dostęp do książki:
Dochodzi do wniosku, że lepiej nie używać obiektu, zamiast tego wysyłać tylko te właściwości, które są absolutnie niezbędne dla metody. Metoda nie powinna wiedzieć nic o obiekcie poza właściwościami, których będzie używał w ramach swoich operacji. Z czasem, jeśli obiekt zostanie kiedykolwiek zmieniony, może to mieć niezamierzone konsekwencje dla metody używającej obiektu.
Zwrócił również uwagę, że jeśli skończysz na metodzie, która akceptuje wiele różnych argumentów, to prawdopodobnie jest to znak, że metoda robi zbyt wiele i powinna zostać podzielona na więcej, mniejszych metod.
Czasami jednak czasami potrzebujesz wielu parametrów. Podany przez niego przykład dotyczy metody, która buduje pełny adres przy użyciu wielu różnych właściwości adresu (choć można to obejść, używając tablicy ciągów, gdy się nad tym zastanowić).
źródło
Student
w tym przypadku). I w ten sposób testowanie wpływa na projekt , całkowicie obejmując większość głosów przy zachowaniu integralności projektu.Znacznie łatwiej jest pisać i czytać testy, jeśli zdasz cały obiekt:
Dla porownania,
wiersz można zapisać, jeśli wartości są przekazywane osobno, jako:
gdzie rzeczywiste wywołanie metody jest gdzieś zakopane
Mówiąc o tym, że nie można umieścić rzeczywistego wywołania metody w teście, jest to znak, że interfejs API jest zły.
źródło
Powinieneś przekazać to, co ma sens, kilka pomysłów:
Łatwiej przetestować. Jeśli trzeba edytować obiekty, co wymaga najmniejszego refaktoryzacji? Czy ponowne użycie tej funkcji do innych celów jest przydatne? Jaka jest najmniejsza ilość informacji potrzebnych do nadania tej funkcji, aby spełniała swoje zadanie? (Po rozbiciu go - może to pozwolić na ponowne użycie tego kodu - należy uważać, aby nie upaść przy projektowaniu tej funkcji, a następnie wąskim gardłem wszystkiego, aby korzystać wyłącznie z tego obiektu).
Wszystkie te zasady programowania są jedynie wskazówkami, które pomogą ci myśleć we właściwym kierunku. Po prostu nie buduj bestii kodowej - jeśli nie masz pewności i chcesz kontynuować, wybierz kierunek / swój własny lub sugestię tutaj, a jeśli osiągniesz punkt, w którym myślisz „och, powinienem to zrobić sposób ”- prawdopodobnie możesz łatwo wrócić i przebudować go z łatwością. (Na przykład, jeśli masz klasę Nauczyciela - potrzebuje tylko tej samej właściwości, co Student, i zmieniasz funkcję, aby zaakceptować dowolny obiekt formularza Osoba)
Byłbym najbardziej skłonny do przekazywania głównego obiektu - ponieważ jak koduję, łatwiej wyjaśnić, co robi ta funkcja.
źródło
Jedną wspólną drogą wokół tego jest wstawienie interfejsu między dwoma procesami.
Czasami robi się to trochę nieporządnie, ale w Javie jest trochę łatwiej, jeśli używasz wewnętrznej klasy.
Następnie możesz przetestować
Window
klasę, podając jej fałszywyHasInfo
obiekt.Podejrzewam, że jest to przykład wzoru dekoratora .
Dodany
Wygląda na pewne zamieszanie spowodowane prostotą kodu. Oto kolejny przykład, który może lepiej zademonstrować tę technikę.
źródło
Student
iString
tutaj dla typu zwrotu służy wyłącznie do celów demonstracyjnych. Prawdopodobnie byłyby dodatkowe parametry,getInfo
takie jakPane
rysowanie, jeśli rysowanie. Chodzi tutaj o przekazanie funkcjonalnych komponentów jako dekoratorów oryginalnego obiektu .HasInfo
obiekty.Student
umie być jednym.getInfo
komendę return void pass, aPane
narysujesz ją, wówczas implementacja (wStudent
klasie) nagle zostanie sprzężona z swingiem lub czymkolwiek, czego używasz. Jeśli sprawisz, że zwróci jakiś ciąg znaków i przyjmie 0 parametrów, twój interfejs użytkownika nie będzie wiedział, co zrobić z łańcuchem bez magicznych założeń i niejawnego łączenia. Jeśli sprawisz, żegetInfo
rzeczywiście zwrócisz jakiś model widoku z odpowiednimi właściwościami, wtedy twojaStudent
klasa ponownie zostanie sprzężona z logiką prezentacji. Nie sądzę, aby którakolwiek z tych alternatyw była pożądanaMasz już wiele dobrych odpowiedzi, ale oto kilka innych sugestii, które mogą pozwolić ci zobaczyć alternatywne rozwiązanie:
Twój przykład pokazuje, że Student (wyraźnie obiekt modelowy) jest przekazywany do Okna (najwyraźniej obiekt na poziomie widoku). Pośredni obiekt Controller lub Presenter może być korzystny, jeśli jeszcze go nie masz, umożliwiając odizolowanie interfejsu użytkownika od modelu. Kontroler / prezenter powinien zapewnić interfejs, którego można użyć do zastąpienia go do testowania interfejsu użytkownika, i powinien używać interfejsów do odwoływania się do obiektów modelu i wyświetlania obiektów, aby móc odizolować go od obu do testowania. Może być konieczne zapewnienie abstrakcyjnego sposobu ich tworzenia lub ładowania (np. Obiekty fabryczne, obiekty repozytorium lub podobne).
Przeniesienie odpowiednich części obiektów modelu do obiektu przenoszenia danych jest użytecznym podejściem do łączenia, gdy model staje się zbyt złożony.
Być może Twój Uczeń narusza Zasadę Segregacji Interfejsu. Jeśli tak, korzystne może być podzielenie go na wiele interfejsów, z którymi łatwiej jest pracować.
Leniwe ładowanie może ułatwić tworzenie wykresów dużych obiektów.
źródło
To właściwie przyzwoite pytanie. Prawdziwym problemem jest tutaj użycie terminu „obiekt”, który może być nieco niejednoznaczny.
Zasadniczo w klasycznym języku OOP termin „obiekt” zaczął oznaczać „instancję klasy”. Instancje klas mogą być dość ciężkie - własności publiczne i prywatne (i te pośrednie), metody, dziedziczenie, zależności itp. Naprawdę nie chciałbyś używać czegoś takiego do przekazywania niektórych właściwości.
W tym przypadku używasz obiektu jako kontenera, który po prostu przechowuje niektóre prymitywy. W C ++ takie obiekty były znane jako
structs
(i nadal istnieją w językach takich jak C #). W rzeczywistości konstrukcje zostały zaprojektowane dokładnie na użytek, o którym mówisz - zgrupowały powiązane obiekty i prymitywy, gdy miały logiczną relację.Jednak we współczesnych językach tak naprawdę nie ma różnicy między strukturą a klasą podczas pisania kodu , więc możesz używać obiektu. (Za kulisami istnieją jednak pewne różnice, o których powinieneś wiedzieć - na przykład struct jest typem wartości, a nie typem odniesienia). Zasadniczo, dopóki utrzymasz prosty obiekt, będzie to łatwe przetestować ręcznie. Współczesne języki i narzędzia pozwalają jednak nieco to złagodzić (poprzez interfejsy, kpiny, wstrzykiwanie zależności itp.)
źródło
Student
) na modele widoków (StudentInfo
lubStudentInfoViewModel
itp.), Ale może nie być to konieczne.