Có lẽ đầu tiên là hữu ích để phân biệt giữa một loại và một lớp và sau đó đi sâu vào sự khác biệt giữa phân nhóm và phân lớp.
Đối với phần còn lại của câu trả lời này, tôi sẽ giả định rằng các loại trong cuộc thảo luận là các loại tĩnh (vì việc phân nhóm thường xuất hiện trong một bối cảnh tĩnh).
Tôi sẽ phát triển một mã giả đồ chơi để giúp minh họa sự khác biệt giữa một loại và một lớp vì hầu hết các ngôn ngữ đều giới thiệu chúng ít nhất một phần (vì lý do chính đáng mà tôi sẽ chạm vào một chút).
Hãy bắt đầu với một loại. Một loại là một nhãn cho một biểu thức trong mã của bạn. Giá trị của nhãn này và liệu nó có nhất quán (đối với một số định nghĩa nhất quán theo hệ thống cụ thể) với tất cả giá trị của các nhãn khác có thể được xác định bởi một chương trình bên ngoài (một máy đánh chữ) mà không cần chạy chương trình của bạn. Đó là những gì làm cho các nhãn này đặc biệt và xứng đáng với tên riêng của họ.
Trong ngôn ngữ đồ chơi của chúng tôi, chúng tôi có thể cho phép tạo ra các nhãn như vậy.
declare type Int
declare type String
Sau đó, chúng tôi có thể gắn nhãn các giá trị khác nhau thuộc loại này.
0 is of type Int
1 is of type Int
-1 is of type Int
...
"" is of type String
"a" is of type String
"b" is of type String
...
Với các câu lệnh này, máy đánh chữ của chúng tôi giờ đây có thể từ chối các câu lệnh như
0 is of type String
nếu một trong những yêu cầu của hệ thống loại của chúng tôi là mọi biểu thức có một loại duy nhất.
Bây giờ chúng ta hãy bỏ qua việc này thật rắc rối và bạn sẽ gặp vấn đề như thế nào khi gán vô số loại biểu thức. Chúng ta có thể quay lại nó sau.
Mặt khác, một lớp là một tập hợp các phương thức và các trường được nhóm lại với nhau (có khả năng với các sửa đổi truy cập như là riêng tư hoặc công khai).
class StringClass:
defMethod concatenate(otherString): ...
defField size: ...
Một thể hiện của lớp này có khả năng tạo hoặc sử dụng các định nghĩa có sẵn của các phương thức và trường này.
Chúng ta có thể chọn liên kết một lớp với một loại sao cho mọi phiên bản của một lớp được tự động gắn nhãn với loại đó.
associate StringClass with String
Nhưng không phải loại nào cũng cần có một lớp liên kết.
# Hmm... Doesn't look like there's a class for Int
Cũng có thể hình dung rằng trong ngôn ngữ đồ chơi của chúng ta, không phải lớp nào cũng có một loại, đặc biệt là nếu không phải tất cả các biểu thức của chúng ta đều có loại. Sẽ khó hơn một chút (nhưng không phải là không thể) để tưởng tượng các quy tắc nhất quán của hệ thống sẽ trông như thế nào nếu một số biểu thức có kiểu và một số thì không.
Hơn nữa, trong ngôn ngữ đồ chơi của chúng tôi, các hiệp hội này không phải là duy nhất. Chúng ta có thể liên kết hai lớp với cùng một loại.
associate MyCustomStringClass with String
Bây giờ hãy nhớ rằng không có yêu cầu nào cho trình đánh máy của chúng tôi để theo dõi giá trị của một biểu thức (và trong hầu hết các trường hợp, nó sẽ không hoặc không thể làm như vậy). Tất cả những gì nó biết là nhãn bạn đã nói với nó. Như một lời nhắc nhở trước đây, người đánh máy chỉ có thể từ chối câu lệnh 0 is of type String
vì quy tắc loại được tạo giả tạo của chúng tôi rằng các biểu thức phải có các kiểu duy nhất và chúng tôi đã gắn nhãn cho biểu thức 0
khác. Nó không có bất kỳ kiến thức đặc biệt nào về giá trị của 0
.
Vậy còn phân nhóm thì sao? Subtyping cũng là một tên cho một quy tắc phổ biến trong việc đánh máy giúp thư giãn các quy tắc khác mà bạn có thể có. Cụ thể là nếu A is subtype of B
sau đó ở khắp mọi nơi máy đánh chữ của bạn yêu cầu một nhãn B
, nó cũng sẽ chấp nhận A
.
Ví dụ, chúng tôi có thể làm như sau cho các số của chúng tôi thay vì những gì chúng tôi đã có trước đây.
declare type NaturalNum
declare type Int
NaturalNum is subtype of Int
0 is of type NaturalNum
1 is of type NaturalNum
-1 is of type Int
...
Phân lớp là một cách viết tắt để khai báo một lớp mới cho phép bạn sử dụng lại các phương thức và trường đã khai báo trước đó.
class ExtendedStringClass is subclass of StringClass:
# We get concatenate and size for free!
def addQuestionMark: ...
Chúng tôi không phải trường hợp sư ExtendedStringClass
với String
giống như chúng ta đã làm với StringClass
từ đó, sau khi tất cả đó là một lớp hoàn toàn mới, chúng tôi chỉ không phải ghi càng nhiều. Điều này sẽ cho phép chúng tôi đưa ra ExtendedStringClass
một loại không tương thích với String
quan điểm của người đánh máy.
Tương tự như vậy, chúng tôi có thể đã quyết định tạo ra một lớp hoàn toàn mới NewClass
và thực hiện
associate NewClass with String
Bây giờ mọi trường hợp StringClass
có thể được thay thế bằng NewClass
quan điểm của người đánh máy.
Vì vậy, trong lý thuyết phân nhóm và phân lớp là những thứ hoàn toàn khác nhau. Nhưng không có ngôn ngữ nào tôi biết có loại và lớp thực sự làm mọi thứ theo cách này. Hãy bắt đầu giảm ngôn ngữ của chúng tôi và giải thích lý do đằng sau một số quyết định của chúng tôi.
Trước hết, mặc dù về mặt lý thuyết, các lớp hoàn toàn khác nhau có thể được cung cấp cùng loại hoặc một lớp có thể được cung cấp cùng loại với các giá trị không phải là phiên bản của bất kỳ lớp nào, điều này cản trở nghiêm trọng tính hữu ích của trình đánh máy. Trình đánh máy được đánh cắp một cách hiệu quả khả năng kiểm tra xem phương thức hoặc trường bạn đang gọi trong một biểu thức có thực sự tồn tại trên giá trị đó hay không, đây có thể là một kiểm tra mà bạn thích nếu bạn gặp rắc rối khi chơi cùng với máy đánh chữ. Rốt cuộc, ai biết giá trị thực sự bên dưới String
nhãn đó là gì; nó có thể là một cái gì đó không có, ví dụ, một concatenate
phương pháp nào cả!
Được rồi, vì vậy hãy quy định rằng mỗi lớp sẽ tự động tạo ra một loại mới cùng tên với các thể hiện của lớp đó và associate
s với loại đó. Điều đó cho phép chúng ta thoát khỏi associate
cũng như các tên khác nhau giữa StringClass
và String
.
Vì lý do tương tự, có lẽ chúng ta muốn tự động thiết lập mối quan hệ phụ giữa các loại của hai lớp trong đó một lớp là lớp con của lớp khác. Sau khi tất cả các lớp con được đảm bảo có tất cả các phương thức và các trường mà lớp cha thực hiện, nhưng điều ngược lại là không đúng. Do đó, trong khi lớp con có thể vượt qua bất cứ lúc nào bạn cần một loại lớp cha, loại của lớp cha nên bị loại bỏ nếu bạn cần loại của lớp con.
Nếu bạn kết hợp điều này với quy định rằng tất cả các giá trị do người dùng xác định phải là các thể hiện của một lớp, thì bạn có thể phải is subclass of
thực hiện nhiệm vụ kép và loại bỏ is subtype of
.
Và điều này đưa chúng ta đến những đặc điểm mà hầu hết các ngôn ngữ OO được gõ tĩnh phổ biến chia sẻ. Có một tập các kiểu "nguyên thủy" (ví dụ int
, float
vv) mà không liên quan đến bất kỳ lớp và không phải là người dùng định nghĩa. Sau đó, bạn có tất cả các lớp do người dùng định nghĩa sẽ tự động có các loại cùng tên và xác định phân lớp với phân nhóm.
Lưu ý cuối cùng tôi sẽ đưa ra là về sự vụng về của việc khai báo các loại tách biệt với các giá trị. Hầu hết các ngôn ngữ liên quan đến việc tạo ra hai, do đó, một khai báo kiểu cũng là một khai báo để tạo các giá trị hoàn toàn mới được tự động gắn nhãn với loại đó. Ví dụ, một khai báo lớp thường tạo cả kiểu cũng như cách khởi tạo các giá trị của kiểu đó. Điều này được loại bỏ một số sự vụng về và, với sự có mặt của các nhà xây dựng, cũng cho phép bạn tạo nhãn vô hạn nhiều giá trị với một kiểu trong một nét.