Những Design pattern thú vị trong Java | Part 3

Tiếp tục chủ đề về Design Pattern trong Java, thiết nghĩ rằng đây là nên tảng tốt cho sự phát triển skill sau này của mỗi developer nên mình tiếp tục bàn luận và đưa ra thêm một số điều mới mẻ hơn. 1. Prototype Pattern 2. Decorator Pattern 3. Memento Pattern Trong bài viết này mình để Memento Pattern ở nội dung cuối các bạn theo đọc đến cuối bài sẽ có những phần thú vị kèm theo đó, đừng bỏ sót nhé ! 😄

Prototype Pattern

Có một lúc nào đó trong quá trình phát triển ứng dụng của bạn mà cần phải liên tục tạo ra các object không ? Và bài toán lúc này là tiết kiệm tài nguyên một cách tối đa, performance sẽ thành vấn đề quan tâm lớn. Giải pháp tốt hơn việc khởi tạo ra một object mới đó là cloning từ một object trước đó. Prototype Pattern : nó cho phép một object tạo ra những customized object mà không cần biết class của nó và bất kỳ chi tiết gì làm thế nào để tạo ra chúng. Mẫu thiết kế này sử dụng java cloning để copy một object. Giải pháp: Quy định cụ thể những loại object muốn tạo ra bằng cách sử dụng một prototypical instance tạo ra các object mới bằng cách sao chép chính nó. Ví dụ: Một bài toán thực tế áp dụng để có cái nhìn rõ nét hơn các bạn nhé, giả sử có một Object được load từ database, chúng ta cần chỉnh sửa object này nhiều lần. Thực sự là không tốt nếu tạo một object mới bằng key-value sau đó load lại data một lần nữa. Giải quyết vấn đề này mình sẽ copy object từ object có trước sau đó thao tác data cần thiết. Triển khai: Employees.java

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

public class Employees implements Cloneable{

	private List<String> empList;
	
	public Employees(){
		empList = new ArrayList<String>();
	}
	
	public Employees(List<String> list){
		this.empList=list;
	}
	public void loadData(){
		//read all employees from database and put into the list
		empList.add("Pankaj");
		empList.add("Raj");
		empList.add("David");
		empList.add("Lisa");
	}
	
	public List<String> getEmpList() {
		return empList;
	}

	@Override
	public Object clone() throws CloneNotSupportedException{
			List<String> temp = new ArrayList<String>();
			for(String s : this.getEmpList()){
				temp.add(s);
			}
			return new Employees(temp);
	}
	
}

***Nhận thấy method clone dùng để copy nguyên list Employees.***Tiếp theo chúng ta sẽ viết class áp dụng mẫu thiết kế này: PrototypePatternTest.java

import java.util.List;

import com.journaldev.design.prototype.Employees;

public class PrototypePatternTest {

	public static void main(String[] args) throws CloneNotSupportedException {
		Employees emps = new Employees();
		emps.loadData();
		
		//Use the clone method to get the Employee object
		Employees empsNew = (Employees) emps.clone();
		Employees empsNew1 = (Employees) emps.clone();
		List<String> list = empsNew.getEmpList();
		list.add("John");
		List<String> list1 = empsNew1.getEmpList();
		list1.remove("Pankaj");
		
		System.out.println("emps List: "+emps.getEmpList());
		System.out.println("empsNew List: "+list);
		System.out.println("empsNew1 List: "+list1);
	}

}

Output:

emps HashMap: [Pankaj, Raj, David, Lisa]
empsNew HashMap: [Pankaj, Raj, David, Lisa, John]
empsNew1 HashMap: [Raj, David, Lisa]

Decorator Pattern

Được dùng để thay đổi chức năng của Object lúc runtime mà không làm ảnh hưởng tới cấu trúc của nó. Vì nằm trong nhóm structural design pattern nên cách triển khai sẽ có phần giống như một số mẫu thiết kế khác mà chúng ta đã làm là dùng abstract class hoặc interface để triển khai. Vì khái niệm khá đơn giản nên mình sẽ đi vào triển khai luôn qua 1 ví dụ từ hình ảnh trên nhé. Giả sử chúng ta muốn tạo ra những loại xe khác nhau, đầu tiên sẽ có interface Car sau đó có Basic Car để có những đặc tính cơ sở của một chiếc xe hơi, tiếp theo là những loại xe chúng ta muốn có. Mọi chuyện thật dễ dàng nếu như chúng ta chỉ làm một lần và không có một yêu cầu thay đổi nào. Cho đến khi mong muốn từ phía khách hàng vừa muốn một chiếc xe mang phong cách sport lại muốn nó thật sang trọng, rồi đến khi quy trình lắp dáp muốn rằng phải đưa tính sang trọng vào đầu tiên sau đó mới là sport , bla bla... Khi đấy bạn biết rằng đã phức tạp hơn yêu cầu ban đầu rất nhiều phải không nào 😄 Tất nhiên rồi, sẽ có cách để làm và câu trả lời các bạn cũng biết đó là Decorator Pattern Triển khai: Sơ đồ tổng thể Car.java component chung cho những loại xe chúng ta cần có.

public interface Car {

	public void assemble();
}

BasicCar.java

public class BasicCar implements Car {

	@Override
	public void assemble() {
		System.out.print("Basic Car.");
	}

}

CarDecorator.java

public class CarDecorator implements Car {

	protected Car car;
	
	public CarDecorator(Car c){
		this.car=c;
	}
	
	@Override
	public void assemble() {
		this.car.assemble();
	}

}

SportsCar.java

public class SportsCar extends CarDecorator {

	public SportsCar(Car c) {
		super(c);
	}

	@Override
	public void assemble(){
		super.assemble();
		System.out.print(" Adding features of Sports Car.");
	}
}

LuxuryCar.java

public class LuxuryCar extends CarDecorator {

	public LuxuryCar(Car c) {
		super(c);
	}
	
	@Override
	public void assemble(){
		super.assemble();
		System.out.print(" Adding features of Luxury Car.");
	}
}

DecoratorPatternTest.java chương trình chạy áp dụng

public class DecoratorPatternTest {

	public static void main(String[] args) {
		Car sportsCar = new SportsCar(new BasicCar());
		sportsCar.assemble();
		System.out.println("\n*****");
		
		Car sportsLuxuryCar = new SportsCar(new LuxuryCar(new BasicCar()));
		sportsLuxuryCar.assemble();
	}
}

Output:

Basic Car. Adding features of Sports Car.
*****
Basic Car. Adding features of Luxury Car. Adding features of Sports Car.

Memento Pattern

Sử dụng khi muốn save trạng thái của một object sau đó có thể dễ dàng khôi phục lại, bảo vệ tính toàn vẹn của object. Giải pháp: Memento sẽ thực thi với 2 object là OriginatorCaretaker Originator khởi tạo đối tượng cần được save và restore trạng thái. Trong đó có một Memento class inner là private nên không thể truy cập từ class bên ngoài. Caretaker là class helper lưu trữ và khôi phục lại Originator thông qua Memento object.

Triển khai: Một ví dụ mà rất hay gặp với Text là save lại trạng thái sau khi input và undo lại trạng thái trước lúc save. FileWriterUtil.java Originator object - tạo ra method save và undo , có Memento inner class.

public class FileWriterUtil {

	private String fileName;
	private StringBuilder content;
	
	public FileWriterUtil(String file){
		this.fileName=file;
		this.content=new StringBuilder();
	}
	
	@Override
	public String toString(){
		return this.content.toString();
	}
	
	public void write(String str){
		content.append(str);
	}
	
	public Memento save(){
		return new Memento(this.fileName,this.content);
	}
	
	public void undoToLastSave(Object obj){
		Memento memento = (Memento) obj;
		this.fileName= memento.fileName;
		this.content=memento.content;
	}
	
	
	private class Memento{
		private String fileName;
		private StringBuilder content;
		
		public Memento(String file, StringBuilder content){
			this.fileName=file;
			//notice the deep copy so that Memento and FileWriterUtil content variables don't refer to same object
			this.content=new StringBuilder(content);
		}
	}
}

FileWriterCaretaker.java Caretaker object - thực thi save - undo object mà không thay đổi dữ liệu và cũng không biết tới cấu trúc của object đó là gì.

public class FileWriterCaretaker {

	private Object obj;
	
	public void save(FileWriterUtil fileWriter){
		this.obj=fileWriter.save();
	}
	
	public void undo(FileWriterUtil fileWriter){
		fileWriter.undoToLastSave(obj);
	}
}

FileWriterClient.java chương trình chạy

public class FileWriterClient {

	public static void main(String[] args) {
		
		FileWriterCaretaker caretaker = new FileWriterCaretaker();
		
		FileWriterUtil fileWriter = new FileWriterUtil("data.txt");
		fileWriter.write("First Set of Data\n");
		System.out.println(fileWriter+"\n\n");
		
		// lets save the file
		caretaker.save(fileWriter);
		//now write something else
		fileWriter.write("Second Set of Data\n");
		
		//checking file contents
		System.out.println(fileWriter+"\n\n");

		//lets undo to last save
		caretaker.undo(fileWriter);
		
		//checking file content again
		System.out.println(fileWriter+"\n\n");
		
	}

}

Ouput:

First Set of Data

First Set of Data
Second Set of Data

First Set of Data

Tổng kết:

Trên đây là những Design Pattern thú vị mà mình muốn gửi tới các bạn yêu thích lập trình hoặc đang trong quá trình học bước đầu để nâng cao kĩ năng, mong rằng bạn sẽ tìm thấy điều gì đó hữu ích từ bài viết này. ^_^ Chúc các bạn ngày càng pro hơn nhé ! 😄


All Rights Reserved