Java favorise-t-il une séparation entre les définitions de classe et les implémentations, tout comme C ++?

10

J'ai un devoir et j'ai besoin d'évaluer quelle approche est la meilleure selon GRASP "Variation Protégée". J'ai trouvé une question sur Stack Overflow sur la séparation des fichiers d'en-tête et de code en C ++ .

Cependant, ce que je veux savoir pourquoi Java ne suit pas C ++ pour promouvoir la séparation entre les définitions de classe et les implémentations de classe. Y a-t-il des avantages avec la méthode Java par rapport à la méthode C ++?

Etienne Noël
la source
Si vous voulez demander "Pourquoi Java n'utilise-t-il pas les fichiers d'en-tête", alors posez-le et supprimez le "qui est mieux" - comme vous l'avez vu, nous y sommes allergiques;) Je suis presque certain que cela (ou du moins des questions étroitement liées) ont déjà été soulevées.
oups, le lien n'a pas fonctionné. Je vais reformuler, ce que je voulais savoir fondamentalement, c'est les différences entre les deux et celle qui a tendance à être plus facile à réutiliser le code ou à l'extensibilité.
Etienne Noël
1
C (et par extension C ++) n'avait vraiment pas d'autre choix que de séparer les fichiers d'en-tête des fichiers d'implémentation, en raison de la technologie de compilation en un seul passage limitée au moment de la création de C.
Channel72
2
Java peut avoir des interfaces qui peuvent séparer la définition de classe et l'implémentation de classe, si la classe en question implémente l'interface. Pas tout à fait la même chose que C ++.
FrustratedWithFormsDesigner
1
En outre, les fichiers d'en-tête C ++ exposent considérablement plus d'implémentation que je n'aime, sauf si vous utilisez l'idiome PIMPL. Il est nécessaire de répertorier tous les membres de données, même si private, afin que l'implémentation connaisse la taille et les privatefonctions de membre également.
David Thornley

Réponses:

13

Combien de lignes de code sont dans le programme suivant?

#include <iostream>

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

Vous avez probablement répondu 7 (ou 6 si vous n'avez pas compté la ligne vierge, ou 4 si vous n'avez pas compté les accolades).

Votre compilateur voit cependant quelque chose de très différent:

~$ cpp hello.cpp | wc
  18736   40822  437015

Oui, c'est 18,7 KLOC juste pour un "Bonjour, monde!" programme. Le compilateur C ++ doit analyser tout cela. C'est une des principales raisons pour lesquelles la compilation C ++ prend si longtemps par rapport aux autres langages, et pourquoi les langages modernes évitent les fichiers d'en-tête.

Une meilleure question serait

Pourquoi ne C ++ ont des fichiers en- tête?

C ++ a été conçu pour être un surensemble de C, il devait donc conserver les fichiers d'en-tête pour une compatibilité descendante.

OK, alors pourquoi C a-t-il des fichiers d'en-tête?

En raison de son modèle de compilation séparé primitif. Les fichiers objets générés par les compilateurs C n'incluent aucune information de type, donc pour éviter les erreurs de type, vous devez inclure ces informations dans votre code source.

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

L'ajout des déclarations de type appropriées corrige le bogue:

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

Notez qu'il n'y a pas de par #include. Mais lorsque vous utilisez un grand nombre de fonctions externes (ce que la plupart des programmes utiliseront), leur déclaration manuelle devient fastidieuse et sujette aux erreurs. Il est beaucoup plus facile d'utiliser des fichiers d'en-tête.

Comment les langues modernes peuvent-elles éviter les fichiers d'en-tête?

En utilisant un format de fichier objet différent qui inclut des informations de type. Par exemple, le format de fichier Java * .class comprend des "descripteurs" qui spécifient les types de champs et les paramètres de méthode.

Ce n'était pas une nouvelle invention. Plus tôt (1987), lorsque Borland a ajouté des «unités» compilées séparément à Turbo Pascal 4.0, il a choisi d'utiliser un nouveau *.TPUformat plutôt que Turbo C *.OBJafin de supprimer le besoin de fichiers d'en-tête.

dan04
la source
Bien qu'intéressant, je suis assez sûr que vous pourriez définir Turbo Pascal pour produire des OBJfichiers plutôt que TPUs ...
un CVn
7

Java a des interfaces pour définir un contrat. Cela donne un niveau d'abstraction plus élevé par rapport aux besoins de l'appelant et à la mise en œuvre réelle. c'est-à-dire que l'appelant n'a pas besoin de connaître la classe d'implémentation, il a seulement besoin de connaître le contrat qu'il prend en charge.

Disons que vous voulez écrire une méthode qui ralentit toutes les clés / valeurs dans une carte.

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

Cette méthode peut appeler entrySet () sur une interface abstraite qui est supprimée de la classe qui l'implémente. Vous pouvez appeler cette méthode avec.

printMap(new TreeMap());
printMap(new LinkedHashMap());
printMap(new ConcurrentHashMap());
printMap(new ConcurrentSkipListMap());
Peter Lawrey
la source
1
Bienvenue aux programmeurs là-bas Peter - pensait que je reconnaissais le nom :-). J'ajouterai que certaines personnes soutiendront que les classes de base abstraites en Java définissent également un contrat - mais c'est probablement un argument pour un thread séparé.
Martijn Verburg
Bonjour @MartijnVerburg, belle addition. Je pense que les classes abstraites brouillent la distinction entre les interfaces sans implémentations et les classes concrètes. Les méthodes d'extension rendront la distinction encore plus floue. J'ai tendance à préférer utiliser une interface si je le peux car elles sont plus simples.
Peter Lawrey
Ouais Java va commencer à emprunter la voie Scala d'avoir plusieurs façons de définir un contrat public - je ne suis pas sûr que ce soit une bonne chose ou pas encore :-)
Martijn Verburg
-1 Ceci est également possible en C ++, avec un simple #define interface class.
Sjoerd
@Sjoerd Je ne savais pas que les méthodes C ++ n'avaient plus besoin d'utiliser le virtualmot - clé pour obtenir le polymorphisme et cela n'a aucune incidence sur les performances si vous n'utilisez qu'un ou deux types concrets, comme ils le sont en Java. Pouvez-vous m'indiquer une documentation sur la façon dont cela fonctionne en C ++?
Peter Lawrey
5

Franchement, les en-têtes existent comme un accident historique. C'est un système incroyablement pauvre, aucune autre langue n'a quelque chose d'aussi terrible, et quiconque n'a pas à s'en occuper devrait s'en réjouir.

DeadMG
la source
3

Les en-têtes sont là pour permettre une compilation séparée. En #incluant les en-têtes, le compilateur n'a besoin de rien savoir de la structure binaire du code C ++ compilé et peut laisser ce travail à un éditeur de liens séparé. Java n'utilise pas un éditeur de liens séparé avec son compilateur, et puisque les fichiers .class sont strictement définis, le compilateur est capable de les lire pour déterminer tous leurs membres avec tous leurs types, sans avoir besoin de les déclarer à nouveau dans chaque unité de compilation.

Vous pouvez inclure toute l'implémentation dans un en-tête C ++, mais cela oblige le compilateur à le recompiler chaque fois qu'il est #inclu, forçant l'éditeur de liens à trier et à supprimer les copies en double.

Chuck Adams
la source
0

Java favorise la séparation de la définition et de l'implémentation des classes, cela dépend juste d'où vous cherchez.

Lorsque vous êtes l'auteur d'une classe Java, vous pouvez voir la définition de la classe ainsi que son implémentation dans un seul fichier. Cela simplifie le processus de développement car vous n'avez qu'à aller à un seul endroit pour maintenir la classe, vous n'avez pas à basculer entre deux fichiers (.h et .cpp comme vous le feriez en C ++). Cependant, lorsque vous êtes le consommateur de la classe, vous ne traitez que la définition, via un fichier .class qui est soit conditionné dans un .jar soit dans un .class autonome

C ++ vous permet de séparer la définition et l'implémentation, mais c'est ad hoc. Par exemple, rien ne vous empêche d'écrire la méthode en ligne dans le fichier d'en-tête, et pour les classes de modèle, cela est obligatoire. Le fichier d'en-tête répertorie également toutes les variables membres, qui sont visibles par toute personne qui regarde le fichier d'en-tête, même si elles sont un détail d'implémentation de la classe et non pertinentes pour un consommateur.

Sean
la source