Zarówno dziedziczenie klas, jak i interfejsy mają swoje miejsce. Dziedziczenie oznacza „jest”, podczas gdy interfejs zawiera umowę określającą, jak coś „zachowuje się”.
Powiedziałbym, że częstsze używanie interfejsów wcale nie jest złą praktyką. Obecnie czytam „Skuteczne C # - 50 konkretnych sposobów na poprawę twojego C #” Billa Wagnera. W pozycji nr 22 znajduje się cytat „Wolę definiowanie i wdrażanie interfejsów niż dziedziczenie”.
Zasadniczo używam klas podstawowych, gdy muszę zdefiniować konkretną implementację typowego zachowania między typami pokrewnymi. Częściej korzystam z interfejsów. W rzeczywistości zwykle zaczynam od zdefiniowania interfejsu dla klasy, kiedy zaczynam ją tworzyć ... nawet jeśli ostatecznie nie skompiluję interfejsu, stwierdzę, że pomaga to od zdefiniowania publicznego interfejsu API klasa od samego początku. Jeśli stwierdzę, że mam wiele klas, które zarówno implementują interfejs, a logika implementacji jest identyczna, dopiero wtedy zadam sobie pytanie, czy sensowne byłoby wdrożenie wspólnej klasy podstawowej między typami.
Kilka cytatów z książki Billa Wagnersa ...
wszystkie klasy pochodne natychmiast uwzględniają to zachowanie. Dodanie elementu do interfejsu powoduje uszkodzenie wszystkich klas, które implementują ten interfejs. Nie będą zawierać nowej metody i nie będą się już kompilować. Każdy implementator musi zaktualizować ten typ, aby uwzględnić nowego członka. Wybór między abstrakcyjną klasą bazową a interfejsem to pytanie, jak najlepiej wspierać swoje abstrakcje w czasie. Interfejsy są naprawione: interfejs wydajesz jako umowę dotyczącą zestawu funkcji, które może zaimplementować każdy typ. Klasy podstawowe mogą być z czasem przedłużane. Rozszerzenia te stają się częścią każdej klasy pochodnej. Oba modele mogą być mieszane w celu ponownego wykorzystania kodu implementacji przy jednoczesnym wsparciu wielu interfejsów. ” Nie będą zawierać nowej metody i nie będą się już kompilować. Każdy implementator musi zaktualizować ten typ, aby uwzględnić nowego członka. Wybór między abstrakcyjną klasą bazową a interfejsem to pytanie, jak najlepiej wspierać swoje abstrakcje w czasie. Interfejsy są naprawione: interfejs wydajesz jako umowę dotyczącą zestawu funkcji, które może zaimplementować każdy typ. Klasy podstawowe mogą być z czasem przedłużane. Rozszerzenia te stają się częścią każdej klasy pochodnej. Oba modele mogą być mieszane w celu ponownego wykorzystania kodu implementacji przy jednoczesnym wsparciu wielu interfejsów. ” Nie będą zawierać nowej metody i nie będą się już kompilować. Każdy implementator musi zaktualizować ten typ, aby uwzględnić nowego członka. Wybór między abstrakcyjną klasą bazową a interfejsem to pytanie, jak najlepiej wspierać swoje abstrakcje w czasie. Interfejsy są naprawione: interfejs wydajesz jako umowę dotyczącą zestawu funkcji, które może zaimplementować każdy typ. Klasy podstawowe mogą być z czasem przedłużane. Rozszerzenia te stają się częścią każdej klasy pochodnej. Oba modele mogą być mieszane w celu ponownego wykorzystania kodu implementacji przy jednoczesnym wsparciu wielu interfejsów. ” Wydajesz interfejs jako umowę dotyczącą zestawu funkcji, które może zaimplementować każdy typ. Klasy podstawowe mogą być z czasem przedłużane. Rozszerzenia te stają się częścią każdej klasy pochodnej. Oba modele mogą być mieszane w celu ponownego wykorzystania kodu implementacji przy jednoczesnym wsparciu wielu interfejsów. ” Wydajesz interfejs jako umowę dotyczącą zestawu funkcji, które może zaimplementować każdy typ. Klasy podstawowe mogą być z czasem przedłużane. Rozszerzenia te stają się częścią każdej klasy pochodnej. Oba modele mogą być mieszane w celu ponownego wykorzystania kodu implementacji przy jednoczesnym wsparciu wielu interfejsów. ”
„Interfejsy kodowania zapewniają większą elastyczność innym programistom niż kodowanie do typów klas podstawowych”.
„Używanie interfejsów do definiowania interfejsów API dla klasy zapewnia większą elastyczność”.
„Gdy Twój typ udostępnia właściwości jako typy klas, udostępnia cały interfejs dla tej klasy. Korzystając z interfejsów, możesz ujawnić tylko metody i właściwości, których mają używać klienci.”
„Klasy podstawowe opisują i wdrażają typowe zachowania w pokrewnych konkretnych typach. Interfejsy opisują atomowe elementy funkcjonalności, które mogą być implementowane przez niepowiązane konkretne typy. Obie mają swoje miejsce. Klasy definiują tworzone typy. Interfejsy opisują zachowanie tych typów jako elementy funkcjonalności. Jeśli zrozumiesz różnice, stworzysz bardziej wyraziste projekty, które będą bardziej odporne na zmiany. Użyj hierarchii klas, aby zdefiniować typy pokrewne. Ujawnij funkcjonalność za pomocą interfejsów zaimplementowanych we wszystkich tych typach. ”