Proxy Pattern là một trong những Design Pattern thường được áp dụng khi phát triển phần mềm cũng như thiết kế game, là một mẫu thiết kế thuộc nhóm Structural patterns. Proxy Pattern là một đối tượng được sử dụng để đại diện cho một đối tượng khác thực thi các phương thức, phương thức đó có thể được định nghĩa lại cho phù hợp với múc đích sử dụng.
Design Pattern C/C++ Huỳnh Minh Tân 2017-11-02 13:49:37

Giới thiệu

Proxy Pattern là một trong những Design Pattern phổ biến trong lập trình hướng đối tượng, thường được ứng dụng nhiều trong phát triển phần mềm cũng như game. Bài viết sẽ cung cấp một cái nhìn tổng quan về Proxy Pattern, các trường hợp thường sử dụng và đi đến hiện thực Proxy Pattern dựa trên ngôn ngữ C/C++.

Tiền đề bài viết

Trong quá trình học tập và củng cố lại kiến thức cho bản thân khi tự tìm hiểu các mẫu thiêt kế Design Pattern để ứng dụng vào phát triển game. Là nguồn tài liệu tham khảo cho những lập trình viên đang gặp khó khăn khi thấu hiểu Proxy Pattern.

Đối tượng hướng đến

Yêu cầu phải có kiến thức lập trình hướng đối tượng OOP.

Proxy Design Pattern là gì?

Proxy Pattern là một những mẫu thiết kế thuộc nhóm Structural Patterns. Proxy có thể hiểu 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à chúng ta á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ổ xung 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

figure_proxy_design_pattern

Architectural patterns.

Thông thường một Proxy Pattern sẽ 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 sẽ định nghĩa các phương thức để thực thi.
  • Lớp CSubjectProxy sẽ 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 trường hợp thường áp dụng Proxy Pattern

  • 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,..) trường hợp này chúng ta sẽ 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 sau đó những lần truy xuất 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, vì vậy sẽ tiếp kiệm được nhiều 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 nào đó hay không.
  • Remote Proxy: trường hợp bạn 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.

Ví dụ minh họa Virtual Proxy

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

example_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ếp 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();

	while (1);
}

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

Chúng ta có thể nhận thấy, theo cách lập trình thông thường không áp dụng Proxy đối tượng sẽ tự động gọi phương thức loadImage() ngay khi chúng ta tạo mới một đối tượng, việc đó sẽ làm tiêu tốn rất nhiều 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. Chúng ta sẽ áp dụng Virtual Proxy để tiếp kiệm tài nguyên, khi thực hiện việc 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. 

Tất nhiên, bạn cũng có thể định nghĩa một phương thức loadImage() tách biệt, khi khởi tạo đối tượng thì không cần thưc hiện load image, 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, chúng ta phải cố gắng nhớ thực hiện việc loadImage() trước khi thực thi phương thức displayImage(), điều này sẽ gây ra nhiều sai sót khi chúng ta thao tác trên nhiều đối tượng ở nhiều file khác nhau. Sẽ rất an toàn và dễ kiểm soát khi chúng ta áp dụng Virtual Proxy trong trường hợp này.

Ví dụ minh họa Protection Proxy

Quản lý quyền tải tập tin.

example_protection_proxy

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();

	while (1);
	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 ta 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. Cách thông thường, các bạn có thể đưa công đoạn kiểm tra vào chính lớp CFileAccount nhưng điều đó thật sự 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 lớn có nhiều đối tượng cùng với nhiều chức năng. Việc đưa các mã kiểm tra vào Proxy sẽ giúp chúng ta có cái nhìn 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.

Tổng kết

Việc hiện thực Proxy Pattern cũng không quá phức tạp nhưng điểm chính là chúng ta phải biết cách vận dụng hợp lý để giải quyết vấn đề một cách linh hoạt. Bài viết đã giới thiệu về Proxy Pattern, các trường hợp thường sử dụng cùng mới những minh họa cụ thể, tôi hy vọng rằng qua bài này sẽ giúp ích cho các bạn đang gặp khó khăn khi tìm hiểu Proxy cũng như các mẫu Design Pattern khác.