【HeadFirst 設計模式學習筆記】13 MVC分析


作者:gnuhpc
出處:http://www.cnblogs.com/gnuhpc/

1.M-V-C ——Model--View--Controller,模式-視圖-控制器,這是一種范型。模型對象正是應用系統存在的理由,你設計的對象,包含了數據、邏輯和其他在你的應用領域創建定制的類。視圖通常是控件,用來顯示和編輯,控制器位於二者中間,負責將每個改變的狀態送進送出。而學習設計模式是理解MVC的鑰匙。書中用一個iTunes的例子直觀描述了MVC:

20100505141922781

2.MVC的基本原理:

  • 視圖:用來呈現模型。視圖通常直接從模型中取得它需要顯示的數據。 視圖不會直接操作模型。
  • 控制器:取得用戶的輸入並解讀其對模型的意思。 控制器不會實現應用邏輯,它為視圖實現行為,將視圖傳過來的行為轉化為模型上的動作。它只負責決定調用哪一個模型。
  • 模型:持有所有的數據,狀態和程序邏輯。模型沒有注意到視圖和控制器,雖然它提供了操縱和檢索狀態的接口,並且發送狀態改變通知觀察者。 模型只知道有一些觀察者它需要通知,並且提供一些接口供視圖和控制器獲得並設置狀態。

他們三者的交互如下圖:

20100505145556390

這里充分體現了我們“單一職責”的這個原則。

3.MVC模式分析:

20100505151703750

1)視圖和控制器實現了經典的策略模式:視圖是一個對象,可以被調整使用不同的策略。視圖只關心顯示,而其行為的控制則都使用控制器進行。這樣一來,視圖和模型之間也完成了解耦,因為控制器負責和模型進行用戶請求的交互。

20100505153644375

2)視圖中的顯示中包含了很多的要素,這就用到了組合模式,當控制器告訴視圖更新時,只需告訴視圖最頂層的組件即可,組合會處理其余的事。

20100505153722906

3)模型則實現了觀察者模式,當狀態改變時,相關對象將持續更新。

20100505153556109

4.MVC實例——DJ View

這是一個控制節拍(BPM,每分鍾XX拍)並產生鼓聲的工具。下邊是這個系統的核心,他負責根據節拍(可以設置可以讀取)產生鼓聲——模型:

20100505154437921

我們先看看模型的接口:

public interface BeatModelInterface { 
    void initialize(); 
    void on(); 
    void off(); 
    void setBPM(int bpm); 
    int getBPM(); 
    void registerObserver(BeatObserver o);//有兩種觀察者,一種觀察者希望每個節拍都被通知,另一種觀察者希望BPM改變時被通知 
    void removeObserver(BeatObserver o); 
    void registerObserver(BPMObserver o); 
    void removeObserver(BPMObserver o); 
}

根據這個接口,我們可以實現模型:

public class BeatModel implements BeatModelInterface, MetaEventListener { 
    Sequencer sequencer; 
    ArrayList beatObservers = new ArrayList(); 
    ArrayList bpmObservers = new ArrayList(); 
    int bpm = 90; 
    Sequence sequence; 
    Track track; 
    public void initialize() { 
        setUpMidi(); 
        buildTrackAndStart(); 
    } 
    public void on() { 
        sequencer.start(); 
        setBPM(90); 
    } 
    public void off() { 
        setBPM(0); 
        sequencer.stop(); 
    } 
    public void setBPM(int bpm) { 
        this.bpm = bpm; 
        sequencer.setTempoInBPM(getBPM()); 
       
notifyBPMObservers(); 
    } 
    public int getBPM() { 
        return bpm; 
    } 
    void beatEvent() { 
        
notifyBeatObservers(); 
    } 
    public void registerObserver(BeatObserver o) { 
        beatObservers.add(o); 
    } 
    public void notifyBeatObservers() { 
        for(int i = 0; i < beatObservers.size(); i++) { 
            BeatObserver observer = (BeatObserver)beatObservers.get(i); 
            observer.updateBeat(); 
        } 
    } 
    public void registerObserver(BPMObserver o) { 
        bpmObservers.add(o); 
    } 
    public void notifyBPMObservers() { 
        for(int i = 0; i < bpmObservers.size(); i++) { 
            BPMObserver observer = (BPMObserver)bpmObservers.get(i); 
            observer.updateBPM(); 
        } 
    }

    public void removeObserver(BeatObserver o) { 
        int i = beatObservers.indexOf(o); 
        if (i >= 0) { 
            beatObservers.remove(i); 
        } 
    }

    public void removeObserver(BPMObserver o) { 
        int i = bpmObservers.indexOf(o); 
        if (i >= 0) { 
            bpmObservers.remove(i); 
        } 
    }

    public void meta(MetaMessage message) { 
        if (message.getType() == 47) { 
            beatEvent(); 
            sequencer.start(); 
            setBPM(getBPM()); 
        } 
    }

    public void setUpMidi() { 
        try { 
            sequencer = MidiSystem.getSequencer(); 
            sequencer.open(); 
            sequencer.addMetaEventListener(this); 
            sequence = new Sequence(Sequence.PPQ,4); 
            track = sequence.createTrack(); 
            sequencer.setTempoInBPM(getBPM()); 
        } catch(Exception e) { 
                e.printStackTrace(); 
        } 
    }

     public void buildTrackAndStart() { 
        int[] trackList = {35, 0, 46, 0}; 
        sequence.deleteTrack(null); 
        track = sequence.createTrack();

          makeTracks(trackList); 
        track.add(makeEvent(192,9,1,0,4));      
         try { 
            sequencer.setSequence(sequence);                    
        } catch(Exception e) { 
            e.printStackTrace(); 
        } 
    } 
    public void makeTracks(int[] list) {        
       for (int i = 0; i < list.length; i++) { 
          int key = list[i];

          if (key != 0) { 
             track.add(makeEvent(144,9,key, 100, i)); 
             track.add(makeEvent(128,9,key, 100, i+1)); 
          } 
       } 
    } 
    public  MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) { 
        MidiEvent event = null; 
        try { 
            ShortMessage a = new ShortMessage(); 
            a.setMessage(comd, chan, one, two); 
            event = new MidiEvent(a, tick); 
        } catch(Exception e) { 
            e.printStackTrace(); 
        } 
        return event; 
    } 
}

我們現在要把視圖掛上去,讓這個模型可視化!BeatModel對視圖一無所知,我們利用觀察者模式當狀態改變時,只要是注冊為觀察者的視圖都會收到通知。而視圖使用模型的API訪問狀態。

public class DJView implements ActionListener,  BeatObserver, BPMObserver {//同時關心時時節拍和BPM的改變 
    BeatModelInterface model; 
    ControllerInterface controller;//視圖持有模型和控制器的引用
 
    JFrame viewFrame; 
    JPanel viewPanel; 
    BeatBar beatBar; 
    JLabel bpmOutputLabel; 
    JFrame controlFrame; 
    JPanel controlPanel; 
    JLabel bpmLabel; 
    JTextField bpmTextField; 
    JButton setBPMButton; 
    JButton increaseBPMButton; 
    JButton decreaseBPMButton; 
    JMenuBar menuBar; 
    JMenu menu; 
    JMenuItem startMenuItem; 
    JMenuItem stopMenuItem;

    public DJView(ControllerInterface controller, BeatModelInterface model) {    
        this.controller = controller; 
        this.model = model; 
       
model.registerObserver((BeatObserver)this);//注冊觀察者 
        model.registerObserver((BPMObserver)this); 
    } 
    public void createView() { 
        // Create all Swing components here 
        viewPanel = new JPanel(new GridLayout(1, 2)); 
        viewFrame = new JFrame("View"); 
        viewFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        viewFrame.setSize(new Dimension(100, 80)); 
        bpmOutputLabel = new JLabel("offline", SwingConstants.CENTER); 
        beatBar = new BeatBar(); 
        beatBar.setValue(0); 
        JPanel bpmPanel = new JPanel(new GridLayout(2, 1)); 
        bpmPanel.add(beatBar); 
        bpmPanel.add(bpmOutputLabel); 
        viewPanel.add(bpmPanel); 
        viewFrame.getContentPane().add(viewPanel, BorderLayout.CENTER); 
        viewFrame.pack(); 
        viewFrame.setVisible(true); 
    } 
    public void createControls() { 
        // Create all Swing components here 
        JFrame.setDefaultLookAndFeelDecorated(true); 
        controlFrame = new JFrame("Control"); 
        controlFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        controlFrame.setSize(new Dimension(100, 80));

        controlPanel = new JPanel(new GridLayout(1, 2));

        menuBar = new JMenuBar(); 
        menu = new JMenu("DJ Control"); 
        startMenuItem = new JMenuItem("Start"); 
        menu.add(startMenuItem); 
        startMenuItem.addActionListener(new ActionListener() { 
            public void actionPerformed(ActionEvent event) { 
                
controller.start();//視圖的點擊觸發控制器的事件 
            } 
        }); 
        stopMenuItem = new JMenuItem("Stop"); 
        menu.add(stopMenuItem); 
        stopMenuItem.addActionListener(new ActionListener() { 
            public void actionPerformed(ActionEvent event) { 
                
controller.stop(); 
            } 
        }); 
        JMenuItem exit = new JMenuItem("Quit"); 
        exit.addActionListener(new ActionListener() { 
            public void actionPerformed(ActionEvent event) { 
                System.exit(0); 
            } 
        });

        menu.add(exit); 
        menuBar.add(menu); 
        controlFrame.setJMenuBar(menuBar);

        bpmTextField = new JTextField(2); 
        bpmLabel = new JLabel("Enter BPM:", SwingConstants.RIGHT); 
        setBPMButton = new JButton("Set"); 
        setBPMButton.setSize(new Dimension(10,40)); 
        increaseBPMButton = new JButton(">>"); 
        decreaseBPMButton = new JButton("<<"); 
        setBPMButton.addActionListener(this); 
        increaseBPMButton.addActionListener(this); 
        decreaseBPMButton.addActionListener(this);

        JPanel buttonPanel = new JPanel(new GridLayout(1, 2));

        buttonPanel.add(decreaseBPMButton); 
        buttonPanel.add(increaseBPMButton);

        JPanel enterPanel = new JPanel(new GridLayout(1, 2)); 
        enterPanel.add(bpmLabel); 
        enterPanel.add(bpmTextField); 
        JPanel insideControlPanel = new JPanel(new GridLayout(3, 1)); 
        insideControlPanel.add(enterPanel); 
        insideControlPanel.add(setBPMButton); 
        insideControlPanel.add(buttonPanel); 
        controlPanel.add(insideControlPanel); 
        bpmLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); 
        bpmOutputLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));

        controlFrame.getRootPane().setDefaultButton(setBPMButton); 
        controlFrame.getContentPane().add(controlPanel, BorderLayout.CENTER);

        controlFrame.pack(); 
        controlFrame.setVisible(true); 
    }

    public void enableStopMenuItem() { 
        stopMenuItem.setEnabled(true); 
    }

    public void disableStopMenuItem() { 
        stopMenuItem.setEnabled(false); 
    }

    public void enableStartMenuItem() { 
        startMenuItem.setEnabled(true); 
    }

    public void disableStartMenuItem() { 
        startMenuItem.setEnabled(false); 
    }

    public void actionPerformed(ActionEvent event) { 
        if (event.getSource() == setBPMButton) { 
            int bpm = Integer.parseInt(bpmTextField.getText()); 
            controller.setBPM(bpm);//視圖的改變會直接傳遞給控制器 
        } else if (event.getSource() == increaseBPMButton) { 
            controller.increaseBPM(); 
        } else if (event.getSource() == decreaseBPMButton) { 
            controller.decreaseBPM(); 
        } 
    }

    public void updateBPM() {//模型發生改變時,這個方法會被調用 
        if (model != null) { 
            int bpm = model.getBPM(); 
            if (bpm == 0) { 
                if (bpmOutputLabel != null) { 
                    bpmOutputLabel.setText("offline"); 
                } 
            } else { 
                if (bpmOutputLabel != null) { 
                    bpmOutputLabel.setText("Current BPM: " + model.getBPM()); 
                } 
            } 
        } 
    } 
    public void updateBeat() {//相應的,當模型開始一個新的節拍時,這個方法會被調用 
        if (beatBar != null) { 
             beatBar.setValue(100); 
        } 
    } 
}

有了視圖,有了模型,我們要構建控制器,使得視圖更加聰明,我們使用策略模式,從控制器接口開始設計:

public interface ControllerInterface { 
    void start(); 
    void stop(); 
    void increaseBPM(); 
    void decreaseBPM(); 
     void setBPM(int bpm); 
}

根據這個接口,我們實現這個控制器:

public class BeatController implements ControllerInterface { 
    BeatModelInterface model;//MVC中,控制器在中間,所以要同時持有模型以及視圖的引用。 
    DJView view;
 
    public BeatController(BeatModelInterface model) { 
        this.model = model; 
    
    view = new DJView(this, model);//控制器創建視圖 
        view.createView(); 
        view.createControls(); 
        view.disableStopMenuItem(); 
        view.enableStartMenuItem(); 
        model.initialize(); 
    } 
    public void start() {//控制器在得到start指令時去操縱模型和視圖,下邊的幾個動作同理。 
        model.on(); 
        view.disableStartMenuItem();
//注意,控制器這時在幫視圖做決定,視圖只知道如何將菜單項變成開或者關而不知道在何時該這么做 
        view.enableStopMenuItem(); 
    } 
    public void stop() { 
        model.off(); 
        view.disableStopMenuItem(); 
        view.enableStartMenuItem(); 
    } 
    public void increaseBPM() {//控制器擴展了模型的動作 
        int bpm = model.getBPM(); 
        model.setBPM(bpm + 1); 
    } 
    public void decreaseBPM() { 
        int bpm = model.getBPM(); 
        model.setBPM(bpm - 1); 
      } 
     public void setBPM(int bpm) { 
        model.setBPM(bpm); 
    } 
}

搞定!我們寫一段測試代碼來使用我們自己的MVC,先創建一個模型,然后創建一個控制器,將模型傳入其中,控制器創建視圖:

public class DJTestDrive {

    public static void main (String[] args) { 
        BeatModelInterface model = new BeatModel(); 
        ControllerInterface controller = new BeatController(model); 
    } 
}

5.我們現在利用這個MVC模型完成另一項工作:心臟監視。我們希望將HeartModel適配成BeatModel

首先我們更換一下模型:

public interface HeartModelInterface { 
    int getHeartRate(); 
    void registerObserver(BeatObserver o); 
    void removeObserver(BeatObserver o); 
    void registerObserver(BPMObserver o); 
    void removeObserver(BPMObserver o); 
}

此時,我們需要知道視圖只知道getBPM而不知道getHeartRate,那么這就需要我們使用適配器模式進行適配了。這就引出了一個MVC中重要的技巧:

使用適配器將模型適配成符合現有視圖和控制器的需要的模型。

public class HeartAdapter implements BeatModelInterface {//適配器要對被適配的接口進行實現,也就是那個在Client中被直接使用的部分 
    HeartModelInterface heart;//適配器中要保留另一部分的引用 
    public HeartAdapter(HeartModelInterface heart) { 
        this.heart = heart; 
    }

    public void initialize() {}//不需要的部分我們在適配器中留空。 
    public void on() {} 
    public void off() {} 
    public int getBPM() { 
        return heart.getHeartRate();//適配器在此處運轉 
    } 
    public void setBPM(int bpm) {} 
    public void registerObserver(BeatObserver o) {//將注冊觀察者Server的方法委托給heart 
        heart.registerObserver(o); 
    } 
    public void removeObserver(BeatObserver o) { 
        heart.removeObserver(o); 
    } 
    public void registerObserver(BPMObserver o) { 
        heart.registerObserver(o); 
    } 
    public void removeObserver(BPMObserver o) { 
        heart.removeObserver(o); 
    } 
}

適配器ready以后,我們可以完成控制器了:

public class HeartController implements ControllerInterface { 
    HeartModelInterface model; 
    DJView view; 
    public HeartController(HeartModelInterface model) { 
        this.model = model; 
       
view = new DJView(this, new HeartAdapter(model)); //用適配器進行包裝 
        view.createView(); 
        view.createControls(); 
        view.disableStopMenuItem(); 
        view.disableStartMenuItem(); 
    } 
    public void start() {} //沒有實際作用的我們留空 
    public void stop() {} 
    public void increaseBPM() {} 
    public void decreaseBPM() {} 
     public void setBPM(int bpm) {} 
}

我們現在就可以寫一段測試代碼了:

public class HeartTestDrive {

    public static void main (String[] args) { 
        HeartModel heartModel = new HeartModel();//首先創建模型 
        ControllerInterface model = new HeartController(heartModel);//然后創建控制器,控制器中創建了視圖 
    } 
}

6.最后我們提一句:在Web開發中,MVC被經常叫做Model 2。有了這個模型,該編程的人就去做編程,該做網頁的人就去做網頁。JSP只知道會從控制器收到一個Bean。在這個場景中,其實Bean其實就是模型,而且JSP只用到這個bean的BPM屬性。

20100507152525515

作者:gnuhpc
出處:http://www.cnblogs.com/gnuhpc/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM