《Head First 设计模式》读书笔记(12)—— 复合模式 Compound

本文共5000词,阅读需要30分钟,本文相关代码地址被托管在 gitee

很多优秀的威力强大的设计模式会同时使用多个设计模式,这就是模式的模式——复合模式。
大多数流行的设计模式都是复合模式,同时用到几个模式,并且解决了一类通用的问题,比如MVC(很期待作者从设计模式怎么理解MVC,因为Spring MVC很普遍,但是这回要从根上看不知道会看到什么)。

入门·又见鸭子

开始的鸭子我们还好实现,而且这次的鸭子只会叫,不会飞,所以组合都不需要。直接声明一个Quackable接口:

public interface Quackable {
    public void quack();
}

现在有个鹅混进来了,也要叫,于是用了适配器模式:

public class GooseAdapter implements Quackable {
    Goose goose;

    public GooseAdapter(Goose goose) {
        this.goose = goose;
    }

    @Override
    public void quack() {
        goose.honk();
    }
}

现在想看它叫了几次,用装饰者模式存次数:

public class QuackCounter implements Quackable {
    Quackable quackable;
    static int numberOfQuacks;

    public QuackCounter(Quackable quackable) {
        this.quackable = quackable;
    }

    @Override
    public void quack() {
        quackable.quack();
        numberOfQuacks++;
    }

    public static int getNumberOfQuacks() {
        return numberOfQuacks;
    }
}

不同的鸭子可以抽象工厂,每个具体的工厂(计数还是不计数)在用工厂方法统一生产:

public abstract class AbstractDuckFactory {
    public abstract Quackable createMallardDuck();

    public abstract Quackable createRedheadDuck();

    public abstract Quackable createDuckCall();

    public abstract Quackable createRubberDuck();
}

统一管理,迭代器模式:

public class Flock implements Quackable {
    ArrayList<Quackable> quacks = new ArrayList<>();

    public void add(Quackable quackable){
        quacks.add(quackable);
    }

    @Override
    public void quack() {
        Iterator iterator = quacks.iterator();
        while (iterator.hasNext()){
            Quackable quackable = (Quackable) iterator.next();
            quackable.quack();
        }

    }
}

最后用观察者模式重写了一遍,为了能记录每次叫的时候能给通知观察者。

public interface QuackObservable {
    public void registerObserver(Observer observer);

    public void notifyObservers();
}

public class Quacklogoist implements Observer {
    @Override
    public void update(QuackObservable quackObservable) {
        System.out.println("log"+quackObservable.toString());
    }
}

复合模式之王——MVC之歌

ModelViewController.mp3
MVC之歌

MVC

从mp3来看MVC

model-view-controller
模型-视图-控制器
拿Mp3举例子,假如mp3现在正在播放某首歌,你现在让他播放新歌曲,控制器会得到请求,控制器可以操纵模型,控制器让player这个model去放歌,player这个model改变了当前播放的歌曲,模型告诉视图状态改变了,视图更新显示新的歌曲。

从三者关系看MVC

控制器取得用户的输入并解读其对模型的意思

从MVC看设计模式

1.视图和控制器实现了经典的策略模式:视图是一个对象,可以被调整使用不同的策略,而控制器提供了策略。视图只关心显示,而其行为的控制则都使用控制器进行。这样一来,视图和模型之间也完成了解耦,因为控制器负责和模型进行用户请求的交互。
2.视图中的显示中包含了很多的要素,这就用到了组合模式,当控制器告诉视图更新时,只需告诉视图最顶层的组件即可,组合会处理其余的事。
3.模型则实现了观察者模式,当状态改变时,相关对象将持续更新。模型完全独立于视图和控制器

从设计模式看MVC

其实和上面差不多,就是换一个角度:
观察者:视图和控制器就像观察者,模式是被观察者。模型状态有改变,视图和控制器就会被通知。
策略:对于视图来说,控制器就是策略,视图委托控制器处理用户动作。
组合:视图是GUI组件的组合。

利用MVC当DJ

当DJ,节拍是头等大事,利用MVC控制节拍。

Model

正常情况,一般都是先定义相关Model,所以先从model看,定义一个drag a beat的BeatModel。
截取一部分,因为model是观察者模式的被观察者,所以除了实现自身逻辑以外,还应该具有通知观察者、注册等相关功能。代码比较长,直接放图吧,里面用到了javax.sound.midi的相关api为了能让设备发出声音(关注点不在这,我们就关注观察者模式)。

还是观察者模式那一套:
每次有变化,就调用这个方法: notifyBPMObservers();
这个方法就是让所有Observer去更新:

public void notifyBeatObservers() {
    for (int i = 0; i < beatObservers.size(); i++) {
        BeatObserver observer = (BeatObserver) beatObservers.get(i);
        observer.updateBeat();
    }
}

View

这个实现就是为了让BeatModel可视化,但是因为观察者模式就不需要考虑Model怎么变,就只需要实现相关的update方法就行了。
一实例化,就立马注册成观察者:

public DJView(ControllerInterface controller, BeatModelInterface model) {
    this.controller = controller;
    this.model = model;
    model.registerObserver((BeatObserver) this);
    model.registerObserver((BPMObserver) this);
}

这里因为有两个面板的Oberver,所以有两个Update方法也写到一起了

注意:这个地方还应该实例化ControllerInterface,目的是为了完成上面流程图中的1那件事——用户做某件事,视图应该告诉控制器。

Controller

控制器放进视图里会让视图变的更聪明,视图就做一些基本的面板改变就行,具体改变过程就给Controller。

这里面既有Model也有View,就是为了实现2,3。
主要是控制Model,稍微修改一下View的相关属性,其实可以在View自己修改。
这个Controller并不是观察者。

测试

下面这段代码测试:

public class DJTestDrive {

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

MVC与Web

c/s服务经常会用到MVC,著名的像是Spring MVC。

Servlet和JSP

Web MVC是怎么工作的:

1.你利用网页浏览器发出http请求。Servlet收到这样的数据,并进行解析。
2.Servlet就是控制器,通常会对模型发出请求(一般是数据库)。处理结果以JavaBean的形式返回。
3.视图就是JSP,而JSP唯一的工作就是产生页面,表现模型的视图
4.模型和视图之间通过JavaBean对象传输。
5.视图通过Http将页面返回浏览器。

把刚才的项目web化

具体怎么build artifacts我就不说了。
还是像DJ那样分开看:

Model

就是一个model类,实现了BeatModelInterface的相关方法。什么观察者模式没了。

public class BeatModel implements BeatModelInterface {
    Sequencer sequencer;
    int bpm = 90;
    String ss;
    Sequence sequence;
    Track track;

    public String getSs() {
        return ss;
    }

    @Override
    public void initialize() {
        setUpMidi();
    }

    @Override
    public void on() {
        System.out.println("Starting the sequencer");
        sequencer.start();
        setBPM(90);
    }

    @Override
    public void off() {
        setBPM(0);
        sequencer.stop();
    }

    @Override
    public int getBPM() {
        return bpm;
    }

    @Override
    public void setBPM(int bpm) {
        this.bpm = bpm;
        sequencer.setTempoInBPM(getBPM());
    }


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

}
View

就是一个jsp页面,里面获取model状态信息通过jsp:useBean、jsp:getProperty,
action="/servlet/DJViewServlet",表示web.xml配置的controller的地址。

<html>
<head>
    <title>DJ View</title>
</head>
<body>

<h1>DJ View</h1>
Beats per minutes =
<jsp:getProperty name="beatModel" property="BPM"/>
<br/>
<hr>
<br/>

<form method="get" action="/servlet/DJViewServlet">
    BPM: <input type=text name="bpm"
                value="<jsp:getProperty name='beatModel' property='BPM' />">
    &nbsp;

    <input type="submit" name="set" value="set"><br/>
    <input type="submit" name="decrease" value="<<">
    <input type="submit" name="increase" value=">>"><br/>
    <input type="submit" name="on" value="on">
    <input type="submit" name="off" value="off"><br/>
</form>

</body>
</html>
Controller

servlet只需要在web.xml编辑配置文件,javax.servlet-api的Servlet会解析里面的内容作为controller。

<servlet>
    <servlet-name>DJViewServlet</servlet-name>
    <servlet-class>code.DJViewServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>DJViewServlet</servlet-name>
    <url-pattern>/servlet/DJViewServlet</url-pattern>
</servlet-mapping>

code.DJViewServlet这个类会继承HttpServlet(http的控制器)。servlet解析来的东西,修改完model,最后利用RequestDispatcher把东西交给view。

request.setAttribute("beatModel", beatModel);
RequestDispatcher dispatcher =
            request.getRequestDispatcher("/djview.jsp");
dispatcher.forward(request, response);

控制器相当于把视图上的操作在model中完成了。

模式还在吗

观察者模式

首先观察者肯定不在了,因为view获取信息不再是因为view注册成观察者,而是Controller给他的。但是其实也是对的,每请求一次其实就是一次返回,不需要观察,利用http本身的功能逻辑解决这个问题才是有意义的而且是正确的。

策略模式

策略模式还是存在的,策略对象是Servlet,只是不像GUI里直接和视图结合。其实还是为视图完成了很多行为,可以通过组合或者更换不同的控制器,视图来实现不同的行为。

组合模式

HTML本身就是组合模式的产物,直接用标记语言就可以变成用户界面,里面各种元素的展示肯定跟组合又关系

此外一些思考

Servlet更像是jsp和model之间的中介者————宽泛意义上的中介者模式(必须全部通过中介)。这也说明MVC其实挺乱的,有时间可以看一下这篇文章,写的挺好。
视图不应该操纵模型,就算访问模型最好也用Servlet实现。

“《Head First 设计模式》读书笔记(12)—— 复合模式 Compound”的一个回复

发表评论

电子邮件地址不会被公开。