Quando devo estender uma classe Java Swing?


35

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 JSwingaplicativo que estamos construindo em sala de aula

Um deve se estender todas as JSwingclasses ( 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 setTextfunçã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 Okaye Cancelbotõ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 setTextem um Okaybotão e mudá-lo. A subclasse apenas se torna um incômodo nesse caso.


3
Por que estender JButtone chamar métodos públicos em seu construtor, quando você pode simplesmente criar um JButtone chamar esses mesmos métodos públicos fora da classe?

17
fique longe desse professor, se puder. Isso está longe de ser uma boa prática . A duplicação de código como você ilustrou é um cheiro muito ruim.
Njzk2 4/16

7
Basta perguntar a ele por que, não hesite em comunicar sua motivação aqui.
Alex

9
Quero diminuir o voto porque esse código é péssimo, mas também quero aumentar o voto porque é ótimo que você esteja questionando, em vez de aceitar cegamente o que um professor ruim diz.
Fund Monica's Lawsuit

11
@Alex Eu atualizei a questão com a justificação do prof
Paras

Respostas:


21

Isso viola o Princípio de Substituição de Liskov, porque OkayButtonnão é possível substituí-lo em qualquer lugar em que Buttonseja esperado. Por exemplo, você pode alterar o rótulo de qualquer botão que desejar. Mas fazer isso com um OkayButtonviola 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.


Isso não viola o LSP, a menos que OkayButtontenha o invariante em que você está pensando. O OkayButtonpode 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.
hvd 5/05

Não o viola porque você pode. Ou seja, se um método activateButton(Button btn)espera um botão, você pode facilmente fornecer uma instância de OkayButton. O princípio não significa que ele precisa ter exatamente a mesma funcionalidade, pois isso tornaria a herança praticamente inútil.
Sebb 5/05

11
@ Sebb, mas você não pode usá-lo com uma função, 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 informar if (myText == "OK") ProcessOK(); else ProcessCancel();? Então o texto faz parte do invariante.
usr

@usr Eu não pensei que o texto fazia parte do invariável. Você está certo, é violado então.
Sebb 5/05

50

É 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.


20
+1. Para obter mais referências, consulte a hierarquia de classes Swing de AbstractButton . Em seguida, observe a hierarquia do JToggleButton . A herança é usada para conceituar diferentes tipos de botão. Mas não é usado para diferenciar conceitos de negócios ( Sim, Não, Continuar, Cancelar ... ).
Laiv 4/16

2
@Laiv: mesmo tendo tipos distintos para, por exemplo 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.
Holger

Sim, eles podem ser manipulados por um único botão. No entanto, a hierarquia real pode ser exposta como boas práticas. Criar um novo botão ou apenas delegar o controle de eventos e comportamentos a outros componentes é uma questão de preferência. Se fosse eu, estenderia os componentes do Swing para modelar meu botão owm e encapsular seus comportamentos, ações, ... Apenas para facilitar sua implementação no projeto e para os juniores. Em envs da web, prefiro componentes da web do que muita manipulação de eventos. Em todo o caso. Você está certo, podemos dissociar a interface do usuário do Behaivors.
Laiv 4/16

12

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?)


Qual é exatamente o "caminho padrão"? Concordo que escrever uma subclasse para cada componente é provavelmente uma má ideia, mas os tutoriais oficiais do Swing ainda favorecem essa prática. Eu acho que o professor apenas segue o estilo mostrado na documentação oficial da estrutura.
VENHA DE

@COMEFROM Admito que não li todo o tutorial do Swing, então talvez eu tenha esquecido onde esse estilo é usado, mas as páginas que eu olhei tendem a usar as classes base e não a conduzir subclasses, por exemplo, docs.oracle .com / JavaSE / tutorial / uiswing / componentes / button.html
Jules

@Jules Desculpe a confusão, eu quis dizer o curso / papel o professor ensina é chamado de Melhores Práticas em Java
Paras

11
Geralmente verdade, mas há uma outra grande exceção - JPanel. Na verdade, é mais útil subclassificar isso do que JFrame, uma vez que um painel é apenas um contêiner abstrato.
Ordous 4/16

10

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:

class MyButton extends JButton{
    protected final MainUI mui;
    public MyButton(MainUI mui, String text) {
        setSize(80,60);
        setText(text);
        this.mui = mui;
        mui.add(this);        
    }
}

class OkayButton extends MyButton{
    public OkayButton(MainUI mui) {
        super(mui, "Okay");
    }
}

class CancelButton extends MyButton{
    public CancelButton(MainUI mui) {
        super(mui, "Cancel");
    }
}

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 é:

public void showPopUp(String text, JButton ok, JButton cancel)

Esses tipos que você acabou de criar não estão fazendo nenhum bem. Mas:

public void showPopUp(String text, OkButton ok, CancelButton cancel)

Agora você criou algo útil.

  1. 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.

  2. 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.

  3. 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 muiponteiro final - ele não precisa ser mutável.


6
Dos seus 3 pontos, 1 e 3 também podem ser tratados com JButtons genéricos e um esquema de nomeação de parâmetros de entrada adequado. o ponto 2 é, na verdade, uma desvantagem, pois torna seu código mais acoplado: e se você quiser que a ordem dos botões seja revertida? E se, em vez dos botões OK / Cancelar, você quiser usar os botões Sim / Não? Você criará um método showPopup totalmente novo que usa um YesButton e um Nobutton? Se você apenas usar JButtons padrão, não precisará criar seus tipos primeiro, porque eles já existem.
Nzall

Estendendo o que o @NateKerkhofs disse, um IDE moderno mostrará os nomes dos argumentos formais enquanto você digita as expressões reais.
Solomon Slow

8

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 extendda JButtonturma apenas para definir seu texto. Você não precisa extendda JFrameclasse 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 extendsvir 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 JPanelclasse. (Mais informações sobre isso aqui .) Você estende a JPanelclasse porque precisa substituir a paintComponent()função. Você está realmente mudando o comportamento da classe fazendo isso. Você não pode fazer pintura personalizada com a JPanelimplementaçã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.


11
Oh, espere, você pode definir um Borderque pinte decorações adicionais. Ou crie JLabele passe uma Iconimplementação personalizada para ele. Não é necessário subclasse JPanelpara pintura (e por que em JPanelvez de JComponent).
Holger

11
Acho que você tem a ideia certa aqui, mas provavelmente poderia escolher melhores exemplos.

11
Mas quero reiterar que sua resposta está correta e você faz bons pontos . Eu apenas acho que o exemplo específico e o tutorial o suportam fracamente.

11
@KevinWorkman Eu não conheço o desenvolvimento de jogos Swing, mas fiz algumas interfaces personalizadas no Swing e "não estendendo as classes Swing, para que seja fácil conectar componentes e renderizadores via config" é bastante padrão lá.

11
"Aposto que eles estão usando isso como um exemplo para forçá-lo a ..." Uma das minhas maiores irritações! Os instrutores que ensinam como fazer X usando exemplos horrivelmente enlouquecido de por que fazer X.
Solomon Lento
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.