openplanning

Hướng dẫn và ví dụ Dart Generics

  1. Lớp Generics
  2. Thừa kế từ một lớp Generics (1)
  3. Thừa kế từ một lớp Generics (2)
  4. Thừa kế từ một lớp Generics (3)
  5. Phương thức Generics (1)
  6. Phương thức Generics (2)
  7. Hàm Generics
Trong lập trình Dart, có thể bạn đã quen thuộc với lớp ListMap và quen thuộc với ký hiệu List<E>Map<K,V>. Trong trường hợp này List<E>Map<K,V> được gọi là Generics Type hoặc Parameterized Type (kiểu được tham số hoá).
Các ký hiệu E, K, V,.. được gọi là các tham số Generics. Theo quy ước các tham số Generics là các ký tự viết hoa.
Tại sao nên sử dụng Generics:
Generics giúp trình biên dịch phát hiện ra các lỗi liên quan tới dữ liệu tại thời điểm biên dịch, giúp giảm thiểu lỗi phát sinh trong quá trình chạy của chương trình. Trong thực tế Generics còn mang lại nhiều lợi ích khác.
  • Việc chỉ định đúng kiểu Generics giúp mã được tạo ra tốt hơn.
  • Bạn có thể sử dụng Generics làm giảm sự trùng lặp mã.
Ví dụ, bạn muốn có một danh sách chỉ chứa các phần tử kiểu String, bạn sẽ sử dụng List<String>, trình biên dịch sẽ thông báo lỗi nếu bạn vô tình thêm một phần tử không phải String vào danh sách.
void main()  {
  List<String> myList= ["Tom", "Jerry"];
  
  myList.add("Donald"); // OK
  
  myList.add(10); // ==> ERROR!
}
Trong bài viết này chúng ta sẽ thảo luận làm thế nào để tạo ra các lớp, hàm và phương thức Generics.

1. Lớp Generics

Trong ví dụ dưới đây, chúng ta định nghĩa một lớp Generics - KeyValue<K,V>:
key_value.dart
class KeyValue<K,V> {

  K key;
  V value;

  KeyValue(this.key, this.value);
}
Khi sử dụng lớp KeyValue<K,V> bạn phải chỉ định kiểu dữ liệu cụ thể cho <K,V>, chẳng hạn:
  • K = String
  • V = int
key_value_demo.dart
import 'key_value.dart';

void main() {
  // K is String
  // V is int
  KeyValue<String, int> tomSalary = KeyValue("Tom", 1000);

  // key is String
  String name = tomSalary.key;
  // value is int
  int salary = tomSalary.value;

  print("Name: $name");
  print("Salary: $salary");
}
Output:
Name: Tom
Salary: 1000

2. Thừa kế từ một lớp Generics (1)

Bạn có thể tạo một lớp con thừa kế từ một lớp Generics và chỉ định rõ kiểu cụ thể cho tham số Generics.
Trong ví dụ này, lớp NameSalaryEntry là một lớp con của lớp KeyValue<K,V> với tham số <K,V> được chỉ định kiểu cụ thể.
  • K = String (Name)
  • V = int (Salary)
name_salary_entry.dart
import 'key_value.dart';

/// This class extends from KeyValue<K,V>
/// 
/// K = String - Name of a person
/// V = int - Salary of a person
///
class NameSalaryEntry extends KeyValue<String, int>  {

  ///
  /// Constructor to assign values to superclass properties
  ///
  NameSalaryEntry(super.key, super.value);

}
Sử dụng lớp NameSalaryEntry:
name_salary_entry_demo.dart
import 'name_salary_entry.dart';

void main() {
  var tom = NameSalaryEntry("Tom", 1000);

  String name = tom.key; // K is a String
  int salary = tom.value; // V is an int

  print("Person Name: $name");
  print("Person Salary: $salary");
}

3. Thừa kế từ một lớp Generics (2)

Bạn cũng có thể tạo một lớp thừa kế từ một lớp Generics và chỉ định kiểu cụ thể cho một vài tham số Generics. Chẳng hạn, lớp StringVEntry dưới đây mở rộng từ lớp KeyValue<K,V> với tham số <K><String>.
string_v_entry.dart
import 'key_value.dart';

///
/// This class extends from KeyValue<K,V> with:
/// K = String
///
class StringVEntry<V> extends KeyValue<String, V> {
  ///
  /// Constructor to assign values to superclass properties
  ///
  StringVEntry(super.key, super.value);
}
StringVEntry<V> là một lớp Generics với chỉ một tham số Generics.
string_v_entry_demo.dart
import 'package:generics/string_v_entry.dart';

void main() {
  ///
  /// V = double - Salary of a Person
  ///
  StringVEntry<double> tom = StringVEntry<double>("Tom", 1001.1);

  String personName = tom.key;
  double personSalary = tom.value;

  print("Person Name: $personName");
  print("Person Salary: $personSalary");
}
Output:
Person Name: Tom
Person Salary: 1001.1

4. Thừa kế từ một lớp Generics (3)

Ví du, tạo một lớp con có 3 tham số Generics mở rộng từ một lớp có 2 tham số Generics:
key_value_info.dart
import 'key_value.dart';

///
/// This class extends KeyValue<K,V> class and has additional Generics <I> parameter.
///
class KeyValueInfo<K, V, I> extends KeyValue<K, V> {
  I info;

  KeyValueInfo(super.key, super.value, this.info);
}

5. Phương thức Generics (1)

Các tham số Generics có thể tham gia trong một phương thức.
generics_method_example.dart
class Team<E> {
  List<E> members;
  ///
  /// Constructor:
  /// 
  Team(this.members);

  E? getFirstMember() {
    if (members.isNotEmpty) {
      return members[0];
    }
    return null;
  }

  E? getLastMember() {
    if (members.isNotEmpty) {
      return members[members.length - 1];
    }
    return null;
  }
}

void main() {
  List<String> list = ["Tom", "Jerry", "Donald"];

  Team<String> myTeam = Team(list);

  String? firstMember = myTeam.getFirstMember();
  print("First Member: $firstMember");

  String? lastMember = myTeam.getLastMember();
  print("Last Member: $lastMember");
}
Output:
First Member: Tom
Last Member: Donald

6. Phương thức Generics (2)

Thông thường các tham số Generics được định nghĩa cùng với tên lớp. Tuy nhiên, nó cũng có thể được định nghĩa trực tiếp bởi phương thức hoặc hàm.
my_utils.dart
import 'key_value.dart';

class MyUtils {
  ///
  /// Define static method with Generics parameters <K,V>.
  /// getKey<K,V>(parameters)
  ///
  static K getKey<K, V>(KeyValue<K, V> kv) {
    return kv.key;
  }

  ///
  /// Define static method with Generics parameter <E>.
  /// getFirstElement<E>(parameters)
  ///
  static E? getFirstElement<E>(List<E> list) {
    if (list.isEmpty) {
      return null;
    }
    return list[0];
  }
}
Kiểu của tham số Generics sẽ được chỉ định tại thời điểm gọi phương thức (hoặc hàm).
my_utils_demo.dart
import 'my_utils.dart';

void main() {
  List<String> names = ["Tom", "Jerry", "Donald"];

  String? firstName = MyUtils.getFirstElement(names);

  print("First Element: $firstName");
}
Output:
First Element: Tom

7. Hàm Generics

Ví dụ, một hàm Generics với một tham số Generics:
generics_function_example.dart
///
/// A Generics Function:
///
F? getLastElement<F>(List<F> list) {
  if (list.isEmpty) {
    return null;
  }
  return list[0];
}

void main() {
  List<int> numbers = [11, 22, 33];

  int? lastNumber = getLastElement(numbers);
  print("Last Number: $lastNumber"); // 33
}