GP Coder

Trang chia sẻ kiến thức lập trình Java

  • Java Core
    • Basic Java
    • OOP
    • Exception Handling
    • Multi-Thread
    • Java I/O
    • Networking
    • Reflection
    • Collection
    • Java 8
  • Design pattern
    • Creational Pattern
    • Structuaral Pattern
    • Behavior Pattern
  • Web Service
    • SOAP
    • REST
  • JPA
  • Java library
    • Report
    • Json
    • Unit Test
  • Message Queue
    • ActiveMQ
    • RabbitMQ
  • All
Trang chủ Design pattern Hướng dẫn Java Design Pattern – Visitor

Hướng dẫn Java Design Pattern – Visitor

Đăng vào 10/01/2019 . Được đăng bởi GP Coder . 21625 Lượt xem . Toàn màn hình

Ở các bài viết trước chúng ta đã cùng tìm hiểu về các Pattern thuộc nhóm hành vi (Behavior Pattern): Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method. Trong bài viết này, chúng ta sẽ cùng tìm hiểu về một Pattern cuối cùng thuộc nhóm Behavior Pattern được giới thiệu trong cuốn “Design Patterns: Elements of Reusable Object-Oriented Software – GOF” là Visitor Design Pattern.

Nội dung

  • 1 Single Dispatch và Double Dispatch là gì?
  • 2 Visitor Pattern là gì?
  • 3 Cài đặt Visitor Pattern như thế nào?
  • 4 Lợi ích của Visitor Pattern là gì?
  • 5 Sử dụng Visitor Pattern khi nào?
  • 6 Giới hạn của Visitor Pattern và Áp dụng Reflection trong Visitor Pattern

Single Dispatch và Double Dispatch là gì?

Trước khi đi vào chi tiết về Visitor Pattern, chúng ta hãy cùng tìm hiểu về Single Dispatch và Double Dispatch.

Single Dispatch là gì?

Hãy xem đoạn code bên dưới:


package com.gpcoder.patterns.behavioral.visitor.singledispatch;

import lombok.Data;

@Data
public class Book {
	private String name;
	private int price;
}


public class ProgramingBook extends Book {

}

public class BusinessBook extends Book {

}


package com.gpcoder.patterns.behavioral.visitor.singledispatch.example1;

public interface Customer {

	void buy(Book book);

	void buy(ProgramingBook book);

	void buy(BusinessBook book);
}


package com.gpcoder.patterns.behavioral.visitor.singledispatch.example1;

public class Developer implements Customer {

	@Override
	public void buy(Book book) {
		System.out.println("Developer buy a Book");
	}

	@Override
	public void buy(ProgramingBook book) {
		System.out.println("Developer buy a Programing Book");

	}

	@Override
	public void buy(BusinessBook book) {
		System.out.println("Developer buy a Business Book");
	}
}


package com.gpcoder.patterns.behavioral.visitor.singledispatch.example1;

public class SingleDispatchExample {

	public static void main(String[] args) {
		Book book = new ProgramingBook(); // (1)
		Customer gpcoder = new Developer();
		gpcoder.buy(book);

		ProgramingBook programingBook = new ProgramingBook(); // (2)
		gpcoder.buy(programingBook); // Developer buy a Programing Book
	}
}

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


Developer buy a Book
Developer buy a Programing Book

Như bạn thấy, kết quả ở (1) không như chúng ta mong đợi, cái chúng ta cần là kết quả như (2).

Tại sao sử dụng như ở (1) lại không đúng?

=> Trong Java, chúng ta có thể định nghĩa các phương thức cùng tên nhưng khác nhau về tham số (tính đa hình). Một phương thức được gọi sẽ dựa trên 2 yếu tố: tên của phương thức và kiểu đối tượng gọi nó. Cơ chế này được gọi là Single Dispatch. Trong ví dụ trên, do ở (1) chúng ta đã cast nó về Book nên phương thức buy(Book book) được gọi.

Để giải quyết vấn đề trên, chúng ta có thể sử dụng từ khóa instanceof để kiểm tra kiểu của đối tượng gọi như sau:


package com.gpcoder.patterns.behavioral.visitor.singledispatch.example2;

import com.gpcoder.patterns.behavioral.visitor.singledispatch.Book;
import com.gpcoder.patterns.behavioral.visitor.singledispatch.BusinessBook;
import com.gpcoder.patterns.behavioral.visitor.singledispatch.ProgramingBook;
import com.gpcoder.patterns.behavioral.visitor.singledispatch.example1.Customer;

public class Developer2 implements Customer {

	@Override
	public void buy(Book book) {
		if (book instanceof ProgramingBook) {
			ProgramingBook programingBook = (ProgramingBook) book;
			buy(programingBook);
		} else if (book instanceof BusinessBook) {
			BusinessBook businessBook = (BusinessBook) book;
			buy(businessBook);
		} else {
			System.out.println("Developer buy a Book");
		}
	}

	@Override
	public void buy(ProgramingBook book) {
		System.out.println("Developer buy a Programing Book");

	}

	@Override
	public void buy(BusinessBook book) {
		System.out.println("Developer buy a Business Book");
	}
}

Chạy lại chương trình trên với implement mới, chúng ta có kết quả như sau:


Developer buy a Programing Book
Developer buy a Programing Book

Double Dispatch là gì?

Ngoài cách sử dụng instanceof như trong ví dụ trên, chúng ta có thể sử dụng kỹ thuật Double Dispatch. Trong Double Dispatch, một phương thức sẽ được gọi dựa trên 3 yếu tố: tên của phương thức, kiểu của cả đối tượng gọi và kiểu của đối số truyền vào.

Vậy áp dụng Double Dispatch như thế nào, chúng ta hãy cùng theo dõi trong phần tiếp theo với Visitor Design pattern.

Visitor Pattern là gì?

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Visitor Pattern là một trong những Pattern thuộc nhóm hành vi (Behavior Pattern). Visitor cho phép định nghĩa các thao tác (operations) trên một tập hợp các đối tượng (objects) không đồng nhất (về kiểu) mà không làm thay đổi định nghĩa về lớp (classes) của các đối tượng đó. Để đạt được điều này, trong mẫu thiết kế visitor ta định nghĩa các thao tác trên các lớp tách biệt gọi các lớp visitors, các lớp này cho phép tách rời các thao tác với các đối tượng mà nó tác động đến. Với mỗi thao tác được thêm vào, một lớp visitor tương ứng được tạo ra.

Đây là một kỹ thuật giúp chúng ta phục hồi lại kiểu dữ liệu bị mất (thay vì dùng instanceof). Nó thực hiện đúng thao tác dựa trên tên của phương thức, kiểu của cả đối tượng gọi và kiểu của đối số truyền vào.

Visitor còn được biết đến như là Double dispatch.

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

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

  • Visitor :
    • Là một interface hoặc một abstract class được sử dụng để khai báo các hành vi cho tất cả các loại visitor.
    • Class này định nghĩa một loạt các các phương thức truy cập chấp nhận các ConcreteElement cụ thể khác nhau làm tham số. Điều này sẽ hơi giống với cơ chế nạp chồng (overloading) nhưng các loại tham số nên khác nhau do đó các hành vi hoàn toàn khác nhau. Các hành vi truy cập sẽ được thực hiện trên từng phần tử cụ thể trong cấu trúc đối tượng thông qua phương thức visit(). Loại phần tử cụ thể đầu vào sẽ quyết định phương thức được gọi.
  • ConcreteVisitor : cài đặt tất cả các phương thức abstract đã khai báo trong Visitor. Mỗi visitor sẽ chịu trách nhiệm cho các hành vi khác nhau của đối tượng.
  • Element (Visitable): là một thành phần trừu tượng, nó khai báo phương thức accept() và chấp nhận đối số là Visitor.
  • ConcreteElement (ConcreteVisitable): cài đặt phương thức đã được khai báo trong Element dựa vào đối số visitor được cung cấp.
  • ObjectStructure : là một lớp chứa tất cả các đối tượng Element, cung cấp một cơ chế để duyệt qua tất cả các phần tử. Cấu trúc đối tượng này có thể là một tập hợp (collection) hoặc một cấu trúc phức tạp giống như một đối tượng tổng hợp (composite).
  • Client : không biết về ConcreteElement và chỉ gọi phương thức accept() của Element.

Ví dụ sử dụng Visitor Pattern

Visitor.java


package com.gpcoder.patterns.behavioral.visitor.doubledispatch;

public interface Visitor {

	void visit(BusinessBook book);

	void visit(DesignPatternBook book);

	void visit(JavaCoreBook book);
}

VisitorImpl.java


package com.gpcoder.patterns.behavioral.visitor.doubledispatch;

public class VisitorImpl implements Visitor {

	@Override
	public void visit(BusinessBook a) {
		System.out.println(a.getPublisher());
	}

	@Override
	public void visit(DesignPatternBook w) {
		System.out.println(w.getBestSeller());
	}

	@Override
	public void visit(JavaCoreBook g) {
		System.out.println(g.getFavouriteBook());
	}
}

Book.java


package com.gpcoder.patterns.behavioral.visitor.doubledispatch;

public interface Book {
	void accept(Visitor v);
}

BusinessBook.java


package com.gpcoder.patterns.behavioral.visitor.doubledispatch;

public class BusinessBook implements Book {
	public void accept(Visitor v) {
		v.visit(this);
	}

	public String getPublisher() {
		return "The publisher of business book";
	}
}

ProgramingBook.java


package com.gpcoder.patterns.behavioral.visitor.doubledispatch;

public interface ProgramingBook extends Book {

	String getResource();
}

DesignPatternBook.java


package com.gpcoder.patterns.behavioral.visitor.doubledispatch;

public class DesignPatternBook implements ProgramingBook {

	@Override
	public void accept(Visitor v) {
		v.visit(this);
	}

	@Override
	public String getResource() {
		return "https://github.com/gpcodervn/Design-Pattern-Tutorial/";
	}

	public String getBestSeller() {
		return "The best Seller of design pattern book";
	}
}

JavaCoreBook.java


package com.gpcoder.patterns.behavioral.visitor.doubledispatch;

public class JavaCoreBook implements ProgramingBook {

	@Override
	public void accept(Visitor v) {
		v.visit(this);
	}

	@Override
	public String getResource() {
		return "https://github.com/gpcodervn/Java-Tutorial/";
	}

	public String getFavouriteBook() {
		return "The most favourite book of java core";
	}
}

VisitorPatternExample.java


package com.gpcoder.patterns.behavioral.visitor.doubledispatch;

public class VisitorPatternExample {

	public static void main(String[] args) throws Exception {
		Book book1 = new BusinessBook();
		Book book2 = new JavaCoreBook();
		Book book3 = new DesignPatternBook();

		Visitor v = new VisitorImpl();
		book1.accept(v);
		book2.accept(v);
		book3.accept(v);
	}
}

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


The publisher of business book
The most favourite book of java core
The best Seller of design pattern book

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

  • Cho phép một hoặc nhiều hành vi được áp dụng cho một tập hợp các đối tượng tại thời điểm run-time, tách rời các hành vi khỏi cấu trúc đối tượng.
  • Đảm bảo nguyên tắc Open/ Close: đối tượng gốc không bị thay đổi, dễ dàng thêm hành vi mới cho đối tượng thông qua visitor.

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

  • Khi có một cấu trúc đối tượng phức tạp với nhiều class và interface. Người dùng cần thực hiện một số hành vi cụ thể của riêng đối tượng, tùy thuộc vào concrete class của chúng.
  • Khi chúng ta phải thực hiện một thao tác trên một nhóm các loại đối tượng tương tự. Chúng ta có thể di chuyển logic hành vi từ các đối tượng sang một lớp khác.
  • Khi cấu trúc dữ liệu của đối tượng ít khi thay đổi nhưng hành vi của chúng được thay đổi thường xuyên.
  • Khi muốn tránh sử dụng toán tử instanceof.

Giới hạn của Visitor Pattern và Áp dụng Reflection trong Visitor Pattern

Hạn chế lớn nhất của Visitor Pattern là chúng ta cần phải biết kiểu trả về của phương thức visit() tại thời điểm thiết kế nếu không chúng ta phải thay đổi interface và tất cả các cài đặt của nó. Như trong ví dụ trên, nếu chúng ta muốn thêm một loại sách khác (chẳng hạn OthersBook), chúng ta phải thêm phương thức visit(OthersBook) trong interface và sửa đổi tất cả các cài đặt tương ứng của Visitor.

Để giải quyết vấn đề này, chúng ta có thể sử dụng kỹ thuật Reflection để xác định chính xác concrete class được gọi tại thời điểm run-time thay vì compile-time. Bằng cách này, chúng ta có thể sử dụng tham số là super class thay vì concrete class. Hãy xem ví dụ bên dưới, các lớp mới nếu không có phương thức cài đặt riêng visit(), thì nó sẽ sử dụng hàm mặc định, chúng ta không cần thêm phương thức visit() mới và tránh sửa đổi tất cả các concrete visitor đã tồn tại.

Thêm một loại sách mới OthersBook.java


package com.gpcoder.patterns.behavioral.visitor.doubledispatch.reflection;

public class OthersBook implements ProgramingBook {

	@Override
	public void accept(Visitor v) {
		v.visit(this);
	}

	@Override
	public String getResource() {
		return "Undefined";
	}
}

Thay đổi Visitor.java


package com.gpcoder.patterns.behavioral.visitor.doubledispatch.reflection;

import java.lang.reflect.Method;

public abstract class Visitor {
	public abstract void visit(Book book);

	protected Method getMethod(Class<?> clazz) {
		while (!clazz.equals(Object.class)) { // Check superclasses
			try {
				return this.getClass().getDeclaredMethod("visit", clazz);
			} catch (NoSuchMethodException ex) {
				clazz = clazz.getSuperclass();
			}
		}
		Class<?>[] interfaces = clazz.getInterfaces(); // Check interfaces
		for (Class<?> anInterface : interfaces) {
			try {
				return this.getClass().getDeclaredMethod("visit", anInterface);
			} catch (NoSuchMethodException ex) {
				ex.printStackTrace();
			}
		}
		return null;
	}

	protected void defaultVisit(Book book) {
		System.out.println("A book");
	}
}

Thay đổi VisitorImpl.java


package com.gpcoder.patterns.behavioral.visitor.doubledispatch.reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class VisitorImpl extends Visitor {

	public void visit(Book book) {
		Method downPolymorphic = getMethod(book.getClass());
		if (downPolymorphic == null) {
			defaultVisit(book);
		} else {
			try {
				downPolymorphic.invoke(this, book);
			} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
				e.printStackTrace();
			}
		}
	}

	public void visit(BusinessBook a) {
		System.out.println(a.getPublisher());
	}

	public void visit(DesignPatternBook w) {
		System.out.println(w.getBestSeller());
	}

	public void visit(JavaCoreBook g) {
		System.out.println(g.getFavouriteBook());
	}
}

ReflectiveVisitorPatternExample.java


package com.gpcoder.patterns.behavioral.visitor.doubledispatch.reflection;

public class ReflectiveVisitorPatternExample {

	public static void main(String[] args) throws Exception {
		Book book1 = new BusinessBook();
		Book book2 = new JavaCoreBook();
		Book book3 = new DesignPatternBook();
		Book book4 = new OthersBook();

		Visitor v = new VisitorImpl();
		book1.accept(v);
		book2.accept(v);
		book3.accept(v);
		book4.accept(v);
	}
}

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


The publisher of business book
Effective Java
Head First Design Patterns
A book

Như bạn thấy, nếu chúng ta không xác định kiểu trả về của phương thức accept(OthersBook), nó sẽ gọi hàm mặc định defaultVisit(Book).

Tài liệu tham khảo:

  • https://sourcemaking.com/design_patterns/visitor
  • https://refactoring.guru/design-patterns/visitor
  • https://vi.wikipedia.org/wiki/Visitor_pattern
  • https://www.journaldev.com/1769/visitor-design-pattern-java
  • https://howtodoinjava.com/design-patterns/behavioral/visitor-design-pattern-example-tutorial/
  • https://www.baeldung.com/java-visitor-pattern
  • Design Patterns: Elements of Reusable Object-Oriented Software – GOF
5.0
18
Nếu bạn thấy hay thì hãy chia sẻ bài viết cho mọi người nhé! Và Donate tác giả

Shares

Chuyên mục: Behavior Pattern, Design pattern Được gắn thẻ: Behavior Pattern, Design pattern, Reflection

Hướng dẫn Java Design Pattern – Template Method
Hướng dẫn Java Design Pattern – Null Object

Có thể bạn muốn xem:

  • Hướng dẫn Java Design Pattern – Builder (26/09/2018)
  • Hướng dẫn Java Design Pattern – Observer (28/12/2018)
  • Hướng dẫn Java Design Pattern – Dependency Injection (28/01/2019)
  • Hướng dẫn Java Design Pattern – Decorator (08/11/2018)
  • Hướng dẫn Java Design Pattern – Intercepting Filter (24/02/2019)

Bình luận

bình luận

Tìm kiếm

Bài viết mới

  • Clean code 13/01/2024
  • Giới thiệu CloudAMQP – Một RabbitMQ server trên Cloud 02/10/2020
  • Kết nối RabbitMQ sử dụng Web STOMP Plugin 19/06/2020
  • Sử dụng publisher confirm trong RabbitMQ 16/06/2020
  • Sử dụng Dead Letter Exchange trong RabbitMQ 13/06/2020

Xem nhiều

  • Hướng dẫn Java Design Pattern – Factory Method (98056 lượt xem)
  • Hướng dẫn Java Design Pattern – Singleton (97696 lượt xem)
  • Giới thiệu Design Patterns (87759 lượt xem)
  • Lập trình đa luồng trong Java (Java Multi-threading) (86427 lượt xem)
  • Giới thiệu về Stream API trong Java 8 (83830 lượt xem)

Nội dung bài viết

  • 1 Single Dispatch và Double Dispatch là gì?
  • 2 Visitor Pattern là gì?
  • 3 Cài đặt Visitor Pattern như thế nào?
  • 4 Lợi ích của Visitor Pattern là gì?
  • 5 Sử dụng Visitor Pattern khi nào?
  • 6 Giới hạn của Visitor Pattern và Áp dụng Reflection trong Visitor Pattern

Lưu trữ

Thẻ đánh dấu

Annotation Authentication Basic Java Behavior Pattern Collection Creational Design Pattern Cấu trúc điều khiển Database Dependency Injection Design pattern Eclipse Exception Executor Service Google Guice Gson Hibernate How to Interceptor IO Jackson Java 8 Java Core JDBC JDK Jersey JMS JPA json JUnit JWT Message Queue Mockito Multithreading OOP PowerMockito RabbitMQ Reflection Report REST SOAP Structuaral Pattern Swagger Thread Pool Unit Test Webservice

Liên kết

  • Clean Code
  • JavaTpoint
  • Refactoring Guru
  • Source Making
  • TutorialsPoint
  • W3Schools Online Web Tutorials

Giới thiệu

GP Coder là trang web cá nhân, được thành lập với mục đích lưu trữ, chia sẽ kiến thức đã học và làm việc của tôi. Các bài viết trên trang này chủ yếu về ngôn ngữ Java và các công nghệ có liên quan đến Java như: Spring, JSF, Web Services, Unit Test, Hibernate, SQL, ...
Hi vọng góp được chút ít công sức cho sự phát triển cộng đồng Coder Việt.

Donate tác giả

Tìm kiếm các bài viết của GP Coder với Google Search

Liên hệ

Các bạn có thể liên hệ với tôi thông qua:
  • Trang liên hệ
  • Linkedin: gpcoder
  • Email: contact@gpcoder.com
  • Skype: ptgiang56it

Follow me

Copyright 2025 © GP Coder · All Rights Reserved · Giới thiệu · Chính sách · Điều khoản · Liên hệ ·

Share

Blogger
Delicious
Digg
Email
Facebook
Facebook messenger
Flipboard
Google
Hacker News
Line
LinkedIn
Mastodon
Mix
Odnoklassniki
PDF
Pinterest
Pocket
Print
Reddit
Renren
Short link
SMS
Skype
Telegram
Tumblr
Twitter
VKontakte
wechat
Weibo
WhatsApp
X
Xing
Yahoo! Mail

Copy short link

Copy link