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 uma relação IS-A estiver presente. Se a classe pai puder ter tipos filhos mais específicos com funcionalidade diferente, mas compartilhará elementos comuns que foram abstraídos no pai.

Estou questionando essa compreensão por causa do que meu professor de Java está nos recomendando a fazer. Ele recomendou que, para um aplicativo JSwing , estamos construindo em classe

Deve-se estender todas as classes JSwing ( JFrame , JButton , JTextBox , etc) em classes personalizadas separadas e especificar a personalização relacionada à GUI nelas (como o tamanho do componente, rótulo do componente, etc.)

Até aí tudo bem, mas ele continua avisando que todo JButton deve ter sua própria classe estendida personalizada, mesmo que o único fator distintivo seja seu rótulo.

Por exemplo Se a GUI tiver dois botões Ok e Cancelar . Ele recomenda que eles sejam estendidos como 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 função setText .

Então, esta prática padrão?

Btw, o curso em que isso foi discutido é chamado Melhores Práticas de Programação em Java

[Resposta do Prof]

Então eu 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 personalizados em uma janela, será mais fácil colocar os mesmos botões em outras janelas também.

Eu entendo o motivo, suponho, mas ainda está apenas explorando herança e tornando o código frágil.

Mais tarde, qualquer desenvolvedor pode acidentalmente chamar o botão setText em um Okay e alterá-lo. A subclasse apenas se torna incômodo nesse caso.

    
por Paras Singh Laddi 04.05.2016 / 09:14
fonte

5 respostas

21

Isso viola o Princípio de substituição de Liskov porque um OkayButton não pode ser substituído em nenhum lugar onde um Button é esperado . Por exemplo, você pode alterar o rótulo de qualquer botão como quiser. Mas fazer isso com um OkayButton viola suas invariantes internas.

Este é um uso incorreto clássico de herança para reutilização de código. Use um método auxiliar em seu lugar.

Outra razão para não fazer isso é que esta é apenas uma maneira complicada de alcançar a mesma coisa que o código linear faria.

    
por 04.05.2016 / 19:56
fonte
50

É completamente terrível de todas as maneiras possíveis. Em mais , use uma função de fábrica para produzir JButtons. Você só deve herdar deles se tiver algumas necessidades de extensão graves.

    
por 04.05.2016 / 09:21
fonte
12

Esta é uma maneira muito pouco padronizada de escrever código Swing. Normalmente, você raramente cria subclasses de componentes de UI do Swing, mais comumente JFrame (para configurar janelas filhas e manipuladores de eventos), mas mesmo essa subclasse é desnecessária e desencorajada por muitos. Fornecer customização de texto para botões e assim por diante geralmente é feito estendendo a classe AbstractAction (ou o Interface de ação que fornece). Isso pode fornecer texto, ícones e outras personalizações visuais necessárias e vinculá-los ao código real que eles representam. Esta é uma maneira muito melhor de escrever código de interface do usuário do que os exemplos que você mostra.

(a propósito, o google scholar nunca ouviu falar do documento que você cita - você tem uma referência mais precisa?)

    
por 04.05.2016 / 10:53
fonte
10

IMHO, Melhores Práticas de Programação em Java são definidas pelo livro de Joshua Bloch, "Effective Java". É ótimo que seu professor esteja lhe dando exercícios OOP e seja importante aprender a ler e escrever estilos de programação de outras pessoas. Mas fora do livro de Josh Bloch, as opiniões variam muito sobre as melhores práticas.

Se você pretende estender essa classe, também pode aproveitar a herança. Crie uma classe MyButton para gerenciar o código comum e subclasse-a 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 isso é uma boa ideia? Quando você usa os tipos que você criou! Por exemplo, se você tiver uma função para criar uma janela pop-up e a assinatura for:

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

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

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

Agora você criou algo útil.

  1. O compilador verifica se showPopUp usa um OkButton e um 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 ela ficar desatualizada. Este é um benefício importante. O 1 ou 2 estudos empíricos sobre os 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 quando você inverte a ordem dos botões que você passa para a função. Esses erros são difíceis de detectar, mas também são muito raros, então esse é um benefício MINOR. Isso foi usado para vender segurança do 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 o tipo de botões que serão necessários. Apenas lembre-se que você tem que testar a função de qualquer botão em mais circunstâncias - se ela só funciona quando você passa exatamente o tipo certo de botões, você não está fazendo nenhum favor fingindo que vai pegar qualquer tipo de botão.

O problema da Programação Orientada a Objetos é que ela coloca o carrinho antes do cavalo. Você precisa escrever a função primeiro para saber qual é a assinatura para saber se vale a pena fazer tipos para ela. Mas em Java, você precisa fazer seus tipos primeiro para poder escrever a assinatura da função.

Por esta razão, você pode querer escrever algum código Clojure para ver o quão incrível pode ser escrever suas funções primeiro. Você pode codificar rapidamente, pensando na complexidade assintótica tanto quanto possível, e a suposição de imutabilidade do Clojure previne erros tanto quanto os tipos em Java.

Eu ainda sou fã de tipos estáticos para grandes projetos e agora uso alguns utilitários funcionais que me permitem escreve primeiro as funções e nomeia os meus tipos mais tarde em Java . Apenas um pensamento.

P.S. Eu fiz o ponteiro mui final - ele não precisa ser mutável.

    
por 04.05.2016 / 13:52
fonte
8

Eu estaria disposto a apostar que o seu professor realmente não acredita que você deve sempre estender um componente do Swing para usá-lo. Aposto que eles estão apenas usando isso como um exemplo para forçá-lo a praticar aulas de extensão. Eu não me preocuparia muito com as melhores práticas do mundo real ainda.

Dito isto, no mundo real favorecemos a composição sobre herança .

Sua regra de "um só deve estender uma classe se uma relação IS-A estiver presente" está incompleta. Ele deve terminar com "... e precisamos alterar o comportamento padrão da classe" em letras maiúsculas grandes.

Seus exemplos não se encaixam nesse critério. Você não precisa extend the JButton classe apenas para definir seu texto. Você não precisa extend the JFrame da classe apenas para adicionar componentes a ela. Você pode fazer essas coisas muito bem usando suas implementações padrão, portanto, adicionar herança é apenas adicionar complicações desnecessárias.

Se eu vir uma classe que extends de outra classe, vou me perguntar o que é essa classe mudando . Se você não está mudando nada, não me faça dar uma olhada na aula.

Voltar para sua pergunta: quando você deve estender uma classe Java? Quando você tem uma razão muito, muito boa para estender uma classe.

Veja um exemplo específico: uma maneira de fazer pintura personalizada (para um jogo ou animação, ou apenas para um componente personalizado) é estendendo a classe JPanel . (Mais informações sobre aqui .) Você estende a classe JPanel porque precisa substituir a função paintComponent() . Você está realmente mudando o comportamento da classe ao fazer isso. Você não pode fazer uma pintura personalizada com a implementação JPanel padrão.

Mas, como eu disse, seu professor provavelmente está apenas usando esses exemplos como uma desculpa para forçá-lo a praticar aulas de extensão.

    
por 04.05.2016 / 18:42
fonte