当需要多个物件同时「监听」一个相同物件,一旦该物件发生变化时,监听的物件们将自动采取相对应的处理。
通常是发生在单个物件的状态变化发生时,其他物件将会动态地跟着调整、执行各自对应的行为。
在模式内是这样称呼的:
作法是:
interface
,使用 update()
作为共同方法。interface
,会建立三个常用的方法:
add()
。remove()
。notify()
。update()
的细节。notify()
要思考的是,通知者因为哪些行为改变自身状态後才需要「通知」观察者。以下范例以「简易判断 Log 等级後发出警示」为核心制作。
制作 Log 资料:Log
public class Log {
private String title;
private String level;
private String message;
private String type;
public Log(String title, String level, String message, String type) {
this.title = title;
this.level = level;
this.message = message;
this.type = type;
}
public String getTitle() {
return title;
}
public String getLevel() {
return level;
}
public String getMessage() {
return title + ", " + message;
}
public String getType() {
return type;
}
}
制作观察者的虚拟层亲代:Siren
public interface Siren {
void update(String logLevel, String message);
}
制作通知者的虚拟层亲代:LogCollectorMethods
public interface LogCollectorMethods {
void addSubject(String subject);
void addSubscriber(String subject, Siren siren);
void removeSubscriber(String subject, Siren siren);
void notifySubscribers(String subject, String level, String message);
}
制作观察者子代:ApiPassFailedSiren
、LogInFailedSiren
(Observer 物件)
public class ApiPassFailedSiren implements Siren {
private String name;
private String level;
public ApiPassFailedSiren(String name, String level) {
this.name = name;
this.level = level;
}
@Override
public void update(String logLevel, String message) {
if (level.equals("low")) {
alert(message);
} else if (level.equals("medium")) {
if (logLevel.equals("high") || logLevel.equals("medium")) {
alert(message);
}
} else if (level.equals("high") && logLevel.equals("high")) {
alert(message);
}
}
private void alert(String message) {
System.out.println("The Siren: '" + name + "' is working now, and the message is:\n" + message);
}
}
public class LogInFailedSiren implements Siren {
private String name;
private String level;
public LogInFailedSiren(String name, String level) {
this.name = name;
this.level = level;
}
@Override
public void update(String logLevel, String message) {
if (level.equals("low")) {
alert(message);
} else if (level.equals("medium")) {
if (logLevel.equals("high") || logLevel.equals("medium")) {
alert(message);
}
} else if (level.equals("high") && logLevel.equals("high")) {
alert(message);
}
}
private void alert(String message) {
System.out.println("The 'Log In Failed' " + name + " Siren is working now, and the message is:\n" + message);
}
}
制作通知者子代:ApiPassFailedSiren
(Subject 物件)
public class LogCollector implements LogCollectorMethods {
private Map<String, List<Siren>> subscribers;
public LogCollector() {
this.subscribers = new HashMap<>();
}
@Override
public void addSubject(String subject) {
if (!subscribers.containsKey(subject)) {
subscribers.put(subject, new ArrayList<>());
}
}
@Override
public void addSubscriber(String subject, Siren siren) {
if (!subscribers.containsKey(subject)) {
subscribers.put(subject, new ArrayList<>());
}
subscribers.get(subject).add(siren);
}
@Override
public void removeSubscriber(String subject, Siren siren) {
List<Siren> sirens = subscribers.get(subject);
sirens.remove(siren);
}
@Override
public void notifySubscribers(String subject, String level, String message) {
List<Siren> sirens = subscribers.get(subject);
for (Siren siren : sirens) {
siren.update(level, message);
}
System.out.println("");
}
public void receiveLog(Log log) {
notifySubscribers(log.getType(), log.getLevel(), log.getMessage());
}
}
测试,建立通知者後,依序注册观察者,最後试着发送讯息:LogCollectorObserverSample
public class LogCollectorObserverSample {
public static void main(String[] args) {
LogCollector logCollector = new LogCollector();
logCollector.addSubject("api");
logCollector.addSubscriber("api", new ApiPassFailedSiren("/users", "low"));
logCollector.addSubscriber("api", new ApiPassFailedSiren("/photos", "medium"));
logCollector.addSubscriber("api", new ApiPassFailedSiren("/cars", "low"));
logCollector.addSubscriber("api", new ApiPassFailedSiren("/keys", "high"));
logCollector.addSubject("logIn");
logCollector.addSubscriber("logIn", new LogInFailedSiren("users", "high"));
logCollector.addSubscriber("logIn", new LogInFailedSiren("admin", "low"));
logCollector.receiveLog(new Log("api pass failed", "high", "the client uses wrong token", "api"));
logCollector.receiveLog(new Log("log in failed", "low", "the account is wrong", "logIn"));
}
}
制作 Log 资料:Log
class Log {
/**
* @param {string} title
* @param {string} level
* @param {string} message
* @param {string} type
*/
constructor(title, level, message, type) {
this.title = title;
this.level = level;
this.message = message;
this.type = type;
}
getTitle() {
return this.title;
}
getLevel() {
return this.level;
}
getMessage() {
return this.title + ", " + this.message;
}
getType() {
return this.type;
}
}
制作观察者的虚拟层亲代:Siren
/** @abstract */
class Siren {
/**
* @param {string} name
* @param {string} level
*/
constructor(name, level) {
this.name = name;
this.level = level;
}
/**
* @override
* @param {string} logLevel
* @param {string} message
*/
update(logLevel, message) {
if (this.level === "low") {
this.alert(message);
} else if (this.level === "medium") {
if (logLevel === "high" || logLevel === "medium") {
this.alert(message);
}
} else if (this.level === "high" && logLevel === "high") {
this.alert(message);
}
}
/** @param {string} message */
alert(message) { return; }
/** @returns {string} */
getName() { return; }
}
制作通知者的虚拟层亲代:LogCollectorMethods
/** @abstract */
class LogCollectorMethods {
constructor() {
/** @type {Map<String, Siren[]>} */
this.subscribers = new Map();
}
/** @param {string} subject */
addSubject(subject) { return; }
/**
* @param {string} subject
* @param {Siren} siren
*/
addSubscriber(subject, siren) { return; }
/**
* @param {string} subject
* @param {Siren} siren
*/
removeSubscriber(subject, siren) { return; }
/**
* @param {string} subject
* @param {string} level
* @param {string} message
*/
notifySubscribers(subject, level, message) { return; }
}
制作观察者子代:ApiPassFailedSiren
、LogInFailedSiren
(Observer 物件)
class ApiPassFailedSiren extends Siren {
/**
* @param {string} name
* @param {string} level
*/
constructor(name, level) {
super(name, level);
}
/**
* @override
* @param {string} message
*/
alert(message) {
console.log("The Siren: '" + this.name + "' is working now, and the message is:\n" + message);
}
/** @override */
getName() {
return this.name;
}
}
class LogInFailedSiren extends Siren {
/**
* @param {string} name
* @param {string} level
*/
constructor(name, level) {
super(name, level);
}
/**
* @override
* @param {string} message
*/
alert(message) {
console.log("The 'Log In Failed' " + this.name + " Siren is working now, and the message is:\n" + message);
}
/** @override */
getName() {
return this.name;
}
}
制作通知者子代:ApiPassFailedSiren
(Subject 物件)
class LogCollector extends LogCollectorMethods {
constructor() {
super();
}
/**
* @override
* @param {string} subject
*/
addSubject(subject) {
if (!this.subscribers.has(subject)) {
this.subscribers.set(subject, []);
}
}
/**
* @override
* @param {string} subject
* @param {Siren} siren
*/
addSubscriber(subject, siren) {
if (!this.subscribers.has(subject)) {
this.subscribers.set(subject, []);
}
this.subscribers.get(subject).push(siren);
}
/**
* @override
* @param {string} subject
* @param {Siren} siren
*/
removeSubscriber(subject, siren) {
const sirens = this.subscribers.get(subject);
const removeSpecificSiren = sirens.filter(item => item.getName() !== siren.getName())
this.subscribers.set(subject, removeSpecificSiren);
}
/**
* @override
* @param {string} subject
* @param {string} level
* @param {string} message
*/
notifySubscribers(subject, level, message) {
const sirens = this.subscribers.get(subject);
for (const siren of sirens) {
siren.update(level, message);
}
console.log("");
}
/** @param {Log} log */
receiveLog(log) {
this.notifySubscribers(log.getType(), log.getLevel(), log.getMessage());
}
}
测试,建立通知者後,依序注册观察者,最後试着发送讯息:logCollectorObserverSample
const logCollectorObserverSample = () => {
const logCollector = new LogCollector();
logCollector.addSubject("api");
logCollector.addSubscriber("api", new ApiPassFailedSiren("/users", "low"));
logCollector.addSubscriber("api", new ApiPassFailedSiren("/photos", "medium"));
logCollector.addSubscriber("api", new ApiPassFailedSiren("/cars", "low"));
logCollector.addSubscriber("api", new ApiPassFailedSiren("/keys", "high"));
logCollector.addSubject("logIn");
logCollector.addSubscriber("logIn", new LogInFailedSiren("users", "high"));
logCollector.addSubscriber("logIn", new LogInFailedSiren("admin", "low"));
logCollector.receiveLog(new Log("api pass failed", "high", "the client uses wrong token", "api"));
logCollector.receiveLog(new Log("log in failed", "low", "the account is wrong", "logIn"));
};
logCollectorObserverSample();
Observer 模式还有另一个称呼:Publish/Subscribe,这称呼直接了当地表达该模式的重点。
自己实作时观察到:
delegate
可以简化。最後要提一点,Observer 模式与 Mediator 模式,两者的初衷不同,前者是建立单向的连结,後者是消除物件们复杂的依赖关系,而实作骑来却十分相似,想想这或许是让程序码维持高弹性、减少复杂度等目的後,必然的结果也说不定。
明天将介绍 Behavioural patterns 的第八个模式:State 模式。
软件开发中的所有角色,产品经理可以说是会议前几名多的角色,而且很常要担任主持会议的角色,因为产品经理...
原始题目 Evaluate the value of an arithmetic expressio...
顾客使用的点菜单 点菜单已经成为我们日常生活中不可或缺的东西之一了, 那我们在开始设计菜单的样式之前...
在HTML文件中加入图片,可以在想置入图片的地方使用img标签。 <img>是单一元素,它不用开头和...
本文章同时发布於: Github(包含程序码) 文章为自己的经验与夥伴整理的内容,设计没有标准答案,...