设计模式之Observer

Observer

tags: design pattern,Observer


要求:

模拟以下情景:

小孩在睡觉
醒了之后要吃东西

第一种设计方法

(说实话这是我第一反应想到的方法,我果然还是图样图森破。。。)

有一个Dad类, 有一个Child类, Dad类持有Child类的引用, Dad监测着Child, 如果Child醒了, Dad就调用feed方法去喂小孩。

package simulation;

class Child implements Runnable {

    private boolean wakeUp = false;

    public void wakeUp() {
        this.wakeUp = true;
    }

    public boolean isWakeUp() {
        return this.wakeUp;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.wakeUp();
    }

}

class Dad implements Runnable {

    private Child child;

    public Dad(Child c) {
        this.child = c;
    }

    @Override
    public void run() {
        while (!child.isWakeUp()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.feed(this.child);
    }

    public void feed(Child c) {
        System.out.println("feed child");
    }

}

public class Test {
    public static void main(String[] args) {
        Child c = new Child();
        new Thread(new Dad(c)).start();
    }
}

分析:
程序可行是可行, 但是有极其不合理的地方:Dad每隔一秒钟看一下Child, 完全干不了别的事。–> CPU的资源被无端消耗, 上面的代码在效率和资源消耗上都有很大问题!!!

那么应该如何改进呢?

第二种设计方法:

化主动为被动!

把Dad主动监测Child变为被动监测, 就是说, 反过来, 让Child监测Dad。换句话说, 在Child睡觉的时候Dad可以干别的事, 但Child一醒过来,Dad马上过来喂他吃东西。

这时候把上面的代码修改成下面的:

package simulation;

class Child implements Runnable {

    private Dad dad;
    // private boolean wakeUp = false;

    public Child(Dad d) {
        this.dad = d;
    }

    public void wakeUp() {
        // this.wakeUp = true;
        this.dad.feed(this);
    }

    // public boolean isWakeUp() {
    // return this.wakeUp;
    // }

    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.wakeUp();
    }

}

class Dad {

    public void feed(Child c) {
        System.out.println("feed child");
    }

}

public class Test {
    public static void main(String[] args) {
        Dad d = new Dad();
        Child c = new Child(d);
        new Thread(c).start();
    }
}

这时候Dad完全可以不用作为一个线程类, Dad这个类里也可以只保留feed这个方法, Child中持有Dad的引用, Child一醒过来, Dad就调用feed方法。

这样修改之后, 明显比第一种方法更具有效率。

但是作为设计来讲, 在一个程序当中, 如果只考虑当前而没有预料到将来一定时间内将会发生的变化, 那么程序不具有可扩展性,弹性很差。

比如上面这个情景, Child醒过来这件事,包含了许多信息:几点醒过来?是在早上还是在晚上?
睡了多久?在哪里醒过来?等等。 针对不同的事件信息,作为监测者的Dad应该有不同的处理方式, 不能说一醒过来就喂, 如果是晚上Child刚吃完饭睡了一下, 醒过来, Dad又喂他吃东西, 那么Child就撑死了。

所以第二种方法仅仅是把程序写通,可扩展性很差。

对于事件的处理

Child醒过来这件事的发生包含了许多具体情况(具体信息),应该把这些情况告诉监测者, 也就是Dad, Dad根据这件事情的具体情况, 来做出具体的处理方式。

因此把事件抽象出来,封装成另外一个类。

package simulation;

class WakeUpEvent {

    private long time;
    private String location;
    private Object source;

    public WakeUpEvent(long time, String location, Object source) {
        this.time = time;
        this.location = location;
        this.source = source;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public Object getSource() {
        return source;
    }

    public void setSource(Object source) {
        this.source = source;
    }

}

class Child implements Runnable {

    private Dad dad;

    // private boolean wakeUp = false;

    public Child(Dad d) {
        this.dad = d;
    }

    public void wakeUp() {
        // this.wakeUp = true;
        this.dad.actionToWakeUp(new WakeUpEvent(System.currentTimeMillis(),
                "bed", this));
    }

    // public boolean isWakeUp() {
    // return this.wakeUp;
    // }

    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.wakeUp();
    }
}

class Dad {

    public void actionToWakeUp(WakeUpEvent event) {
        // do something according to the event
    }

}

public class Test {
    public static void main(String[] args) {
        Dad d = new Dad();
        Child c = new Child(d);
        new Thread(c).start();
    }
}

注意上面WakeUpEvent这个类里面, 属性除了有timelocation之外还有一个source, 而且类型是Object(其实也可以写成Child类型, 但为了更像AWT, 所以写成Object),这里表示一个事件源对象,就是发生这件事的对象。比如说Child醒了这个事件, Child就是事件源。

然后再思考:Child一醒过来Dad就要喂他, 那么喂这个动作就已经被固定下来了。假如Child醒来之后不想让Dad喂他, 而是想让Dad抱他出去玩,那么明显feed这个方法已经不合适了。更灵活的方法是:Child一醒过来, 发生了这么一件事, Dad便对这件事做出反应,至于是喂他还是抱他出去玩都可以。

所以把Dad这个类中原来的feed方法修改了,方法名改成了actionToWakeUp,参数改成了WakeUpEvent event 在方法体内, 便可以增加判断,根据事件的不同具体信息做出不同的反应。这样写明显比使用feed方法灵活得多。

继续思考:如果现在不只有Dad, Child醒过来之后,他的Grandpa也要做出反应,那么应该怎么办呢?按照之前的思路, 增加一个Grandpa类, 里面也有一个actionToWakeUp(WakeUpEvent event)方法,另外在Child这个类里增加一个Grandpa的引用。那么如果现在不仅是爸爸、爷爷对小孩醒过来做出反应,小孩的妈妈、奶奶、外公、外婆甚至是家里的狗都要做出反应呢?那岂不是要不断的修改Child这个类的源代码?!在OO里面有一个极其重要的核心原则————OCP, open close principle, 开闭原则, 对扩展开放, 对修改关闭。上面的方法要不断修改Child的源代码,显然是不符合这个原则的, 说明了设计还不到位!

第三种设计方法 Observer

现在有好多好多监测小孩醒过来这件事的人,而且每个人对这件事的响应各不相同,但是有一个共同点,可以把响应的方法都叫actionToWakeUp(WakeUpEvent event),参数都是小孩醒过来这件事。这时候可以考虑使用接口把变化的这部分给抽象出来,因为接口抽象出来的是一系列的类所具有的共同特点。
因此增加一个WakeUpListener的接口,里面有actionToWakeUp(WakeUpEvent event)这个方法。 然后让Dad和Grandpa这两个类实现这个接口。

这时候再思考一下,为什么这个方法里传的参数是WakeUpEvent而不是Child? 假如写成Child, 那么这个方法就只能用在小孩身上了,但是如果是WakeUpEvent,那么小狗醒了也可以用这个方法,小猫醒了也可以用这个方法,也就是说, 事件本身也是和事件源是脱离的。这个时候灵活性最高!

把上面的代码修改:

package simulation;

import java.util.*;

class WakeUpEvent {

    private long time;
    private String location;
    private Object source;

    public WakeUpEvent(long time, String location, Object source) {
        this.time = time;
        this.location = location;
        this.source = source;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public Object getSource() {
        return source;
    }

    public void setSource(Object source) {
        this.source = source;
    }

}

class Child implements Runnable {

    private List<WakeUpListener> listeners = new ArrayList<WakeUpListener>();

    public void addWakeUpListener(WakeUpListener listener) {
        this.listeners.add(listener);
    }

    public void wakeUp() {

        for (Iterator<WakeUpListener> it = this.listeners.iterator(); it
                .hasNext();) {
            WakeUpListener l = it.next();
            l.actionToWakeUp(new WakeUpEvent(System.currentTimeMillis(), "bed",
                    this));
        }
    }

    // public boolean isWakeUp() {
    // return this.wakeUp;
    // }

    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.wakeUp();
    }

}

interface WakeUpListener {
    public void actionToWakeUp(WakeUpEvent event);
}

class Dad implements WakeUpListener {

    public void actionToWakeUp(WakeUpEvent event) {
        System.out.println("feed child");
    }
}

class Grandpa implements WakeUpListener {
    public void actionToWakeUp(WakeUpEvent event) {
        System.out.println("hug child");
    }
}

public class Test {
    public static void main(String[] args) {
        Dad d = new Dad();
        Child c = new Child();
        c.addWakeUpListener(d);
        new Thread(c).start();
    }
}

注意:这时候Child这个类里没有Dad或者Grandpa的引用, 而是改成了一个List<WakeUpListener>, 并且多了一个addWakeUpListener(WakeUpListener listener)的方法,当需要添加监听器的时候,添加一个实现了WakeUpListener这个接口的类, 在main方法里new一个新的监听器对象,然后直接调用这个方法添加便可以,完全无需修改Child的代码。扩展程序而无需修改Child的源代码, 这才符合OCP原则!

比如我还想添加一个小狗, 他也在监听着小孩醒来这件事:

package simulation;

import java.util.*;

class WakeUpEvent {

    private long time;
    private String location;
    private Object source;

    public WakeUpEvent(long time, String location, Child source) {
        this.time = time;
        this.location = location;
        this.source = source;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public Object getSource() {
        return source;
    }

    public void setSource(Object source) {
        this.source = source;
    }

}

class Child implements Runnable {

    private List<WakeUpListener> listeners = new ArrayList<WakeUpListener>();

    public void addWakeUpListener(WakeUpListener listener) {
        this.listeners.add(listener);
    }

    public void wakeUp() {

        for (Iterator<WakeUpListener> it = this.listeners.iterator(); it
                .hasNext();) {
            WakeUpListener l = it.next();
            l.actionToWakeUp(new WakeUpEvent(System.currentTimeMillis(), "bed",
                    this));
        }
    }

    // public boolean isWakeUp() {
    // return this.wakeUp;
    // }

    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.wakeUp();
    }

}

interface WakeUpListener {
    public void actionToWakeUp(WakeUpEvent event);
}

class Dad implements WakeUpListener {

    public void actionToWakeUp(WakeUpEvent event) {
        System.out.println("feed child");
    }
}

class Grandpa implements WakeUpListener {
    public void actionToWakeUp(WakeUpEvent event) {
        System.out.println("hug child");
    }
}


class Dog implements WakeUpListener {
    public void actionToWakeUp(WakeUpEvent event) {
        System.out.println("Wang!!!!");
    }
}


public class Test {
    public static void main(String[] args) {
        Dad d = new Dad();
        Grandpa g = new Grandpa();
        Child c = new Child();
        c.addWakeUpListener(d);
        c.addWakeUpListener(g);

        Dog dog = new Dog();
        c.addWakeUpListener(dog);

        new Thread(c).start();
    }
}

由上面的代码可以看见, 只是新建了一个Dog的类, 然后再在main方法里添加了

        Dog dog = new Dog();
        c.addWakeUpListener(dog);

这两句话,其他的完全没有修改。主要的逻辑类Child完全没有变,可扩展性很高!

再进一步思考!假如我现在有一个Student类,他也可以发出WakeUpEvent这件事,那么我只需要复制Child里的代码到Student这个类里面就可以了,WakeUpEvent这个类也得到了复用,灵活性更高!!!想想AWT里面,除了Button这个类会发出ActionEvent这件事, TextField也会, 因此ActionEvent也被重用了。在这里,Button相当于Child, Textfield相当于Student, ActionEvent相当于WakeUpEvent。

另外还可以封装一个CryEvent类,HappyEvent类等等各种各样的event,然后再封装一个abstract class Event,让各种event从这个抽象类继承,方法传参数的时候形参定义为Event类型,这样灵活性更强。

反思与总结

从一开始第一种设计方法的一两个类实现了基本功能,到最后的多个类、接口,可以看到:使用设计模式是一把双刃剑。

  • 优点:

    1. 可扩展性强
    2. 维护成本降低
  • 缺点:

    1. 复杂度增加
    2. 开发成本增加

但是切忌为了使用设计模式而使用设计模式,要具体问题具体分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值