Enums không có enum trong PHP


Enums vẫn còn thiếu PHP – TRƯỚC RẰNG: enum được thêm vào trong PHP 8.1 — tuy nhiên, có một cách rõ ràng để có hành vi giống như enum trong cơ sở mã của bạn mà không cần sử dụng các phần phụ thuộc bên ngoài. Lấy ví dụ về ranh giới phạm vi ngày: ranh giới của nó có thể được bao gồm hoặc loại trừ. Đây là cách Boundaries enum sẽ được sử dụng:

$dateRange = DateRange::make(
    '2020-02-01', 
    '2020-03-01', 
    Boundaries::INCLUDE_ALL()
);

Đây là những gì chữ ký của nhà xây dựng của DateRange giống như:

public function __construct($start, $end, Boundaries $boundaries);

Đó là yêu cầu đầu tiên: chúng tôi muốn sử dụng hệ thống loại để đảm bảo chỉ sử dụng các giá trị enum hợp lệ.

Tiếp theo, chúng ta muốn có thể hỏi enum những ranh giới nào được bao gồm, như sau:

$dateRange->boundaries->startIncluded();
$dateRange->boundaries->endIncluded();

Điều này có nghĩa là mỗi giá trị enum sẽ hỗ trợ việc triển khai riêng của nó startIncludedendIncluded.

Đó là yêu cầu thứ hai: chúng tôi muốn enum của chúng tôi hỗ trợ hành vi có giá trị cụ thể.

Ngay từ cái nhìn đầu tiên, giải pháp đơn giản nhất là có một Boundaries lớp và thực hiện startIncludedendIncluded như vậy:

final class Boundaries
{
    private const INCLUDE_NONE = 'none';
    private const INCLUDE_START = 'start';
    private const INCLUDE_END = 'end';
    private const INCLUDE_ALL = 'all';

    private string $value;

    public static function INCLUDE_START(): self
    
        return $this->value === self::INCLUDE_START
            

    private function __construct(string $value) 
    

    public function startIncluded(): bool
    
        return $this->value === self::INCLUDE_START
            
    
    public function endIncluded(): bool
    
}

Tóm lại: sử dụng các điều kiện trên giá trị của enum để thêm hành vi.

Đối với ví dụ này, đó là một giải pháp đủ sạch. Tuy nhiên: nó không có quy mô tốt như vậy. Hãy tưởng tượng enum của chúng ta cần chức năng có giá trị cụ thể phức tạp hơn; bạn thường kết thúc với các hàm lớn chứa các khối điều kiện lớn.

Càng có nhiều điều kiện, mã của bạn càng có nhiều đường dẫn, việc hiểu và duy trì càng phức tạp và càng dễ xảy ra lỗi.

Đó là yêu cầu thứ ba: chúng tôi muốn tránh sử dụng các điều kiện trên các giá trị enum.

Tóm lại, chúng ta muốn enum của mình đáp ứng được ba yêu cầu sau:

  • Các giá trị Enum phải được gõ mạnh để hệ thống kiểu có thể kiểm tra chúng
  • Enums nên hỗ trợ hành vi giá trị cụ thể
  • Phải tránh các điều kiện về giá trị cụ thể bằng mọi giá

Đa hình có thể đưa ra một giải pháp ở đây: mỗi giá trị enum có thể được biểu diễn bằng lớp riêng của nó, mở rộng Boundaries enum. Do đó, mỗi giá trị có thể triển khai phiên bản riêng của nó startIncludedendIncludedtrả về một boolean đơn giản.

Có lẽ chúng ta sẽ làm một cái gì đó như thế này:

abstract class Boundaries
{
    public static function INCLUDE_NONE(): IncludeNone
    {
        return new IncludeNone();
    }
    
    
    
    abstract public function startIncluded(): bool;

    abstract public function endIncluded(): bool;
}

Và có biện pháp triển khai cụ thể Boundaries như thế này – bạn có thể tưởng tượng ba cái còn lại sẽ trông như thế nào:

final class IncludeNone extends Boundaries
{
    public function startIncluded(): bool
    {
        return false;
    }

    public function endIncluded(): bool
    {
        return false;
    }
} 

Mặc dù còn nhiều công việc ban đầu để lập trình các enum này nhưng hiện tại chúng tôi đã đáp ứng tất cả các yêu cầu.

Có một cải tiến nữa được thực hiện. Không cần sử dụng các lớp dành riêng cho các giá trị cụ thể; chúng sẽ không bao giờ được sử dụng một mình. Vì vậy, thay vì mở rộng bốn lớp Boundarieschúng ta có thể sử dụng các lớp ẩn danh:

abstract class Boundaries
{
    abstract public function startIncluded(): bool;

    abstract public function endIncluded(): bool;

    public static function INCLUDE_NONE(): Boundaries
    {
        return new class extends Boundaries 
        {
            public function startIncluded(): bool {
                return false; 
            }

            public function endIncluded(): bool {
                return false; 
            }
        };
    }

    public static function INCLUDE_START(): Boundaries
    {
        return new class extends Boundaries 
        {
            public function startIncluded(): bool {
                return true; 
            }

            public function endIncluded(): bool {
                return false; 
            }
        };
    }

    public static function INCLUDE_END(): Boundaries
    {
        return new class extends Boundaries 
        {
            public function startIncluded(): bool {
                return false; 
            }

            public function endIncluded(): bool {
                return true; 
            }
        };
    }

    public static function INCLUDE_ALL(): Boundaries
    {
        return new class extends Boundaries 
        {
            public function startIncluded(): bool {
                return true; 
            }

            public function endIncluded(): bool {
                return true; 
            }
        };
    }
}

Được rồi, tôi đã nhầm: còn hai cải tiến nữa cần được thực hiện. Đây là rất nhiều mã lặp đi lặp lại! Nhưng một lần nữa lại có giải pháp cho điều đó! Chúng ta hãy định nghĩa đơn giản hai thuộc tính trên mỗi lớp có giá trị cụ thể ($startIncluded$endIncluded) và hãy triển khai getters của họ trên bản tóm tắt Boundaries thay vào đó là lớp học!

abstract class Boundaries
{
    protected bool $startIncluded;
    protected bool $endIncluded;
    
    public function startIncluded(): bool 
    {
        return $this->startIncluded;
    }
    
    public function endIncluded(): bool 
    {
        return $this->endIncluded;
    }

    public static function INCLUDE_NONE(): Boundaries
    {
        return new class extends Boundaries 
        {
            protected bool $startIncluded = false;
            protected bool $endIncluded = false;
        };
    }

    public static function INCLUDE_START(): Boundaries
    {
        return new class extends Boundaries
        {
            protected bool $startIncluded = true;
            protected bool $endIncluded = false;
        };
    }

    public static function INCLUDE_END(): Boundaries
    {
        return new class extends Boundaries
        {
            protected bool $startIncluded = false;
            protected bool $endIncluded = true;
        };
    }

    public static function INCLUDE_ALL(): Boundaries
    {
        return new class extends Boundaries
        {
            protected bool $startIncluded = true;
            protected bool $endIncluded = true;
        };
    }
}

Trên đây là cách tiếp cận yêu thích của tôi để triển khai enum trong PHP. Nếu có một nhược điểm mà tôi có thể nghĩ đến thì đó là chúng yêu cầu một chút công việc thiết lập, mặc dù tôi thấy rằng đây là một khoản chi phí nhỏ, chỉ trả một lần nhưng sẽ mang lại lợi ích cao về lâu dài.



Leave a Comment

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Scroll to Top