12/08/2018, 16:08

Mô hình hoá "State" trong swift

Một trong những bước khó nhất khi xây dựng các ứng dụng và thiết kễ hệ thống đó là việc quyết định mô hình hoá và xử lý các state như thế nào . Việc quản lý đoạn code liên quan đến state rất hay xảy ra lỗi , khi 1 phần ứng dụng của chúng ta có thể kết thúc với state mà ta không mong muốn. Ở bài ...

Một trong những bước khó nhất khi xây dựng các ứng dụng và thiết kễ hệ thống đó là việc quyết định mô hình hoá và xử lý các state như thế nào . Việc quản lý đoạn code liên quan đến state rất hay xảy ra lỗi , khi 1 phần ứng dụng của chúng ta có thể kết thúc với state mà ta không mong muốn.

Ở bài viết này, tôi sẽ chỉ cho bạn một số kỹ thuật giúp bạn dễ dàng viết code hơn và ít lỗi hơn khi phải đối mặt với việc mô hình hoá các state .

Một Nguồn Chuẩn Duy Nhất

Một nguyên tắc cốt lõi tốt nhất để nhớ khi mô hình hoá các state khác nhau là cố gắng bám lấy một “nguồn chuẩn duy nhất” càng chặt càng tốt . Một cách để làm điều này là bạn đừng bao giờ kiểm tra nhiều điều kiện để xác định là bạn đang ở state nào . Ví dụ :

Giả sử chúng ta đang xây dựng một game , ở đó kẻ thù có 1 sức khoẻ nhất định và 1 lá cờ để xác định xem nó có còn sống hay không , Chúng ta có thể mô hình hoá bằng việc sử dụng hai thuộc tính trên một lớp Enemy, như sau: :

class Enemy {
    var health = 10
    var isInPlay = false
}

Tuy nhiên nếu chúng ta rơi vào tình huống khi sức khoẻ của kẻ thù còn lại là 0 , kẻ thù phải biến mất khỏi trò chơi , vì vậy 1 nơi nào đó trong đoạn code của chúng ta có 1 đoạn xử lý logic :

func enemyDidTakeDamage() {
    if enemy.health <= 0 {
        enemy.isInPlay = false
    }
}

Vấn đề xảy ra khi bây giờ tôi muốn thêm 1 đoạn code mới nhưng quên không check đoạn code như bên trên . Ví dụ ,tôi cung cấp cho người chơi một cuộc tấn công đặc biệt, tiêu diệt toàn bộ kẻ thù :

func performSpecialAttack() {
    for enemy in allEnemies {
        enemy.health = 0
    }
}

Như bạn thấy đấy , tôi mới chỉ cập nhật sức khoẻ , nhưng quên chưa cập nhật isInPlay . Điều này có thể sẽ gây ra lỗi và tình huống kết thúc của tôi rơi vào trạng thái không xác định .

Ở tình huống trên ta có thể khắc phục bằng cách multi check như sau :

if enemy.isInPlay && enemy.health > 0 {
    // Enemy is *really* in play
} else {
    // Enemy is *really* defeated
}

Mặc dù đoạn code trên hoạt động bình thường , nhưng nó sẽ gây khó khăc trong việc đọc code logic và dễ dàng bị phá vỡ bất cứ khi nào nếu như tôi thêm một số điều kiện phức tạp hơn .

Một cách để giải quyết vấn đề này và để đảm bảo rằng chúng ta có một nguồn chuẩn duy nhất là tự động cập nhật thuộc tính isInPlay bên trong lớp Enemy, sử dụng một didSet trên thuộc tính health:

class Enemy {
    var health = 10 {
        didSet { putOutOfPlayIfNeeded() }
    }
    private(set) var isInPlay = true

    private func putOutOfPlayIfNeeded() {
        guard health <= 0 else {
            return
        }

        isInPlay = false
        remove()
    }
}

Với cách trên , chúng ta chỉ cần quan tâm đến sức khoẻ của kẻ thù , còn thuộc tính isInPlay sẽ luôn được cập nhật trực tiếp qua thuộc tính health

0