设计模式学习(二)——观察者模式
一.需求
时间过的真快,小王的便利店已经开了大半年了,生意很是红火。
一天,小王远在乡下的表哥来城里办事,顺便来看小王。表哥在乡下开了个养鸡场,都是散养的柴鸡,每过一段时间,表哥就会来城里送一批柴鸡蛋。
小王突发奇想:能不能在自己的便利店也卖柴鸡蛋呢?这个想法一说,表哥当即就赞同了。几天后,就送来了第一批柴鸡蛋。
小区张大妈经常给孙女做糕点,看到便利店卖柴鸡蛋,就买了一些;便利店隔壁是赵阿姨开的一家餐馆,也买了一些回去。吃过小王便利店里柴鸡蛋的人都说不错,渐渐的,柴鸡蛋开始供不应求了。不少顾客来买东西都要问还有没有柴鸡蛋卖,甚至有一些顾客每天都来一次专门问柴鸡蛋到货了没有。
小王感到很是不安,这么多人来买鸡蛋,但是鸡蛋根本不够卖,害的顾客空跑,有没有什么好办法呢?
二.初步尝试
经过一番仔细分析,小王觉得,眼下最需要解决的问题是让大家知道店里还有没有鸡蛋卖,不能让大家空跑一趟。
小王想到的办法是:在店门口贴一张通知栏,在布告栏上写明当前店里有没有柴鸡蛋卖,并及时更新。每个顾客每天从小区门口路过,如果自己想买柴鸡蛋,并且布告栏上写着有鸡蛋,则可以进店购买。
在这个过程中,有两个对象:通知栏,顾客。通知栏有一个状态来实时展示当前是否有柴鸡蛋,顾客每天看通知栏,决定是否进店购买。如果用程序实现就是下面这样:
// 布告栏
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》
本文已迁移至我的博客:http://ipenge.com/631.html