วิธีแยก decouple Model ออกจาก View / Controller ใน Java Swing อย่างสมบูรณ์


10

มีคอลเลกชันของแนวทางการออกแบบที่ตกลงกันโดยทั่วไปสำหรับการแยกคลาส Model จากคลาส View / Controller ในแอพ Java Swing หรือไม่? ฉันไม่ได้เป็นห่วงว่า View / Controller ไม่รู้อะไรเลยเกี่ยวกับตัวแบบเหมือนกับที่อยู่รอบ ๆ : ฉันต้องการออกแบบตัวแบบให้ไม่มีความรู้อะไรเลยใน javax.swing โดยหลักการแล้วมันควรจะมี API ง่าย ๆ ที่ทำให้มันสามารถขับเคลื่อนด้วยบางสิ่งที่เป็นพื้นฐานในฐานะ CLI มันควรจะเป็น "เครื่องยนต์" อย่างหลวม ๆ

การสื่อสารเหตุการณ์ GUI กับตัวแบบนั้นไม่ยากเกินไปนักดำเนินการ Action สามารถเรียก API ของโมเดลได้ แต่จะทำอย่างไรเมื่อตัวแบบทำการเปลี่ยนแปลงสถานะของตัวเองซึ่งต้องสะท้อนกลับไปยัง GUI นั่นคือสิ่งที่ "การฟัง" มีไว้สำหรับ แต่แม้กระทั่ง "การฟัง" ก็ไม่ได้เป็นเชิงรับ มันต้องการให้โมเดลรู้เกี่ยวกับการเพิ่มฟัง

ปัญหาเฉพาะที่ทำให้ฉันคิดว่าเกี่ยวข้องกับคิวของไฟล์ ทางด้าน GUI นั้นมี a DefaultListModelอยู่ข้างหลังJListและมี GUI บางอย่างให้เลือกไฟล์จากระบบไฟล์และเพิ่มลงใน JList ที่ด้านรูปแบบมันต้องการดึงไฟล์ออกจากด้านล่างของ "คิว" นี้ (ทำให้ไฟล์หายไปจาก JList) และประมวลผลไฟล์ด้วยวิธีใดวิธีหนึ่ง อันที่จริงรหัสรุ่นได้ถูกเขียนขึ้นแล้ว - ขณะนี้ยังคงรักษาArrayList<File>และเปิดเผยadd(File)วิธีการสาธารณะ แต่ฉันกำลังสูญเสียเกี่ยวกับวิธีทำให้ Model ของฉันทำงานร่วมกับ View / Controller โดยไม่ต้องมีการดัดแปลงรูปแบบเฉพาะของ Swing อย่างหนัก

ฉันยังใหม่กับทั้งการเขียนโปรแกรม Java และ GUI การเขียนโปรแกรมแบบ "แบ็กเอนด์" และ "แบ็คเอนด์" เสมอมาจนถึงตอนนี้ - ดังนั้นฉันสนใจที่จะรักษาส่วนที่เข้มงวดระหว่างโมเดลและ UI หากเป็นไปได้และถ้าทำได้ ได้รับการสอน



Btw: ใน MVC โมเดลควรถูกแยกออกจากมุมมองและตัวควบคุมตามค่าเริ่มต้น คุณควรอ่านหนังสือเรียนอีกครั้งฉันคิดว่าคุณยังไม่เข้าใจแนวคิด และคุณสามารถใช้คอลเลกชันที่กำหนดเองซึ่งจะแจ้งเตือนเมื่อมีการเปลี่ยนแปลงเช่นอินเทอร์เฟซ INotifyCollectionChanged ของ. NET
Falcon

@ Falcon: ขอบคุณสำหรับลิงค์ ไม่แน่ใจว่าฉันเข้าใจความคิดเห็นของคุณอย่างไร .. "โมเดลควรถูกแยกออกจากมุมมองและตัวควบคุมตามค่าเริ่มต้น" คุณสามารถใช้ถ้อยคำใหม่หรือทำอย่างละเอียดได้หรือไม่
Chap

คำตอบ:


10

ไม่มีแนวทางการออกแบบ(เช่นdefacto ) ที่ตกลงกันตามปกติสำหรับ MVC มันไม่ยากเลยที่จะทำด้วยตัวเอง แต่ต้องการการวางแผนในชั้นเรียนของคุณรวมถึงเวลาและความอดทน

เหตุผลที่ไม่มีวิธีแก้ปัญหาที่แน่นอนก็คือมีหลายวิธีในการทำ MVC ซึ่งทั้งหมดมีข้อดีและข้อเสีย ดังนั้นจงฉลาดกับมันและทำสิ่งที่เหมาะสมกับคุณที่สุด

ในการตอบคำถามของคุณคุณต้องการแยกตัวควบคุมออกจากมุมมองเช่นกัน (เพื่อให้คุณสามารถใช้ตรรกะกฎธุรกิจเดียวกันสำหรับทั้งแอป Swing และในแอปคอนโซล) ในตัวอย่าง Swing คุณต้องการแยกตัวควบคุมออกจากJWindowวิดเจ็ตและอะไรก็ตามใน Swing วิธีที่ฉันใช้ในการทำ (ก่อนใช้กรอบงานจริง) คือการสร้างอินเตอร์เฟสสำหรับมุมมองที่คอนโทรลเลอร์ใช้:

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

}

สำหรับวิธีแก้ปัญหานี้ในระหว่างการเริ่มต้นคุณจะต้องลงทะเบียนคอนโทรลเลอร์ในมุมมอง

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
    }

}

อาจเป็นความคิดที่ดีที่จะสร้าง IoC-container เพื่อทำการตั้งค่าทั้งหมดสำหรับคุณแทน

ด้วยวิธีนี้คุณสามารถใช้มุมมองคอนโซลเท่านั้นโดยใช้ตัวควบคุมเดียวกัน:

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

}    

ส่วนที่สนุกคือวิธีการจัดการกิจกรรม ฉันใช้สิ่งนี้โดยให้มุมมองลงทะเบียนตัวเองกับคอนโทรลเลอร์โดยใช้อินเตอร์เฟสสิ่งนี้ทำได้โดยใช้รูปแบบ Observer (หากคุณใช้. NET คุณจะใช้ตัวจัดการเหตุการณ์แทน) นี่คือตัวอย่างของ "ผู้สังเกตการณ์เอกสาร" แบบง่ายซึ่งจะส่งสัญญาณเมื่อเอกสารถูกบันทึกหรือโหลด

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

ด้วยวิธีนี้มุมมองสามารถอัปเดตตัวเองได้อย่างเหมาะสมเนื่องจากกำลังสมัครรับข้อมูลอัปเดตเอกสาร สิ่งที่ต้องทำคือใช้DocumentObserverอินเทอร์เฟซ:

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
    }

    // ...

}

ฉันหวังว่าตัวอย่างที่สร้างแรงบันดาลใจเหล่านี้จะช่วยให้คุณมีความคิดเกี่ยวกับวิธีการทำด้วยตัวเอง อย่างไรก็ตามฉันขอแนะนำให้คุณพิจารณาใช้เฟรมเวิร์กใน Java ที่ทำสิ่งต่าง ๆ ให้คุณมากที่สุดมิฉะนั้นคุณจะต้องมีรหัสสำเร็จรูปจำนวนมากที่ใช้เวลาเขียนนาน มีสองสาม Rich Client Platforms (RCP) ที่คุณสามารถใช้เพื่อใช้งานฟังก์ชันพื้นฐานบางอย่างที่คุณต้องการเช่นการจัดการเอกสารทั่วทั้งแอปพลิเคชันและการจัดการเหตุการณ์พื้นฐานมากมาย

มีคู่ที่ฉันนึกได้จากหัวของฉัน: EclipseและNetbeans RCP's

คุณยังต้องพัฒนาตัวควบคุมและรุ่นสำหรับตัวคุณเอง แต่นั่นเป็นเหตุผลที่คุณใช้ ORM ตัวอย่างจะเป็นไฮเบอร์เนต

คอนเทนเนอร์ IoC นั้นยอดเยี่ยม แต่มีกรอบสำหรับเรื่องนี้เช่นกัน เช่นSpring (ซึ่งจัดการข้อมูลได้ดีเช่นกัน)


ขอบคุณที่สละเวลาเขียนคำตอบยาว ๆ ส่วนใหญ่มันเป็นเรื่องเล็กน้อยที่หัวของฉัน ณ จุดนี้ แต่ฉันแน่ใจว่า Wikipedia จะช่วย ฉันกำลังใช้ทั้ง Eclipse และ Netbeans และที่พิงหลัง ฉันไม่แน่ใจว่าจะแยกความแตกต่างของคอนโทรลเลอร์จากมุมมองที่นั่นได้อย่างไร แต่ตัวอย่างก่อนเฟรมเวิร์กของคุณที่ด้านบนช่วยได้
Chap

0

สิ่งที่ฉันทำในตอนนี้คือบางครั้งเราจำเป็นต้องประนีประนอม

อย่างที่คุณพูดมันคงจะดีถ้าเราสามารถเผยแพร่การแจ้งเตือนการเปลี่ยนแปลงโดยนัยโดยไม่มีวัตถุที่สังเกตเห็นซึ่งมีโครงสร้างพื้นฐานที่ชัดเจนสำหรับสิ่งนี้ สำหรับภาษาที่จำเป็นทั่วไปเช่น Java, C #, C ++ สถาปัตยกรรมรันไทม์ของพวกเขานั้นมีน้ำหนักเบาเกินไปในตอนนี้ โดยที่ฉันหมายความว่ามันไม่ได้เป็นส่วนหนึ่งของข้อกำหนดภาษาในเวลานี้

ในกรณีเฉพาะของคุณฉันไม่คิดว่ามันเป็นสิ่งที่ไม่ดีที่จะกำหนด / ใช้อินเทอร์เฟซทั่วไปบางอย่างเช่นINotifyPropertyChanged (ใน c #) เนื่องจากมันไม่ได้เชื่อมโยงกับมุมมองโดยอัตโนมัติ - มันแค่บอกว่าถ้าฉันเปลี่ยน ฉันจะบอกคุณ

อีกครั้งฉันยอมรับว่ามันจะดีถ้าเราไม่ต้องกำหนดการแจ้งเตือนการเปลี่ยนแปลงด้วยตัวเอง แต่ถ้าเป็นแบบนั้นสำหรับทุกคลาสอาจมีค่าใช้จ่าย


ยิ่งฉันคิดเกี่ยวกับมันผมรู้ว่าคุณกำลังขวา - วัตถุรูปแบบมีการมีส่วนร่วมในการบางขอบเขตในการเริ่มต้นการแจ้งเตือนไปยัง GUI ที่มีการเปลี่ยนแปลงเกิดขึ้น; ไม่เช่นนั้น GUI จะต้องสำรวจแบบจำลองเพื่อค้นหาการเปลี่ยนแปลง - และนั่นก็ไม่ดี
Chap
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.