Osheep

时光不回头,当下最重要。

设计模式学习(二)——观察者模式

一.需求

时间过的真快,小王的便利店已经开了大半年了,生意很是红火。

一天,小王远在乡下的表哥来城里办事,顺便来看小王。表哥在乡下开了个养鸡场,都是散养的柴鸡,每过一段时间,表哥就会来城里送一批柴鸡蛋。

小王突发奇想:能不能在自己的便利店也卖柴鸡蛋呢?这个想法一说,表哥当即就赞同了。几天后,就送来了第一批柴鸡蛋。

小区张大妈经常给孙女做糕点,看到便利店卖柴鸡蛋,就买了一些;便利店隔壁是赵阿姨开的一家餐馆,也买了一些回去。吃过小王便利店里柴鸡蛋的人都说不错,渐渐的,柴鸡蛋开始供不应求了。不少顾客来买东西都要问还有没有柴鸡蛋卖,甚至有一些顾客每天都来一次专门问柴鸡蛋到货了没有。

小王感到很是不安,这么多人来买鸡蛋,但是鸡蛋根本不够卖,害的顾客空跑,有没有什么好办法呢?

二.初步尝试

经过一番仔细分析,小王觉得,眼下最需要解决的问题是让大家知道店里还有没有鸡蛋卖,不能让大家空跑一趟。

小王想到的办法是:在店门口贴一张通知栏,在布告栏上写明当前店里有没有柴鸡蛋卖,并及时更新。每个顾客每天从小区门口路过,如果自己想买柴鸡蛋,并且布告栏上写着有鸡蛋,则可以进店购买。

在这个过程中,有两个对象:通知栏,顾客。通知栏有一个状态来实时展示当前是否有柴鸡蛋,顾客每天看通知栏,决定是否进店购买。如果用程序实现就是下面这样:

// 布告栏
class Notice {
    private boolean hasEgg;

    public boolean hasEgg() {
        return hasEgg;
    }

    public void setHasEgg(boolean hasEgg) {
        this.hasEgg = hasEgg;
    }
}

// 顾客
class Customer {

    private String name;

    private boolean needEgg;

    private Notice notice;

    public Customer(String name, boolean needEgg, Notice notice) {
        this.name = name;
        this.needEgg = needEgg;
        this.notice = notice;
    }

    public void bugEgg() {
        if (needEgg && notice.hasEgg()) {
            System.out.println(this.name + "进店购买鸡蛋");
        } else {
            System.out.println(this.name + "不进店购买鸡蛋");
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isNeedEgg() {
        return needEgg;
    }

    public void setNeedEgg(boolean needEgg) {
        this.needEgg = needEgg;
    }
}

// 客户端
public class Main {

    public static void main(String[] args) {
        Notice notice = new Notice();
        notice.setHasEgg(true); // 鸡蛋到货了

        Customer motherZhang = new Customer("张大妈", true, notice);
        Customer auntZhao = new Customer("赵阿姨", true, notice);
        Customer uncleLi = new Customer("李叔叔", false, notice);

        motherZhang.bugEgg();
        auntZhao.bugEgg();
        uncleLi.bugEgg();
    }
}

运行一下试试:

张大妈进店购买鸡蛋
赵阿姨进店购买鸡蛋
李叔叔不进店购买鸡蛋

看起来一切正常!所有的顾客不用进店就可以知道有没有柴鸡蛋卖,大大方便了顾客。

三.更好的方案

上面的方案可以正常工作,实现通知的目的。但是仍然有几个问题:
1.通知不够及时,依赖于顾客自己主动去看通知。如果鸡蛋到货时,自己刚好在家里没有看到,就会错过买鸡蛋;
2.一些顾客不关心柴鸡蛋的通知。尽管有很多顾客喜欢小王家的柴鸡蛋,但是也有不少顾客其实并不关心,门口的布告栏对他们来说毫无价值。

第二个问题倒是无关紧要,第一个问题的确是存在的,需要进一步改进。

为了进一步方便大家来买柴鸡蛋,小王想到了一个更好的办法:对于那些经常买鸡蛋的顾客,留下他们的手机号码,一旦有鸡蛋可卖,就给他们群发短信,通知他们可以来买鸡蛋了。

这样一来,就解决了通知不及时的问题,而且第二个问题同时也解决了:短信只会发给那些想买鸡蛋的人,不想买鸡蛋的人不会收到。之前想买鸡蛋但是后来不想买了,可以到小王这里说一下,下次就不会给他发短息了;之前不想买鸡蛋的人也可以随时到小王这里留下电话,下次就可以接到通知了。

新的方案如下:

// 鸡蛋管理
class EggAdmin {
    private List<Customer> customerList;

    private boolean hasEgg;

    public EggAdmin() {
        customerList = new ArrayList<Customer>();
    }

    public void addCustomer(Customer customer) {
        System.out.println(customer.getName() + "订阅鸡蛋到货消息");
        customerList.add(customer);
    }

    public void removeCustomer(Customer customer) {
        System.out.println(customer.getName() + "退订鸡蛋到货消息");
        customerList.remove(customer);
    }

    public void notice() {
        if (hasEgg) { // 一旦鸡蛋到货就通知所有想买鸡蛋的人来买鸡蛋
            for (Customer customer : customerList) {
                customer.bugEgg();
            }
        }
    }

    public boolean hasEgg() {
        return hasEgg;
    }

    public void setHasEgg(boolean hasEgg) {
        this.hasEgg = hasEgg;
    }
}
// 顾客
class Customer {

    private String name;

    public Customer(String name) {
        this.name = name;
    }

    public void bugEgg() {
        System.out.println(this.name + "进店购买鸡蛋");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if ( ! (obj instanceof  Customer)) {
            return false;
        }
        Customer newCustomer = (Customer) obj;
        return this.name.equals(newCustomer.getName());
    }
}

// 客户端
public class Main {

    public static void main(String[] args) {

        Customer motherZhang = new Customer("张大妈");
        Customer auntZhao = new Customer("赵阿姨");
        Customer uncleLi = new Customer("李叔叔");

        EggAdmin eggAdmin = new EggAdmin();
        eggAdmin.addCustomer(motherZhang);
        eggAdmin.addCustomer(auntZhao);
        eggAdmin.setHasEgg(true);
        eggAdmin.notice();

        eggAdmin.addCustomer(uncleLi);
        eggAdmin.removeCustomer(auntZhao);
        eggAdmin.notice();

    }
}

运行一下:

张大妈订阅鸡蛋到货消息
赵阿姨订阅鸡蛋到货消息
张大妈进店购买鸡蛋
赵阿姨进店购买鸡蛋
李叔叔订阅鸡蛋到货消息
赵阿姨退订鸡蛋到货消息
张大妈进店购买鸡蛋
李叔叔进店购买鸡蛋

感觉还不错,这下想买鸡蛋的顾客就可以及时收到提醒,来店里买鸡蛋了。

四.模式总结

我们在上面的新方案中用到了观察者模式。但是我们只是简单的应用,真实的观察者模式要更为灵活,定义了抽象的主题、具体的主题,抽象的观察者、具体的观察者,采用面向接口编程。

使用场景

当你需要维护一个一对多的关系时,其中一个主题的状态发生变化,其他几个依赖者都需要接收到通知并自动更新。

一个典型的使用场景是:在交互界面,我们可以对每个按钮添加Listener,当按钮被点击时,所有Listener都会得到通知。

类图
《设计模式学习(二)——观察者模式》

观察者模式类图

在观察者模式中,有主题和观察者两种角色,主题可以有多种实现,观察者亦可以有多种实现,每个具体主题中维护了观察者列表,以便通知所有观察者。每个观察者中也维护了需要观察的主题,以便将来取消观察。

在主题中通常还会有一个subjectState属性用来表示状态,这可以更灵活的控制通知发生的时机,例如在我们上面的例子中,有一个hasEgg字段用来表示是否有鸡蛋,只有当有鸡蛋时才会真正发出通知。

优点

1.观察者模式定义了一种稳定的消息传递机制,可以优雅的在主题与观察者之间发布更新;
2.主题和观察者之间是松耦合的,彼此间不知道对方的实现细节,且基于接口编程,当有新的观察者出现时,主题类无需修改,易于扩展;

缺点

如果一个主题的观察者较多,则观察者全部接到通知会花费一定的时间,不同的观察者接收到通知的时间也会有先后,由此可能会导致一些问题。

参考资料:
《Head First Java》

点赞