Ghi đè noSuchMethod
Thuộc phần Lập trình hướng đối tượng trong hành trình Dart của Coddy. Bài học 74 trên 110.
Khi bạn gọi một phương thức không tồn tại trên một đối tượng, Dart thường ném ra lỗi NoSuchMethodError. Tuy nhiên, bạn có thể ghi đè phương thức đặc biệt noSuchMethod() để chặn các lần gọi này và xử lý chúng một cách phù hợp.
Để dùng noSuchMethod(), lớp của bạn cũng phải triển khai một interface hoặc dùng kiểu dynamic.
Phương thức nhận một đối tượng Invocation chứa thông tin về lời gọi được thử thực hiện:
class Flexible {
@override
dynamic noSuchMethod(Invocation invocation) {
var methodName = invocation.memberName.toString();
print('Called: $methodName');
return null;
}
}
void main() {
dynamic obj = Flexible();
obj.anyMethod(); // Đã gọi: Symbol("anyMethod")
obj.anotherOne(42); // Đã gọi: Symbol("anotherOne")
}Đối tượng Invocation cung cấp thông tin hữu ích về lời gọi: memberName cho bạn biết tên phương thức dưới dạng Symbol, positionalArguments chứa các đối số được truyền vào, còn isMethod cho biết đó là lời gọi phương thức hay getter hoặc setter.
class Logger {
@override
dynamic noSuchMethod(Invocation invocation) {
print('Method: ${invocation.memberName}');
print('Args: ${invocation.positionalArguments}');
return 'handled';
}
}
void main() {
dynamic log = Logger();
var result = log.save('data', 123);
print(result); // handled
}Kỹ thuật này hữu ích khi tạo đối tượng proxy, lớp mô phỏng để kiểm thử hoặc xây dựng API linh hoạt có thể phản hồi các lệnh gọi phương thức tùy ý. Tuy nhiên, chỉ nên dùng kỹ thuật này khi cần, vì nó bỏ qua bước kiểm tra kiểu dữ liệu tại thời điểm biên dịch.
Thử thách
DễHãy tạo một trình ghi nhật ký phương thức để chặn và ghi lại mọi lời gọi phương thức trên một đối tượng! Bạn sẽ tạo một lớp dùng noSuchMethod() để thu thập thông tin về các lời gọi phương thức chưa được định nghĩa và phản hồi phù hợp dựa trên tên phương thức.
Bạn sẽ sắp xếp code vào hai tệp:
method_logger.dart: Tạo một lớpMethodLoggerđể chặn các lời gọi đến phương thức chưa được định nghĩa. Trình ghi nhật ký của bạn cần ghi đènoSuchMethod()để:- Lấy tên phương thức từ lời gọi (tên này sẽ là một Symbol như
Symbol("methodName"), bạn cần chuyển nó thành chuỗi rồi lấy riêng tên phương thức) - In tên phương thức và mọi đối số vị trí theo định dạng này:
Called: [methodName] with args: [arguments] - Trả về phản hồi đặc biệt dựa trên tên phương thức:
- Nếu tên phương thức có chứa
get, hãy trả về chuỗi'data retrieved' - Nếu tên phương thức có chứa
save, hãy trả về chuỗi'data saved' - Nếu không, hãy trả về chuỗi
'unknown action'
- Nếu tên phương thức có chứa
- Lấy tên phương thức từ lời gọi (tên này sẽ là một Symbol như
main.dart: Nhập trình ghi nhật ký và minh họa cách nó xử lý nhiều lời gọi phương thức chưa được định nghĩa. Nhớ dùng kiểudynamicđể Dart cho phép gọi những phương thức không tồn tại:- Tạo một đối tượng
MethodLoggerbằng một biến kiểu dynamic - Gọi
getUserData('Alice', 42)trên đối tượng đó rồi in kết quả trả về - Gọi
saveRecord('important')trên đối tượng đó rồi in kết quả trả về - Gọi
processItem()trên đối tượng đó (không có đối số) rồi in kết quả trả về
- Tạo một đối tượng
Để lấy tên phương thức từ một Symbol, bạn có thể chuyển nó thành chuỗi rồi phân tích để lấy tên nằm giữa các dấu ngoặc kép. Ví dụ, Symbol("myMethod").toString() cho kết quả 'Symbol("myMethod")'.
Đầu ra mong đợi:
Called: getUserData with args: [Alice, 42]
data retrieved
Called: saveRecord with args: [important]
data saved
Called: processItem with args: []
unknown actionTự làm thử
import 'method_logger.dart';
void main() {
// TODO: Tạo một instance MethodLogger với kiểu dữ liệu dynamic
// Dùng dynamic để gọi các phương thức (method) chưa tồn tại tại thời điểm biên dịch
dynamic logger;
// TODO: Gọi getUserData('Alice', 42) và in ra kết quả
// TODO: Gọi saveRecord('important') và in ra kết quả
// TODO: Gọi processItem() không có đối số (argument) và in ra kết quả
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng về lập trình hướng đối tượng
Tệp bên ngoàiThư viện và câu lệnh importGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTừ khóa thisPhương thứcBiến thể hiệnKiến thức cơ bản về hàm khởi tạoÔn tập: Máy tính đơn giản4An toàn với giá trị null
Giới thiệu về an toàn với giá trị nullCó thể null và không thể nullToán tử ? và !Từ khóa late và an toàn với giá trị nullToán tử xử lý nullAn toàn với giá trị null trong lớpÔn tập: hệ thống hồ sơ người dùng7Lớp trừu tượng và interface
Lớp trừu tượngPhương thức trừu tượngInterface trong DartInterface ngầm địnhTriển khai và kế thừaNhiều interfaceÔn tập: Máy tính hình10Bộ sưu tập và kiểu tổng quát
Tổng quan về List, Set và MapBộ sưu tập an toàn về kiểuLớp tổng quátPhương thức tổng quátRàng buộc kiểu tổng quátIterable và IteratorÔn tập: Lưu trữ tổng quát2Hàm khởi tạo trong Dart
Hàm khởi tạo mặc địnhHàm khởi tạo có tênDanh sách khởi tạoHàm khởi tạo hằng sốHàm khởi tạo nhà máyHàm khởi tạo chuyển hướngÔn tập: Trình tạo hình dạng5Đóng gói
Thành viên công khai và riêng tưQuy ước tiền tố _Tính riêng tư ở cấp thư việnTìm hiểu sâu về getter và setterChe giấu thông tinÔn tập: hồ sơ học sinh8Mixin
Giới thiệu về MixinTạo MixinSử dụng nhiều MixinTừ khóa on trong MixinMixin và kế thừaMixin và giao diệnÔn tập: hệ thống động vật11Phương thức đặc biệt
Ghi đè toString()Ghi đè hashCode và ==Giao diện ComparablePhương thức call()Ghi đè noSuchMethodÔn tập: bộ sưu tập tùy chỉnh3Thuộc tính của lớp
Thành viên instance và thành viên tĩnhTrường final và constBiến latePhương thức và trường tĩnhGetter và setterÔn tập: trình quản lý tài khoản ngân hàng6Kế thừa
Kế thừa cơ bảnTừ khóa superGhi đè phương thứcChú thích @overrideTừ khóa lớp finalHàm khởi tạo và kế thừaÔn tập: phân cấp nhân viên9Đa hình
Khái niệm cơ bản về đa hìnhĐa hình qua giao diệnKiểm tra kiểu lúc chạyToán tử is và asTừ khóa hiệp biếnÔn tập: Bộ xử lý thanh toánTự luyện tập: Trình biên dịch Dart online