O desenvolvimento Java normalmente envolve mais subclasses do que C # / .NET?

34

Recentemente, comecei a analisar o desenvolvimento do Android. Isso me trouxe de volta ao mundo do desenvolvimento de software Java. A última vez que trabalhei com Java, admito, não entendi OOP tanto quanto (acho) agora.

Tendo usado principalmente o C # em minha carreira, percebo uma diferença surpreendente em como a herança é usada Java e C #.

Em C #, parecia que a herança poderia ser evitada na maioria das situações. A tarefa em questão geralmente pode ser realizada usando classes concretas da estrutura .NET .

Em Java, pelo que estou coletando de amostras de código, parece que a estrutura Java fornece muitas interfaces ou classes abstratas que devem ser implementadas / estendidas pelo desenvolvedor.

Parece ser uma diferença muito grande para se resumir ao estilo. Qual é o raciocínio por trás disso? Sinto que não vou escrever código Java limpo até entender isso.

Além disso, isso é limitado apenas ao SDK do Android ou é uma abordagem de OOP em todo o Java?

Ou, de outra forma,

O que há no design dessas duas linguagens que (parece encorajar) mais ou menos o uso de herança do que a outra?

Se as linguagens tratam a herança de forma idêntica, e assumindo que minha observação é válida, significa que isso está relacionado ao design das estruturas / bibliotecas e não às linguagens. Qual seria a motivação para esse tipo de design?

MetaFight
fonte
1
É verdade. Java possui muitas interfaces. Gostaria de saber por que esse comportamento do desenvolvedor é comum para um idioma específico. Vejo isso acontecendo com mais frequência em projetos Java do que qualquer outro. Tem que haver uma razão para isso e não apenas uma opinião.
Reactgular
1
@MathewFoscarini é apenas sua opinião . Nos projetos Java que participei, os desenvolvedores estavam inclinados a evitar a herança como uma praga. Nada autoritário aqui, apenas minha opinião . Quer enquete ?
Gnat
2
Você quase nunca precisa derivar uma classe de outra em Java. Em vez disso, você pode implementar interfaces para obter polimorfismo e implementar objetos compostos e proxy que o método chama para obter funcionalidade conectada.
DwB
1
@ThinkingMedia Em geral, o Java é invadido por fanáticos / puristas do OSS. Os princípios acadêmicos são a principal preocupação. Os desenvolvedores de .Net são pragmatistas preocupados em fazer o trabalho. Eles valorizam o código de trabalho mais do que o código mais limpo.
Andy

Respostas:

31

Parece ser uma diferença muito grande para se resumir ao estilo. Qual é o raciocínio por trás disso?

Meu entendimento é que, em grande parte, é simplesmente uma decisão estilística. Bem, talvez não seja estilo, mas os idiomas da linguagem / ambiente. Os desenvolvedores de bibliotecas padrão do Java seguiram um conjunto de diretrizes de design e os desenvolvedores do .NET outro (apesar de terem a capacidade de ver como a abordagem do Java funcionava).

Há muito pouco nas línguas atuais para incentivar ou dissuadir a herança. Apenas duas coisas me parecem relevantes:

  1. O .NET introduziu os genéricos mais cedo, antes de implementar muito código não genérico. A alternativa é muita herança para digitar coisas especializadas.

  2. Uma mudança maior foi que o .NET deu suporte a delegados. Em Java, você está preso à herança (anônima) para fornecer a funcionalidade mais básica das variáveis. Isso leva a uma diferença relativamente grande na maneira como o código é projetado para tirar proveito dos delegados ou para evitar as estruturas estranhas de herança necessárias para fazê-lo em Java.

Telastyn
fonte
3
Penso que esta resposta oferece uma explicação muito plausível. Especialmente o ponto sobre herança anônima no lugar dos delegados. Eu tenho observado muito isso. Obrigado.
MetaFight #
3
Não se esqueça dos tipos anônimos e das árvores de sintaxe / expressão lambda que foram introduzidas no .NET 3.5. E, é claro, optar pela digitação dinâmica no .NET 4. É quase inquestionavelmente uma linguagem de paradigma misto agora, não estritamente orientada a objetos.
Aaronaught 30/11
Sim, na minha experiência de ter usado as duas (inclusive em projetos mistos em que as mesmas pessoas usam as duas linguagens), as pessoas tendem a preferir usar um número maior de classes menores ao codificar Java, um número menor de classes maiores (tendendo para as classes god) quando usando c #. Depois de criar a prototipagem deliberada da mesma coisa usando o mesmo estilo em ambas, você acaba com um número semelhante de classes (usando classes parciais, você provavelmente acabaria com mais arquivos de código, mesmo usando C #).
usar o seguinte código
6

Estes são idiomas muito diferentes, especialmente nesta área. Digamos que você tenha uma aula. Nesse caso, tornaremos um controle de usuário, algo como uma caixa de texto. Chame de UIControl. Agora queremos colocar isso em outra classe. Nesse caso, como estamos usando uma interface do usuário para o nosso exemplo, a chamaremos de classe CleverPanel. Nossa instância CleverPanel vai querer saber sobre o que está acontecendo com sua instância UIControl por vários motivos. Como fazer isso?

Em C #, a abordagem básica é verificar vários eventos, configurando métodos que serão executados quando cada evento interessante for acionado. Em Java, que não possui eventos, a solução usual é passar um objeto com vários métodos de manipulação de "eventos" para um método UIControl:

boolean  stillNeedIt =  ... ;
uiControl.whenSomethingHappens( new DoSomething()  {
    public void resized( Rectangle r )  { ... }
    public boolean canICloseNow()  { return !stillNeedIt; }
    public void closed()  { ... }
    ...
} );

Até agora, a diferença entre C # e Java não é profunda. No entanto, temos a interface DoSomething que não é necessária em C #. Além disso, essa interface pode incluir muitos métodos que não são necessários na maioria das vezes. Em C #, simplesmente não lidamos com esse evento. Em Java, criamos uma classe que fornece uma implementação nula para todos os métodos de interface, DoSomethingAdapter. Agora substituímos DoSomething por DoSomethingAdapter e não precisamos escrever nenhum método para uma compilação limpa. Acabamos substituindo os métodos necessários para que o programa funcione corretamente. Então, acabamos precisando de uma interface e usando herança em Java para corresponder ao que fizemos com os eventos em C #.

Este é um exemplo, não uma discussão abrangente, mas fornece os fundamentos do motivo pelo qual há tanta herança em Java em vez de C #.

Agora, por que o Java funciona dessa maneira? Flexibilidade. O objeto passado para whenSomethingHappens poderia ter sido passado para o CleverPanel de outro lugar completamente. Pode ser algo que várias instâncias do CleverPanel devem passar para seus objetos do tipo UIControl para ajudar um objeto do CleverWindow em algum lugar. Ou o UIControl poderia entregá-lo a um de seus componentes.

Além disso, em vez de um adaptador, pode haver uma implementação DoSomething em algum lugar que tenha milhares de linhas de código por trás. Nós poderíamos criar uma nova instância disso e passá-la. Talvez seja necessário substituir um método. Um truque comum em Java é ter uma classe grande com um método como:

public class BigClass implements DoSomething  {
    ...many long methods...
    protected int getDiameter()  { return 5; }
}

Então, no CleverlPanel:

uiControl.whenSomethingHappens( new BigClass()  {
    @Override
    public int getDiameter()  { return UIPanel.currentDiameter; }
} );

A plataforma Java de código-fonte aberto faz muito disso, o que tende a pressionar os programadores a fazerem mais - tanto porque eles o seguem como um exemplo e simplesmente para usá-lo. Eu acho que o design básico da linguagem está por trás do design da estrutura da Sun e por trás dos programadores Java que usam as técnicas quando não estão usando a estrutura.

É muito fácil criar uma classe em tempo real em Java. A classe, anônima ou nomeada, só precisa ser referenciada em um pequeno bloco de código enterrado profundamente em um método. Ele pode ser criado completamente novo ou com pequenas modificações em uma classe existente muito grande. (E a classe existente pode ser de nível superior em seu próprio arquivo, ou aninhada em uma classe de nível superior, ou definida apenas dentro de um único bloco de código). A nova instância de classe pode ter acesso total a todos os dados do objeto de criação. E a nova instância pode ser transmitida e usada em todo o programa, representando o objeto que o criou.

(Como um aparte, observe que um grande uso de herança aqui - como em outros lugares em Java - é simplesmente para fins DRY. Permite que diferentes classes reutilizem o mesmo código. Observe também a facilidade de herança em Java que incentiva isso. )

Novamente, esta não é uma discussão abrangente; Estou apenas arranhando a superfície aqui. Mas sim, há uma diferença surpreendente em como a herança é usada entre Java e C #. Eles são, nesse sentido, idiomas muito diferentes. Não é sua imaginação.

RalphChapin
fonte
Nota: Você pode evitar ter que fornecer métodos que não fazem nada estendendo uma classe abstrata cheia de implementações vazias. Dessa forma, você pode substituir seletivamente os métodos que fazem alguma coisa. Embora não seja tão feio, significa outro nível de herança.
Peter Lawrey
-2

Não há absolutamente nenhuma diferença na maneira como a herança é tratada entre Java e C #. Quando você realmente usará herança ou composição é definitivamente uma decisão de design e de maneira alguma é algo que Java ou C # incentiva ou desencoraja. Eu gostaria de sugerir a leitura deste artigo .

Espero ter ajudado!

Pantelis Natsiavas
fonte
5
Você está falando sobre os próprios idiomas, mas acho que a questão também é sobre as bibliotecas e o uso comum.
Svick
3
Mas não são peculiaridades no resto da língua que incentivam herança extra no Java; veja a resposta de
RalphChapin