O Java promove uma separação entre definições e implementações de classe, como é o C ++?

10

Tenho um trabalho de casa e preciso avaliar qual abordagem é melhor, de acordo com o GRASP "Variação protegida". Encontrei uma pergunta no Stack Overflow sobre a separação dos arquivos de cabeçalho e código em C ++ .

No entanto, o que eu quero saber por que o Java não segue o C ++ na promoção da separação entre definições de classe e implementações de classe. Há alguma vantagem com o método Java sobre o método C ++?

Etienne Noël
fonte
Se você quiser perguntar "Por que o Java não usa arquivos de cabeçalho", basta perguntar e acabar com as coisas "o que é melhor" - como você viu, somos alérgicos a isso;) Tenho certeza de que isso (ou pelo menos questões estreitamente relacionadas) foram levantadas antes.
opa, o link não funcionou. Vou reformular, o que eu queria saber basicamente, são as diferenças entre os dois e qual tende a ser mais fácil reutilizar o código ou extensibilidade.
Etienne Noël
11
C (e por extensão C ++) realmente não teve escolha a não ser separar os arquivos de cabeçalho dos arquivos de implementação, devido à tecnologia limitada do compilador de uma passagem no momento em que C foi criado.
usar o seguinte código
2
Java pode ter interfaces que podem separar a definição e a implementação da classe, se a classe em questão implementar a interface. Não é exatamente o mesmo que C ++.
FrustratedWithFormsDesigner
11
Além disso, os arquivos de cabeçalho C ++ expõem consideravelmente mais implementação do que eu gostaria, a menos que você use o idioma PIMPL. É necessário listar todos os membros de dados, mesmo que private, para que a implementação saiba o tamanho e as privatefunções de membro também.
precisa

Respostas:

13

Quantas linhas de código estão no programa a seguir?

#include <iostream>

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

Você provavelmente respondeu 7 (ou 6, se não contou a linha em branco, ou 4, se não contou as chaves).

Seu compilador, no entanto, vê algo muito diferente:

~$ cpp hello.cpp | wc
  18736   40822  437015

Sim, são 18,7 KLOC apenas para um "Olá, mundo!" programa. O compilador C ++ precisa analisar tudo isso. Esse é um dos principais motivos pelos quais a compilação do C ++ demora tanto tempo em comparação com outras linguagens, e porque as linguagens modernas evitam os arquivos de cabeçalho.

Uma pergunta melhor seria

Por que o C ++ possui arquivos de cabeçalho?

O C ++ foi projetado para ser um superconjunto de C, portanto, era necessário manter os arquivos de cabeçalho para compatibilidade com versões anteriores.

OK, então por que C tem arquivos de cabeçalho?

Por causa de seu modelo de compilação primitivo separado. Os arquivos de objeto gerados pelos compiladores C não incluem nenhuma informação de tipo, portanto, para evitar erros de tipo, você precisa incluir essas informações em seu código-fonte.

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

Adicionar as declarações de tipo adequadas corrige o erro:

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

Observe que não há #includes. Mas quando você usa um grande número de funções externas (o que a maioria dos programas fará), declará-las manualmente fica entediante e propenso a erros. É muito mais fácil usar arquivos de cabeçalho.

Como as linguagens modernas são capazes de evitar arquivos de cabeçalho?

Usando um formato de arquivo de objeto diferente que inclui informações de tipo. Por exemplo, o formato de arquivo Java * .class inclui "descritores" que especificam os tipos de campos e parâmetros de método.

Esta não era uma nova invenção. Antes (1987), quando a Borland adicionou "unidades" compiladas separadamente ao Turbo Pascal 4.0, optou por usar um novo *.TPUformato em vez do Turbo C, a *.OBJfim de remover a necessidade de arquivos de cabeçalho.

dan04
fonte
Embora interessante, estou bastante certo de que você pode definir Turbo Pascal para saída de OBJarquivos em vez de TPUs ...
um CVn
7

Java possui interfaces para definir um contrato. Isso fornece um nível mais alto de abstração do que o chamador precisa e da implementação real. ou seja, o chamador não precisa conhecer a classe de implementação, precisa apenas conhecer o contrato que oferece suporte.

Digamos que você queira escrever um método que reduz a velocidade de todas as chaves / valores em um mapa.

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

Este método pode chamar entrySet () em uma interface abstrata que é removida da classe que o implementa. Você pode chamar esse método com.

printMap(new TreeMap());
printMap(new LinkedHashMap());
printMap(new ConcurrentHashMap());
printMap(new ConcurrentSkipListMap());
Peter Lawrey
fonte
11
Bem-vindo aos programadores lá Peter - pensei que reconhecia o nome :-). Acrescentarei que algumas pessoas argumentam que as classes base abstratas em Java também definem um contrato - mas isso provavelmente é um argumento para um encadeamento separado.
Martijn Verburg
Olá @MartijnVerburg, adição agradável. Penso que classes abstratas ofuscam a distinção entre interfaces sem implementações e classes concretas. Os métodos de extensão embaçarão ainda mais a distinção. Costumo preferir usar uma interface, se puder, pois são mais simples.
precisa saber é o seguinte
Sim Java vai começar tomando o caminho Scala de ter várias maneiras de definir um contrato público - não tenho certeza que é uma coisa boa ou não ainda :-)
Martijn Verburg
-1 Isso também é possível em C ++, com um simples #define interface class.
Sjoerd
@ Sjoerd Eu não sabia que os métodos C ++ não precisam mais usar a virtualpalavra-chave para obter polimorfismo e isso não afeta o desempenho se você usar apenas um ou dois tipos concretos, como em Java. Você pode me indicar alguma documentação sobre como isso funciona em C ++?
Peter Peterrey
5

Os cabeçalhos existem, francamente, como um acidente histórico. É um sistema incrivelmente ruim, nenhum outro idioma tem algo tão terrível e qualquer pessoa que não precise lidar com eles deve se alegrar.

DeadMG
fonte
3

Os cabeçalhos existem para permitir a compilação separada. Ao incluir os cabeçalhos, o compilador não precisa saber nada sobre a estrutura binária do código C ++ compilado e pode deixar esse trabalho para um vinculador separado. Java não usa um vinculador separado com seu compilador e, como os arquivos .class são estritamente definidos, o compilador pode lê-los para determinar todos os seus membros com todos os seus tipos, sem a necessidade de declará-los novamente em cada unidade de compilação.

Você pode incluir toda a implementação em um cabeçalho C ++, mas isso faz com que o compilador recompile toda vez que for #incluído, forçando o vinculador a classificar e descartar as cópias duplicadas.

Chuck Adams
fonte
0

Java promove a separação da definição e implementação de classe, depende apenas de onde você está procurando.

Quando você é o autor de uma classe Java, pode ver a definição da classe e sua implementação em um arquivo. Isso simplifica o processo de desenvolvimento, já que você só precisa ir a um local para manter a classe, não precisa alternar entre dois arquivos (.h e .cpp, como faria em C ++). No entanto, quando você é consumidor da classe, lida apenas com a definição, por meio de um arquivo .class que é empacotado em uma classe .jar ou em uma classe.

O C ++ permite separar a definição e a implementação, mas é ad-hoc. Por exemplo, não há nada para impedir que você escreva o método embutido no arquivo de cabeçalho, e para as classes de modelo isso é obrigatório. O arquivo de cabeçalho também lista todas as variáveis ​​de membro, que são visíveis para quem olha para o arquivo de cabeçalho, mesmo sendo um detalhe de implementação da classe e irrelevante para um consumidor.

Sean
fonte