Sử dụng cơ bản

Kiểu dữ liệu dùng để liệt kê là các kiểu dữ liệu rất phổ biến trong các ngôn ngữ lật trình. Nó được sử dụng để biển diễn các biến có giá trị không đổi(const). Trong Dart, bạn có thể khai báo kiểu dữ liệu dùng để liệt kê này với từ khóa Enum.

enum Pet { cat, dog, fish }

void main() {
  final values = Pet.values;
  print(values); // [Pet.cat, Pet.dog, Pet.fish]
  print(Pet.cat); // Pet.cat
  print(Pet.dog.index); // 1
  print(Pet.fish == values[2]); // true
}

Tất cả các enum đều được kế thừa từ Class Enum. Cách viết khác của đoạn code ở trên là:

class Pet extends Enum {  
  static const Pet cat = Pet._$(0, "cat");  
  static const Pet dog = Pet._$(1, "dog");  
  static const Pet fish = Pet._$(2, "fish");  
  static const List<Pet> values = [cat, dog, fish];  
  const Pet._$(int _$index, String _$name) : super._(_$index, _$name);  

  String _$enumToString() => "Pet.${_$name}";
}

abstract class Enum {
  Enum._(this.index, this._name);  
  final int index;
  final String _name;
  String _$enumToString();
  String toString() => _$enumToString();
}

Các kiểu Enum được thiết kế để trở thành đẳng thức nguyên thủy, vì vậy Enum có thể sử dụng trong câu lệch switch-case.

void main() {
  final pet = Pet.fish;

  switch (pet) {
    case Pet.cat:
      print('Cat');
      break;
    case Pet.dog:
      print('Dog');
      break;
    default:
      print("Fish");
  }
}

Các kiểu Enum được niêm phong hoàn toàn theo mặc định. Bị niêm phong nghĩa là chúng không thể được phân lớp, triển khai, pha trộn hoặc khởi tạo rõ ràng.

class FurryPet extends Pet {} 
// Lỗi: 'Pet' là một enum và không thể mở rộng hoặc triển khai.
class PocketPet implements Pet {} 
// Lỗi: 'Pet' là một enum và không thể mở rộng hoặc triển khai.
mixin PetMixin on Pet {} 
// Lỗi: 'Pet' là một enum và không thể mở rộng hoặc triển khai.
const bird = Pet(3, 'Bird'); 
// Lỗi: Không thể khởi tạo Enums.

Khá đơn giản phải không? Nhưng nếu chúng ta muốn lấy tên của một giá trị enum hoặc lấy giá trị theo tên thì sao. Chúng ta chỉ cần làm như sau:

void main() {
  print(Pet.cat.toString().split('.').last); // cat

 // tên không phải tiếng Anh
  const names = ['chat', 'chien', 'poisson']; 
  print(names[Pet.fish.index]); // poisson

// tìm giá trị enum theo tên 
final dog = Pet.values.firstWhere((e) => e.toString().split('.').last == 'dog');
  print(dog); // Pet.dog
}

Enum mở rộng

Kể từ Dart 2.6, chúng ta có thể thêm các phương thức tùy chỉnh vào enum bằng các extension methods(https://dart.dev/guides/language/extension-methods) .

enum Color {
  red,
  green,
  blue,
}

extension ColorExt on Color {
// tên không phải tiếng Anh
String get name {
    switch (this) {
      case Color.red:
        return 'rojo';
      case Color.green:
        return 'verde';
      default:
        return 'azul';
    }
  }

  String get hexCode {
    const hexCodes = ['#FF0000', '#00FF00', '#0000FF'];
    return hexCodes[this.index];
  }
}

void main() {
  print(Color.green.name); // verde
  print(Color.blue.hexCode); // #0000FF
}

Vì việc lấy tên của một giá trị enum dưới dạng Chuỗi thường khá cần thiết, trong dart 2.15 đã giới thiệu một EnumExtension chính thức để thực hiện điều tương tự.

void main() {
  // dart < 2.15
  print(Color.red.toString().split('.').last) // red
  // dart >= 2.15, với EnumExtension chính thức
  print(Color.red.name); // red
}

Enum nâng cao

Dart 2.17 được chuyển đổi với enum nâng cao , khiến cho enum hoạt động giống một lớp hơn. Enum nâng cao hỗ trợ chung và có thể được khai báo với các trường (thành viên tĩnh hoặc cá thể), phương thức hoặc triển khai giao diện và áp dụng pha trộn.

// cú pháp enum nâng cao
enum Name<T extends Object?> with Mixin1, Mixin2 implements Interface1, Interface2 {
  id1<int>(args1), id2<String>(args2), id3<bool>(args3);
  memberDeclaration*
  const Name(params) : initList;
}

Ta viết lại phương thức mở rộng ở trên là Color enum thành một phiên bản nâng cao.

enum Color {
  red('#FF0000', 'Red'),
  green('#00FF00', 'Green'),
  blue('#0000FF', 'Blue');

  const Color(this.hexCode, this.name);

  final String hexCode;
  final String name;

  @override
  String toString() => '$name: $hexCode';
}

void main() {
  final blue = Color.blue;
  print(blue.hexCode); // #00FF00
  print(blue.name); // Blue
  print(blue); // Blue: #00FF00
}

Nhờ tính năng này, mã trở nên rõ ràng hơn nhiều để đọc và dễ bảo trì.

Phát triển thêm từ đoạn code trên

mixin Pet {
  keepCompany() => print('keep you company');
}

enum Dog with Pet implements Comparable<Dog> {
  akita.guardDog('Akita', 'japan'),
  // tạo cá thể với có tên hàm khởi tạo
  bulldog('Bulldog', 'england', false),
  chihuahua('Chihuahua', 'mexico', false);

  final bool guardDog;
  final String name;
  final String originCountry;

  // hàm khởi tạo tổng quát chỉ có thể được gọi trong enum
  const Dog(this.name, this.originCountry, this.guardDog);

// hàm khởi tạo được đặt tên với bộ khởi tạo 
const Dog.guardDog(String name, String country)
      : this.name = name,
        this.originCountry = country,
        this.guardDog = true;

// hàm khởi tạo ở đây chỉ có thể trả về một trong các giá trị đã biết
   // chuyển hướng đến hàm khởi tạo chung là bất hợp pháp
  factory Dog.japanDog() {
    return values.firstWhere((value) => value.originCountry == 'japan');
  }

// triển khai giao diện So sánh để làm cho nó có thể sắp xếp được
  int compareTo(Dog another) => this.index - another.index;

// ghi đè phương thức toString mặc định
  String toString() => name;
}

void main() {
  List<Dog> dogs = [Dog.bulldog, Dog.chihuahua, Dog.akita];
  dogs.sort((a, b) => a.compareTo(b));
  print(dogs);
  print(Dog.chihuahua.keepCompany());
}

Tóm lược

Vì Enum được sử dụng cho số lượng giá trị không đổi cố định, nên có một số hạn chế được áp dụng cho enum nâng cao.

  • Enum nâng cao không thể mở rộng class khác ngoài Enum claas.
  • Tất cả các hàm khởi tạo tổng quát (hàm khởi tạo tạo ra ví dụ của lớp) phải không đổi. điều này đảm bảo tất cả các trường hợp enum là giá trị không đổi.
  • Factory constructors chỉ có thể trả về một trong các trường hợp enum cố định và đã biết.
  • Tất cả các trường hợp phải được khai báo khi bắt đầu khai báo enum và phải có ít nhất một trường hợp (enum trống là vô dụng).
  • Trường hợp biến của enum phải khai báo là final
  • Ghi đè indexvà hasCodegetters, cũng như == operator
  • Trường hợp static hoặc có tên values không được phép , vì sẽ xảy ra xung đột với static values getter mặc định.

Tham khảo và dịch lại: https://dev.to/catoverflow/how-to-use-enum-in-dart-3274