Jako programista C ++ jestem przyzwyczajony do plików nagłówkowych C ++ i uważam, że korzystne jest posiadanie pewnego rodzaju wymuszonej „dokumentacji” w kodzie. Zwykle mam zły czas, kiedy muszę czytać kod C # z tego powodu: nie mam takiej mentalnej mapy klasy, z którą pracuję.
Załóżmy, że jako inżynier oprogramowania projektuję framework programu. Czy byłoby zbyt szalone, aby zdefiniować każdą klasę jako abstrakcyjną klasę niezaimplementowaną, podobnie do tego, co zrobilibyśmy z nagłówkami C ++ i pozwolić programistom na jej wdrożenie?
Domyślam się, że może istnieć kilka powodów, dla których ktoś może uznać to za okropne rozwiązanie, ale nie jestem pewien, dlaczego. Co należałoby rozważyć przy takim rozwiązaniu?
c#
c++
object-oriented-design
abstract-class
Dani Barca Casafont
źródło
źródło
Odpowiedzi:
Powodem, dla którego dokonano tego w C ++, było przyspieszenie i łatwiejsze wdrożenie kompilatorów. To nie był projekt ułatwiający programowanie .
Celem plików nagłówkowych było umożliwienie kompilatorowi wykonania super szybkiego pierwszego przejścia w celu poznania wszystkich oczekiwanych nazw funkcji i przydzielenia im lokalizacji pamięci, aby można było do nich odwoływać się po wywołaniu w plikach cp, nawet jeśli klasa je definiująca miała jeszcze nie zostały przeanalizowane.
Próba odtworzenia konsekwencji starych ograniczeń sprzętowych w nowoczesnym środowisku programistycznym nie jest zalecana!
Zdefiniowanie interfejsu lub klasy abstrakcyjnej dla każdej klasy obniży produktywność; co jeszcze mogłeś zrobić z tym czasem ? Ponadto inni programiści nie będą przestrzegać tej konwencji.
W rzeczywistości inni programiści mogą usunąć twoje abstrakcyjne klasy. Jeśli znajdę interfejs w kodzie, który spełnia oba te kryteria, usuwam go i refaktoryzuję z kodu:
1. Does not conform to the interface segregation principle
2. Only has one class that inherits from it
Inną sprawą jest to, że w Visual Studio są narzędzia, które robią to, co chcesz osiągnąć automatycznie:
Class View
Object Browser
Solution Explorer
możesz kliknąć trójkąty, aby wydać klasy, aby zobaczyć ich funkcje, parametry i typy zwrotów.Wypróbuj powyższe przed poświęceniem czasu na replikację plików nagłówkowych C ++ w języku C #.
Co więcej, istnieją techniczne powody, aby tego nie robić ... sprawi, że Twój ostateczny plik binarny będzie większy, niż powinien. Powtórzę ten komentarz Mark Benningfield:
Ponadto, wspomniany przez Roberta Harveya, technicznie najbliższym odpowiednikiem nagłówka w C # byłby interfejs, a nie klasa abstrakcyjna.
źródło
Po pierwsze, zrozum, że czysto abstrakcyjna klasa jest tak naprawdę interfejsem, który nie może wykonywać wielokrotnego dziedziczenia.
Napisz klasę, wyodrębnij interfejs. Tak bardzo, że mamy na to refaktoryzację. Szkoda. Po tym, że wzorzec „każda klasa dostaje interfejs” nie tylko powoduje bałagan, ale całkowicie nie ma sensu.
Interfejsu nie należy uważać za po prostu formalne przekształcenie tego, co klasa może zrobić. Interfejs należy traktować jako umowę narzuconą przez użycie kodu klienta szczegółowo opisującego jego potrzeby.
Nie mam żadnych problemów z napisaniem interfejsu, który obecnie ma tylko jedną klasę implementującą go. Nie obchodzi mnie to, czy żadna klasa jeszcze tego nie implementuje. Ponieważ myślę o tym, czego potrzebuje mój kod. Interfejs wyraża wymagania związane z użyciem kodu. Cokolwiek przyjdzie później, może robić, co lubi, o ile spełnia te oczekiwania.
Teraz nie robię tego za każdym razem, gdy jeden obiekt używa innego. Robię to, gdy przekraczam granicę. Robię to, gdy nie chcę, aby jeden obiekt dokładnie wiedział, z którym innym przedmiotem rozmawia. To jedyny sposób, w jaki zadziała polimorfizm. Robię to, gdy oczekuję, że obiekt, o którym mówi mój kod klienta, prawdopodobnie się zmieni. Z pewnością nie robię tego, gdy używam klasy String. Klasa String jest ładna i stabilna i nie czuję potrzeby, aby się przed nią nie zmieniać.
Decydując się na bezpośrednią interakcję z konkretną implementacją, a nie poprzez abstrakcję, przewidujesz, że implementacja jest wystarczająco stabilna, aby zaufać, że się nie zmieni.
Właśnie w ten sposób hartuję zasadę inwersji zależności . Nie powinieneś ślepo fanatycznie stosować tego do wszystkiego. Kiedy dodajesz abstrakcję, naprawdę mówisz, że nie ufasz, że klasa implementacyjna ma być stabilna przez cały czas trwania projektu.
To wszystko zakłada, że próbujesz postępować zgodnie z zasadą otwartej zamkniętej . Ta zasada jest ważna tylko wtedy, gdy koszty związane z wprowadzaniem bezpośrednich zmian w ustalonym kodzie są znaczne. Jednym z głównych powodów, dla których ludzie nie zgadzają się co do tego, jak ważne jest odsprzęganie obiektów, jest to, że nie wszyscy ponoszą takie same koszty przy dokonywaniu bezpośrednich zmian. Jeśli ponowne testowanie, ponowna kompilacja i redystrybucja całej bazy kodu jest dla ciebie trywialna, wówczas zaspokojenie potrzeby zmiany za pomocą bezpośredniej modyfikacji jest prawdopodobnie bardzo atrakcyjnym uproszczeniem tego problemu.
Po prostu nie ma mózgowej odpowiedzi na to pytanie. Interfejs lub klasa abstrakcyjna nie jest czymś, co powinieneś dodać do każdej klasy i nie możesz po prostu policzyć liczby klas implementujących i zdecydować, że nie jest to konieczne. Chodzi o radzenie sobie ze zmianami. Co oznacza, że przewidujesz przyszłość. Nie zdziw się, jeśli pomylisz się. Uprość to, jak to możliwe, bez cofania się w kąt.
Dlatego nie pisz abstrakcji, aby pomóc nam w czytaniu kodu. Mamy do tego narzędzia. Wykorzystaj abstrakcje, aby oddzielić to, co wymaga oddzielenia.
źródło
Tak, byłoby to okropne, ponieważ (1) Wprowadza niepotrzebny kod (2) Wprowadzi czytelnika w błąd.
Jeśli chcesz programować w C #, powinieneś po prostu przyzwyczaić się do czytania w C #. Kod napisany przez inne osoby i tak nie będzie zgodny z tym wzorem.
źródło
Testy jednostkowe
Zdecydowanie sugerowałbym pisanie testów jednostkowych zamiast zaśmiecania kodu interfejsami lub klasami abstrakcyjnymi (z wyjątkiem przypadków uzasadnionych z innych powodów).
Dobrze napisany test jednostkowy nie tylko opisuje interfejs twojej klasy (jak zrobiłby to plik nagłówkowy, klasa abstrakcyjna lub interfejs), ale także opisuje, na przykład, pożądaną funkcjonalność.
Przykład: Gdzie mógłbyś napisać plik nagłówkowy myclass.h, taki jak ten:
Zamiast tego w c # napisz taki test:
Ten bardzo prosty test przekazuje te same informacje, co plik nagłówkowy. (Powinniśmy mieć klasę o nazwie „MyClass” z funkcją parametryczną „Foo”). Chociaż plik nagłówkowy jest bardziej zwarty, test zawiera znacznie więcej informacji.
Zastrzeżenie: taki proces polegający na tym, że starszy inżynier oprogramowania testuje (nie zalicza się) testy dla innych programistów, aby brutalnie rozwiązywać konflikty z metodologiami takimi jak TDD, ale w twoim przypadku byłaby to ogromna poprawa.
źródło