Bagaimana sepenuhnya memisahkan Model dari View / Controller di Java Swing

10

Apakah ada koleksi pedoman desain yang disepakati bersama untuk memisahkan kelas Model dari kelas View / Controller di aplikasi Java Swing? Saya tidak begitu khawatir bahwa View / Controller tidak tahu apa-apa tentang Model sebagai sebaliknya: Saya ingin merancang Model saya untuk tidak memiliki pengetahuan tentang apa pun di javax.swing. Idealnya itu harus memiliki API sederhana yang memungkinkannya untuk didorong oleh sesuatu yang primitif seperti CLI. Seharusnya, secara longgar, "mesin."

Mengkomunikasikan acara GUI ke model tidak terlalu sulit - Pelaku Aksi dapat memanggil API model. Tetapi bagaimana dengan ketika model membuat perubahan negara sendiri yang perlu dipantulkan kembali ke GUI? Untuk itulah "mendengarkan", tetapi bahkan "didengarkan" tidak sepenuhnya pasif; itu mengharuskan model tahu tentang menambahkan pendengar.

Masalah khusus yang membuat saya berpikir melibatkan antrian File. Di sisi GUI ada di DefaultListModelbelakang a JList, dan ada beberapa hal GUI untuk memilih file dari sistem file dan menambahkannya ke JList. Di sisi Model, ia ingin menarik file dari bagian bawah "antrian" ini (menyebabkan mereka menghilang dari JList) dan memprosesnya dengan cara tertentu. Bahkan, kode Model sudah ditulis - saat ini mempertahankan ArrayList<File>dan memperlihatkan add(File)metode publik . Tapi saya bingung bagaimana cara mendapatkan Model saya bekerja dengan View / Controller tanpa modifikasi berat, ayunan khusus untuk Model.

Saya sangat baru untuk pemrograman Java dan GUI, karena selalu melakukan pemrograman "batch" dan "back-end" sampai sekarang - maka minat saya dalam mempertahankan pembagian yang ketat antara model dan UI, jika memungkinkan dan jika dapat diajarkan.

Bab
sumber
Btw: Dalam MVC model harus dipisahkan dari tampilan dan pengontrol secara default. Anda harus membaca buku teks Anda lagi, saya pikir Anda belum mengerti konsepnya. Dan Anda bisa mengimplementasikan koleksi kustom yang memberi tahu ketika itu berubah, seperti antarmuka .NET's INotifyCollectionChanged.
Falcon
@ Falcon: terima kasih atas tautannya. Tidak yakin saya mengerti komentar Anda, namun .. "model harus dipisahkan dari tampilan dan pengontrol secara default". Bisakah Anda ulangi atau rumit?
Babak

Jawaban:

10

Tidak ada pedoman desain (yaitu defacto ) yang disepakati bersama untuk MVC. Ini tidak sepenuhnya sulit untuk dilakukan sendiri tetapi membutuhkan perencanaan kelas Anda dan banyak waktu dan kesabaran.

Alasan tidak ada solusi pasti adalah karena ada banyak cara untuk melakukan MVC, semua dengan pro dan kontra mereka. Jadi, cerdaslah tentang hal itu dan lakukan yang terbaik untuk Anda.

Untuk menjawab pertanyaan Anda, Anda sebenarnya juga ingin memisahkan pengontrol dari tampilan (sehingga Anda dapat menggunakan logika aturan bisnis yang sama untuk aplikasi Swing dan aplikasi konsol). Dalam contoh Swing Anda ingin memisahkan controller dari JWindowwidget apa pun di Swing. Cara yang biasa saya lakukan (sebelum menggunakan kerangka kerja aktual) adalah membuat antarmuka untuk tampilan yang digunakan pengontrol:

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());
    }

}

Untuk solusi ini selama startup Anda perlu mendaftarkan controller ke tampilan.

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
    }

}

Ini mungkin ide yang baik untuk membuat wadah-IOC untuk melakukan semua pengaturan untuk Anda sebagai gantinya.

Bagaimanapun, dengan cara ini Anda dapat mengimplementasikan tampilan konsol saja, menggunakan pengontrol yang sama:

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

}    

Bagian yang menyenangkan adalah bagaimana melakukan penanganan acara. Saya menerapkan ini dengan membiarkan tampilan mendaftar sendiri ke controller menggunakan antarmuka, ini dilakukan menggunakan pola Observer (jika Anda menggunakan .NET Anda akan menggunakan event handler sebagai gantinya). Berikut adalah contoh "pengamat dokumen" yang sederhana, yang memberi sinyal ketika dokumen telah disimpan atau dimuat.

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);
    }        
}

Dengan begitu, tampilan dapat memperbarui dirinya dengan benar karena berlangganan pembaruan dokumen. Yang harus dilakukan hanyalah mengimplementasikan DocumentObserverantarmuka:

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
    }

    // ...

}

Saya harap contoh-contoh yang memotivasi ini memberi Anda beberapa ide tentang bagaimana melakukannya sendiri. Namun saya sangat menyarankan Anda untuk mempertimbangkan menggunakan kerangka kerja di Jawa yang melakukan sebagian besar hal untuk Anda, atau Anda akan berakhir memiliki banyak kode boilerplate yang membutuhkan waktu lama untuk menulis. Ada beberapa Rich Client Platforms (RCP) yang dapat Anda gunakan yang mengimplementasikan beberapa fungsi dasar yang paling mungkin Anda butuhkan, seperti penanganan dokumen di seluruh aplikasi dan banyak penanganan acara dasar.

Ada beberapa yang bisa saya pikirkan dari kepala saya: Eclipse dan Netbeans RCP.

Anda masih perlu mengembangkan pengontrol dan model untuk diri Anda sendiri, tetapi itu sebabnya Anda menggunakan ORM. Contohnya adalah Hibernate .

Kontainer IoC memang keren, tapi ada kerangka kerja untuk ini juga. Seperti Spring (yang juga menangani data juga antara lain).

Spoike
sumber
Terima kasih telah meluangkan waktu untuk menulis tanggapan yang begitu panjang. Sebagian besar sedikit di luar kepala saya saat ini, tapi saya yakin Wikipedia akan membantu. Saya sedang menggunakan kedua Eclipse dan Netbeans, dan condong ke yang terakhir. Saya tidak yakin bagaimana membedakan Controller dari View di sana, tetapi contoh pra-kerangka kerja Anda di bagian atas membantu.
Babak
0

Menurut saya, terkadang kita perlu berkompromi

Seperti yang Anda katakan, alangkah baiknya jika kita dapat menyebarkan pemberitahuan perubahan secara implisit tanpa objek yang diamati memiliki infrastruktur eksplisit untuk ini. Untuk bahasa imperatif umum seperti Java, C #, C ++ arsitektur runtime mereka terlalu ringan, seperti yang sekarang. Maksud saya itu bukan bagian dari spesifikasi bahasa saat ini.

Dalam kasus khusus Anda, saya tidak berpikir itu adalah hal yang buruk untuk mendefinisikan / menggunakan beberapa antarmuka umum seperti INotifyPropertyChanged (seperti dalam c #), karena itu tidak secara otomatis digabungkan ke tampilan - itu hanya mengatakan bahwa jika saya berubah, Aku akan memberitahumu .

Sekali lagi, saya setuju itu akan bagus jika kita tidak harus menentukan sendiri notifikasi perubahan, tetapi sekali lagi jika itu implisit untuk semua kelas yang mungkin menimbulkan biaya tambahan.

Maks
sumber
semakin saya memikirkannya, saya sadar bahwa Anda benar sedang - model objek memiliki untuk terlibat untuk beberapa batas dalam memulai pemberitahuan kepada GUI bahwa perubahan telah terjadi; jika tidak, GUI harus melakukan polling pada model untuk menemukan perubahan - dan itu buruk.
Babak