Tự học Flutter 2023

New Dart

Class trong Dart

Dart là một ngôn ngữ hướng đối tượng với Class và kế thừa dựa trên mixin. Tất cả các đối tượng đều là một thể hiện của Class, và tất cả class trừ Null ra đều bắt đầu từ Object. Kế thừa dựa trên mixin nghĩa là mặc dù mỗi class (ngoại trừ top class, Object?) chỉ có một lớp cha, nội dung lớp có thể tái sử dụng trong multiple class hierarchies. Phương thức mở rộng (Extension methods) là một cách để thêm các chức năng vào một class mà không thay đổi class hoặc tạo ra class con.

  • Using class members
  • Using constructors
  • Getting an object’s type
  • Instance variable
  • Constructor
  • Initializing formal parameters
  • Default constructors
  • Constructors aren’t inherited
  • Named constructors
  • Invoking a non-default superclass constructor
  • Initializer list
  • Redirecting constructors
  • Constant constructors
  • Factory constructors
  • Method
  • Abstract class
  • Implicit interface
  • Extending class
  • Extension method
  • Enumerated types
  • Declaring simple enums
  • Declaring enhanced enums
  • Using enums
  • Mixin
  • Class variable and method
  • Static variables
  • Static methods
  • Using class members

    Các đối tượng có các thành viên bao gồm các hàm và dữ liệu. Khi bạn gọi một phương thức, bạn gọi nó trên một đối tượng: phương thức đó có quyền truy cập vào các phương thức và dữ liệu của đối tượng đó. Sử dụng dấu . để truy cập một phương thức hoặc biến của đối tượng.

    
    var p = Point(2, 2);
    
    // Get the value of y.
    assert(p.y == 2);
    
    // Invoke distanceTo() on p.
    double distance = p.distanceTo(Point(4, 4));
                              

    Using constructors

    Bạn có thể tạo một đối tượng bằng cách sử dụng hàm dựng (constructor). Tên hàm dựng có thể là ClassName hoặc ClassName.identifier. Ví dụ: đoạn mã sau tạo các đối tượng Point bằng cách sử dụng các hàm dựng Point() và Point.fromJson():

    
    var p1 = Point(2, 2);
    var p2 = Point.fromJson({'x': 1, 'y': 2});
                              

    Đoạn mã sau có tác dụng tương tự, nhưng sử dụng từ new tùy chọn trước tên hàm dựng:

    
    var p1 = new Point(2, 2);
    var p2 = new Point.fromJson({'x': 1, 'y': 2});
                              

    Một số lớp cung cấp các hàm dựng hằng số. Để tạo một hằng số thời gian biên dịch bằng cách sử dụng hàm dựng const, hãy đặt từ khóa const trước tên hàm dựng:

    
    var p = const ImmutablePoint(2, 2);
                              

    Việc tạo ra hai hằng số thời gian biên dịch giống hệt nhau sẽ chỉ tạo ra một đối tượng duy nhất (đã nói ở phần const và final):

    
    var p1 = const ImmutablePoint(2, 2);
    var p2 = const ImmutablePoint(2, 2);
    assert(identical(p1, p2)); // They are the same instance!
                              

    Trong ngữ cảnh const, bạn có thể bỏ qua const trước hàm dựng hoặc "literal". Ví dụ: hãy xem đoạn mã tạo một map const:

    
    // Lots of const keywords here.
    const pointAndLine = const {
      'point': const [const ImmutablePoint(0, 0)],
      'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
    };
                              

    Bạn có thể bỏ qua tất cả trừ từ khóa const đầu tiên.

    
    // Lots of const keywords here.
    const pointAndLine =  {
      'point':  [ ImmutablePoint(0, 0)],
      'line':  [ ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
    };
                              

    Nếu một hàm dựng hằng số (const constructor) nằm ngoài ngữ cảnh const và được gọi mà không có từ khóa const, nó sẽ tạo ra một non-constant object.

    
    var a = const ImmutablePoint(1, 1); // Creates a constant
    var b = ImmutablePoint(1, 1); // Does NOT create a constant
    
    assert(!identical(a, b)); // NOT the same instance!
                              

    Getting an object’s type

    Để có được kiểu của đối tượng trong thời gian chạy, bạn có thể sử dụng thuộc tính runtimeType của Object, thuộc tính này trả về một đối tượng Type.

    
    print('The type of a is ${a.runtimeType}');
                              

    Instance variable

    Đây là cách bạn khai báo các biến đối tượng (biến thể hiện, biến thành viên, bla bla):

    
    class Point {
      double? x // Declare instance variable x, initially null.
      double? y; // Declare y, initially null.
      double z = 0; // Declare z, initially 0.
    }
                              

    Tất cả các biến thể hiện chưa được khởi tạo đều có giá trị null.

    Tất cả các biến đối tượng tạo ra một phương thức getter ẩn. Các biến không phải là final và các biến late final không có bộ khởi tạo cũng tạo ra một phương thức setter ngầm định. Để biết chi tiết, hãy xem Getters và setters.

    Nếu bạn đặt giá trị cho một biến non-late (ko dùng từ khóa late) giá trị sẽ được đặt khi đối tượng được tạo ra, nghĩa là trước cả khi hàm dựng và its initializer list được thực hiện.

    
    class Point {
      double? x; // Declare instance variable x, initially null.
      double? y; // Declare y, initially null.
    }
    
    void main() {
      var point = Point();
      point.x = 4; // Use the setter method for x.
      assert(point.x == 4); // Use the getter method for x.
      assert(point.y == null); // Values default to null.
    }
                              

    Các biến thể hiện cũng có thể là biến final, trường hợp đó chúng phải được gán giá trị duy nhất một lần. Khởi tạo biến final sử dụng tham số hàm dựng, hoặc sử dụng constructor’s initializer list.

    
    class ProfileMark {
      final String name;
      final DateTime start = DateTime.now();
    
      ProfileMark(this.name);
      ProfileMark.unnamed() : name = '';
    }
                              

    Nếu bạn cần gán giá trị cho biến final sau khi hàm dựng đã chạy, bạn có thể sử dụng một trong hai cách sau:

  • Use a factory constructor.
  • Sử dụng từ khóa late, nhưng hãy cẩn thận: biến late final không đi kèm giá trị khởi tạo sẽ tạo ra một setter.
  • Constructor

    Khai báo một hàm dựng bằng cách tạo một hàm có cùng tên với lớp của nó. Từ khóa this đề cập đến thể hiện (đối tượng) hiện tại.

    
    class Point {
      double x = 0;
      double y = 0;
    
      Point(double x, double y) {
        // See initializing formal parameters for a better way
        // to initialize instance variables.
        this.x = x;
        this.y = y;
      }
    }
                            

    Initializing formal parameters

    Cách gán một đối số hàm dựng cho một biến thể hiện rất phổ biến - xưa rồi, Dart có cách khác dễ dàng hơn: initializing formal parameters.

    
    class Point {
      double x = 0;
      double y = 0;
    
      // Sets the x and y instance variables
      // before the constructor body runs.
      Point(this.x, this.y);
    }
                            

    Default constructors

    Nếu bạn không khai báo hàm dựng, hàm dựng mặc định sẽ được cung cấp cho bạn. Hàm dựng mặc định không có đối số và gọi hàm dựng không có đối số trong lớp cha.

    Constructors aren’t inherited

    Các lớp con không kế thừa các hàm dựng từ lớp cha của chúng. Một lớp con khai báo không có hàm dựng nào chỉ có hàm dựng mặc định (không có đối số, không có tên).

    Named constructors

    Sử dụng một hàm dựng có tên để triển khai nhiều hàm dựng cho một lớp hoặc để cung cấp thêm sự rõ ràng:

    
    const double xOrigin = 0;
    const double yOrigin = 0;
    
    class Point {
      final double x;
      final double y;
    
      Point(this.x, this.y);
    
      // Named constructor
      Point.origin()
      : x = xOrigin,
      y = yOrigin;
    }
                            

    Hãy nhớ rằng hàm dựng không được kế thừa, điều đó có nghĩa là hàm dựng có tên của lớp cha không được kế thừa bởi lớp con. Nếu bạn muốn một lớp con được tạo với một hàm dựng có tên được xác định trong lớp cha, bạn phải triển khai hàm dựng đó trong lớp con.

    Invoking a non-default superclass constructor

    Theo mặc định, một hàm dựng trong một lớp con gọi hàm dựng không có đối số, không tên của lớp cha. Hàm dựng của lớp cha được gọi ở phần đầu của thân hàm dựng. Nếu initializer list được sử dụng, nó sẽ thực thi trước khi lớp cha được gọi. Tóm lại, trình tự thực hiện như sau:

  • Initializer list
  • Hàm dựng không có đối số của lớp cha
  • Hàm dựng không có đối số của lớp chính
  • Nếu lớp cha không có hàm dựng không tên, không đối số, thì bạn phải gọi một trong các hàm dựng trong lớp cha theo cách thủ công. Chỉ định hàm dựng của lớp cha sau dấu hai chấm (:), ngay trước phần thân của hàm dựng (nếu có).

    Trong ví dụ sau, hàm dựng của lớp Employee gọi hàm dựng được đặt tên cho lớp cha của nó, Person. Nhấp vào Run để thực thi mã.

                          
    class Person {
      String? firstName;
    
      Person.fromJson(Map data) {
        print('in Person');
      }
    }
    
    class Employee extends Person {
      // Person does not have a default constructor;
      // you must call super.fromJson().
      Employee.fromJson(super.data) : super.fromJson() {
        print('in Employee');
      }
    }
    
    void main() {
      var employee = Employee.fromJson({});
      print(employee);
      // Prints:
      // in Person
      // in Employee
      // Instance of 'Employee'
    }
                          
                          

    Bởi vì các đối số cho hàm dựng của lớp bậc trên được đánh giá trước khi gọi hàm dựng, nên một đối số có thể là một biểu thức, chẳng hạn như một lệnh gọi hàm:

    
    class Employee extends Person {
      Employee() : super.fromJson(fetchDefaultData());
      // ···
    }
                            

    Cảnh báo: Các đối số của hàm dựng của lớp cha không có quyền truy cập vào this. Ví dụ: các đối số có thể gọi các phương thức tĩnh nhưng không thể gọi các phương thức thể hiện (instance methods).

    Để tránh phải chuyển từng tham số theo cách thủ công vào lời gọi hàm dựng của lớp cha, bạn có thể sử dụng initializer parameters của lớp cha để chuyển tiếp các tham số tới hàm dựng lớp cha được chỉ định hoặc mặc định. Không thể sử dụng tính năng này với các hàm dựng chuyển hướng [xem ở bên dưới]. Super-initializer parameters have similar syntax and semantics to initializing formal parameters:

    
    class Vector2d {
      final double x
      final double y;
    
      Vector2d(this.x, this.y);
    }
    
    class Vector3d extends Vector2d {
      final double z;
    
      // Forward the x and y parameters to the default super constructor like:
      // Vector3d(final double x, final double y, this.z) : super(x, y);
      Vector3d(super.x, super.y, this.z);
    }
                            

    Initializer list

    Bên cạnh việc gọi một hàm dựng của lớp cha, bạn cũng có thể khởi tạo các biến thể hiện trước khi thân hàm dựng chạy. Chia các trình khởi tạo riêng biệt bằng dấu phẩy.

    
    // Initializer list sets instance variables before
    // the constructor body runs.
    Point.fromJson(Map json)
        : x = json['x']!,
          y = json['y']! {
      print('In Point.fromJson(): ($x, $y)');
    }
                            

    Redirecting constructors

    Đôi khi mục đích duy nhất của một hàm dựng là để chuyển tiếp sang một hàm dựng khác. Thân của một hàm dựng chuyển tiếp thì trống, lời gọi hàm dựng khác (sử dụng this thay vì tên class) hiển thị ngay sau dấu hai chấm (:).

    
    class Point {
      double x, y;
    
      // The main constructor for this class.
      Point(this.x, this.y);
    
      // Delegates to the main constructor.
      Point.alongXAxis(double x) : this(x, 0); // <====
    }
                            

    Constant constructors

    Nếu lớp của bạn tạo ra các đối tượng không bao giờ thay đổi, bạn có thể làm cho các đối tượng này trở thành hằng số thời gian biên dịch (compile-time constants). Để thực hiện việc này, hãy định nghĩa một hàm dựng const và đảm bảo rằng tất cả các biến đối tượng đều là biến final.

    
    class ImmutablePoint {
      static const ImmutablePoint origin = ImmutablePoint(0, 0);
    
      final double x, y;
    
      const ImmutablePoint(this.x, this.y);
    }
                            

    Factory constructors

    Sử dụng từ khóa factory khi triển khai một hàm dựng không phải lúc nào cũng tạo một phiên bản mới của lớp của nó. Ví dụ: một factory constructor có thể trả về một phiên bản từ bộ đệm hoặc nó có thể trả về một phiên bản của một kiểu con (subclass). Một trường hợp sử dụng khác cho các factory constructor là khởi tạo biến final bằng cách sử dụng logic mà không thể xử lý trong danh sách trình khởi tạo (initializer list).

    Method

    Method (phương thức) là các hàm cung cấp hành vi cho một đối tượng.

    Instance methods

    Instance methods on objects can access instance variables and this. The distanceTo() method in the following sample is an example of an instance method:

    
    import 'dart:math';
    
    class Point {
      final double x;
      final double y;
    
      Point(this.x, this.y);
    
      double distanceTo(Point other) {
        var dx = x - other.x;
        var dy = y - other.y;
        return sqrt(dx * dx + dy * dy);
      }
    }
                            

    Getters and setters

    Getters và setters là các phương thức đặc biệt cung cấp quyền truy cập đọc và ghi vào các thuộc tính của đối tượng. Hãy nhớ lại rằng mỗi biến thể hiện có một getter ẩn, cộng với một setter nếu thích hợp. Bạn có thể tạo các thuộc tính bổ sung bằng cách triển khai getters và setters, sử dụng từ khóa get và set:

    
    class Rectangle {
      double left, top, width, height;
    
      Rectangle(this.left, this.top, this.width, this.height);
    
      // Define two calculated properties: right and bottom.
      double get right => left + width;
      set right(double value) => left = value - width;
      double get bottom => top + height;
      set bottom(double value) => top = value - height;
    }
    
    void main() {
      var rect = Rectangle(3, 4, 20, 15);
      assert(rect.left == 3);
      rect.right = 12;
      assert(rect.left == -8);
    }
                          

    Abstract methods

    Instance, getter, and setter đều có thể trừu tượng, định nghĩa một interface và để phần thực hiện cho các lớp khác. Phương thức trừu tượng chỉ được tồn tại trong lớp trừu tượng. Để tạo phương thức trừu tượng, sử dụng dấu ; thay cho nội dung hàm.

    
    abstract class Doer {
      // Define instance variables and methods...
    
      void doSomething(); // Define an abstract method.
    }
    
    class EffectiveDoer extends Doer {
      void doSomething() {
        // Provide an implementation, so the method is not abstract here...
      }
    }
                          

    Abstract class

    Use the abstract modifier to define an abstract class—a class that can’t be instantiated. Abstract classes are useful for defining interfaces, often with some implementation. Dùng abstract modifier để định nghĩa một lớp trừu tượng - lớp không thể tạo ra thể hiện. Lớp trừu tượng hữu ích để định nghĩa interface, thường đi kèm cùng một số triển khai. If you want your abstract class to appear to be instantiable, define a factory constructor. Lớp trừu tượng thường có các phương thức trừu tượng. Đây là một ví dụ về khai báo một lớp trừu tượng có một phương thức trừu tượng.

    
    // This class is declared abstract and thus
    // can't be instantiated.
    abstract class AbstractContainer {
      // Define constructors, fields, methods...
    
      void updateChildren(); // Abstract method.
    }                        

    Implicit interface

    Mỗi lớp đều ngầm định nghĩa một interface chứa tất cả các thành viên thể hiện của lớp và của bất kỳ giao diện nào mà nó thực hiện (implement). Nếu bạn muốn tạo một lớp A hỗ trợ API của lớp B mà không kế thừa triển khai của B, thì lớp A nên triển khai giao diện B. Một lớp triển khai một hoặc nhiều giao diện bằng cách khai báo chúng trong mệnh đề triển khai và sau đó cung cấp API theo yêu cầu của giao diện. Ví dụ:

    
    // A person. The implicit interface contains greet().
    class Person {
      // In the interface, but visible only in this library.
      final String _name;
    
      // Not in the interface, since this is a constructor.
      Person(this._name);
    
      // In the interface.
      String greet(String who) => 'Hello, $who. I am $_name.';
    }
    
    // An implementation of the Person interface.
    class Impostor implements Person {
      String get _name => '';
    
      String greet(String who) => 'Hi $who. Do you know who I am?';
    }
    
    String greetBob(Person person) => person.greet('Bob');
    
    void main() {
      print(greetBob(Person('Kathy')));
      print(greetBob(Impostor()));
    }
                            

    Một ví dụ về class implement nhiều interface:

    
    class Point implements Comparable, Location {...}
                            

    Extending class

    Use extends to create a subclass, and super to refer to the superclass: Sử dụng từ khóa extends để tạo lớp con, và super để đề cập đến lớp cha.

                              
    class Television {
      void turnOn() {
        _illuminateDisplay();
        _activateIrSensor();
      }
      // ···
    }
    
    class SmartTelevision extends Television {
      void turnOn() {
        super.turnOn();
        _bootNetworkInterface();
        _initializeMemory();
        _upgradeApps();
      }
      // ···
    }
                            

    Overriding members

    Các lớp con có thể ghi đè các phương thức thể hiện (bao gồm cả toán tử), getters và setters. Bạn có thể sử dụng chúthích @override để cho biết rằng bạn đang cố ý ghi đè một thành viên:

    
    class Television {
      // ···
      set contrast(int value) {...}
    }
    
    class SmartTelevision extends Television {
      @override
      set contrast(num value) {...}
      // ···
    }
                            

    Một khai báo phương thức ghi đè phải khớp với phương thức (hoặc các phương thức) mà nó ghi đè theo một số cách:

  • Kiểu trả về phải cùng kiểu với (hoặc một kiểu con của) kiểu trả về của phương thức bị ghi đè.
  • Các loại tham số phải cùng loại với (hoặc là kiểu cha) các loại đối số của phương thức được ghi đè. Trong ví dụ trước, setter contrast của SmartTelevision thay đổi loại đối số từ int thành kiểu cha num.
  • Nếu phương thức ghi đè chấp nhận n tham số vị trí, thì phương thức ghi đè cũng phải chấp nhận n tham số vị trí.
  • Một phương thức chung (generic) không thể ghi đè một phương thức không chung và một phương thức không chung không thể ghi đè một phương thức chung.
  • Sometimes you might want to narrow the type of a method parameter or an instance variable. This violates the normal rules, and it’s similar to a downcast in that it can cause a type error at runtime. Still, narrowing the type is possible if the code can guarantee that a type error won’t occur. In this case, you can use the covariant keyword in a parameter declaration. For details, see the Dart language specification.

    
    class Animal {
      void chase(Animal x) { ... }
    }
    
    class Mouse extends Animal { ... }
    
    class Cat extends Animal {
      @override
      void chase(covariant Mouse x) { ... }
    }
                            

    noSuchMethod()

    Để phát hiện hoặc phản ứng bất cứ khi nào có mã cố gắng sử dụng một phương thức hoặc biến đối tượng không tồn tại, bạn có thể ghi đè noSuchMethod():

    
    class A {
      // Unless you override noSuchMethod, using a
      // non-existent member results in a NoSuchMethodError.
      @override
      void noSuchMethod(Invocation invocation) {
        print('You tried to use a non-existent member: '
            '${invocation.memberName}');
      }
    }
                            

    Extension method

    Các phương thức mở rộng là một cách để thêm chức năng vào các thư viện hiện có. Bạn có thể sử dụng các phương thức mở rộng mà không hề biết. Ví dụ: khi bạn sử dụng tính năng nhắc mã trong IDE, nó sẽ đề xuất các phương thức mở rộng cùng với các phương thức thông thường.

    Đây là một ví dụ về việc sử dụng một phương thức mở rộng trên String có tên là parseInt() được định nghĩa trong string_apis.dart:

    
    import 'string_apis.dart';
    ...
    print('42'.padLeft(5)); // Use a String method.
    print('42'.parseInt()); // Use an extension method.
                            

    Chi tiết về việc sử dụng và triển khai các phương thức mở rộng, hãy xem ở đây: phương thức mở rộng .

    Enumerated types

    ...

    Declaring simple enums

    Declaring enhanced enums

    Using enums

    Mixin

    ...

    Class variable and method

    Sử dụng từ khóa static để triển khai các biến và phương thức trên toàn lớp.

    Static variables

    Các biến tĩnh (biến lớp) rất hữu ích cho các hằng số và trạng thái toàn lớp. Các biến tĩnh không được khởi tạo cho đến khi chúng được sử dụng.

    
    class Queue {
      static const initialCapacity = 16;
      // ···
    }
    
    void main() {
      assert(Queue.initialCapacity == 16);
    }
                            

    Static methods

    Các phương thức tĩnh (các phương thức lớp) không hoạt động trên một thể hiện và do đó không có quyền truy cập vào điều này. Tuy nhiên, chúng có quyền truy cập vào các biến tĩnh. Như ví dụ sau cho thấy, bạn gọi các phương thức tĩnh trực tiếp trên một lớp:

    
    import 'dart:math';
    
    class Point {
      double x, y;
      Point(this.x, this.y);
    
      static double distanceBetween(Point a, Point b) {
        var dx = a.x - b.x;
        var dy = a.y - b.y;
        return sqrt(dx * dx + dy * dy);
      }
    }
    
    void main() {
      var a = Point(2, 2);
      var b = Point(4, 4);
      var distance = Point.distanceBetween(a, b);
      assert(2.8 < distance && distance < 2.9);
      print(distance);
    }
                          

    Lưu ý: Cân nhắc sử dụng các top-level function, thay vì các phương thức tĩnh, cho các tiện ích và chức năng phổ biến hoặc được sử dụng rộng rãi.