思考问题:在网上书店买一本书,当你支付成功后,会计需要开具发票,同时出纳员需要登记入账,同时配送员需要发货,如果由你来设计该下单业务,你应该如何设计?
解答:
1. 直接的,我们将设计书本类,会计类,出纳员类,配送员类,然后再里面添加相应的方法。
2. 当书本被购买成功(假设是sales方法)时,在sales方法内手动编码调用会计员的开发票方法、调用出纳员登记入账方法、调用配送员的配送方法,即可完成业务。
这种方法虽然简单的处理了业务,但耦合性很强;假如系统即将开发完成时,系统的客户突然说不需要出纳员登记入账功能了,或者需要在购买者支付成功后发信息通知购买者下单成功;如果按照上面的设计的话是不是必须改动源码了?
在一个事件发生时如果触发多个事件并行执行时就应该考虑观察者模式了,还以上面的为例子,使用观察者模式设计:
我们设计书本类,会计类,出纳员类,配送员类,然后再里面添加相应的方法。我们在书本Book类中添加一个容器,它装载那些当书本被销售时需要触发事件的对象(比如装会计对象、出纳员对象、配送员对象等)
当书本被购买成功(假设是sales方法)时,我们不再在sales方法内手动编码调用会计员的开发票方法或调用出纳员登记入账方法等。而仅仅是通知容器里面的对象(观察者),告诉它们书本钱已经收了,你们自己看着办。(比如逐个观察者调用sales(Book b)方法(观察者的通知方法);)
然后在每个观察者中,当sales(Book b)方法被触发时,自己执行自己应该执行的操作,比如会计开具发票,出纳员登记入账,配送员发货等,当客户的业务逻辑需要调整,比如取消会计开发票功能时,我们就取消往Book的那容器添加会计对象即可,如果需要添加下单成功时短信通知购买者,那么可以设计一个短信观察者添加到Book的容器中,然后短信观察者自身需要怎样发送就怎样实现就好了,拓展性好,体现了开闭原则(对拓展开放,对修改关闭)。
- 4.
图中使用BookI接口来提取公共方法,让电子书类(或纸质版书类)实现,然后在具体的电子书类中保存一个装载观察者的容器(当书本销售出去后就通知它们干他们应该干的,书本对象不需要知道他们干什么)
下面是实例代码:
BookI接口:
package com.shusheng.observer;
public interface BookI {
public void sales();//销售书本的方法
public void addObserver(Observer observer);//添加观察者
public void removeObserver(Observer observer);//移除观察者
}
ElectronicBook:
package com.shusheng.observer;
import java.util.Vector;
/**电子书*/
public class ElectronicBook implements BookI{
private boolean sold = false;//表示是否已出售
private Vector<Observer> observers = new Vector<>();//装观察者的容器
/**销售书本的方法*/
@Override
public void sales() {
this.sold = true;//已售
System.out.println("书本已经出售");
for(Observer observer : observers){
observer.sales(this);//通知所有观察者书本已经销售了,让那些观察者看着办。
}
}
/**添加观察者*/
@Override
public void addObserver(Observer observer) {
this.observers.add(observer);
}
/**移除观察者*/
@Override
public void removeObserver(Observer observer) {
this.observers.remove(observer);
}
}
ObserverI观察者接口:
package com.shusheng.observer;
/**观察者*/
public interface ObserverI {
//该书本销售的方法通知观察者销售的book的信息
public void sales(ElectronicBook electronicBook);
}
会计观察者:
package com.shusheng.observer;
/**会计(观察者)*/
public class Accountant implements ObserverI{
/**通知的方法*/
@Override
public void sales(ElectronicBook electronicBook) {
this.account();
}
/**会计开具发票函数*/
public void account(){
System.out.println("我是会计,我要开具发票啦");
}
}
出纳员观察者:
package com.shusheng.observer;
/**出纳员*/
public class Cashier implements ObserverI {
@Override
public void sales(ElectronicBook electronicBook) {
System.out.println("我是出纳员,我要记账啦");
}
}
快递员观察者:
package com.shusheng.observer;
/**快递员(观察者)*/
public class Courier implements ObserverI {
@Override
public void sales(ElectronicBook electronicBook) {
System.out.println("我是快递员,我要送出这本书啦");
}
}
测试类:
package com.shusheng.observer;
public class ObserverTest {
public static void main(String[] args) {
BookI bookI = new ElectronicBook();//电子书
ObserverI accountant = new Accountant();
ObserverI cashier = new Cashier();
ObserverI courier = new Courier();
bookI.addObserver(accountant);
bookI.addObserver(cashier);
bookI.addObserver(courier);
bookI.sales();//出售书本
}
}
代码运行结果:
观察者模式优点:
观察者与被观察者(上面的电子书)之间只是抽象耦合,被观察者所知道的只是一个具体的观察者集合(observers)而无需知道观察者内部细节,所以很容易拓展,直接添加一个实现Observer接口的实例或移除一个实例即可修改业务功能。
(观察者模式适用于一个事件发生后同时触发其他事件发生的情况)
缺点:
1. 如果在观察者之间有循环依赖,被观察者会触发他们之间进行循环调用,导致系统崩溃。
2. 如果对观察者的通知是通过另外的线程进行异步投递,系统必须保证投递的顺序执行。