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.