Lập trình thủ tục / chức năng không yếu hơn OOP , thậm chí không đi sâu vào các đối số Turing (ngôn ngữ của tôi có sức mạnh Turing và có thể làm bất cứ điều gì khác sẽ làm), điều đó không có nghĩa gì nhiều. Trên thực tế, các kỹ thuật hướng đối tượng đã được thử nghiệm đầu tiên bằng các ngôn ngữ không có sẵn chúng. Theo nghĩa này, lập trình OO chỉ là một kiểu lập trình thủ tục cụ thể . Nhưng nó giúp thực thi các quy tắc cụ thể, chẳng hạn như mô-đun, trừu tượng hóa và ẩn thông tin rất cần thiết cho sự hiểu biết và bảo trì chương trình.
Một số mô hình lập trình phát triển từ tầm nhìn lý thuyết về tính toán. Một ngôn ngữ như Lisp phát triển từ lambda-tính toán và ý tưởng về tính tuần hoàn của các ngôn ngữ (tương tự như tính phản xạ trong ngôn ngữ tự nhiên). Mệnh đề sừng cha Prolog và lập trình ràng buộc. Gia đình Algol cũng nợ lambda-tính, nhưng không có tính phản xạ tích hợp.
Lisp là một ví dụ thú vị, vì nó đã là minh chứng cho sự đổi mới ngôn ngữ lập trình, có thể truy nguyên theo di sản kép của nó.
Tuy nhiên, ngôn ngữ sau đó phát triển, thường dưới tên mới. Một yếu tố chính của sự tiến hóa là thực hành lập trình. Người dùng xác định các thực tiễn lập trình cải thiện các thuộc tính của các chương trình như khả năng đọc, khả năng duy trì, khả năng chính xác. Sau đó, họ cố gắng thêm vào các tính năng ngôn ngữ hoặc các ràng buộc sẽ hỗ trợ và đôi khi thực thi các thực tiễn này để cải thiện chất lượng của các chương trình.
Điều này có nghĩa là những thực tiễn này đã có sẵn trong ngôn ngữ lập trình cũ hơn, nhưng cần có sự hiểu biết và kỷ luật để sử dụng chúng. Việc kết hợp chúng vào các ngôn ngữ mới như các khái niệm chính với cú pháp cụ thể làm cho các thực tiễn này dễ sử dụng và dễ hiểu hơn, đặc biệt đối với người dùng ít tinh vi hơn (ví dụ, đại đa số). Nó cũng làm cho cuộc sống dễ dàng hơn một chút cho người dùng tinh vi.
Theo một cách nào đó, nó là để thiết kế ngôn ngữ chương trình con / hàm / thủ tục là gì đối với chương trình. Khi khái niệm hữu ích được xác định, nó được đặt tên (có thể) và cú pháp, để có thể dễ dàng sử dụng nó trong tất cả các chương trình được phát triển với ngôn ngữ đó. Và khi thành công, nó cũng sẽ được kết hợp trong các ngôn ngữ trong tương lai.
Ví dụ: tái tạo hướng đối tượng
Bây giờ tôi cố gắng minh họa điều đó trên một ví dụ (chắc chắn có thể được đánh bóng thêm, theo thời gian nhất định). Mục đích của ví dụ này không phải là để chỉ ra rằng một chương trình hướng đối tượng có thể được viết theo kiểu lập trình thủ tục, có thể phải trả giá bằng tính khả thi và khả năng duy trì. Tôi sẽ cố gắng chỉ ra rằng một số ngôn ngữ không có cơ sở OO thực sự có thể sử dụng các hàm và cấu trúc dữ liệu bậc cao hơn để thực sự tạo ra các phương tiện để bắt chước Định hướng đối tượng một cách hiệu quả , để hưởng lợi từ các phẩm chất của nó đối với tổ chức chương trình, bao gồm mô đun hóa, trừu tượng hóa và ẩn thông tin .
Như tôi đã nói, Lisp là người thử nghiệm nhiều tiến hóa ngôn ngữ, bao gồm cả mô hình OO (mặc dù thứ có thể được coi là ngôn ngữ OO đầu tiên là Simula 67, thuộc họ Algol). Lisp rất đơn giản và mã cho trình thông dịch cơ bản của nó ít hơn một trang. Nhưng bạn có thể làm lập trình OO trong Lisp. Tất cả bạn cần là chức năng thứ tự cao hơn.
Tôi sẽ không sử dụng cú pháp Lisp bí truyền, mà là mã giả, để đơn giản hóa việc trình bày. Và tôi sẽ xem xét một vấn đề thiết yếu đơn giản: ẩn thông tin và mô đun hóa . Xác định một lớp đối tượng trong khi ngăn người dùng truy cập (hầu hết) việc thực hiện.
Giả sử tôi muốn tạo một lớp gọi là vectơ, đại diện cho vectơ 2 chiều, với các phương thức bao gồm: thêm vectơ, kích thước vectơ và song song.
function vectorrec () {
function createrec(x,y) { return [x,y] }
function xcoordrec(v) { return v[0] }
function ycoordrec(v) { return v[1] }
function plusrec (u,v) { return [u[0]+v[0], u[1]+v[1]] }
function sizerec(v) { return sqrt(v[0]*v[0]+v[1]*v[1]) }
function parallelrec(u,v) { return u[0]*v[1]==u[1]*v[0]] }
return [createrec, xcoordrec, ycoordrec, plusrec, sizerec, parallelrec]
}
Sau đó tôi có thể gán vector đã tạo cho các tên hàm thực tế sẽ được sử dụng.
[vector, xcoord, ycoord, vplus, vsize, vabul] = vectơ ()
Tại sao lại phức tạp như vậy? Bởi vì tôi có thể định nghĩa trong các cấu trúc trung gian vectorrec mà tôi không muốn hiển thị với phần còn lại của chương trình, để duy trì tính mô đun.
Chúng ta có thể tạo một bộ sưu tập khác trong tọa độ cực
function vectorpol () {
...
function pluspol (u,v) { ... }
function sizepol (v) { return v[0] }
...
return [createpol, xcoordpol, ycoordpol, pluspol, sizepol, parallelpol]
}
Nhưng tôi có thể muốn sử dụng một cách thờ ơ cả hai triển khai. Một cách để làm điều đó là thêm một thành phần kiểu cho tất cả các giá trị, xác định tất cả các hàm trên trong cùng một môi trường: Sau đó tôi có thể xác định từng hàm được trả về để trước tiên nó sẽ kiểm tra loại tọa độ, sau đó áp dụng hàm cụ thể cho nó.
function vector () {
...
function plusrec (u,v) { ... }
...
function pluspol (u,v) { ... }
...
function plus (u,v) { if u[2]='rec' and v[2]='rec'
then return plusrec (u,v) ... }
return [ ..., plus, ...]
}
Những gì tôi đã đạt được: các hàm cụ thể vẫn ở chế độ ẩn (vì phạm vi định danh cục bộ) và phần còn lại của chương trình chỉ có thể sử dụng các hàm trừu tượng nhất được trả về bởi lệnh gọi đến lớp vectơ.
Một sự phản đối là tôi có thể định nghĩa trực tiếp từng hàm trừu tượng trong chương trình và để lại bên trong định nghĩa của các hàm phụ thuộc kiểu tọa độ. Sau đó cũng sẽ được ẩn. Điều đó là đúng, nhưng sau đó mã cho từng loại tọa độ sẽ được cắt thành từng phần nhỏ trải đều trên chương trình, ít có thể điều chỉnh và duy trì được.
Trên thực tế, tôi thậm chí không cần đặt tên cho chúng và tôi chỉ có thể giữ các giá trị chức năng ẩn danh trong cấu trúc dữ liệu được lập chỉ mục theo loại và chuỗi đại diện cho tên hàm. Cấu trúc này là cục bộ của vectơ chức năng sẽ vô hình từ phần còn lại của chương trình.
Để đơn giản hóa việc sử dụng, thay vì trả về một danh sách hàm, tôi có thể trả về một hàm duy nhất gọi là áp dụng làm đối số một giá trị kiểu rõ ràng và một chuỗi, và áp dụng hàm với loại và tên thích hợp. Điều này trông rất giống như gọi một phương thức cho một lớp OO.
Tôi sẽ dừng lại ở đây, trong lần tái thiết này của một cơ sở hướng đối tượng.
Những gì tôi đã cố gắng làm là chỉ ra rằng không quá khó để xây dựng hướng đối tượng có thể sử dụng được bằng một ngôn ngữ đủ mạnh, bao gồm cả tính kế thừa và các tính năng khác như vậy. Metacircularity của trình thông dịch có thể giúp đỡ, nhưng chủ yếu ở mức độ cú pháp, vẫn còn xa không đáng kể.
Những người dùng đầu tiên của định hướng đối tượng đã thử nghiệm các khái niệm theo cách đó. Và điều đó thường đúng với nhiều cải tiến đối với các ngôn ngữ lập trình. Tất nhiên, phân tích lý thuyết cũng có một vai trò và giúp hiểu hoặc tinh chỉnh các khái niệm này.
Nhưng ý tưởng rằng các ngôn ngữ không có tính năng OO chắc chắn sẽ thất bại trong một số dự án chỉ đơn giản là không có cơ sở. Nếu cần, họ có thể bắt chước việc thực hiện các tính năng này khá hiệu quả. Nhiều ngôn ngữ có sức mạnh cú pháp và ngữ nghĩa để thực hiện định hướng đối tượng khá hiệu quả, ngay cả khi nó không được tích hợp. Và đó là nhiều hơn một đối số Turing.
OOP không giải quyết các hạn chế của các ngôn ngữ khác, nhưng nó hỗ trợ hoặc thực thi các phương pháp lập trình giúp viết chương trình tốt hơn, do đó giúp người dùng ít kinh nghiệm tuân theo các thực tiễn tốt mà các lập trình viên tiên tiến hơn đã sử dụng và phát triển mà không cần sự hỗ trợ đó.
Tôi tin rằng một cuốn sách tốt để hiểu tất cả điều này có thể là Abelson & Sussman: cấu trúc và giải thích các chương trình máy tính .