Menu
CoddyTech

Mixin và giao diện

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 55 trên 110.

Bạn đã thấy mixin khác với kế thừa như thế nào. Giờ hãy so sánh mixin với interface, vì cả hai đều cho phép bạn làm việc với nhiều kiểu dữ liệu. Điểm khác biệt chính nằm ở những gì chúng cung cấp: interface xác định một hợp đồng, còn mixin cung cấp cách triển khai.

Khi triển khai một giao diện, bạn cam kết cung cấp một số phương thức nhất định, nhưng phải tự viết toàn bộ code. Khi dùng mixin, bạn có sẵn code mà không cần viết.

// Cách dùng interface: bạn phải tự triển khai mọi thứ
abstract class Printable {
  void printInfo();
}

class Report implements Printable {
  @override
  void printInfo() {
    print('Printing report...');  // Bạn viết phần này
  }
}

// Cách dùng mixin: đã có sẵn phần triển khai
mixin Printable {
  void printInfo() {
    print('Printing...');  // Phần này đã được viết sẵn cho bạn
  }
}

class Report with Printable {}  // Tự động có printInfo()

Vậy khi nào bạn nên dùng từng loại? Hãy dùng interface khi các lớp khác nhau cần cùng chữ ký phương thức nhưng cách triển khai khác nhau. Hãy dùng mixin khi các lớp cần chia sẻ chính xác cùng một hành vi.

Tính năngGiao diệnMixin
Cung cấpChỉ hợp đồngHợp đồng và code
Cách triển khaiBạn tự viếtCó sẵn
Từ khóaimplementswith
Phù hợp nhất vớiCác hành vi khác nhau, cùng APIHành vi dùng chung

Bạn thậm chí có thể kết hợp cả hai cách: dùng interface để xác định lớp có thể làm gì, và dùng mixin để cung cấp các cách triển khai dùng chung khi phù hợp. Trong Dart, khi kết hợp cả hai, luôn đặt with trước implements:

class MyClass with SomeMixin implements SomeInterface {
  // with đứng trước implements
}
challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống thông báo để minh họa khi nào nên dùng giao diện (interface) và mixin. Bạn sẽ tạo một giao diện cho các thông báo cần những cách triển khai khác nhau, cùng một mixin cho hành vi ghi nhật ký dùng chung, vốn cần giống nhau ở tất cả các loại thông báo.

Bạn sẽ sắp xếp code thành ba tệp:

  • notifiable.dart: Định nghĩa một lớp trừu tượng Notifiable đóng vai trò như một giao diện. Lớp này cần khai báo hai phương thức trừu tượng: send(String message) và getChannel(), phương thức sau trả về một String. Mỗi loại thông báo sẽ triển khai các phương thức này theo cách riêng, tùy vào kênh cụ thể.
  • logging.dart: Tạo một mixin Logging cung cấp hành vi dùng chung cho mọi thông báo. Thêm phương thức logSent(String channel, String message) để in ra [LOG] Sent via [channel]: [message] và phương thức logTimestamp() để in ra [LOG] Timestamp recorded. Hành vi này giống nhau ở mọi loại thông báo, nên rất phù hợp để đưa vào mixin.
  • main.dart: Nhập cả hai tệp và tạo hai lớp thông báo kết hợp giao diện với mixin:
    • Một lớp EmailNotification sử dụng mixin Logging và triển khai Notifiable. Phương thức getChannel() cần trả về 'Email', còn phương thức send() cần in ra Sending email: [message], sau đó gọi logSent() với kênh và thông điệp, rồi gọi logTimestamp()
    • Một lớp SMSNotification sử dụng mixin Logging và triển khai Notifiable. Phương thức getChannel() cần trả về 'SMS', còn phương thức send() cần in ra Sending SMS: [message], sau đó gọi logSent() với kênh và thông điệp, rồi gọi logTimestamp()
    Sau đó, tạo một EmailNotification và gọi send('Hello World'), in ra một dòng trống, tạo một SMSNotification và gọi send('Quick update')

Lưu ý rằng giao diện buộc mỗi lớp phải tự viết phần triển khai send() và getChannel() (hành vi khác nhau), còn mixin cung cấp các phương thức ghi nhật ký hoạt động giống nhau ở cả hai lớp (hành vi dùng chung).

Đầu ra dự kiến:

Sending email: Hello World
[LOG] Sent via Email: Hello World
[LOG] Timestamp recorded

Sending SMS: Quick update
[LOG] Sent via SMS: Quick update
[LOG] Timestamp recorded

Tự làm thử

import 'notifiable.dart';
import 'logging.dart';

// TODO: Tạo lớp EmailNotification có các đặc điểm sau:
//       - sử dụng mixin Logging (with)
//       - triển khai Notifiable (implements)
//       - getChannel() trả về 'Email'
//       - send() in ra 'Sending email: [message]', rồi gọi logSent(), sau đó gọi logTimestamp()

// TODO: Tạo lớp SMSNotification có các đặc điểm sau:
//       - sử dụng mixin Logging (with)
//       - triển khai Notifiable (implements)
//       - getChannel() trả về 'SMS'
//       - send() in ra 'Sending SMS: [message]', rồi gọi logSent(), sau đó gọi logTimestamp()

void main() {
  // TODO: Tạo một EmailNotification và gọi send('Hello World')
  
  // TODO: In ra một dòng trống
  
  // TODO: Tạo một SMSNotification và gọi send('Quick update')
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch Dart online