openplanning

Hướng dẫn và ví dụ phương thức trong Dart

  1. Phương thức là gì?
  2. Phương thức thông thường
  3. Phương thức tĩnh
  4. Phương thức trừu tượng
  5. Các tham số tuỳ chọn
  6. Các tham số tuỳ chọn được đặt tên
  7. Ghi đè một phương thức
  8. Final method?

1. Phương thức là gì?

Trong ngôn ngữ lập trình Dart, phương thức là một khối mã (block of code) được định nghĩa bên trong một lớp và chỉ chạy khi được gọi. Các phương thức chia nhiệm vụ lớn thành các phần nhỏ và thực hiện một hoạt động cụ thể của chương trình. Quá trình này làm tăng khả năng tái sử dụng mã và nâng cao cách tiếp cận mô-đun của chương trình.
Về cơ bản, phương thức được chia làm 3 loại:
  • Phương thức thông thường (không tĩnh và không trừu tượng).
  • Phương thức tĩnh (static method).
  • Phương thức trừu tượng (abstract method).

2. Phương thức thông thường

Cú pháp định nghĩa một phương thức thông thường:
return_type method_name(arguments)    {  
     // statement(s)  
}
  • return_type: Kiểu dữ liệu trả về của phương thức. Sử dụng từ khoá void như một kiểu trả về nếu phương thức không trả về gì cả.
  • method_name: Tên phương thức. Khác với các ngôn ngữ khác như Java, Dart không cho phép 2 phương thức cùng tên, kể cả khi chúng có các tham số khác nhau. Điều này là cần thiết để có thể chuyển đổi mã Dart sang JavaScript.
  • arguments: Các tham số của phương thức.
Ví dụ: Lớp Cat và phương thức sayHello(..) của nó. Để gọi phương thức sayHello(..) bạn phải tạo một đối tượng Cat và gọi phương thức bằng cách sử dụng ký hiệu dấu chấm (dot notation).
method_ex1.dart
class Cat {
  void sayHello(String name) {
    print("Hello $name");
  }
}
void main() {
  Cat tom = new Cat(); // Create a Cat object.  
  tom.sayHello("Jerry"); // Call the method through the object.
}
Output:
Hello Jerry
Một phương thức có thể chứa 0, 1 hoặc nhiều tham số, các tham số cách nhau bởi dấu phẩy.
method_ex2.dart
class Rectangle {
  int width;
  int height;
  Rectangle(this.width, this.height); // Constructor

  int getArea() {
    return width * height;
  }
  void changeWidthHeight(int newWidth, int newHeight)  {
      width = newWidth;
      height = newHeight;
  }
  void showMe()  {
     print('I am a rectangle, width: $width, height: $height');
  }
}
void main() {
   Rectangle rec = Rectangle(5, 10); // Create an object.
   
   rec.showMe();  // Call the method.
   var area = rec.getArea();  // Call the method.
   print('Area: $area');
   print(' --- Change width and height --- ');
   rec.changeWidthHeight(25, 15); // Set newWidth, newHeight
   rec.showMe(); // Call the method.  
   area = rec.getArea(); // Call the method.
   print('Area: $area');
}
Output:
I am a rectangle, width: 5, height: 10
Area: 50
--- Change width and height ---
I am a rectangle, width: 25, height: 15
Area: 375

3. Phương thức tĩnh

Dart sử dụng từ khoá static và cú pháp định nghĩa một phương thức thông thường để định nghĩa một phương thức tĩnh.
Cú pháp định nghĩa phương thức tĩnh:
static return_type method_name(arguments)    {  
     // statement(s)  
}
Các đặc điểm của phương thức tĩnh:
  • Phương thức tĩnh được gọi thông qua tên lớp và ký hiệu dấu chấm. Chẳng hạn MyUtility.sum(100, 50). Tên lớp và ký hiệu dấu chấm có thể được bỏ qua nếu gọi phương thức tĩnh trong nội bộ lớp đó.
  • Các thành viên không tĩnh của một lớp không thể xuất hiện trong phương thức tĩnh trừ khi chúng được truy cập thông qua đối tượng (Xem thêm ví dụ phía dưới).
method_static_ex1.dart
class MyUtility {
  static int sum(int a, int b)  {
    return a + b;
  }
  static int minus(int a, int b)  {
    return a - b;
  }
}
void main() {
   var result = MyUtility.sum(100, 50);
   print('Sum Result: $result');
   result = MyUtility.minus(100, 50);
   print('Minus Result: $result');
}
Output:
Sum Result: 150
Minus Result: 50
Tiếp theo, nhìn vào ví dụ dưới đây:
  • side1, side2side3 là các trường (field) không tĩnh của lớp Triangle, chúng không thể xuất hiện trong phương thức tĩnh.
  • Gọi phương tĩnh trong nội bộ lớp đã định nghĩa nó có thể không cần tên lớp và ký hiệu dấu chấm.
method_static_ex2.dart
class Triangle {
  late double side1;
  late double side2;
  late double side3;
  static const String DEFAULT_COLOR  = 'blue'; // Static field

  Triangle(double s1, double s2, double s3) {
    bool valid = isValidSides(s1, s2, s3); // Check if all sides are valid.
    if(!valid) {
      throw ArgumentError('Invalid Sides!'); // Throw an Error.
    }
     side1 = s1;
     side2 = s2;
     side3 = s3;
  }
  static bool isValidSides(double s1, double s2, double s3)  {
     if(s1 < 0 || s2 < 0 || s3 < 0)  {
       return false;
     }
     return s1 + s2 > s3 && s1 + s3 > s2 && s2 + s3 > s1;
  }
  bool isEquilateralTriangle() {
      return side1 == side2 && side2 == side3;
  }
}
void main() {
   bool valid = Triangle.isValidSides(6, 8, 10);
   print('Are sides 6, 8 and 10 valid to make a triangle? $valid');
   var triangle = Triangle(3.0, 4.0, 5.0);
   // Check if the triangle is equilateral triangle.
   var check = triangle.isEquilateralTriangle();
   print('Is Equilateral Triangle? $check');
}
Output:
Are sides 6, 8 and 10 valid to make a triangle? true
Is Equilateral Triangle? false

4. Phương thức trừu tượng

Trong ngôn ngữ Dart, phương thức trừu tượng là một phương thức không tĩnh và không có thân (body).
abstract return_type method_name(arguments);
Một lớp có ít nhất một phương thức trừu tượng phải được khai báo là trừu tượng. Một trong các lớp con của nó sẽ ghi đè (override) các phương thức trừu tượng và viết nội dung cho chúng.
Ví dụ: Lớp Person dưới đây phải được khai báo là lớp trừu tượng vì nó có ít nhất một phương thức trừu tượng.
method_abstract_ex1.dart
abstract class Person {
  void sayHello(); // An abstract method.
}
class EnglishPerson extends Person {
  void sayHello() {
    print("Hello");
  }
}
class RussianPerson extends Person {
   void sayHello() {
      print("Привет");
   }
}
void main() {
    Person enPerson = EnglishPerson();
    enPerson.sayHello();
    Person ruPerson = RussianPerson();
    ruPerson.sayHello();
}
Output:
Hello
Привет
  • Dart Classes

5. Các tham số tuỳ chọn

Như đã đề cập ở trên, các lớp trong DartJavaScript không hỗ trợ các phương thức trùng tên, nhưng một phương thức có thể bao gồm các tham số tuỳ chọn.
Cú pháp:
// Non-static method with optional arguments:
return_type method_name(typeM argM, typeN argN,[typeP? argP, typeQ? argQ]){  
     // statement(s)  
}
// Static method with optional arguments:
static return_type method_name(typeM argM, typeN argN,[typeP? argP, typeQ? argQ]){   
     // statement(s)  
}
// Abstract method with optional arguments:
return_type method_name(typeM argM, typeN argN,[typeP? argP, typeQ? argQ]);
Hoặc cú pháp - Các tham số tuỳ chọn với giá trị mặc định:
// Non-static method with optional arguments:
return_type method_name(typeM argM, typeN argN,
                        [typeP? argP = defaultValueP, typeQ? argQ]){  
     // statement(s)  
}
// Static method with optional arguments:
static return_type method_name(typeM argM, typeN argN,
                        [typeP? argP = defaultValueP, typeQ? argQ]){   
     // statement(s)  
}
// Abstract method with optional arguments:
return_type method_name(typeM argM, typeN argN,
                        [typeP? argP = defaultValueP, typeQ? argQ]);
Ví dụ:
method_optional_args_ex1.dart
class MyUtility {
  static String concat(String s1, String s2, [String? s3]) {
    if (s3 != null) {
      return s1 + s2 + s3;
    }
    return s1 + s2;
  }
  static double sum(double v1, double v2, [double? v3, double? v4]) {
    return v1 + v2 + (v3 ?? 0) + (v4 ?? 0);
  }
}
void main() {
  String result1 = MyUtility.concat('One', 'Two');
  print('result1: $result1');
  String result2 = MyUtility.concat('One', 'Two', 'Three');
  print('result2: $result2');
  double value1 = MyUtility.sum(1, 2, 3, 4);
  print('value1: $value1');
  double value2 = MyUtility.sum(1, 2, 3);
  print('value2: $value2');
  double value3 = MyUtility.sum(1, 2);
  print('value3: $value3');
}
Output:
result1: OneTwo
result2: OneTwoThree
value1: 10.0
value2: 6.0
value3: 3.0
Ví dụ:
method_optional_args_ex2.dart
class Team {
   List<String> members = [];

   void addMembers(String member1, [String? member2, String? member3]) {
      members.add(member1);
      if(member2!= null) {
         members.add(member2);
      }
      if(member3!= null) {
         members.add(member3);
      }
   }
   void printAllMembers()  {
      print(' --- All members: --- ');
      for(var member in members) {
          print(member);
      }
   }
}  
void main() {
  var team = Team(); // Create Team object.
  team.addMembers('Tom', 'Jerry');
  team.printAllMembers();
}
Output:
--- All members: ---
Tom
Jerry

6. Các tham số tuỳ chọn được đặt tên

Các tham số tuỳ chọn được đặt tên (Optional Named Parameters) là các tham số được đặt trong cặp dấu ngặc { } và là các tham số cuối cùng trong danh sách các tham số. Tất cả các tham số này đều có giá trị mặc định.
Cú pháp:
// Non-static method with optional named parameters:
return_type method_name(typeM argM, typeN argN,
                  {typeP paramP = defaultValueP, typeQ paramQ = defaultValueQ}){  
     // statement(s)  
}
// Static method with optional arguments:
static return_type method_name(typeM argM, typeN argN,
                  {typeP paramP = defaultValueP, typeQ paramQ = defaultValueQ}){  
     // statement(s)  
}
// Abstract method with optional arguments:
return_type method_name(typeM argM, typeN argN,
                  {typeP paramP = defaultValueP, typeQ paramQ = defaultValueQ});
Ví dụ:
method_optional_named_args_ex1.dart
class StringUtility {
  static String concatAndTrim(String s1, String s2, {bool trimLeft = true, bool trimRight = true}) {
    var s = s1 + s2;
    if (trimLeft && trimRight) {
      return s.trim();
    } else if (trimLeft) {
      return s.trimLeft();
    } else if (trimRight) {
      return s.trimRight();
    }
    return s;
  }
}
void main() {
  var s1 = '  One  ';
  var s2 = '  Two  ';
  var result1 = StringUtility.concatAndTrim(s1, s2); // trim left and right
  print('result1: -->$result1<-- (Trim left and right)');

  var result2 = StringUtility.concatAndTrim(s1, s2, trimLeft: false); // trim right only
  print('result2: -->$result2<-- (Trim right only)');

  var result3 = StringUtility.concatAndTrim(s1, s2, trimRight: false); // trim left only
  print('result3: -->$result3<-- (Trim left only)' );

  var result4 = StringUtility.concatAndTrim(s1, s2, trimLeft: false, trimRight: false); // no trim
  print('result4: -->$result4<!-- (No Trim)');
}
Output:
result1: -->One    Two<-- (Trim left and right)
result2: -->  One    Two<-- (Trim right only)
result3: -->One    Two  <-- (Trim left only)
result4: -->  One    Two  <!-- (No Trim)

7. Ghi đè một phương thức

Lớp con có thể ghi đè (override) một phương thức của lớp cha nếu các điều kiện sau được thoả mãn:
  • Hai phương thức phải cùng tên và có các tham số giống nhau.
  • Kiểu trả về của 2 phương thức phải giống nhau hoặc kiểu trả về của phương thức tại lớp con phải là kiểu con của kiểu trả về của phương thức tại lớp cha.
Ví dụ:
  • Lớp Cat ghi đè (override) phương thức sayAnything() của lớp Animal.
  • @override là một chú thích (annotation), nó là một tuỳ chọn, được đặt phía trước của phương thức ghi đè (tại lớp con). Nếu bạn vô tình đổi tên phương thức này ở lớp cha, trình biên dịch Dart sẽ thông báo lỗi cho bạn.
method_override_ex1.dart
class Animal {
  void sayAnything() {
    print('<Nothing>');
  }
}
class Cat extends Animal {
  @override
  void sayAnything() { // Override method from parent class.
    print('Meow');
  }
}
void main() {
  Animal animal = Cat();
  animal.sayAnything();
}
Output:
Meow
Ví dụ: Sử dụng từ khoá super để gọi tới phương thức của lớp cha.
method_override_ex2.dart
class BoldFormatter {
  String formatText(String text) {
    return '<b>' + text + '</b>';
  }
}
class BoldItalicFormatter extends BoldFormatter {
  @override
  String formatText(String text) { // Override method from parent class.
    var boldText = super.formatText(text); // Call super method.
    return '<i>' + boldText + '</i>';
  }
}
void main() {
  var formatter = BoldItalicFormatter();
  var formattedText = formatter.formatText('Hello');
  print(formattedText);
}
Output:
<i><b>Hello</b></i>

8. Final method?

Trong ngôn ngữ Java, bạn có thể sử dụng từ khoá final cho một phương thức để đảm bảo rằng các lớp con không thể ghi đè (override) phương thức này. Vậy có gì đó tương tự trong Dart không?
package:meta cung cấp cho bạn @nonVirtual để ngăn chặn việc một lớp con ghi đè một phương thức nào đó của lớp cha. Nó giúp Analyzer (trình phân tích mã) thông báo lỗi nếu bạn vi phạm điều đó, nhưng không có hành động nào khác được thực hiện.
method_final_ex1.dart
import 'package:meta/meta.dart';

class Foo {
   @nonVirtual
   void bar() {
     print("bar from Foo!");
   }
}
class SubFoo extends Foo {
  @override
   void bar()  {
      print("bar from SubFoo");
   }
}
Về cơ bản, bạn sẽ nhận được một cảnh báo (warning) từ Analyzer giống như thế này:
The member 'bar' is declared non-virtual in 'Foo'
and can't be overridden in subclasses.dart(invalid_override_of_non_virtual_member)
  • Thêm thư viện vào dự án Dart