源码git地址 https://github.com/dlovetco/designMode
问题提出
同学们都在上自习课,老师没人所以开始纷纷玩起来。坐在门口的同学需要完成望风任务,即发现老师回来的时候要告诉全班同学不要玩了,继续上自习。要求用代码实现上述场景。
由上述问题,我们可以定义两个角色:望风的同学(小明),玩耍的同学(小刚和小红)。
先举一个简单的高耦合的例子。
高耦合代码
package observerMode;
import java.util.ArrayList;
import java.util.List;
public class ObserverMode {
public static void main(String[] args) {
Ming ming = new Ming();
Student gang = new Student("小刚", "玩篮球", ming);
Student hong = new Student("小红", "画画", ming);
ming.addStudents(gang);
ming.addStudents(hong);
ming.seeTeacherComing();
}
}
class Ming {
private List<Student> students = new ArrayList<>();
void addStudents(Student student) {
students.add(student);
}
void seeTeacherComing() {
students.forEach(Student::update);
}
String sendNotice() {
return "小明说老师回来了";
}
}
class Student {
private String name;
private String action;
private Ming ming;
public Student(String name, String action, Ming ming) {
this.name = name;
this.action = action;
this.ming = ming;
}
void update() {
System.out.println(ming.sendNotice() + name + "停止" + action + "认真学习");
}
}
上述代码只是表现了最简单的一种情况,各位看官可以适当写的复杂一点(#^.^#)。这是最简单的代码,但也是耦合性最高的代码。
1. 首先没有抽象,直接把望风的同学定死了是小明,其他同学也已经定死了是小刚和小红(有可能不同的同学在做不同的事情)。没有体现依赖倒转原则
2. 因为有1的缺点,所以类与类之间必然存在高耦合(没有接口)。
观察者模式
package observerMode;
import java.util.ArrayList;
import java.util.List;
public class ObserverMode {
public static void main(String[] args) {
Ming ming = new Ming();
Observer gang = new Student("小刚", "打篮球", ming);
Observer hong = new Student("小红", "画画", ming);
ming.addObserver(gang);
ming.addObserver(hong);
ming.seeTeacherComing();
}
}
/**
* 观察者 希望得到通知的人
*/
interface Observer {
void update();
}
/**
* 通知者 发出通知的人
*/
interface Informer {
void seeTeacherComing();
String sendNotice();
}
class Ming implements Informer {
private List<Observer> observers = new ArrayList<>();
void addObserver(Observer observer) {
observers.add(observer);
}
void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void seeTeacherComing() {
observers.forEach(Observer::update);
}
@Override
public String sendNotice() {
return "小明说老师回来了";
}
}
class Student implements Observer {
public Student(String name, String action, Informer informer) {
this.name = name;
this.action = action;
this.informer = informer;
}
String name;
String action;
Informer informer;
@Override
public void update() {
System.out.println(informer.sendNotice() + name + "停止" + action + "认真学习");
}
}
观察者模式:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,使他们能够自动更新自己。
就这个例子而言,我们把望风的同学抽象成通知者,上自习的同学抽象成观察者。而观察者的状态(玩还是自习)取决于通知者是否发出通知(老师是否回来了)。
plantuml
@startuml
interface Observer{
update()
}
interface Informer{
seeTeacherComing()
sendNotice()
}
Observer <|.. Student
Informer<--Student
class Student{
Informer informer
}
Informer <|.. Ming
Observer <-- Ming
class Ming{
List<Observer> observers
seeTeacherComing()
sendNotice()
}
@enduml
注:
书上使用抽象类,而我是用接口。所以uml图看起来会有所不同。
观察者模式的缺点:
如果说每一个具体的观察者他们的接收到通知后应变的方法不同(方法名自然也都不一样,比如小刚是stopPlaying,小红是stopPainting),就不能够使用接口来实现观察者模式。在C#中可以使用委托来处理这个事情,而在java中,我们可以通过函数式接口来实现相应的功能。
使用java的函数式接口实现C#的委托
package observerMode;
import java.util.function.Supplier;
public class ObserverMode {
public static void main(String[] args) {
Informer ming = new Ming();
Gang gang = new Gang(ming);
Hong hong = new Hong(ming);
ming.seeTeacherComing(() -> {
gang.stopPlaying();
hong.stopPainting();
return null;
});
}
}
/**
* 通知者 发出通知的人
*/
interface Informer {
void seeTeacherComing(Supplier<?> supplier);
String sendNotice();
}
class Ming implements Informer {
@Override
public void seeTeacherComing(Supplier<?> supplier) {
supplier.get();
}
@Override
public String sendNotice() {
return "小明说老师回来了";
}
}
class Gang {
Informer informer;
public Gang(Informer informer) {
this.informer = informer;
}
void stopPlaying() {
System.out.println(informer.sendNotice() + "小刚停止打篮球");
}
}
class Hong {
Informer informer;
public Hong(Informer informer) {
this.informer = informer;
}
void stopPainting() {
System.out.println(informer.sendNotice() + "小红停止画画");
}
}
这里就是把通知哪些同学的选择交给了客户端。通知者中不需要再有观察者的引用,减少了这两个角色的耦合。不过反过来加大了客户端的工作。