Como desacoplar completamente o modelo do View / Controller no Java Swing


10

Existe uma coleção de diretrizes de design comumente acordadas para separar as classes Model das classes View / Controller em um aplicativo Java Swing? Não estou tão preocupado com o fato de o View / Controller não saber nada sobre o modelo como o contrário: eu gostaria de projetar meu modelo para não ter conhecimento de nada no javax.swing. Idealmente, ele deve ter uma API simples, permitindo que ela seja impulsionada por algo tão primitivo quanto uma CLI. Deveria ser, de maneira geral, um "mecanismo".

A comunicação de eventos da GUI ao modelo não é muito difícil - os Action Performers podem chamar a API do modelo. Mas e quando o modelo faz suas próprias alterações de estado que precisam ser refletidas na GUI? É para isso que serve "ouvir", mas mesmo "ser ouvido" não é inteiramente passivo; requer que o modelo saiba sobre a adição de um ouvinte.

O problema específico que me fez pensar envolve uma fila de arquivos. No lado da GUI, há um DefaultListModelpor trás de JListe há algumas coisas da GUI para escolher arquivos do sistema de arquivos e adicioná-los à JList. No lado do modelo, ele deseja extrair os arquivos da parte inferior dessa "fila" (fazendo com que eles desapareçam da JList) e processá-los de alguma maneira. De fato, o código do modelo já está gravado - atualmente mantém ArrayList<File>e expõe um add(File)método público . Mas não sei como fazer com que meu modelo funcione com o View / Controller sem algumas modificações pesadas e específicas do Swing no modelo.

Sou muito novo em programação Java e GUI, sempre tendo feito programação em "lote" e "back-end" até agora - daí o meu interesse em manter uma divisão estrita entre modelo e interface do usuário, se possível e possível ser ensinado.



Btw: No MVC, o modelo deve ser dissociado da visualização e do controlador por padrão. Você deveria ler seu livro novamente, acho que ainda não entendeu bem o conceito. E você pode implementar uma coleção personalizada que notifica quando ela muda, assim como a interface INotifyCollectionChanged do .NET.
Falcon

@ Falcon: obrigado pelos links. Não tenho certeza se entendi o seu comentário, no entanto .. "o modelo deve ser dissociado da visualização e do controlador por padrão". Você poderia reformular ou elaborar?
Chap

Respostas:


10

Não há diretrizes de projeto comumente acordadas (ou seja, defacto ) para o MVC. Não é tão difícil fazê-lo você mesmo, mas exige algum planejamento em suas aulas e muito tempo e paciência.

A razão pela qual não existe uma solução definitiva é porque existem várias maneiras de fazer o MVC, todas com seus prós e contras. Portanto, seja esperto e faça o que melhor lhe convier.

Para responder sua pergunta, você também deseja desacoplar o controlador da visualização (para poder usar a mesma lógica de regra de negócios para um aplicativo Swing e no aplicativo do console). No exemplo do Swing, você deseja desacoplar o controlador do JWindowe de qualquer widget no Swing. O jeito que eu costumava fazer (antes de usar estruturas reais) é criar uma interface para a visualização que o controlador usa:

public interface PersonView {
    void setPersons(Collection<Person> persons);
}

public class PersonController {

    private PersonView view;
    private PersonModel model;

    public PersonController(PersonView view, PersonModel model) {
        this.view = view;
        this.model = model;
    }
    // ... methods to affect the model etc. 
    // such as refreshing and sort:

    public void refresh() {
        this.view.setPersons(model.getAsList());
    }

    public void sortByName(boolean descending) {
       // do your sorting through the model.
       this.view.setPersons(model.getSortedByName());
    }

}

Para esta solução durante a inicialização, você precisa registrar o controlador na visualização.

public class PersonWindow extends JWindow implements PersonView {

    PersonController controller;
    Model model;

    // ... Constructor etc.

    public void initialize() {
        this.controller = new PersonController(this, this.model);

        // do all the other swing stuff

        this.controller.refresh();
    }

    public void setPersons(Collection<Person> persons) {
        // TODO: set the JList (in case that's you are using) 
        // to use the given parameter
    }

}

Pode ser uma boa ideia criar um contêiner de IoC para fazer toda a configuração para você.

De qualquer forma, dessa maneira, você pode implementar visualizações somente do console, usando os mesmos controladores:

public class PersonConsole implements PersonView {

    PersonController controller;
    Model model;

    public static void main(String[] args) {
        new PersonConsole().run();
    }

    public void run() {
        this.model = createModel();
        this.controller = new PersonController(this, this.model);

        this.controller.refresh();
    }

    public void setPersons(Collection<Person> persons) {
        // just output the collection to the console

        StringBuffer output = new StringBuffer();
        for(Person p : persons) {
            output.append(String.format("%s%n", p.getName()));
        }

        System.out.println(output);
    }

    public void createModel() {
        // TODO: create this.model
    }

    // this could be expanded with simple console menu with keyboard
    // input and other console specific stuff

}    

A parte divertida é como lidar com eventos. Eu implementei isso permitindo que a visualização se registrasse no controlador usando uma interface, isso é feito usando o padrão Observer (se você estiver usando o .NET, você usaria manipuladores de eventos). Aqui está um exemplo de um "observador de documentos" simples, que sinaliza quando o documento foi salvo ou carregado.

public interface DocumentObserver {
    void onDocumentSave(DocModel saved);
    void onDocumentLoad(DocModel loaded);
}

// in your controller you implement register/unregister methods
private List<DocumentObserver> observers;

// register observer in to the controller
public void addObserver(DocumentObserver o) {
    this.observers.add(o);
}

// unregisters observer from the controller
public void removeObserver(DocumentObserver o) {
    this.observers.remove(o);
}

public saveDoc() {
    DocModel model = model.save();
    for (DocumentObserver o : observers) {
        o.onDocumentSave(model);
    }
}

public loadDoc(String path) {
    DocModel model = model.load(path);
    for (DocumentObserver o : observers) {
        o.onDocumentLoad(model);
    }        
}

Dessa forma, a exibição pode se atualizar adequadamente, pois está assinando as atualizações do documento. Tudo o que precisa fazer é implementar a DocumentObserverinterface:

public class DocumentWindow extends JWindow 
        implements DocView, DocumentObserver {

    //... all swing stuff

    public void onDocumentSave(DocModel saved) {
        // No-op
    }

    public void onDocumentLoad(DocModel loaded) {
        // do what you need with the loaded model to the
        // swing components, or let the controller do it on
        // the view interface
    }

    // ...

}

Espero que esses exemplos motivadores lhe dê algumas idéias sobre como fazer você mesmo. No entanto, eu recomendo fortemente que você considere o uso de estruturas em Java que fazem a maioria das coisas para você; caso contrário, você acabará tendo muito código clichê que demora muito tempo para escrever. Existem algumas RCP (Rich Client Platforms) que você pode usar para implementar algumas das funcionalidades básicas que você provavelmente precisa, como manipulação de documentos em todo o aplicativo e muita manipulação de eventos básicos.

Há alguns que consigo pensar na minha cabeça: Eclipse e RCP do Netbeans .

Você ainda precisa desenvolver controladores e modelos para si mesmo, mas é por isso que você usa um ORM. O exemplo seria o Hibernate .

Os contêineres de IoC são legais, mas também existem estruturas para isso. Como o Spring (que também lida com dados, entre outras coisas).


Obrigado por reservar um tempo para escrever uma resposta tão longa. A maior parte está um pouco acima da minha cabeça neste momento, mas tenho certeza que a Wikipedia ajudará. Eu estou usando tanto Eclipse e NetBeans, e inclinando-se para o último. Não sei como distinguir o Controller da View, mas seu exemplo de pré-estrutura no topo ajuda.
Chap

0

Minha opinião é que, às vezes, precisamos comprometer

Como você disse, seria ótimo se pudéssemos propagar a notificação de alteração implicitamente sem que o objeto observado tivesse infraestrutura explícita para isso. Para linguagens imperativas comuns como Java, C #, C ++, sua arquitetura de tempo de execução é muito leve, a partir de agora, de qualquer maneira. Com isso, quero dizer que não faz parte da especificação de linguagem no momento.

No seu caso particular, não acho que seja exatamente ruim definir / usar alguma interface genérica como INotifyPropertyChanged (como em c #), pois isso não é acoplado automaticamente a uma exibição de qualquer maneira - apenas diz que, se eu mudar, Eu vou te contar .

Mais uma vez, concordo que seria ótimo se não tivéssemos que definir a notificação de alteração, mas, novamente, se estivesse implícita em todas as classes, poderia ocorrer uma sobrecarga.


quanto mais penso nisso, percebo que você está certo - o objeto modelo deve estar envolvido, até certo ponto, no início da notificação à GUI de que ocorreu uma alteração; caso contrário, a GUI teria que pesquisar o modelo para descobrir mudanças - e isso é ruim.
Chap
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.