Czy Java promuje rozdzielenie definicji klas i implementacji, podobnie jak C ++?

10

Mam zadanie domowe i muszę ocenić, które podejście jest lepsze zgodnie z GRASP „Protected Variation”. Znalazłem pytanie na temat przepełnienia stosu dotyczące rozdzielenia plików nagłówka i kodu w C ++ .

Jednak chcę wiedzieć, dlaczego Java nie podąża za C ++ w promowaniu rozdzielenia definicji klas i implementacji klas. Czy są jakieś zalety metody Java w porównaniu z metodą C ++?

Etienne Noël
źródło
Jeśli chcesz zapytać „Dlaczego Java nie używa plików nagłówkowych”, po prostu zapytaj o to i zrezygnuj z „co jest lepsze” - jak widzieliście, jesteśmy uczuleni na to;) Również szukaj, ja Jestem prawie pewien, że to (lub przynajmniej ściśle powiązane pytania) zostały poruszone wcześniej.
Ups, link nie działał. Przeformułuję, to, co chciałem zasadniczo wiedzieć, to różnice między nimi obiema i które z nich łatwiej jest użyć ponownie lub rozszerzyć.
Etienne Noël,
1
C (i przez rozszerzenie C ++) naprawdę nie miał innego wyjścia, jak oddzielić pliki nagłówkowe od plików implementacyjnych, ze względu na ograniczoną technologię jednoprzebiegowego kompilatora w czasie tworzenia C.
Channel72
2
Java może mieć interfejsy, które mogą oddzielić definicję klasy i implementację klasy, jeśli dana klasa implementuje interfejs. Jednak nie do końca taki sam jak C ++.
FrustratedWithFormsDesigner
1
Ponadto pliki nagłówkowe C ++ ujawniają znacznie więcej implementacji niż lubię, chyba że użyjesz idiomu PIMPL. Konieczne jest wyświetlenie listy wszystkich elementów danych, nawet jeśli privatetak, aby implementacja znała rozmiar, a element privateczłonkowski również działa.
David Thornley,

Odpowiedzi:

13

Ile wierszy kodu znajduje się w następującym programie?

#include <iostream>

int main()
{
   std::cout << "Hello, world!\n";
   return 0;
}

Prawdopodobnie odpowiedziałeś 7 (lub 6, jeśli nie policzyłeś pustej linii lub 4, jeśli nie policzyłeś nawiasów klamrowych).

Twój kompilator widzi jednak coś zupełnie innego:

~$ cpp hello.cpp | wc
  18736   40822  437015

Tak, to 18,7 KLOC tylko dla „Cześć, świecie!” program. Kompilator C ++ musi to wszystko przeanalizować . Jest to główny powód, dla którego kompilacja C ++ trwa tak długo w porównaniu z innymi językami i dlaczego współczesne języki unikają plików nagłówkowych.

Lepszym pytaniem byłoby

Dlaczego nie C ++ mają nagłówka pliki?

C ++ został zaprojektowany jako nadzbiór C, więc musiał zachować pliki nagłówkowe w celu zachowania kompatybilności wstecznej.

OK, więc dlaczego C ma pliki nagłówkowe?

Ze względu na jego pierwotny, osobny model kompilacji. Pliki obiektowe generowane przez kompilatory C nie zawierają żadnych informacji o typie, dlatego aby uniknąć błędów typu, należy uwzględnić te informacje w kodzie źródłowym.

~$ cat sqrtdemo.c 
int main(void)
{
    /* implicit declaration int sqrt(int) */
    double sqrt2 = sqrt(2);
    printf("%f\n", sqrt2);
    return 0;
}

~$ gcc -Wall -ansi -lm -Dsqrt= sqrtdemo.c
sqrtdemo.c: In function main’:
sqrtdemo.c:5:5: warning: implicit declaration of function printf [-Wimplicit-function-declaration]
sqrtdemo.c:5:5: warning: incompatible implicit declaration of built-in function printf [enabled by default]
~$ ./a.out 
2.000000

Dodanie odpowiedniego typu deklaracji naprawia błąd:

~$ cat sqrtdemo.c 
#undef printf
#undef sqrt

int printf(const char*, ...);
double sqrt(double);

int main(void)
{
    double sqrt2 = sqrt(2);
    printf("%f\n", sqrt2);
    return 0;
}

~$ gcc -Wall -ansi -lm sqrtdemo.c
~$ ./a.out 
1.414214

Zauważ, że nie ma żadnych #include. Ale gdy używasz dużej liczby funkcji zewnętrznych (co zrobi większość programów), ręczne ich deklarowanie staje się uciążliwe i podatne na błędy. Pliki nagłówkowe są znacznie łatwiejsze.

W jaki sposób nowoczesne języki są w stanie uniknąć plików nagłówkowych?

Przy użyciu innego formatu pliku obiektowego, który zawiera informacje o typie. Na przykład format pliku Java * .class obejmuje „deskryptory”, które określają typy pól i parametry metody.

To nie był nowy wynalazek. Wcześniej (1987), kiedy Borland dodał osobno skompilowane „jednostki” do Turbo Pascal 4.0, zdecydował się użyć nowego *.TPUformatu zamiast Turbo C *.OBJ, aby wyeliminować potrzebę plików nagłówkowych.

dan04
źródło
Co ciekawe, jestem całkiem pewien, że można ustawić Turbo Pascal tak, aby wyprowadzał OBJpliki, a nie TPUs ...
CVn
7

Java ma interfejsy do definiowania umowy. Daje to wyższy poziom abstrakcji od potrzeb dzwoniącego i faktycznej implementacji. tzn. osoba dzwoniąca nie musi znać klasy implementującej, musi jedynie znać obsługiwaną umowę.

Powiedz, że chcesz napisać metodę, która spowalnia wszystkie klucze / wartości na mapie.

public static <K,V> void printMap(Map<K,V> map) {
    for(Entry<K,V> entry: map.entrySet())
        System.out.println(entry);
}

Ta metoda może wywoływać metodę entrySet () na abstrakcyjnym interfejsie, który jest usuwany z klasy implementującej ją. Możesz wywołać tę metodę za pomocą.

printMap(new TreeMap());
printMap(new LinkedHashMap());
printMap(new ConcurrentHashMap());
printMap(new ConcurrentSkipListMap());
Peter Lawrey
źródło
1
Witamy u programistów tam Peter - myślałem, że rozpoznałem nazwę :-). Dodam, że niektórzy twierdzą, że abstrakcyjne klasy podstawowe w Javie również definiują kontrakt - ale to prawdopodobnie argument za oddzielnym wątkiem.
Martijn Verburg,
Witam @MartijnVerburg, miły dodatek. Myślę, że klasy abstrakcyjne zacierają rozróżnienie między interfejsami bez implementacji a konkretnymi klasami. Metody rozszerzenia jeszcze bardziej zacierają rozróżnienie. Wolę używać interfejsu, jeśli mogę, ponieważ są one prostsze.
Peter Lawrey,
Tak, Java zacznie podążać ścieżką Scali, mając wiele sposobów definiowania zamówienia publicznego - nie jestem pewien, czy to dobrze, czy jeszcze nie :-)
Martijn Verburg,
-1 Jest to również możliwe w C ++, za pomocą prostego #define interface class.
Sjoerd,
@ Sjoerd Nie wiedziałem, że metody C ++ nie muszą już używać virtualsłowa kluczowego, aby uzyskać polimorfizm, a to nie ma negatywnego wpływu na wydajność, jeśli używasz tylko jednego lub dwóch konkretnych typów, tak jak w Javie. Czy możesz wskazać mi jakąkolwiek dokumentację dotyczącą tego, jak to działa w C ++?
Peter Lawrey,
5

Nagłówki istnieją, szczerze mówiąc, jako historyczny wypadek. To niesamowicie słaby system, żaden inny język nie ma nic tak okropnego, a każdy, kto nie musi się z nimi obchodzić, powinien się cieszyć.

DeadMG
źródło
3

Nagłówki umożliwiają oddzielną kompilację. # Włączając nagłówki, kompilator nie musi nic wiedzieć o strukturze binarnej skompilowanego kodu C ++ i może pozostawić to zadanie osobnemu linkerowi. Java nie używa osobnego linkera ze swoim kompilatorem, a ponieważ pliki .class są ściśle zdefiniowane, kompilator jest w stanie je odczytać, aby określić wszystkich swoich członków ze wszystkimi typami, bez potrzeby ponownego deklarowania ich w każdej jednostce kompilacji.

Możesz dołączyć całą implementację do nagłówka C ++, ale powoduje to, że kompilator rekompiluje ją za każdym razem, gdy jest # włączany, zmuszając linker do uporządkowania i odrzucenia duplikatów.

Chuck Adams
źródło
0

Java promuje rozdzielenie definicji klasy i implementacji, zależy to tylko od tego, od czego szukasz.

Gdy jesteś autorem klasy Java, zobaczysz definicję klasy oraz jej implementację w jednym pliku. Upraszcza to proces programowania, ponieważ musisz tylko udać się w jedno miejsce, aby utrzymać klasę, nie musisz przełączać się między dwoma plikami (.h i .cpp, jak w C ++). Jednak jeśli jesteś konsumentem klasy, zajmujesz się tylko definicją za pomocą pliku .class, który jest albo spakowany w pliku .jar, albo jako samodzielny plik .class

C ++ pozwala rozdzielić definicję i implementację, ale jest to ad-hoc. Na przykład nic nie stoi na przeszkodzie, abyś napisał metodę bezpośrednio w pliku nagłówkowym, a dla klas szablonów jest to obowiązkowe. Plik nagłówka zawiera również listę wszystkich zmiennych składowych, które są widoczne dla każdego, kto patrzy na plik nagłówka, mimo że są one szczegółami implementacji klasy i nie mają znaczenia dla konsumenta.

Sean
źródło