Google đưa ra một câu hỏi tương tự với một câu trả lời mà tôi nghĩ là rất tốt. Tôi đã trích dẫn nó dưới đây.
Có một sự khác biệt khác ẩn giấu ở đây được giải thích trong bài tiểu luận Cook tôi liên kết.
Đối tượng không phải là cách duy nhất để thực hiện trừu tượng. Không phải tất cả mọi thứ là một đối tượng. Các đối tượng thực hiện một cái gì đó mà một số người gọi là trừu tượng hóa dữ liệu thủ tục. Các kiểu dữ liệu trừu tượng thực hiện một hình thức trừu tượng khác nhau.
Một sự khác biệt chính xuất hiện khi bạn xem xét các phương thức / hàm nhị phân. Với sự trừu tượng hóa dữ liệu thủ tục (đối tượng), bạn có thể viết một cái gì đó như thế này cho giao diện Int set:
interface IntSet {
void unionWith(IntSet s);
...
}
Bây giờ hãy xem xét hai triển khai của Intset, giả sử một cái được hỗ trợ bởi danh sách và một cái được hỗ trợ bởi cấu trúc cây nhị phân hiệu quả hơn:
class ListIntSet implements IntSet {
void unionWith(IntSet s){ ... }
}
class BSTIntSet implements IntSet {
void unionWith(IntSet s){ ... }
}
Lưu ý rằng unionWith phải có một đối số Intset. Không phải loại cụ thể hơn như ListIntset hoặc BSTIntset. Điều này có nghĩa là việc triển khai BSTIntset không thể cho rằng đầu vào của nó là BSTIntset và sử dụng thực tế đó để đưa ra một triển khai hiệu quả. (Nó có thể sử dụng một số thông tin loại thời gian chạy để kiểm tra và sử dụng thuật toán hiệu quả hơn nếu có, nhưng nó vẫn có thể được thông qua ListIntset và phải quay lại thuật toán kém hiệu quả hơn).
So sánh điều này với ADT, nơi bạn có thể viết một cái gì đó giống như sau trong tệp chữ ký hoặc tiêu đề:
typedef struct IntSetStruct *IntSetType;
void union(IntSetType s1, IntSetType s2);
Chúng tôi lập trình chống lại giao diện này. Đáng chú ý, các loại là trái trừu tượng. Bạn không biết nó là gì. Sau đó, chúng tôi có triển khai BST sau đó cung cấp một loại cụ thể và hoạt động:
struct IntSetStruct {
int value;
struct IntSetStruct* left;
struct IntSetStruct* right;
}
void union(IntSetType s1, IntSetType s2){ ... }
Bây giờ union thực sự biết các biểu diễn cụ thể của cả s1 và s2, vì vậy nó có thể khai thác điều này để thực hiện hiệu quả. Chúng tôi cũng có thể viết một danh sách thực hiện được hỗ trợ và chọn liên kết với điều đó thay vào đó.
Tôi đã viết cú pháp C (ish), nhưng bạn nên xem ví dụ Standard ML cho các loại dữ liệu trừu tượng được thực hiện đúng (trong đó bạn có thể thực sự sử dụng nhiều hơn một triển khai ADT trong cùng một chương trình bằng cách đủ điều kiện các loại: BSTImpl. IntsetSturation và ListImpl.IntSetSturation, nói)
Điều ngược lại ở đây là sự trừu tượng hóa dữ liệu thủ tục (đối tượng) cho phép bạn dễ dàng giới thiệu các triển khai mới hoạt động với những cái cũ của bạn. ví dụ: bạn có thể viết triển khai LoggingIntset tùy chỉnh của riêng bạn và kết hợp nó với BSTIntset. Nhưng đây là một sự đánh đổi: bạn mất các loại thông tin cho các phương thức nhị phân! Thường thì bạn sẽ phải tiết lộ nhiều chức năng và chi tiết triển khai trong giao diện của mình hơn so với triển khai ADT. Bây giờ tôi cảm thấy như mình vừa đọc lại bài luận Cook, thật vậy, hãy đọc nó!
Tôi muốn thêm một ví dụ cho điều này.
Cook gợi ý rằng một ví dụ về kiểu dữ liệu trừu tượng là một mô-đun trong C. Thật vậy, các mô-đun trong C liên quan đến việc ẩn thông tin, vì có các hàm công khai được xuất qua tệp tiêu đề và các hàm tĩnh (riêng tư) không có. Ngoài ra, thường có các hàm tạo (ví dụ list_new ()) và các trình quan sát (ví dụ: list_getListHead ()).
Điểm mấu chốt của việc tạo ra một mô-đun danh sách có tên LIST_MODULE_SINGLYEYEDED một ADT là các chức năng của mô-đun (ví dụ list_getListHead ()) cho rằng dữ liệu được nhập bởi nhà xây dựng của LIST_MODULE_SINGLYEDED, trái ngược với bất kỳ " "Thực hiện danh sách (ví dụ: LIST_MODULE_DYNAMIC_ARRAY). Điều này có nghĩa là các chức năng của LIST_MODULE_SINGLY_LINKED có thể giả sử, trong quá trình triển khai của họ, một đại diện cụ thể (ví dụ: danh sách liên kết đơn).
LIST_MODULE_SINGLYEDED không thể hoạt động với LIST_MODULE_DYNAMIC_ARRAY vì chúng tôi không thể cung cấp dữ liệu được tạo ra, nói với người xây dựng LIST_MODULE_DYNAMIC_ARRAY, cho người quan sát của LIST_MODULE
Điều này tương tự như cách hai nhóm khác nhau từ đại số trừu tượng không thể tương tác với nhau (nghĩa là bạn không thể lấy sản phẩm của một phần tử của một nhóm với một phần tử của một nhóm khác). Điều này là do các nhóm giả định thuộc tính đóng của nhóm (sản phẩm của các phần tử trong một nhóm phải nằm trong nhóm). Tuy nhiên, nếu chúng ta có thể chứng minh rằng hai nhóm khác nhau trên thực tế là các nhóm con của một nhóm G khác, thì chúng ta có thể sử dụng sản phẩm của G để thêm hai yếu tố, một từ hai trong hai nhóm.
So sánh các ADT và các đối tượng
Cook liên kết sự khác biệt giữa các ADT và các đối tượng một phần cho vấn đề biểu hiện. Nói một cách đơn giản, các ADT được kết hợp với các hàm chung thường được triển khai trong các ngôn ngữ lập trình chức năng, trong khi các đối tượng được ghép với các "đối tượng" Java được truy cập thông qua các giao diện. Đối với mục đích của văn bản này, một hàm chung là một hàm có trong một số đối số ARGS và một kiểu TYPE (tiền điều kiện); dựa trên TYPE, nó chọn chức năng phù hợp và đánh giá nó với ARGS (điều kiện hậu). Cả hai hàm chung và các đối tượng đều thực hiện đa hình, nhưng với các hàm chung, lập trình viên KNOWS mà hàm này sẽ được thực hiện bởi hàm chung mà không cần nhìn vào mã của hàm chung. Mặt khác, với các đối tượng, lập trình viên không biết đối tượng sẽ xử lý các đối số như thế nào, trừ khi các lập trình viên nhìn vào mã của đối tượng.
Thông thường, vấn đề biểu hiện được nghĩ đến theo nghĩa "tôi có nhiều đại diện không?" so với "tôi có rất nhiều chức năng với ít đại diện". Trong trường hợp đầu tiên, ta nên tổ chức mã theo cách biểu diễn (như là phổ biến nhất, đặc biệt là trong Java). Trong trường hợp thứ hai, người ta nên tổ chức mã theo các hàm (tức là có một hàm chung duy nhất xử lý nhiều biểu diễn).
Nếu bạn sắp xếp mã của mình theo cách biểu diễn, thì, nếu bạn muốn thêm chức năng bổ sung, bạn buộc phải thêm chức năng cho mỗi đại diện của đối tượng; trong ý nghĩa này, thêm chức năng không phải là "phụ gia". Nếu bạn sắp xếp mã của mình theo chức năng, thì, nếu bạn muốn thêm một đại diện bổ sung - bạn buộc phải thêm đại diện cho mọi đối tượng; trong ý nghĩa này, thêm các đại diện trong không "phụ gia".
Lợi thế của ADT so với các đối tượng
Thêm chức năng là phụ gia
Có thể tận dụng kiến thức về việc đại diện cho một ADT cho hiệu suất, hoặc để chứng minh rằng ADT sẽ đảm bảo một số điều kiện hậu điều kiện đưa ra một điều kiện tiên quyết. Điều này có nghĩa là lập trình với các ADT là thực hiện đúng việc theo đúng thứ tự (kết hợp các điều kiện trước và điều kiện sau với điều kiện bài "mục tiêu").
Ưu điểm của các đối tượng so với ADT
Thêm đại diện trong phụ gia
Các đối tượng có thể tương tác
Có thể chỉ định các điều kiện trước / sau cho một đối tượng và xâu chuỗi các điều kiện này với nhau như trường hợp với ADT. Trong trường hợp này, ưu điểm của các đối tượng là (1) dễ dàng thay đổi biểu diễn mà không thay đổi giao diện và (2) các đối tượng có thể hoạt động lẫn nhau. Tuy nhiên, điều này đánh bại mục đích của OOP theo nghĩa của smalltalk. (xem phần "Phiên bản OOP của Alan Kay)
Công văn động là chìa khóa để OOP
Bây giờ, rõ ràng là công văn động (tức là ràng buộc muộn) là điều cần thiết cho lập trình hướng đối tượng. Điều này là để có thể định nghĩa các thủ tục theo cách chung, không giả sử một đại diện cụ thể. Để cụ thể - lập trình hướng đối tượng là dễ dàng trong python, bởi vì có thể lập trình các phương thức của một đối tượng theo cách không giả sử một đại diện cụ thể. Đây là lý do tại sao python không cần giao diện như Java.
Trong Java, các lớp là ADT. tuy nhiên, một lớp được truy cập thông qua giao diện mà nó triển khai là một đối tượng.
Phụ lục: Phiên bản OOP của Alan Kay
Alan Kay gọi rõ ràng các đối tượng là "gia đình của đại số" và Cook gợi ý rằng một ADT là một đại số. Do đó, Kay có nghĩa là một đối tượng là một gia đình ADT. Nghĩa là, một đối tượng là tập hợp của tất cả các lớp thỏa mãn giao diện Java.
Tuy nhiên, hình ảnh các vật thể được vẽ bởi Cook hạn chế hơn nhiều so với tầm nhìn của Alan Kay. Ông muốn các đối tượng hoạt động như các máy tính trong mạng hoặc như các tế bào sinh học. Ý tưởng là áp dụng nguyên tắc ít cam kết nhất đối với lập trình - để dễ dàng thay đổi các lớp ADT cấp thấp một khi các lớp cấp cao đã được xây dựng bằng cách sử dụng chúng. Với hình ảnh này, các giao diện Java quá hạn chế vì chúng không cho phép một đối tượng diễn giải ý nghĩa của một thông điệp hoặc thậm chí bỏ qua nó hoàn toàn.
Tóm lại, ý tưởng chính của các đồ vật, đối với Kay - không phải là chúng là một họ của đại số (như được nhấn mạnh bởi Cook). Thay vào đó, ý tưởng chính của Kay là áp dụng một mô hình hoạt động trong máy tính lớn (máy tính trong mạng) cho máy tính nhỏ (các đối tượng trong một chương trình).
chỉnh sửa: Một cách làm rõ khác về phiên bản OOP của Kay: Mục đích của các đối tượng là tiến gần hơn đến một lý tưởng khai báo. Chúng ta nên nói cho đối tượng biết phải làm gì - không nói cho nó biết bằng cách nào vi mô là trạng thái, như thông lệ với lập trình thủ tục và ADT. Thông tin thêm có thể được tìm thấy ở đây , ở đây , ở đây và ở đây .
sửa: Tôi tìm thấy một giải thích rất, rất tốt về định nghĩa OOP của Alan Kay ở đây .