Thuật ngữ: Tôi sẽ đề cập đến cấu trúc ngôn ngữ interface
như giao diện và giao diện của một loại hoặc đối tượng là bề mặt (vì thiếu một thuật ngữ tốt hơn).
Khớp nối lỏng lẻo có thể đạt được bằng cách có một đối tượng phụ thuộc vào sự trừu tượng thay vì một loại cụ thể.
Chính xác.
Điều này cho phép ghép lỏng vì hai lý do chính: 1 - trừu tượng ít có khả năng thay đổi hơn các loại cụ thể, điều đó có nghĩa là mã phụ thuộc ít có khả năng bị phá vỡ. 2 - các loại bê tông khác nhau có thể được sử dụng trong thời gian chạy, bởi vì tất cả chúng đều phù hợp với sự trừu tượng. Các loại bê tông mới cũng có thể được thêm vào sau mà không cần thay đổi mã phụ thuộc hiện có.
Không hoàn toàn chính xác. Các ngôn ngữ hiện tại thường không dự đoán rằng một sự trừu tượng sẽ thay đổi (mặc dù có một số mẫu thiết kế để xử lý điều đó). Tách biệt cụ thể khỏi những điều chung chung là trừu tượng. Điều này thường được thực hiện bởi một số lớp trừu tượng . Lớp này có thể được thay đổi thành một số chi tiết cụ thể khác mà không vi phạm mã xây dựng dựa trên sự trừu tượng hóa này - đạt được khớp nối lỏng lẻo. Ví dụ không phải OOP: Một sort
thói quen có thể được thay đổi từ Quicksort trong phiên bản 1 sang Tim Sắp xếp trong phiên bản 2. Mã chỉ phụ thuộc vào kết quả được sắp xếp (nghĩa là được xây dựng dựa trên sự sort
trừu tượng hóa) do đó được tách rời khỏi triển khai sắp xếp thực tế.
Những gì tôi gọi là bề mặt ở trên là phần chung của một sự trừu tượng. Bây giờ xảy ra trong OOP rằng một đối tượng đôi khi phải hỗ trợ nhiều trừu tượng. Một ví dụ không hoàn toàn tối ưu: Java java.util.LinkedList
hỗ trợ cả List
giao diện về giao diện trừu tượng có trật tự, bộ sưu tập có thể lập chỉ mục, và hỗ trợ Queue
giao diện (nói một cách thô thiển) là về sự trừu tượng hóa của FIFO.
Làm thế nào một đối tượng có thể hỗ trợ nhiều trừu tượng?
C ++ không có giao diện, nhưng nó có nhiều kế thừa, phương thức ảo và các lớp trừu tượng. Một sự trừu tượng hóa sau đó có thể được định nghĩa là một lớp trừu tượng (nghĩa là một lớp không thể được khởi tạo ngay lập tức) mà khai báo, nhưng không định nghĩa các phương thức ảo. Các lớp thực hiện các chi tiết cụ thể của một sự trừu tượng sau đó có thể kế thừa từ lớp trừu tượng đó và thực hiện các phương thức ảo cần thiết.
Vấn đề ở đây là việc thừa kế nhiều lần có thể dẫn đến vấn đề kim cương , trong đó thứ tự các lớp được tìm kiếm để thực hiện phương thức (thứ tự giải quyết phương thức MRO) có thể dẫn đến mâu thuẫn trên băng. Có hai câu trả lời cho điều này:
Xác định một trật tự lành mạnh và từ chối các đơn đặt hàng không thể được tuyến tính hóa hợp lý. Các C3 MRO là khá hợp lý và hoạt động tốt. Nó được xuất bản năm 1996.
Đi theo con đường dễ dàng và từ chối nhiều kế thừa trong suốt.
Java đã chọn tùy chọn thứ hai và chọn kế thừa hành vi đơn. Tuy nhiên, chúng ta vẫn cần khả năng của một đối tượng để hỗ trợ nhiều trừu tượng. Do đó, các giao diện phải được sử dụng không hỗ trợ các định nghĩa phương thức, chỉ khai báo.
Kết quả là MRO là hiển nhiên (chỉ cần nhìn vào từng siêu lớp theo thứ tự) và đối tượng của chúng ta có thể có nhiều bề mặt cho bất kỳ số lượng trừu tượng nào.
Điều này hóa ra là không thỏa đáng, bởi vì thường thì một chút hành vi là một phần của bề mặt. Hãy xem xét một Comparable
giao diện:
interface Comparable<T> {
public int cmp(T that);
public boolean lt(T that); // less than
public boolean le(T that); // less than or equal
public boolean eq(T that); // equal
public boolean ne(T that); // not equal
public boolean ge(T that); // greater than or equal
public boolean gt(T that); // greater than
}
Điều này rất thân thiện với người dùng (một API đẹp với nhiều phương thức thuận tiện), nhưng tẻ nhạt để thực hiện. Chúng tôi muốn giao diện chỉ bao gồm cmp
và tự động thực hiện các phương thức khác theo phương thức được yêu cầu đó. Mixins , nhưng quan trọng hơn là Đặc điểm [ 1 ], [ 2 ] giải quyết vấn đề này mà không rơi vào bẫy của nhiều kế thừa.
Điều này được thực hiện bằng cách định nghĩa một thành phần tính trạng để các tính trạng không thực sự tham gia vào MRO - thay vào đó các phương thức được xác định được đưa vào lớp triển khai.
Các Comparable
giao diện có thể được thể hiện bằng Scala như
trait Comparable[T] {
def cmp(that: T): Int
def lt(that: T): Boolean = this.cmp(that) < 0
def le(that: T): Boolean = this.cmp(that) <= 0
...
}
Khi một lớp sau đó sử dụng đặc điểm đó, các phương thức khác sẽ được thêm vào định nghĩa lớp:
// "extends" isn't different from Java's "implements" in this case
case class Inty(val x: Int) extends Comparable[Inty] {
override def cmp(that: Inty) = this.x - that.x
// lt etc. get added automatically
}
Vì vậy, Inty(4) cmp Inty(6)
sẽ -2
và Inty(4) lt Inty(6)
sẽ được true
.
Nhiều ngôn ngữ có một số hỗ trợ cho các đặc điểm và bất kỳ ngôn ngữ nào có Giao thức Metaobject Giao thức (MOP) có thể có các đặc điểm được thêm vào đó. Bản cập nhật Java 8 gần đây đã thêm các phương thức mặc định tương tự như các đặc điểm (các phương thức trong giao diện có thể có các triển khai dự phòng để tùy chọn triển khai các lớp để thực hiện các phương thức này).
Thật không may, đặc điểm là một phát minh khá gần đây (2002), và do đó khá hiếm trong các ngôn ngữ chính lớn hơn.