Чи сприяє Java розмежування між визначеннями класів та реалізаціями, як це C ++?


10

У мене є домашнє завдання, і мені потрібно оцінити, який підхід кращий відповідно до GRASP "Захищена варіація". Я знайшов запитання на Stack Overflow про розділення файлів заголовка та коду в C ++ .

Однак те, що я хочу знати, чому Java не дотримується C ++, сприяючи розділенню між визначеннями класів та реалізацією класу. Чи є якісь переваги у методу Java над методом C ++?


Якщо ви хочете запитати "Чому Java не використовує заголовні файли", тоді просто запитайте це і не втрачайте речі "що краще" - як ви вже бачили, у нас алергія на це;) Також шукайте, я Я впевнений, що це (або принаймні тісно пов'язані з цим питання) піднімалися раніше.

На жаль, посилання не працювало. Я переформулюю, що я хотів би в основному знати, - це різниці між ними, і те, що, як правило, простіше використовувати код або для розширення.
Etienne Noël

1
C (і за допомогою розширення C ++) дійсно не мав іншого вибору, крім відокремлення файлів заголовків від файлів реалізації, через обмежену технологію компіляції в один прохід на час створення C.
Канал72

2
Java може мати інтерфейси, які можуть розділяти визначення класу та реалізацію класу, якщо відповідний клас реалізує інтерфейс. Не зовсім те саме, що C ++, хоча.
FrustratedWithFormsDesigner

1
Крім того, файли заголовків C ++ виявляють значно більшу реалізацію, ніж мені подобається, якщо ви не використовуєте ідіому PIMPL. Потрібно перерахувати всі учасники даних, навіть якщо вони знаходяться private, тому реалізація буде знати розмір, а також privateфункції члена.
Девід Торнлі

Відповіді:


13

Скільки рядків коду в наступній програмі?

#include <iostream>

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

Ви, ймовірно, відповіли 7 (або 6, якщо ви не рахували порожній рядок, або 4, якщо ви не рахували дужки).

Однак ваш компілятор бачить щось зовсім інше:

~$ cpp hello.cpp | wc
  18736   40822  437015

Так, це 18,7 KLOC лише для "Привіт, світ!" програма. Компілятор C ++ повинен проаналізувати все це. Це основна причина, чому компіляція C ++ займає так багато часу порівняно з іншими мовами, і чому сучасні мови уникають файлів заголовків.

Краще питання було б

Чому ж C ++ є файли заголовків?

C ++ був розроблений як набір C, тому він мав зберігати файли заголовків для зворотної сумісності.

Добре, так чому C має файли заголовків?

Через свою примітивну окрему модель компіляції. Об’єктні файли, згенеровані компіляторами C, не містять інформації про тип, тому для запобігання помилок типу потрібно включити цю інформацію у свій вихідний код.

~$ 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

Додавання належних оголошень типу виправляє помилку:

~$ 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

Зауважте, що немає #includes. Але коли ви використовуєте велику кількість зовнішніх функцій (які будуть використовувати більшість програм), їх вручну декларування стає втомливим і схильним до помилок. Набагато простіше використовувати файли заголовків.

Як сучасні мови можуть уникнути файлів заголовків?

Використовуючи інший формат файлу об'єкта, що включає інформацію про тип. Наприклад, формат файлу Java * .class включає "дескриптори", які задають типи полів та параметри методу.

Це не був новий винахід. Раніше (1987), коли Borland додав окремо складені "одиниці" до Turbo Pascal 4.0, він вирішив скористатися новим *.TPUформатом, а не Turbo C, *.OBJщоб усунути потребу у файлах заголовків.


Хоча що цікаво, я досить впевнений, що ви можете встановити Turbo Pascal для виведення OBJфайлів, а не TPUs ...
CVn

7

У Java є інтерфейси для визначення контракту. Це дає більш високий рівень абстрагування від необхідного абоненту та реальної реалізації. тобто абоненту не потрібно знати клас реалізації, він повинен знати лише контракт, який він підтримує.

Скажімо, ви хочете написати метод, який уповільнює всі ключі / значення на карті.

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

Цей метод може викликати entrySet () на абстрактному інтерфейсі, який видаляється з класу, що реалізує його. Ви можете викликати цей метод за допомогою.

printMap(new TreeMap());
printMap(new LinkedHashMap());
printMap(new ConcurrentHashMap());
printMap(new ConcurrentSkipListMap());

1
Ласкаво просимо до тамтешніх програмістів Петро - подумав, що я впізнав ім'я :-). Додам, що деякі люди будуть стверджувати, що абстрактні базові класи на Java також визначають контракт, але це, мабуть, аргумент для окремої нитки.
Martijn Verburg

Привіт @MartijnVerburg, приємне доповнення. Я думаю, що абстрактні класи розмивають різницю між інтерфейсами без реалізації та конкретними класами. Методи розширення ще більше розмиють розрізнення. Я, як правило, вважаю за краще використовувати інтерфейс, якщо можу, оскільки вони простіші.
Пітер Лорі

Так, Java почне прямувати вниз по шляху Скали, коли є декілька способів визначити державний контракт - я не впевнений, що це гарна річ чи ще немає :-)
Martijn Verburg

-1 Це можливо і в C ++, і з простим #define interface class.
Sjoerd

@Sjoerd Я не знав методів C ++, більше не потрібно використовувати virtualключове слово, щоб отримати поліморфізм, і це не має покарання за ефективність, якщо ви використовуєте лише один або два конкретні типи, як вони є на Java. Чи можете ви вказати мені будь-яку документацію про те, як це працює в C ++?
Пітер Лорі

5

Заголовки існують, відверто кажучи, як історична аварія. Це надзвичайно бідна система, жодна інша мова не має нічого такого жахливого, і кожен, хто не має з ними справи, повинен радіти.


3

Заголовки є там, щоб включити окрему компіляцію. Включаючи заголовки, компілятору не потрібно нічого знати про двійкову структуру компільованого коду C ++ і може залишити це завдання окремим посиланням. Java не використовує окремий лінкер зі своїм компілятором, а оскільки .class файли строго визначені, компілятор може прочитати їх, щоб визначити всіх їх членів з усіма їх типами, без необхідності повторного оголошення їх у кожному блоці компіляції.

Ви можете включити всю реалізацію до заголовка C ++, але він змушує компілятор перекомпілювати його щоразу, коли він #included, змушує лінкер сортувати та відкидати дублюючі копії.


0

Java дійсно сприяє розділенню визначення класу та реалізації, це просто залежить від того, звідки ви шукаєте.

Коли ви автор класу Java, ви можете побачити визначення класу, а також його реалізацію в одному файлі. Це спрощує процес розробки, оскільки вам потрібно лише перейти на одне місце для підтримки класу, вам не доведеться перемикатися між двома файлами (.h та .cpp, як у C ++). Однак, коли ви споживач класу, ви маєте справу лише з визначенням через файл .class, який упакований у .jar або окремий .class

C ++ дозволяє розділити визначення та реалізацію, але це спеціально. Наприклад, нічого не зупинить написання методу, вбудованого у файл заголовка, а для класів шаблонів це обов'язково. У заголовковому файлі також перелічені будь-які змінні учасника, які видно всім, хто дивиться на файл заголовка, навіть якщо вони деталі реалізації класу та не стосуються споживача.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.