Proxy Pattern - Facade Pattern by Dan Tech
Proxy Pattern - Facade Pattern by Dan Tech

Học Design Pattern: Proxy Pattern – Người Đại Diện Hợp Pháp

Proxy Pattern mang đến một góc nhìn mới trong thiết kế kiến trúc phần mềm, đó là việc Ủy quyền xử lý. Thay vì các logic nghiệp vụ được gọi trực tiếp mỗi khi cần, chúng ta sẽ đặt một lớp trung gian (Proxy) vào giữa để đại diện cho các xử lý quan trọng từ phía Caller.

Lớp Proxy này sẽ đóng vai trò như một Người gác cổng, quyết định xem có nên cho phép lời gọi được thực hiện tiếp hay không, hoặc tạm thời HOLD lệnh chờ xử lý, hoặc thậm chí tự mình thực hiện logic và trả về kết quả ngay lập tức, mà không cần truy cập vào nguồn dữ liệu gốc.

Quyết định này hoàn toàn phụ thuộc vào bài toán thiết kế chịu tải của phần mềm và các yêu cầu cụ thể về tốc độ phản hồi của từng Module / Feature.

Bạn đừng nhầm tưởng rằng chỉ có code ở Backend, hay trong các kiến trúc Multi-Services người ta mới cần đền Proxy. Proxy Pattern là 1 tư duy phần mềm, ta sẽ cần dùng nó mỗi khi cần cho dù là Web, Mobile hay Backend, Data, … miễn là nó đáp ứng được các đặc tả của Phần mềm / tính năng đang xây dựng.

Mô Tả Proxy Pattern Với Kotlin

// 1. Define the Subject Interface
interface Database {
    fun getData(id: Int): String?
    fun saveData(id: Int, data: String): Boolean
}

// 2. The Real Subject: The Actual Database Implementation
class RealDatabase : Database {
    init {
        println("RealDatabase: Initializing database connection...")
        // Simulate a costly initialization (e.g., connection setup)
        Thread.sleep(1000)
        println("RealDatabase: Database connection established.")
    }

    override fun getData(id: Int): String? {
        println("RealDatabase: Reading data with ID: $id")
        // Simulate database read operation
        return "Data for ID $id from the database."
    }

    override fun saveData(id: Int, data: String): Boolean {
        println("RealDatabase: Saving data: $data with ID: $id")
        // Simulate database write operation
        return true
    }
}

// 3. The Proxy: Manages Access to the Real Database
class DatabaseProxy(private val userId: String) : Database {
    private var realDatabase: RealDatabase? = null
    private val cache = mutableMapOf<Int, String>()

    private fun getRealDatabase(): RealDatabase {
        if (realDatabase == null) {
            realDatabase = RealDatabase() // Lazy initialization
        }
        return realDatabase!!
    }

    override fun getData(id: Int): String? {
        if (cache.containsKey(id)) {
            println("DatabaseProxy: Returning data for ID $id from cache.")
            return cache[id]
        }

        if (!authorize(userId, "READ")) {
            println("DatabaseProxy: User $userId is not authorized to read data.")
            return null
        }

        val data = getRealDatabase().getData(id)
        data?.let { cache[id] = it }  // Cache the data
        logAccess("READ", id)
        return data
    }

    override fun saveData(id: Int, data: String): Boolean {
        if (!authorize(userId, "WRITE")) {
            println("DatabaseProxy: User $userId is not authorized to write data.")
            return false
        }

        val success = getRealDatabase().saveData(id, data)
        if (success) {
            cache[id] = data // Update the cache
        }
        logAccess("WRITE", id)
        return success
    }

    private fun authorize(userId: String, permission: String): Boolean {
        // Simulate authorization logic (e.g., check user roles)
        return userId != "guest" || permission != "WRITE"  // Guests can't write
    }

    private fun logAccess(operation: String, id: Int) {
        println("DatabaseProxy: $operation operation on data with ID $id at ${LocalDateTime.now()}")
        // Simulate logging (e.g., write to a file)
    }
}

// 4. The Client
fun main() {
    val user1 = DatabaseProxy("admin")
    val user2 = DatabaseProxy("guest")

    println("\n--- User 1 (Admin) Actions ---")
    user1.saveData(1, "Important Data")
    println("Data read: ${user1.getData(1)}")
    println("Data read again: ${user1.getData(1)}") // From cache

    println("\n--- User 2 (Guest) Actions ---")
    user2.saveData(2, "Attempted Guest Write") // Authorization failure
    println("Data read: ${user2.getData(2)}")
}

So Sánh Proxy Pattern với Adapter, Facade, hay Decorator

Phần này quan trọng. Anh em dev chúng ta nên đọc và ghi nhớ để có dịp mang tra đối đáp trong phỏng vấn nha.

XProxyFacadeAdapterDecorator
OwnerModule / Provider / Library / ServerModule / Provider / Library / ServerCaller / ClientCaller / Client
IntentionTo modify the response behaviour when receive a request. Should be 1-1 mapping with the Service it stands for.To provide a simple inteface for Caller to use. Can mix-up with multiple Controller / Data SourceTo wrap the existing Code / Service that incompatible with the current codebase. To use it properly for the project.To extend the use of existed Class without modifying it.

Đọc đến đây là ok rồi! @dantech chúc anh em thành công!