Java wyraźnie rozróżnia między class
i interface
. (Wierzę, że C # też, ale nie mam z tym doświadczenia). Jednak podczas pisania C ++ nie ma wymuszonego rozróżnienia między klasą a interfejsem.
W związku z tym zawsze postrzegałem interfejs jako obejście braku wielokrotnego dziedziczenia w Javie. Dokonanie takiego rozróżnienia jest w C ++ arbitralne i pozbawione znaczenia.
Zawsze stosowałem podejście „pisz rzeczy w najbardziej oczywisty sposób”, więc jeśli w C ++ mam coś, co można nazwać interfejsem w Javie, np .:
class Foo {
public:
virtual void doStuff() = 0;
~Foo() = 0;
};
i wtedy zdecydowałem, że większość implementatorów Foo
chciała udostępnić jakąś wspólną funkcjonalność, którą prawdopodobnie napisałbym:
class Foo {
public:
virtual void doStuff() = 0;
~Foo() {}
protected:
// If it needs this to do its thing:
int internalHelperThing(int);
// Or if it doesn't need the this pointer:
static int someOtherHelper(int);
};
Co sprawia, że nie jest to już interfejs w sensie Java.
Zamiast tego C ++ ma dwie ważne koncepcje związane z tym samym podstawowym problemem związanym z dziedziczeniem:
virtual
dziedziczenieKlasy bez zmiennych składowych nie mogą zajmować dodatkowego miejsca, gdy są używane jako baza
„Podobiekty klasy podstawowej mogą mieć rozmiar zerowy”
Spośród tych, których staram się unikać w miarę możliwości numer 1 - rzadko spotyka się scenariusz, w którym naprawdę jest to „najczystszy” projekt. # 2 jest jednak subtelną, ale ważną różnicą między moim rozumieniem terminu „interfejs” a funkcjami języka C ++. W rezultacie obecnie (prawie) nigdy nie nazywam rzeczy „interfejsami” w C ++ i mówię o klasach podstawowych i ich rozmiarach. Powiedziałbym, że w kontekście C ++ „interfejs” jest mylący.
Zwróciłem jednak uwagę, że niewiele osób dokonuje takiego rozróżnienia.
- Czy mogę coś stracić, dopuszczając (np.
protected
) Niefunkcjevirtual
w „interfejsie” w C ++? (Moje odczucie jest dokładnie odwrotne - bardziej naturalna lokalizacja dla wspólnego kodu) - Czy termin „interfejs” ma znaczenie w C ++ - czy oznacza tylko czysty,
virtual
czy też sprawiedliwe byłoby nazywanie klas C ++ bez zmiennych składowych nadal interfejsem?
źródło
internalHelperThing
mogą być symulowane w prawie wszystkich przypadkach.~Foo() {}
w klasie abstrakcyjnej jest błędem w (prawie) każdych okolicznościach.Odpowiedzi:
W C ++ termin „interfejs” ma nie tylko jedną powszechnie akceptowaną definicję - więc zawsze, gdy będziesz go używać, powinieneś powiedzieć, co masz na myśli - wirtualną klasę bazową z domyślnymi implementacjami lub bez nich, plik nagłówkowy, członkowie publiczni dowolnej klasy i tak dalej.
Jeśli chodzi o twój przykład: w Javie (i podobnym w C #), twój kod prawdopodobnie oznaczałby rozdzielenie problemów:
W C ++ możesz to zrobić, ale nie musisz tego robić. A jeśli chcesz nazwać klasę interfejsem, jeśli nie ma zmiennych składowych, ale zawiera domyślne implementacje niektórych metod, po prostu zrób to, ale upewnij się, że wszyscy, z którymi rozmawiasz, wiedzą, co masz na myśli.
źródło
public
części.h
pliku.Brzmi trochę tak, jakbyś wpadł w pułapkę mylenia znaczenia tego, że interfejs jest koncepcyjnie zarówno implementacją (interfejs - małe litery „i”), jak i abstrakcją (interfejs - wielkie litery „ja”) ).
Jeśli chodzi o twoje przykłady, twój pierwszy fragment kodu jest tylko klasą. Podczas gdy klasa ma interfejs w tym sensie, że zapewnia metody umożliwiające dostęp do jego zachowania, nie jest to interfejs w sensie deklaracji interfejsu, który zapewnia warstwę abstrakcji reprezentującą rodzaj zachowania, które mogą wymagać od klas wprowadzić w życie. Doc Brown odpowiedź na Twój post pokazuje dokładnie to, co mówię tutaj.
Interfejsy są często reklamowane jako „obejście” dla języków, które nie obsługują wielokrotnego dziedziczenia, ale wydaje mi się, że jest to bardziej nieporozumienie niż twarda prawda (i podejrzewam, że mogę zostać oskarżony o stwierdzenie tego!). Interfejsy naprawdę nie mają nic wspólnego z wielokrotnym dziedziczeniem, ponieważ nie wymagają ani dziedziczenia, ani bycia przodkiem, aby zapewnić funkcjonalną kompatybilność między klasami lub abstrakcję implementacji dla klas. W rzeczywistości, mogą one pozwalają skutecznie pozbyć się dziedziczenia całości powinny chcesz zaimplementować kod w ten sposób - nie, że będę całkowicie go polecam, ale ja tylko twierdzę, że mógłbyZrób to. Tak więc rzeczywistość jest taka, że bez względu na dziedziczenie interfejsy zapewniają środki, za pomocą których można definiować typy klas, ustanawiając reguły określające, w jaki sposób obiekty mogą się ze sobą komunikować, dzięki czemu pozwalają określić, jakie zachowania powinny wspierać klasy narzucając konkretną metodę zastosowaną do wdrożenia tego zachowania.
Czysty interfejs ma być całkowicie abstrakcyjny, ponieważ umożliwia zdefiniowanie umowy zgodności między klasami, które niekoniecznie odziedziczyły swoje zachowanie od wspólnego przodka. Po wdrożeniu chcesz dokonać wyboru, czy zezwolić na rozszerzenie zachowania implementacji w podklasie. Jeśli twoje metody nie są
virtual
, tracisz możliwość późniejszego przedłużenia tego zachowania, jeśli zdecydujesz, że musisz utworzyć klasy potomne. Niezależnie od tego, czy implementacja jest wirtualna, czy nie, interfejs sam w sobie określa zgodność behawioralną, podczas gdy klasa zapewnia implementację tego zachowania dla instancji reprezentowanych przez klasę.Wybacz mi, ale minęło dużo czasu, odkąd napisałem poważną aplikację w C ++. Pamiętam, że Interfejs jest słowem kluczowym do celów abstrakcji, jak je tutaj opisałem. Nie nazwałbym klas C ++ jakimkolwiek interfejsem, powiedziałbym natomiast, że klasa ma interfejs w znaczeniu, które opisałem powyżej. W tym sensie termin JEST znaczący, ale tak naprawdę zależy od kontekstu.
źródło
Interfejsy Java nie są „obejściem”, są świadomą decyzją projektową, aby uniknąć niektórych problemów z wielokrotnym dziedziczeniem, takich jak dziedziczenie diamentów, i zachęcić do praktyk projektowych, które minimalizują sprzężenie.
Twój interfejs z metodami chronionymi to podręcznikowy przypadek konieczności „preferowania kompozycji zamiast dziedziczenia”. Cytując Suttera i Alexandrescu w sekcji o tej nazwie z ich doskonałych standardów kodowania C ++ :
Uwzględniając funkcje pomocnicze w interfejsie, możesz teraz zaoszczędzić trochę pisania, ale wprowadzasz sprzężenie, które zrani cię na drodze. Prawie zawsze lepiej jest w dłuższej perspektywie rozdzielić funkcje pomocnika i przekazać je
Foo*
.STL jest tego dobrym przykładem. Wyciągnięto jak najwięcej funkcji pomocniczych,
<algorithm>
zamiast być w klasach kontenerów. Na przykład, ponieważsort()
używa publicznego interfejsu API kontenera do wykonania swojej pracy, wiesz, że możesz zaimplementować własny algorytm sortowania bez konieczności zmiany kodu STL. Ten projekt umożliwia bibliotekom takim jak boost, które zwiększają STL zamiast go zastępować, bez potrzeby, aby STL musiał wiedzieć coś o boost.źródło
Można by tak pomyśleć, ale umieszczenie chronionej, nie-wirtualnej metody w klasie abstrakcyjnej skłania do implementacji każdego, kto napisze podklasę. Takie postępowanie przeczy celowi interfejsu w jego czystym sensie, jakim jest zapewnienie forniru, który ukrywa to, co pod spodem.
Jest to jeden z tych przypadków, w których nie ma jednej uniwersalnej odpowiedzi, a do podjęcia decyzji musisz wykorzystać swoje doświadczenie i osąd. Jeśli możesz powiedzieć ze 100% pewnością, że każda możliwa podklasa twojej w pełni wirtualnej klasy
Foo
zawsze będzie wymagała implementacji chronionej metodybar()
, toFoo
jest to odpowiednie miejsce. Kiedy masz już podklasęBaz
, która nie jest potrzebnabar()
, musisz albo żyć z faktem, żeBaz
będzie miał dostęp do kodu, którego nie powinien, albo przejść przez proces zmiany hierarchii klas. Ten pierwszy nie jest dobrą praktyką, a drugi może zająć więcej czasu niż kilka minut, które zajęłoby mu właściwe załatwienie sprawy.Sekcja 10.4 standardu C ++ wspomina o użyciu klas abstrakcyjnych do implementacji interfejsów, ale nie definiuje ich formalnie. Termin ma znaczenie w ogólnym kontekście informatycznym i każdy kompetentny powinien zrozumieć, że „
Foo
jest interfejsem dla (czegokolwiek)” implikuje jakąś formę abstrakcji. Osoby z ekspozycją na języki ze zdefiniowanymi konstrukcjami interfejsu mogą myśleć czysto wirtualnie, ale każdy, kto musi faktycznie pracowaćFoo
, przejrzy jego definicję przed kontynuowaniem.źródło
baz
zawsze może mieć implementację podobnąreturn false;
lub inną. Jeśli metoda nie ma zastosowania do wszystkich podklas, nie należy do żadnej podstawowej klasy abstrakcyjnej.