Hướng dẫn Java Design Pattern – Observer – GP Coder (Lập trình Java)

Chúng ta không thể nói về Lập trình hướng đối tượng mà không xem xét trạng thái của các đối tượng. Tất cả các chương trình hướng đối tượng là về các đối tượng và sự tương tác của chúng. Trong trường hợp khi một số đối tượng nhất định cần được thông báo thường xuyên về những thay đổi xảy ra trong các đối tượng khác. Để có một thiết kế tốt có nghĩa là tách rời càng nhiều càng tốt và giảm sự phụ thuộc. Mẫu thiết kế Observer (quan sát) có thể được sử dụng bất cứ khi nào mà một đối tượng có sự thay đổi trạng thái, tất các thành phần phụ thuộc của nó sẽ được thông báo và cập nhật một cách tự động.

Observer Pattern là gì ?

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically .

Observer Pattern là một trong những Pattern thuộc nhóm hành vi (Behavior Pattern). Nó định nghĩa mối phụ thuộc mộtnhiều giữa các đối tượng để khi mà một đối tượng có sự thay đổi trạng thái, tất các thành phần phụ thuộc của nó sẽ được thông báo và cập nhật một cách tự động.

Observer có thể đăng ký với hệ thống. Khi hệ thống có sự thay đổi, hệ thống sẽ thông báo cho Observer biết. Khi không cần nữa, mẫu Observer sẽ được gỡ khỏi hệ thống.

  • Hình 1-8, cho phép observer thứ 1 đăng ký với hệ thống.
  • Hình 1-9, cho phép observer thứ 2 đăng ký với hệ thống.
  • Hiện tại hệ thống đang liên lạc với 2 observer: Observer 1 và Observer 2. Khi hệ thống phát sinh một sự kiện cụ thể nào đó, nó sẽ thông báo (notification) với cả 2 observer như hình số 1-10.

Observer Pattern còn gọi là Dependents, Publish/Subcribe hoặc Source/Listener.

Cài đặt Observer Pattern như thế nào ?

Các thành phần tham gia Observer Pattern :

  • Subject : chứa danh sách các observer,  cung cấp phương thức để có thể thêm và loại bỏ observer.
  • Observer : định nghĩa một phương thức update() cho các đối tượng sẽ được subject thông báo đến khi có sự thay đổi trạng thái.
  • ConcreteSubject : cài đặt các phương thức của Subject, lưu trữ trạng thái danh sách các ConcreateObserver, gửi thông báo đến các observer của nó khi có sự thay đổi trạng thái.
  • ConcreteObserver : cài đặt các phương thức của Observer, lưu trữ trạng thái của subject, thực thi việc cập nhật để giữ cho trạng thái đồng nhất với subject gửi thông báo đến.

Sự tương tác giữa subject và những observer như sau : mỗi khi subject có sự biến hóa trạng thái, nó sẽ duyệt qua list những observer của nó và gọi phương pháp update trạng thái ở từng observer, hoàn toàn có thể truyền chính nó vào phương pháp để những observer hoàn toàn có thể lấy ra trạng thái của nó và giải quyết và xử lý .

Ví dụ Observer Pattern với ứng dụng Tracking thao tác một Account

Giả sử mạng lưới hệ thống của tất cả chúng ta cần theo dõi về thông tin tài khoản của người dùng. Mọi thao tác của người dùng đều cần được ghi log lại, sẽ triển khai gửi mail thông tin khi thông tin tài khoản hết hạn, triển khai chặn người dùng nếu truy vấn không hợp lệ, …

Chương trình của tất cả chúng ta như sau :

  • Subject : cung cấp các phương thức để thêm, loại bỏ, thông báo observer.
  • AccountService : đóng vai trò là ConcreteSubject, sẽ thông báo tới tất cả các observers bất cứ khi nào có thao tác của người dùng liên quan đến đăng nhập, tài khoản hết hạn.
  • Observer : định nghĩa một phương thức update() cho các đối tượng sẽ được subject thông báo đến khi có sự thay đổi trạng thái. Phương thức này chấp nhận đối số là SubjectState, cho phép các ConcreteObserver sử dụng dữ liệu của nó.
  • LoggerMailer và Protector là các ConcreteObserver. Sau khi nhận được thông báo rằng có thao tác với user và gọi tới phương thức update(), các ConcreteObserver sẽ sử dụng dữ liệu SubjectState để xử lý.

Subject.java


package com.gpcoder.patterns.behavioral.observer.account;

public interface Subject {

	void attach(Observer observer);

	void detach(Observer observer);

	void notifyAllObserver();
}

AccountService.java


package com.gpcoder.patterns.behavioral.observer.account;

import java.util.ArrayList;
import java.util.List;

import lombok.Data;

enum LoginStatus {
	SUCCESS, FAILURE, INVALID, EXPIRED
}

@Data
class User {
	private String email;
	private String ip;
	private LoginStatus status;
}

public class AccountService implements Subject {

	private User user;
	private List observers = new ArrayList<>();

	public AccountService(String email, String ip) {
		user = new User();
		user.setEmail(email);
		user.setIp(ip);
	}

	@Override
	public void attach(Observer observer) {
		if (!observers.contains(observer))
			observers.add(observer);
	}

	@Override
	public void detach(Observer observer) {
		if (observers.contains(observer)) {
			observers.remove(observer);
		}
	}

	@Override
	public void notifyAllObserver() {
		for (Observer observer : observers) {
			observer.update(user);
		}
	}

	public void changeStatus(LoginStatus status) {
		user.setStatus(status);
		System.out.println("Status is changed");
		this.notifyAllObserver();
	}

	public void login() {

		if (!this.isValidIP()) {
			user.setStatus(LoginStatus.INVALID);
		} else if (this.isValidEmail()) {
			user.setStatus(LoginStatus.SUCCESS);
		} else {
			user.setStatus(LoginStatus.FAILURE);
		}

		System.out.println("Login is handled");
		this.notifyAllObserver();
	}

	private boolean isValidIP() {
		return "127.0.0.1".equals(user.getIp());
	}

	private boolean isValidEmail() {
		return "[email protected]".equalsIgnoreCase(user.getEmail());
	}
}

Observer.java


package com.gpcoder.patterns.behavioral.observer.account;

public interface Observer {
	void update(User user);
}

Logger.java


package com.gpcoder.patterns.behavioral.observer.account;

public class Logger implements Observer {

	@Override
	public void update(User user) {
		System.out.println("Logger: " + user);
	}
}

Mailer.java


package com.gpcoder.patterns.behavioral.observer.account;

public class Mailer implements Observer {

	@Override
	public void update(User user) {
		if (user.getStatus() == LoginStatus.EXPIRED) {
			System.out.println("Mailer: User " + user.getEmail() + " is expired. An email was sent!");
		}
	}
}

Protector.java


package com.gpcoder.patterns.behavioral.observer.account;

public class Protector implements Observer {

	@Override
	public void update(User user) {
		if (user.getStatus() == LoginStatus.INVALID) {
			System.out.println("Protector: User " + user.getEmail() + " is invalid. " 
					+ "IP " + user.getIp() + " is blocked");
		}
	}
}

ObserverPatternExample.java


package com.gpcoder.patterns.behavioral.observer.account;

public class ObserverPatternExample {

	public static void main(String[] args) {
		AccountService account1 = createAccount("[email protected]", "127.0.0.1");
		account1.login();
		account1.changeStatus(LoginStatus.EXPIRED);

		System.out.println("---");
		AccountService account2 = createAccount("[email protected]", "116.108.77.231");
		account2.login();
	}

	private static AccountService createAccount(String email, String ip) {
		AccountService account = new AccountService(email, ip);
		account.attach(new Logger());
		account.attach(new Mailer());
		account.attach(new Protector());
		return account;
	}
}

Output của chương trình:


Login is handled
Logger: User([email protected], ip=127.0.0.1, status=SUCCESS)
Status is changed
Logger: User([email protected], ip=127.0.0.1, status=EXPIRED)
Mailer: User [email protected] is expired. An email was sent!
---
Login is handled
Logger: User([email protected], ip=116.108.77.231, status=INVALID)
Protector: User [email protected] is invalid. IP 116.108.77.231 is blocked

Lợi ích của Observer Pattern là gì ?

Lợi ích :

  • Dễ dàng mở rộng với ít sự thay đổi : mẫu này cho phép thay đổi Subject và Observer một cách độc lập. Chúng ta có thể tái sử dụng các Subject mà không cần tái sử dụng các Observer và ngược lại. Nó cho phép thêm Observer mà không sửa đổi Subject hoặc Observer khác. Vì vậy, nó đảm bảo nguyên tắc Open/Closed Principle (OCP).
  • Sự thay đổi trạng thái ở 1 đối tượng có thể được thông báo đến các đối tượng khác mà không phải giữ chúng liên kết quá chặt chẽ.
  • Một đối tượng có thể thông báo đến một số lượng không giới hạn các đối tượng khác.

Bên cạnh những quyền lợi, tất cả chúng ta cần xem xét đến trường hợp update không mong ước ( Unexpected update ) của Subject. Bởi vì những Observer không biết về sự hiện hữu của nhau, nó hoàn toàn có thể gây tốn nhiều ngân sách của việc biến hóa Subject .

Sử dụng Observer Pattern khi nào ?

  • Thường được sử dụng trong mối quan hệ 1-n giữa các object với nhau. Trong đó một đối tượng thay đổi và muốn thông báo cho tất cả các object liên quan biết về sự thay đổi đó.
  • Khi thay đổi một đối tượng, yêu cầu thay đổi đối tượng khác và chúng ta không biết có bao nhiêu đối tượng cần thay đổi, những đối tượng này là ai.
  • Sử dụng trong ứng dụng broadcast-type communication.
  • Sử dụng để quản lý sự kiện (Event management).
  • Sử dụng trong mẫu mô hình MVC (Model View Controller Pattern) : trong MVC, mẫu này được sử dụng để tách Model khỏi View. View đại diện cho Observer và Model là đối tượng Observable.

Tài liệu tham khảo:

  • https://sourcemaking.com/design_patterns/observer
  • https://refactoring.guru/design-patterns/observer
  • https://www.baeldung.com/java-observer-pattern
  • https://pawelgrzybek.com/the-observer-pattern-in-javascript-explained/
  • Design Patterns: Elements of Reusable Object-Oriented Software – GOF
  • Design Pattern for dummies

4.8

Nếu bạn thấy hay thì hãy chia sẻ bài viết cho mọi người nhé!

Shares

Bình luận

phản hồi