Search…

Design Pattern: Proxy Pattern

27/08/20205 min read
Tổng quan về Proxy Pattern và hiện thực Proxy Pattern dựa trên ngôn ngữ C/C++.

Bài viết cung cấp cái nhìn tổng quan về Proxy Pattern, các trường hợp thường sử dụng và hiện thực Proxy Pattern dựa trên ngôn ngữ C/C++.

Proxy Design Pattern là gì?

Proxy Pattern là một những mẫu thiết kế thuộc nhóm Structural design patterns.

Proxy là một đối tượng sẽ đại diện cho một đối tượng khác.

Tùy theo yêu cầu bài toán mà áp dụng Proxy một cách linh hoạt, với mục đích:

  • Kiểm soát quyền truy xuất các phương thức của đối tượng.
  • Bổ sung thêm chức năng trước khi thực thi phương thức.
  • Tạo ra đối tượng mới có chức năng nâng cao hơn đối tượng ban đầu.
  • Giảm chi phí khi có nhiều truy cập vào đối tượng có chi phí khởi tạo ban đầu lớn.

Kiến trúc Proxy Pattern

Kiến trúc Proxy Pattern

Proxy Pattern gồm 3 phần chính:

  • Interface (hoặc abstract class) ISubject: gọi thực hiện các phương thức từ Client. 
  • Lớp CRealSubject: định nghĩa các phương thức để thực thi.
  • Lớp CSubjectProxy: thực hiện các thao tác như kiểm tra, thêm tính năng trước khi gọi lớp CRealSubject để thực thi phương thức.

Các loại Proxy Pattern và trường hợp áp dụng

  • Virtual Proxy: khi có nhiều lượt truy xuất vào một đối tượng cấu trúc phức tạp, chứa dữ liệu lớn (hình ảnh, video,..), cần tạo ra một Proxy để đại diện cho đối tượng đó. Đối tượng chỉ được tạo ở lần truy xuất đầu tiên, những lần tiếp theo chỉ cần tái sử dụng lại mà không cần khởi tạo, tránh trường hợp sao chép ra nhiều đối tượng, tiết kiệm tài nguyên.
  • Protection Proxy: muốn kiểm tra một yêu cầu có quyền truy cập vào một nội dung hay không.
  • Remote Proxy: muốn thực thi một phương thức của đối tượng đang tồn tại khác vùng địa chỉ hoặc ở máy tính khác.

Virtual Proxy: Giảm chi phí khởi tạo nhiều đối tượng

Virtual Proxy

Hiện thực

#include <stdio.h>

// abstract class 
class Image
{
public:
	virtual void displayImage() = 0;
};

// real subject
class CRealImage : public Image
{
private:
	char* m_path;

public:
	CRealImage(char* path)
	{
		m_path = path;
		loadImage();
	}

	~CRealImage()
	{
		delete m_path;
	}

	void displayImage()
	{
		printf("Display image! %s\n", m_path);
	}

	void loadImage()
	{
		printf("Load image! %s\n", m_path);
	}

};

// subject proxy
class CImageVirtualProxy : public Image
{
private:
	CRealImage* m_realImage;
	char* m_path;

public:
	CImageVirtualProxy(char* path)
	{
		m_path = path;
		m_realImage = NULL;
	}

	~CImageVirtualProxy()
	{
		delete m_path, m_realImage;
	};

	void displayImage()
	{
		if (m_realImage == NULL)
			m_realImage = new CRealImage(m_path);

		m_realImage->displayImage();
	}
};

int main()
{
	// không sử dụng proxy
	Image* img1 = new CRealImage("sample/veryLargeImage1.png");
	Image* img2 = new CRealImage("sample/veryLargeImage2.png");
	Image* img3 = new CRealImage("sample/veryLargeImage3.png");

	img1->displayImage();

	printf("\n");
    
	// sử dụng proxy tiết kiệm chi phí
	Image* imgProxy1 = new CImageVirtualProxy("sample/veryLargeImage1.png");
	Image* imgProxy2 = new CImageVirtualProxy("sample/veryLargeImage2.png");
	Image* imgProxy3 = new CImageVirtualProxy("sample/veryLargeImage3.png");

	imgProxy1->displayImage();
	imgProxy1->displayImage();

	return 0;
}

Output:

Load image! sample/veryLargeImage1.png
Load image! sample/veryLargeImage2.png
Load image! sample/veryLargeImage3.png
Display image! sample/veryLargeImage1.png

Load image! sample/veryLargeImage1.png
Display image! sample/veryLargeImage1.png
Display image! sample/veryLargeImage1.png

Theo cách không áp dụng Proxy, đối tượng sẽ tự động gọi phương thức loadImage() khi tạo mới một đối tượng, tiêu tốn tài nguyên không cần thiết cho dù sau này có thực thi phương thức displayImage() hay không.

Áp dụng Virtual Proxy để tiết kiệm tài nguyên, khi thực hiện displayImage() sẽ tự động thực thi phương thức loadImage() trước và chỉ load một lần cho lời gọi displayImage() đầu tiên. 

Có thể định nghĩa một phương thức loadImage() tách biệt, khi khởi tạo đối tượng không cần thực hiện loadImage(), phương thức loadImage() khi định nhĩa có quyền truy cập là public để chúng ta gọi bất kỳ nơi đâu trước khi thực thi phương thức displayImage(). Nhưng có điều bất lợi là phải cố gắng nhớ thực hiện việc loadImage() trước khi thực thi phương thức displayImage(), gây ra nhiều sai sót khi thao tác trên nhiều đối tượng ở nhiều file khác nhau.

Trong trường hợp này, việc áp dụng Virtual Proxy an toàn và dễ kiểm soát hơn.

Protection Proxy: Quản lý quyền tải tập tin

Hiện thực

#include <stdio.h>

enum MEMBERSHIP_TYPE {
	VIP,
	FREE,
	FREEDOM
};

// abstract class
class ActionAccount
{
public:
	virtual void downMaxSpeed() = 0;
	virtual void downLimitSpeed() = 0;
};

// real subject
class CFileAccount : public ActionAccount
{
private:
	char*			m_userName;
	MEMBERSHIP_TYPE		m_membership_type;

public:
	CFileAccount(char* userName, MEMBERSHIP_TYPE membership_type = FREEDOM)
	{
		m_userName = userName;
		m_membership_type = membership_type;
	}

	~CFileAccount()
	{
		delete m_userName;
	}

	MEMBERSHIP_TYPE getMembershipType()
	{
		return m_membership_type;
	}

	void downMaxSpeed()
	{
		printf("%s :: download file MAX SPEED success!\n", m_userName);
	}

	void downLimitSpeed()
	{
		printf("%s :: download file LIMIT SPEED success!\n", m_userName);
	}

};

// subject proxy
class CFileAccountProxy : public ActionAccount
{
private:
	CFileAccount*		m_fileAccount;
	MEMBERSHIP_TYPE		m_membership_type;
	char*				m_userName;

public:
	CFileAccountProxy(char* userName, MEMBERSHIP_TYPE membership_type)
	{
		m_fileAccount = NULL;
		m_membership_type = membership_type;
		m_userName = userName;
	}

	~CFileAccountProxy()
	{
		delete m_fileAccount, m_userName;
	}

	void downMaxSpeed()
	{
		if (m_fileAccount == NULL)
			m_fileAccount = new CFileAccount(m_userName, m_membership_type);

		if (m_fileAccount->getMembershipType() == VIP)
			m_fileAccount->downMaxSpeed();
		else
			printf("%s :: download fail!\n", m_userName);
	}

	void downLimitSpeed()
	{
		if (m_fileAccount == NULL)
			m_fileAccount = new CFileAccount(m_userName, m_membership_type);

		// VIP member is king
		if (m_fileAccount->getMembershipType() == VIP)
		{
			m_fileAccount->downMaxSpeed();
			return;
		}

		if (m_fileAccount->getMembershipType() == FREE)
			m_fileAccount->downLimitSpeed();
		else
			printf("%s :: download fail!\n", m_userName);
	}
};

int main()
{
	// không áp dụng proxy
	ActionAccount* actionAccount = new CFileAccount("FREEDOM");
	actionAccount->downMaxSpeed();

	// sử dụng proxy giới hạn quyền truy cập
	ActionAccount* actionAccountProxyVip = new CFileAccountProxy("STDIO Training", VIP);
	actionAccountProxyVip->downMaxSpeed();

	ActionAccount* actionAccountProxyFree = new CFileAccountProxy("OTHER Training", FREE);
	actionAccountProxyFree->downMaxSpeed();

	return 0;
}

Output:

FREEDOM :: download file MAX SPEED success!
STDIO Training :: download file MAX SPEED success!
OTHER Training :: download fail!

Áp dụng Protection Proxy giúp dễ dàng kiểm soát và tùy biến lại quyền truy xuất để thực thi phương thức của một đối tượng.

Thông thường, có thể đưa công đoạn kiểm tra vào chính lớp CFileAccount nhưng điều đó không hiệu quả cho việc quản lý và tái sử dụng khi phát triển phần mềm quy mô lớn gồm nhiều đối tượng và chức năng.

Việc đưa các mã kiểm tra vào Proxy giúp trực quan, mỗi lớp đều đảm nhận một nhiệm vụ riêng biệt.

  • Lớp CFileAccount chỉ lưu thông tin và định nghĩa các hành động của một tài khoản.
  • Lớp CFileAccountProxy đảm nhận công việc quản lý quyền tải file của một tài khoản.
IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.co

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024