typy szablonów powinny podążać za „koncepcją” (Iterator wejściowy, Iterator do przodu itp.), w której rzeczywiste szczegóły koncepcji są definiowane całkowicie przez implementację funkcji / klasy szablonu, a nie przez klasę typu używane z szablonem, który jest nieco anty-użycie OOP.
Myślę, że źle rozumiesz zamierzone użycie pojęć według szablonów. Na przykład Iterator do przodu jest bardzo dobrze zdefiniowaną koncepcją. Aby znaleźć wyrażenia, które muszą być poprawne, aby klasa mogła być iteratorem do przodu, oraz ich semantykę, w tym złożoność obliczeniową, zapoznaj się ze standardem lub na stronie http://www.sgi.com/tech/stl/ForwardIterator.html (aby zobaczyć to wszystko, musisz użyć linków do Input, Output i Trivial Iterator).
Ten dokument jest doskonale dobrym interfejsem, a „rzeczywiste szczegóły koncepcji” są zdefiniowane właśnie tam. Nie są one zdefiniowane przez implementacje Iteratorów do przodu, ani nie są zdefiniowane przez algorytmy wykorzystujące Iteratory do przodu.
Różnice w obsłudze interfejsów między STL i Javą są trzykrotne:
1) STL definiuje prawidłowe wyrażenia za pomocą obiektu, podczas gdy Java definiuje metody, które muszą być wywoływalne na obiekcie. Oczywiście prawidłowe wyrażenie może być wywołaniem metody (funkcji członka), ale nie musi tak być.
2) Interfejsy Java są obiektami wykonawczymi, podczas gdy koncepcje STL nie są widoczne w środowisku wykonawczym, nawet przy RTTI.
3) Jeśli nie sprawdzisz poprawnych wymaganych poprawnych wyrażeń dla koncepcji STL, pojawi się nieokreślony błąd kompilacji, gdy utworzysz szablon z tym typem. Jeśli nie uda się wdrożyć wymaganej metody interfejsu Java, pojawi się komunikat o błędzie kompilacji.
Trzecia część dotyczy sytuacji, gdy podoba Ci się rodzaj „pisania kaczego”: interfejsy mogą być niejawne. W Javie interfejsy są dość wyraźne: klasa „jest” Iterowalna tylko wtedy, gdy mówi, że implementuje Iterable. Kompilator może sprawdzić, czy wszystkie podpisy jego metod są obecne i poprawne, ale semantyka jest nadal niejawna (tj. Jest albo udokumentowana, czy nie, ale tylko więcej kodu (testy jednostkowe) może powiedzieć, czy implementacja jest poprawna).
W C ++, podobnie jak w Pythonie, zarówno semantyka, jak i składnia są niejawne, chociaż w C ++ (i w Pythonie, jeśli masz preprocesor silnego pisania), otrzymujesz pomoc od kompilatora. Jeśli programista wymaga jawnej deklaracji interfejsów podobnej do języka Java przez klasę implementującą, wówczas standardowym podejściem jest użycie cech typu (a wielokrotne dziedziczenie może zapobiec zbyt szczegółowemu określeniu). W porównaniu z Javą brakuje jednego szablonu, który mogę utworzyć z moim typem i który skompiluje się wtedy i tylko wtedy, gdy wszystkie wymagane wyrażenia będą poprawne dla mojego typu. To powiedziałoby mi, czy zaimplementowałem wszystkie wymagane bity „zanim go użyję”. To wygoda, ale nie jest to podstawa OOP (i nadal nie testuje semantyki,
STL może, ale nie musi, być wystarczająco OO dla twojego gustu, ale z pewnością oddziela interfejs czysto od implementacji. Brakuje mu zdolności Java do refleksji nad interfejsami i inaczej raportuje naruszenia wymagań interfejsu.
możesz powiedzieć tej funkcji ... oczekuje, że iterator będzie tylko patrząc na jej definicję, w której musisz spojrzeć na implementację lub dokumentację ...
Osobiście uważam, że typy ukryte są siłą, jeśli są właściwie stosowane. Algorytm mówi, co robi ze swoimi parametrami szablonu, a implementator upewnia się, że te rzeczy działają: jest to dokładnie wspólny mianownik tego, co powinny robić „interfejsy”. Ponadto w przypadku STL jest mało prawdopodobne, abyś używał, powiedzmy, std::copy
na podstawie znalezienia swojej deklaracji przekazywania w pliku nagłówkowym. Programiści powinni opracowywać to, co funkcja bierze na podstawie dokumentacji, a nie tylko podpisu funkcji. Dzieje się tak w C ++, Python lub Java. Istnieją ograniczenia dotyczące tego, co można osiągnąć za pomocą pisania w dowolnym języku, a próba pisania na klawiaturze w celu zrobienia czegoś, czego nie robi (sprawdź semantykę) byłaby błędem.
To powiedziawszy, algorytmy STL zwykle nazywają parametry swoich szablonów w sposób, który wyjaśnia, która koncepcja jest wymagana. Ma to jednak na celu dostarczenie użytecznych dodatkowych informacji w pierwszym wierszu dokumentacji, a nie uczynienie deklaracji przekazywanych bardziej pouczającymi. Jest więcej rzeczy, które musisz wiedzieć, niż można zawrzeć w typach parametrów, więc musisz przeczytać dokumenty. (Na przykład w algorytmach, które przyjmują zakres wejściowy i iterator wyjściowy, są szanse, że iterator wyjściowy potrzebuje wystarczającej „przestrzeni” na pewną liczbę wyników na podstawie wielkości zakresu wejściowego i być może zawartych w nim wartości. Spróbuj mocno to wpisać. )
Oto Bjarne na temat wyraźnie zadeklarowanych interfejsów: http://www.artima.com/cppsource/cpp0xP.html
W przypadku generics argumentem musi być klasa wywodząca się z interfejsu (odpowiednikiem interfejsu C ++ jest klasa abstrakcyjna) określonego w definicji rodzaju ogólnego. Oznacza to, że wszystkie ogólne typy argumentów muszą pasować do hierarchii. Nakładające niepotrzebne ograniczenia na projekty wymagają nieuzasadnionego przewidywania ze strony programistów. Na przykład, jeśli napiszesz ogólną, a ja zdefiniuję klasę, ludzie nie będą mogli użyć mojej klasy jako argumentu dla twojej ogólnej, chyba że będę wiedział o interfejsie, który określiłeś i wyprowadził z niego moją klasę. To jest sztywne.
Patrząc na to odwrotnie, za pomocą pisania kaczego można zaimplementować interfejs, nie wiedząc, że interfejs istnieje. Albo ktoś może napisać interfejs celowo, tak aby klasa go zaimplementowała, po skonsultowaniu się z dokumentami, aby zobaczyć, że nie prosi o nic, czego jeszcze nie zrobiłeś. To jest elastyczne.