Có nhiều loại đa hình khác nhau, một trong những mối quan tâm thường là đa hình thời gian chạy / công văn động.
Một mô tả rất cao về đa hình thời gian chạy là một cuộc gọi phương thức thực hiện những điều khác nhau tùy thuộc vào kiểu thời gian chạy của các đối số của nó: chính đối tượng chịu trách nhiệm giải quyết một cuộc gọi phương thức. Điều này cho phép một lượng lớn tính linh hoạt.
Một trong những cách phổ biến nhất để sử dụng tính linh hoạt này là tiêm phụ thuộc , ví dụ để tôi có thể chuyển đổi giữa các triển khai khác nhau hoặc tiêm các đối tượng giả để thử nghiệm. Nếu tôi biết trước rằng sẽ chỉ có một số lượng hạn chế các lựa chọn có thể, tôi có thể cố gắng mã hóa chúng bằng các điều kiện, ví dụ:
void foo() {
if (isTesting) {
... // do mock stuff
} else {
... // do normal stuff
}
}
Điều này làm cho mã khó theo dõi. Cách khác là giới thiệu một giao diện cho hoạt động foo đó và viết một triển khai bình thường và thực hiện giả của giao diện đó, và tiêm chích vào các triển khai mong muốn trong thời gian chạy. Căng tin phụ thuộc vào phòng ăn là một thuật ngữ phức tạp cho việc vượt qua đối tượng chính xác như là một đối số.
Là một ví dụ thực tế, tôi hiện đang làm việc với một vấn đề máy học tử tế. Tôi có một thuật toán đòi hỏi một mô hình dự đoán. Nhưng tôi muốn thử các thuật toán học máy khác nhau. Vì vậy, tôi xác định một giao diện. Tôi cần gì từ mô hình dự đoán của tôi? Cho một số mẫu đầu vào, dự đoán và lỗi của nó:
interface Model {
def predict(sample) -> (prediction: float, std: float);
}
Thuật toán của tôi có một hàm xuất xưởng đào tạo một mô hình:
def my_algorithm(..., train_model: (observations) -> Model, ...) {
...
Model model = train_model(observations);
...
y, std = model.predict(x)
...
}
Bây giờ tôi có nhiều triển khai khác nhau của giao diện mô hình và có thể so sánh chúng với nhau. Một trong những triển khai này thực sự cần hai mô hình khác và kết hợp chúng thành một mô hình được tăng cường. Vì vậy, nhờ giao diện này:
- thuật toán của tôi không cần biết về các mô hình cụ thể trước,
- Tôi có thể dễ dàng trao đổi các mô hình, và
- Tôi có rất nhiều sự linh hoạt trong việc thực hiện các mô hình của tôi.
Một trường hợp sử dụng cổ điển của đa hình là trong GUI. Trong một khung GUI như Java AWT / Swing /, có các thành phần khác nhau . Giao diện thành phần / lớp cơ sở mô tả các hành động như vẽ chính nó lên màn hình hoặc phản ứng với các nhấp chuột. Nhiều thành phần là các container quản lý các thành phần phụ. Làm thế nào một container như vậy có thể tự vẽ?
void paint(Graphics g) {
super.paint(g);
for (Component child : this.subComponents)
child.paint(g);
}
Ở đây, container không cần biết về các loại chính xác của các thành phần con trước - miễn là chúng phù hợp với Component
giao diện, container có thể chỉ cần gọi paint()
phương thức đa hình . Điều này cho tôi tự do mở rộng hệ thống phân cấp lớp AWT với các thành phần mới tùy ý.
Có nhiều vấn đề định kỳ trong suốt quá trình phát triển phần mềm có thể được giải quyết bằng cách áp dụng đa hình như một kỹ thuật. Các cặp giải pháp vấn đề định kỳ này được gọi là các mẫu thiết kế và một số trong số chúng được thu thập trong cuốn sách cùng tên. Theo thuật ngữ của cuốn sách đó, mô hình học máy được tiêm của tôi sẽ là một chiến lược mà tôi sử dụng để định nghĩa một họ các thuật toán, gói gọn từng thuật toán và biến chúng thành hoán đổi cho nhau. Ví dụ Java-AWT trong đó một thành phần có thể chứa các thành phần phụ là một ví dụ về hỗn hợp .
Nhưng không phải mọi thiết kế đều cần sử dụng đa hình (ngoài việc cho phép tiêm phụ thuộc để thử nghiệm đơn vị, đây là trường hợp sử dụng thực sự tốt). Hầu hết các vấn đề là rất tĩnh. Kết quả là, các lớp và các phương thức thường không được sử dụng cho đa hình, mà chỉ đơn giản là các không gian tên thuận tiện và cho cú pháp gọi phương thức đẹp. Ví dụ, nhiều nhà phát triển thích các cuộc gọi phương thức như account.getBalance()
trên một cuộc gọi chức năng phần lớn tương đương Account_getBalance(account)
. Đó là một cách tiếp cận hoàn toàn tốt, chỉ là nhiều cuộc gọi của Phương thức mà không liên quan gì đến đa hình.