学习 Netty 前的准备工作!!
这里,先来复习一下 OOP(面向对象)的七大原则:
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立
- 依赖倒置原则:要面向接口编程,不要面向实现编程
- 单一职责原则:要控制类的粒度大小,将对象解耦,提高其内聚性
- 接口隔离原则:要为各个类创建它们需要的专用接口
- 迪米特法则:只与你的直接朋友交谈,不跟陌生人说话
- 合成复用原则:尽量先使用或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
观察者(Observer)模式的定义:指多个对象之间存在一对多的依赖关系,当一个对象的状态发生改变的时候,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布 - 订阅模式、模型 - 视图模式,它是对象行为型模式。
观察者模式是一种对象行为模式,其主要优点如下:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
- 当观察者对象很多的时候,通知的发布会花费很多的时间,影响程序的效率。
它的主要缺点如下:
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多的时候,通知的发布会花费很多时间,影响程序的效率。
实现观察者模式的时候要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。
2.1、观察者模式的主要角色如下:- 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用户保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变的时候,通知所有注册过的观察者对象。
- 抽象观察者(Observer)角色:它是一个接口或者抽象类,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知的时候被调用。
- 具体观察者(Concrete Observer)角色:它实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知的时候更新自身的状态。
观察者模式的结构图如所示:
模式的实现
public class ObserverPattern {
public static void main(String[] args) {
Subject subject = new ConcreteSubject();
Observer observer1 = new ConcreteObserver1();
Observer observer2 = new ConcreteObserver2();
subject.add(observer1);
subject.add(observer2);
subject.notifyAllObserver();
}
}
// 抽象目标类
abstract class Subject {
// 用于保存观察者对象的聚集类对象
protected List observers = new ArrayList<>();
// 增加观察者对象方法
public void add(Observer observer){
observers.add(observer);
}
// 删除观察者对象方法
public void remove(Observer observer){
observers.remove(observer);
}
// 通知观察者对象方法
public abstract void notifyAllObserver();
}
// 具体目标类
class ConcreteSubject extends Subject {
@Override
public void notifyAllObserver() {
System.out.println("具体目标发生改变...");
System.out.println("=========================");
for (Observer observer : observers){
observer.response();
}
}
}
// 抽象观察者类
interface Observer {
void response();
}
// 具体观察者1类
class ConcreteObserver1 implements Observer {
@Override
public void response() {
System.out.println("具体观察者1做出反应");
}
}
// 具体观察者2类
class ConcreteObserver2 implements Observer {
@Override
public void response() {
System.out.println("具体观察者2做出反应");
}
}
测试结果:
利用观察者模式设计一个程序,分析人民币汇率的升值或者贬值对进口公司进口产品成本或者出口公司的出口产品收入以及公司利润率的影响。
分析:当人民币汇率升值的时候,进口公司得到进口产品成本降低且利润率提升,出口公司的出口产品收入降低且利润率降低;当人民币贬值的时候,进口公司的进口产品成本提升且利润率降低,出口公司的出口产品收入提升且利润率提升。
这里的汇率(Rate)类是抽象目标类,它包含了保存观察者( Company)的 List 的增加和删除的方法,以及有关汇率改变的抽象的方法 change(int number) ;而人民币的汇率(RMBrate) 类是具体目标类,它实现父类的 change(int number) 方法,即当人民币汇率发生改变的时候通过相关公司;公司(Company)类是抽象观察者,它定义了一个有关汇率反应的抽象方法 response(int number) ;进口公司(importCompany)类和出口公司类(ExportCompany)类是具体观察者类,它们实现了父类的 response(int number)方法,即当它们接收到汇率发生改变的通知时作出相应的反应。
程序代码如下:
public class RMBRateTest {
public static void main(String[] args) {
Rate rate = new RMBRate();
Company watcher1 = new importCompany();
Company watcher2 = new ExportCompany();
rate.add(watcher1);
rate.add(watcher2);
rate.change(10);
rate.change(-9);
}
}
// 抽象目标类:汇率
abstract class Rate {
protected List companies = new ArrayList<>();
// 增加观察者方法
public void add(Company company){
companies.add(company);
}
// 删除观察者方法
public void remove(Company company){
companies.remove(company);
}
// 通知观察者对象的方法
public abstract void change(int number);
}
// 具体目标:人民币汇率
class RMBRate extends Rate {
@Override
public void change(int number) {
for (Company company : companies){
company.response(number);
}
}
}
// 抽象观察者:公司
interface Company {
void response(int number);
}
// 具体观察者对象1:进口公司
class importCompany implements Company {
@Override
public void response(int number) {
if(number > 0){
System.out.println("人民币汇率升值"+number+"个基点,降低了进口产品成本,提升了进口公司的利润率。");
}else if (number<0){
System.out.println("人民币汇率贬值"+number+"个基点,提升了进口产品成本,降低了进口公司的利润率。");
}
}
}
// 具体观察者对象2:进口公司
class ExportCompany implements Company {
@Override
public void response(int number) {
if(number > 0){
System.out.println("人民币汇率升值"+number+"个基点,提升了进口产品成本,降低了进口公司的利润率。");
}else if (number<0){
System.out.println("人民币汇率贬值"+number+"个基点,降低了进口产品成本,提升了进口公司的利润率。");
}
}
}
观察者模式的具体应用实例2:
利用观察者模式设计一个学校铃声的事件处理程序。
分析:在本实例中,学校的铃声是事件源和目标,老师和学生是事件监听器和具体观察者,铃声是事件类。学生和老师来到学校的教学区,都会注意学校的铃声,这叫事件绑定;当上课事件或者下课事件到,会触发铃声,这时会生成铃声事件;学生和老师听到铃声会开始上课或者下课,这叫事件处理。这个实例非常适合用观察者模式实现。
现在用观察者模式来实现该事件处理模型。
首先,定义一个铃声事件(RingEvent)类,它记录了铃声的类型(上课铃声、下课铃声)Listener,可以绑定监听者(学生或者老师),并且有产生铃声事件和通知所有监听者的方法。
然后,定义铃声事件监听者(BellEventListener)类,它是抽象观察者,它包含了铃声事件处理方法 HeardBell(RingEvent e)。
最后,定义老师类( TeacherEventListener)和学生类(StudentEventListener),它们是事件监听器,是具体观察者,听到铃声会去上课或者下课。
public class BellEventTest {
public static void main(String[] args) {
BellEventSource bell = new BellEventSource(); //铃(事件源)
bell.addPersonListener(new TeachEventListener()); //注册监听器(老师)
bell.addPersonListener(new StudentEventListener()); //注册监听器(学生)
bell.ring(true); //打上课铃声
System.out.println("------------");
bell.ring(false); //打下课铃声
}
}
// 铃声事件类,用于封装事件源及一些于事件相关的参数
class RingEvent extends EventObject {
private static final long serialVersionUID = 1L;
private boolean sound; // true 表示上课铃声,false 表示下课铃声
public RingEvent(Object source,boolean sound) {
super(source);
this.sound = sound;
}
public boolean getSound() {
return sound;
}
public void setSound(boolean sound) {
this.sound = sound;
}
}
// 目标类:事件源,铃声
class BellEventSource {
// 观察者监听器对象容器
private List listeners = new ArrayList<>();
// 给事件源绑定监听器
public void addPersonListener(BellEventListener ren){
listeners.add(ren);
}
// 事件触发器:敲钟,当铃声sound的值发生变化的时候,触发事件
public void ring(boolean sound){
String type = sound ? "上课铃声" : "下课铃声";
System.out.println(type + "响了");
RingEvent event = new RingEvent(this,sound);
notifyAll(event);
}
protected void notifyAll(RingEvent e){
BellEventListener bellEventListener = null;
Iterator iterator = listeners.iterator();
while (iterator.hasNext()){
bellEventListener = iterator.next();
bellEventListener.headBell(e);
}
}
}
// 抽象观察者类:铃声事件监听器
interface BellEventListener extends EventListener {
// 事件处理方法。听到铃声
public void headBell(RingEvent e);
}
// 具体观察者类1:老师事件监听器
class TeachEventListener implements BellEventListener {
@Override
public void headBell(RingEvent e) {
if(e.getSound()){
System.out.println("老师上课了...");
}else{
System.out.println("老师下课了...");
}
}
}
// 具体观察者类2:学生事件监听器
class StudentEventListener implements BellEventListener {
@Override
public void headBell(RingEvent e) {
if(e.getSound()){
System.out.println("同学们上课了...");
}else{
System.out.println("同学们下课了...");
}
}
}
模式的扩展
在 JAVA 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
1、Observable 类
- Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的三个方法:
- void addObserver(Observer o)方法:用于将新的观察者对象添加到向量中。
- void notifyObservers(Object arg)方法:调用向量中的所有观察者对象的 update()方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
- void setChange()方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真的时候,notifyObservwers()才会通知观察者。
2、Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化的时候,观察者得到通知,并调用 void update(Observable 0,Object arg)方法,进行相应的工作。
【例子3】:利用Observable 类和 Observer 接口实现原油期货的观察者模式实例
分析:当原油价格上涨的时候,空方伤心,多方局兴;当油价下跌的时候,空方局兴,多方伤心。本实例中的抽象目标(Observable)类在 JAVA 中已经定义,可以直接定义其子类,即原油期货(OliFutures)类,它是具体目标类,该类中定义一个 setPrice(float price)方法,当原油数据发送变化的时候调用其父类的 notifyObservers(Object arg)方法来通知所有观察者;另外,本实例中的抽象观察者接口( Observer)在 JAVA 中已经定义,只要定义其子类,即具体观察者类(包括多方类 Bull 和空方类 Bear),并实现 update(Observable o,Object arg)方法即可。
public class CrudeOliFutures {
public static void main(String[] args) {
OliFutures oliFutures = new OliFutures();
Observer bull = new Bull();
Observer bear = new Bear();
oliFutures.addObserver(bull);
oliFutures.addObserver(bear);
oliFutures.setPrice(10);
oliFutures.setPrice(-8);
}
}
// 具体目标类:原油期货
class OliFutures extends Observable {
private float price;
public float getPrice() {
return price;
}
public void setPrice(float price) {
super.setChanged(); // 设置内部标志位,注明数据发生了变化
super.notifyObservers(price); // 通知观察者价格发生了改变
this.price = price;
}
}
// 具体观察者:多方
class Bull implements Observer {
@Override
public void update(Observable o, Object arg) {
Float price = ((Float) arg).floatValue();
if(price > 0){
System.out.println("油价上涨" + price + "元,多方高兴了!");
}else{
System.out.println("油价下跌" + (-price) + "元,多方伤心了!");
}
}
}
// 具体观察者类:空方
class Bear implements Observer {
public void update(Observable o, Object arg) {
Float price = ((Float) arg).floatValue();
if (price > 0) {
System.out.println("油价上涨" + price + "元,空方伤心了!");
} else {
System.out.println("油价下跌" + (-price) + "元,空方高兴了!");
}
}
}
测试结果如下:



