Sự khác biệt giữa các kiểu dữ liệu trừu tượng và các đối tượng là gì?


11

Một câu trả lời trên Lập trình viên. E mô tả một bài tiểu luận của Cook ( Đối tượng không phải là ADT ) khi nói

  • Các đối tượng hoạt động giống như một hàm đặc trưng trên các giá trị của một loại, chứ không phải là một đại số. Đối tượng sử dụng trừu tượng hóa thủ tục thay vì trừu tượng kiểu

  • ADT thường có một triển khai duy nhất trong một chương trình. Khi ngôn ngữ của một người có các mô-đun, có thể có nhiều triển khai ADT, nhưng chúng thường không thể tương tác với nhau.

Dường như với tôi, trong bài tiểu luận của Cook, tình cờ là ví dụ cụ thể của một tập hợp được sử dụng trong bài viết của Cook, một đối tượng có thể được xem như là một hàm đặc trưng . Tôi không nghĩ rằng các đối tượng, nói chung có thể được xem như là các chức năng đặc trưng.

Ngoài ra, bài viết của Aldritch Sức mạnh của khả năng tương tác: Tại sao các vật thể không thể tránh khỏi ¹ gợi ý

Định nghĩa của Cook về cơ bản xác định công văn động là đặc tính quan trọng nhất của đối tượng

đồng ý với điều này và với Alan Kay khi anh nói

OOP với tôi có nghĩa là chỉ nhắn tin, duy trì và bảo vệ cục bộ và che giấu quá trình nhà nước, và cực kỳ ràng buộc tất cả mọi thứ.

Tuy nhiên, các bài giảng đồng hành này trình bày trên bài báo của Aldritch cho thấy các lớp Java là ADT trong khi các giao diện Java là các đối tượng - và thực sự sử dụng các "đối tượng" giao diện có thể hoạt động lẫn nhau (một trong những tính năng chính của OOP được đưa ra bởi một trong các điểm đạn ở trên ).

Câu hỏi của tôi là

  1. Tôi có đúng không khi nói rằng các chức năng đặc trưng không phải là một tính năng chính của các đối tượng và Frank Shearar bị nhầm lẫn?

  2. Các dữ liệu nói chuyện với nhau thông qua các ví dụ về giao diện Java của các đối tượng mặc dù chúng không sử dụng công văn động? Tại sao? (Hiểu biết của tôi là công văn động linh hoạt hơn và các giao diện là một bước tiến tới nhắn tin theo kiểu mục tiêu-C / smalltalk / erlang.)

  3. Là ý tưởng của nguyên tắc đảo ngược phụ thuộc có liên quan đến sự phân biệt giữa ADT và các đối tượng? (Xem trang Wikipedia hoặc Đối tượng nói chuyện: Câu chuyện về lập trình hướng thông điệp ) Mặc dù tôi chưa quen với khái niệm này, tôi hiểu rằng nó liên quan đến việc thêm giao diện giữa các "lớp" của chương trình (xem sơ đồ trang wikipedia).

  4. Vui lòng cung cấp bất kỳ ví dụ / làm rõ khác về sự khác biệt giữa các đối tượng và ADT, nếu bạn muốn.

¹ Bài viết này (xuất bản năm 2013) là dễ dàng đọc và tóm tắt giấy Cook 2009 với ví dụ trong Java. Tôi đặc biệt khuyên bạn nên lướt qua nó, không trả lời câu hỏi này, mà chỉ vì đó là một bài báo hay.


5
Thú vị, nhưng hãy cố gắng hạn chế một câu hỏi cho mỗi bài viết.
Raphael

có vẻ như một phần của sự phân biệt / tranh luận học thuật (tinh tế?). rõ ràng các ADT gần như các đối tượng, ví dụ như trong java và các ngôn ngữ OOP hiện đại khác. trong nhiều ngôn ngữ OOP, sự trừu tượng được coi là cách mà các đối tượng mô hình hóa (theo cách bị ràng buộc / tập trung) vào thế giới thực. thấy cũng bối rối về định nghĩa "trừu tượng" trong OOP , Kỹ thuật phần mềm
vzn

Câu trả lời:


8

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ở đâ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 .


3

Nếu bạn nhìn vào những người đề xuất ADT, họ coi ADT là cái mà OOP sẽ gọi là một lớp (trạng thái nội bộ, riêng tư; một bộ hoạt động giới hạn được phép), nhưng về cơ bản không có mối quan hệ nào giữa các lớp (không kế thừa). Thay vào đó, điểm tương tự có thể thu được cùng một hành vi với các triển khai khác nhau. Ví dụ, một tập hợp có thể được thực hiện như một danh sách, các phần tử trong một mảng hoặc hàm băm hoặc một loại cây nào đó.


2

Tôi luôn hiểu nó theo cách này:

  1. Một ADT là một giao diện: nó chỉ là một tập hợp các phương thức, chữ ký loại của chúng, có thể với các điều kiện trước và sau.

  2. Một lớp có thể thực hiện một hoặc nhiều ADT, bằng cách đưa ra các triển khai thực tế cho các phương thức được chỉ định trong ADT.

  3. Một đối tượng là một thể hiện của một lớp, với bản sao riêng của bất kỳ biến không tĩnh nào.

Có thể trong tài liệu, sự khác biệt là khác nhau, nhưng đây là thuật ngữ "tiêu chuẩn" mà bạn sẽ nghe trong khoa học máy tính.

Ví dụ, trong Java, Collectionlà một ADT, ArrayListlà một lớp và bạn có thể tạo một ArrayListđối tượng với newtoán tử.

Đối với tuyên bố rằng ADT thường chỉ có một triển khai, điều này thường không phải là trường hợp. Ví dụ: có thể bạn sẽ muốn sử dụng cả từ điển dựa trên cây và có thể băm trong chương trình của mình, tùy thuộc vào nội dung bạn đang lưu trữ. Họ sẽ chia sẻ một ADT, nhưng sẽ sử dụng các triển khai khác nhau.


1
Từ góc độ lập trình chức năng, các ADT không có những hạn chế nhất định mà các lớp học nói chung không có?
Raphael

@Raphael Thích gì?
jmite

1
Đây là một quan điểm chung về ADT và nó là một xấp xỉ hợp lý. Tuy nhiên, theo tôi hiểu, ADT như được xem xét trong tài liệu PL và như được định nghĩa chính thức thực sự có một ý nghĩa cụ thể hơn. Một ADT là một đặc điểm kỹ thuật của một loại cấu trúc dữ liệu: không phải cách nó được triển khai hay cách thể hiện dữ liệu, mà là giao diện với nó (loại hoạt động nào có thể được thực hiện?) hành vi / ngữ nghĩa của từng hoạt động đó. Vì vậy, nó không chỉ là một giao diện Java (một danh sách các phương thức có chữ ký loại), mà còn là một đặc tả về hành vi của chúng.
DW

1
Chẳng hạn, ấn tượng của tôi là Collectiongiao diện Java không phải là một ADT. Nó cung cấp một danh sách các phương thức nhưng không chỉ định ngữ nghĩa của chúng. Nó có cung cấp ngữ nghĩa của một bộ không? một multiset (túi)? một danh sách đặt hàng? Điều đó không được chỉ định. Vì vậy, tôi không chắc nó được tính là một ADT. Đó là ấn tượng của tôi, nhưng hoàn toàn có thể là sự hiểu biết của tôi có thể sai ...
DW

Trong các slide bài giảng mà tôi đã liên kết đến, một lớp Java (thậm chí không phải là giao diện!) Được coi là một ADT, vì một lớp có cả phần riêng tư và công khai (tôi giả sử một phần của lớp sẽ được chỉ định không chính thức nhưng tôi không chắc) . Mặt khác, một lớp được truy cập thông qua một giao diện được coi là một đối tượng, với các phương thức được xác định bởi giao diện là "thông điệp" (ý định cấp cao). Khi các đối tượng nói chuyện với nhau thông qua ý định, việc triển khai khác nhau của một đối tượng có thể "nói chuyện" với nhau.
LMZ
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.