Meu entendimento atual da implementação de herança é que só se deve estender uma classe se houver uma relação IS-A . Se a classe pai puder ainda ter tipos filhos mais específicos com funcionalidade diferente, mas compartilhará elementos comuns abstraídos no pai.
Estou questionando esse entendimento por causa do que meu professor de Java está nos recomendando a fazer. Ele recomendou que, para um JSwing
aplicativo que estamos construindo em sala de aula
Um deve se estender todas as JSwing
classes ( JFrame
, JButton
, JTextBox
, etc) em classes personalizadas separadas e especificar GUI personalização relacionado neles (como o tamanho do componente, etiqueta componente, etc)
Até aí tudo bem, mas ele continua aconselhando que todo JButton deve ter sua própria classe estendida personalizada, mesmo que o único fator distintivo seja o rótulo.
Por exemplo, se a GUI tiver dois botões OK e Cancelar . Ele recomenda que eles sejam estendidos conforme abaixo:
class OkayButton extends JButton{
MainUI mui;
public OkayButton(MainUI mui) {
setSize(80,60);
setText("Okay");
this.mui = mui;
mui.add(this);
}
}
class CancelButton extends JButton{
MainUI mui;
public CancelButton(MainUI mui) {
setSize(80,60);
setText("Cancel");
this.mui = mui;
mui.add(this);
}
}
Como você pode ver, a única diferença está na setText
função.
Então, essa é uma prática padrão?
Btw, o curso em que isso foi discutido é chamado de Melhores Práticas de Programação em Java
[Resposta do Prof]
Então, discuti o problema com o professor e levantei todos os pontos mencionados nas respostas.
Sua justificativa é que a subclasse fornece código reutilizável enquanto segue os padrões de design da GUI. Por exemplo, se o desenvolvedor usou botões Okay
e Cancel
botões personalizados em uma janela, será mais fácil colocar os mesmos botões em outro Windows também.
Entendo o motivo que suponho, mas ainda está apenas explorando a herança e tornando o código frágil.
Mais tarde, qualquer desenvolvedor pode acidentalmente chamar o setText
em um Okay
botão e mudá-lo. A subclasse apenas se torna um incômodo nesse caso.
JButton
e chamar métodos públicos em seu construtor, quando você pode simplesmente criar umJButton
e chamar esses mesmos métodos públicos fora da classe?Respostas:
Isso viola o Princípio de Substituição de Liskov, porque
OkayButton
não é possível substituí-lo em qualquer lugar em queButton
seja esperado. Por exemplo, você pode alterar o rótulo de qualquer botão que desejar. Mas fazer isso com umOkayButton
viola seus invariantes internos.Este é um mau uso clássico da herança para reutilização de código. Use um método auxiliar em seu lugar.
Outro motivo para não fazer isso é que essa é apenas uma maneira complicada de obter a mesma coisa que o código linear faria.
fonte
OkayButton
tenha o invariante em que você está pensando. OOkayButton
pode alegar não ter nenhum invariante extra, pode apenas considerar o texto como padrão e pretender dar suporte completo a modificações no texto. Ainda não é uma boa ideia, mas não por esse motivo.activateButton(Button btn)
espera um botão, você pode facilmente fornecer uma instância deOkayButton
. O princípio não significa que ele precisa ter exatamente a mesma funcionalidade, pois isso tornaria a herança praticamente inútil.setText(button, "x")
porque isso viola a (suposta) invariante. É verdade que esse OkayButton em particular pode não ter nenhum invariante interessante, então acho que peguei um mau exemplo. Mas pode. Por exemplo, e se o manipulador de cliques informarif (myText == "OK") ProcessOK(); else ProcessCancel();
? Então o texto faz parte do invariante.É completamente terrível de todas as maneiras possíveis. No máximo , use uma função de fábrica para produzir JButtons. Você só deve herdar deles se tiver algumas sérias necessidades de extensão.
fonte
JButton
,JCheckBox
,JRadioButton
, é apenas uma concessão para os desenvolvedores que são familiarizados com outros kits de ferramentas, como o AWT simples, onde esses tipos são o padrão. Em princípio, todos eles poderiam ser manipulados por uma única classe de botão. É a combinação do modelo e do delegado da interface do usuário que faz a diferença real.Esta é uma maneira muito fora do padrão de escrever código Swing. Normalmente, você raramente cria subclasses de componentes da UI do Swing, mais comumente o JFrame (para configurar janelas filho e manipuladores de eventos), mas mesmo essa subclasse é desnecessária e desencorajada por muitos. O fornecimento de personalização de texto para botões e assim por diante geralmente é realizado estendendo a classe AbstractAction (ou a interface Action que ele fornece). Isso pode fornecer texto, ícones e outras personalizações visuais necessárias e vinculá-los ao código real que eles representam. Essa é uma maneira muito melhor de escrever código da interface do que os exemplos que você mostra.
(a propósito, o Google Scholar nunca ouviu falar do artigo que você cita - você tem uma referência mais precisa?)
fonte
JPanel
. Na verdade, é mais útil subclassificar isso do queJFrame
, uma vez que um painel é apenas um contêiner abstrato.IMHO, Melhores Práticas de Programação em Java são definidas pelo livro de Joshua Bloch, "Java Efetivo". É ótimo que seu professor esteja dando exercícios de POO e é importante aprender a ler e escrever os estilos de programação de outras pessoas. Mas fora do livro de Josh Bloch, as opiniões variam bastante sobre as melhores práticas.
Se você deseja estender essa classe, também pode tirar proveito da herança. Crie uma classe MyButton para gerenciar o código comum e subclasse-o para as partes variáveis:
Quando é uma boa ideia? Quando você usa os tipos que você criou! Por exemplo, se você tem uma função para criar uma janela pop-up e a assinatura é:
Esses tipos que você acabou de criar não estão fazendo nenhum bem. Mas:
Agora você criou algo útil.
O compilador verifica se showPopUp usa OkButton e CancelButton. Alguém lendo o código sabe como essa função deve ser usada, porque esse tipo de documentação causará um erro em tempo de compilação se ficar desatualizado. Este é um benefício MAIOR. Os 1 ou 2 estudos empíricos dos benefícios da segurança de tipo descobriram que a compreensão do código humano era o único benefício quantificável.
Isso também evita erros nos quais você inverte a ordem dos botões que passa para a função. Esses erros são difíceis de detectar, mas também são bastante raros, portanto, esse é um benefício MENOR. Isso foi usado para vender segurança de tipo, mas não foi comprovado empiricamente como sendo particularmente útil.
A primeira forma da função é mais flexível porque exibirá dois botões. Às vezes, é melhor do que restringir que tipo de botões serão necessários. Lembre-se de que você deve testar a função de qualquer botão em mais circunstâncias - se funcionar apenas quando você passar exatamente o tipo certo de botão, não estará fazendo nenhum favor a ninguém, fingindo que precisará de qualquer tipo de botão.
O problema com a programação orientada a objetos é que ela coloca a carroça diante do cavalo. Você precisa escrever a função primeiro para saber qual é a assinatura para saber se vale a pena criar tipos para ela. Mas em Java, você precisa primeiro criar seus tipos para poder escrever a assinatura da função.
Por esse motivo, você pode escrever um código Clojure para ver como é incrível escrever suas funções primeiro. Você pode codificar rapidamente, pensando na complexidade assintótica o máximo possível, e a suposição de imutabilidade de Clojure evita os erros tanto quanto os tipos em Java.
Ainda sou fã de tipos estáticos para projetos grandes e agora uso alguns utilitários funcionais que me permitem escrever funções primeiro e nomear meus tipos posteriormente em Java . Apenas um pensamento.
PS Fiz o
mui
ponteiro final - ele não precisa ser mutável.fonte
Eu gostaria de apostar que seu professor não acredita que você sempre deve estender um componente Swing para usá-lo. Aposto que eles estão usando isso como um exemplo para forçá-lo a praticar a extensão de aulas. Ainda não me preocuparia muito com as melhores práticas do mundo real.
Dito isto, no mundo real, favorecemos a composição sobre a herança .
Sua regra de "só se deve estender uma classe se houver uma relação IS-A" está incompleta. Deve terminar com "... e precisamos alterar o comportamento padrão da classe" em grandes letras em negrito.
Seus exemplos não se enquadram nesse critério. Você não precisa
extend
daJButton
turma apenas para definir seu texto. Você não precisaextend
daJFrame
classe apenas para adicionar componentes a ela. Você pode fazer essas coisas perfeitamente usando suas implementações padrão, portanto, adicionar herança é apenas adicionar complicações desnecessárias.Se eu
extends
vir uma classe que outra classe, vou me perguntar o que essa classe está mudando . Se você não está mudando nada, não me faça olhar a classe.Voltando à sua pergunta: quando você deve estender uma classe Java? Quando você tem um motivo muito, muito, muito bom para estender uma aula.
Aqui está um exemplo específico: uma maneira de fazer pintura personalizada (para um jogo ou animação ou apenas para um componente personalizado) é estendendo a
JPanel
classe. (Mais informações sobre isso aqui .) Você estende aJPanel
classe porque precisa substituir apaintComponent()
função. Você está realmente mudando o comportamento da classe fazendo isso. Você não pode fazer pintura personalizada com aJPanel
implementação padrão .Mas, como eu disse, seu professor provavelmente está apenas usando esses exemplos como uma desculpa para forçá-lo a praticar a extensão das aulas.
fonte
Border
que pinte decorações adicionais. Ou crieJLabel
e passe umaIcon
implementação personalizada para ele. Não é necessário subclasseJPanel
para pintura (e por que emJPanel
vez deJComponent
).