Tại sao khái niệm hướng đối tượng lại là đồ bỏ đi?
Bài đăng này đã không được cập nhật trong 8 năm
Đây là một bài viết của Joe Armstrong, tác giả của ngôn ngữ Erlang – một ngôn ngữ vẫn được nhiều người xếp vào loại hướng đối tượng. Bài viết được rất nhiều chuyên gia về công nghệ chú ý, hi vọng sẽ phục vụ cho việc thiết lập một cái nhìn đa chiều về khái niệm lập trình hướng đối tượng dành cho các bạn.
Ngay từ ngày đầu được giới thiệu về lập trình hướng đối tượng, tôi đã có cảm giác hoài nghi nhưng tôi không thật sự hiểu tại sao. Tôi chỉ cảm thấy nó có một điều gì đó “rất tệ”.
Sau khi được giới thiệu, lập trình hướng đối tượng đã trở nên rất phổ biến (mà phần dưới tôi sẽ nói rõ là vì sao) đến mức sau đó, việc phê phán nó đã trở nên không khác nào việc “chửi bậy trong nhà thờ” vậy.
"Hướng đối tượng" đã trở thành một thứ mà mọi ngôn ngữ lập trình được đánh giá cao đều phải có.
Erlang cũng vậy, khi nó bắt đầu trở nên phổ biến chúng tôi cũng hay được đặt câu hỏi “Erlang là ngôn ngữ hướng đối tượng phải không?”. Tất nhiên câu trả lời là “Không, chẳng liên quan” - nhưng chúng tôi không thể nói oang oang điều đó.
Vì thế, chúng tôi đã phát minh ra một cách trả lời tài tình cho câu hỏi đó. Đó là cách trả lời sao cho đối với người bình thường, Erlang sẽ có vẻ là “một kiểu ngôn ngữ hướng đối tượng", còn đối với người thực sự tinh tế và lắng nghe chăm chú thì sẽ nhận ra không phải là như vậy.
Viết đến đây, tôi chợt nhớ lại bài phát biểu của nguyên Giám đốc Chi nhánh IBM tại Pháp, trong hội thảo về Logic programming lần thứ 7 do IEEE tổ chức. Khi đó, ông đã được hỏi về lí do tại sao ngôn ngữ Prolog của IBM được bổ sung thêm nhiều chức năng hướng đối tượng.
Ông đã trả lời rằng “vì khách hàng của chúng tôi muốn nên chúng tôi thêm vào thôi”. Tôi nhớ rất rõ mình đã cảm thấy câu trả lời đó vô hồn, đơn giản và không coi trọng sự đúng sai như thế nào.
Tại sao hướng đối tượng lại là một khái niệm rất khó chịu?
Sự phản đối của tôi đối với khái niệm hướng đối tượng nằm ở những thứ cơ bản nhất của nó, mà tôi sẽ liệt kê một vài trong số đó như dưới đây.
Điều 1 – Cấu trúc dữ liệu và hàm số không nên gói chung lại với nhau
Đối tượng móc nối cấu trúc dữ liệu (data structure) và hàm số (function) lại với nhau trong một thực thể vô hình. Tôi nghĩ đây là một sai lầm rất cơ bản vì “dữ liệu” và “hàm số” tồn tại ở 2 thế giới khác nhau.
Tại sao?
Hàm số là thứ thực hiện một điều gì đó. Nó có đầu vào và đầu ra. Đầu vào và đầu ra của nó là những cấu trúc dữ liệu, cấu trúc dữ liệu sẽ bj biến đối bởi hàm số. Trong hầu hết các ngôn ngữ lập trình, hàm số được xây dựng bằng một chuỗi câu lệnh : làm cái này, xong rồi đến cái kia. Để hiểu được hàm số bạn phải hiểu được các công việc trên đời này được thực hiện theo thứ tự nào (trong lập trình kiểu hàm số và lập trình logic thì điều này ít được yêu cầu hơn).
Cấu trúc dữ liệu thì chẳng thực hiện một điều gì cả. Đơn thuần nó chỉ là những sự khai báo. “Hiểu một cấu trúc dữ liệu” đơn giản hơn “hiểu một chức năng” rất nhiều.
Hàm số là một cái hộp đen biến đối đầu vào thành đầu ra. Tôi hiểu đầu vào, tôi hiểu đầu ra nghĩa là tôi đã hiểu hàm số. Tuy nhiên, điều đó không có nghĩa là tôi có thể tự viết ra được hàm số đó.
Hàm số là một thứ hay được quan sát và lí giải dựa trên cách nó biến đổi cấu trúc dữ liệu kiểu T1 thành cấu trúc dữ liệu kiểu T2 như thế nào trong một hệ thống máy tính.
“Hàm số” và “cấu trúc dữ liệu” là 2 loại sinh vật khác nhau, nhốt chúng lại với nhau trong cùng một cái lồng là sai về bản chất.
Điều 2 – Bất cứ thứ gì cũng cần phải là một đối tượng
Chúng ta hãy cùng xem thử khái niệm “thời gian”. Trong ngôn ngữ hướng đối tượng, thời gian cần phải là một đối tượng.
Tuy nhiên, trong ngôn ngữ không hướng đối tượng, “thời gian” chỉ là thể hiện của một loại dữ liệu. Chẳng hạn, trong Erlang có rất nhiều loại thời gian, bằng cách dùng các câu khai báo khác nhau, “thời gian” có thể được hiểu một cách rất rõ ràng, đơn sơ và không tham vọng như sau:
-deftype day() = 1..31.
-deftype month() = 1..12.
-deftype year() = int().
-deftype hour() = 1..24.
-deftype minute() = 1..60.
-deftype second() = 1..60.
-deftype abstime() = {abstime, year(), month(), day(), hour(), min(), sec()}.
-deftype hms() = {hms, hour(), min(), sec()}.
...
Bạn hãy chú ý rằng những khai báo đó không thuộc về một đối tượng cụ thể nào. Chúng rất hiện hữu, thông dụng và những cấu trúc dữ liệu thể hiện “thời gian” có thể được điều khiển bởi bất cứ hàm số nào trong hệ thống.
Ngoài ra, chúng cũng chẳng cần đi kèm với một method nào.
Điều 3 - Việc định nghĩa dữ liệu bị rải rác khắp mọi nơi
Trong lập trình hướng đối tượng, kiểu dữ liệu (data type) được sở hữu bởi đối tượng.
Do đó, tôi không thể nào định nghĩa nhiều dữ liệu trong cùng một chỗ.
Trong Erlang hoặc C, tôi có thể định nghĩa mọi kiểu dữ liệu trong một data dictionary hoặc một include file. Trong ngôn ngữ hướng đối tượng thì việc đó là không thể và do đó, định nghĩa về các kiểu dữ liệu rải rác khắp mọi nơi.
Các lập trình viên ngôn ngữ lisp có lẽ biết rất rõ điều này – rằng chúng ta nên có một số lượng nhỏ kiểu dữ liệu thông dụng, và một số lượng lớn hàm sử dụng chúng, hơn là có một số lượng lớn kiểu dữ liệu và chỉ một số lượng nhỏ hàm sử dụng chúng.
Dữ liệu thông dụng là những thứ chẳng hạn như 1 linked list, 1 array, 1 hash table hoặc hơn nữa là giờ phút giây, ngày tháng hoặc file name.
Trong lập trình hướng đối tượng, tôi phải chọn một số đối tượng cơ sở để định nghĩa những dữ liệu thông dụng trong đó. Tất cả những đối tượng khác nếu muốn sử dụng những dữ liệu này đều phải kế thừa lại từ đối tượng cơ sở.
Giả sử tôi muốn tạo một đối tượng “time”, tôi không hiểu nó thuộc về đối tượng nào và bản thân nó là đối tượng như thế nào đây?
Điều 4 - Các đối tượng có mang trạng thái private
“Trạng thái” (state) là khởi nguồn của mọi điều ngang trái. Về cơ bản, những hàm số có mang tác dụng phụ cần phải tránh.
Trạng thái là thứ không mong muốn đối với các ngôn ngữ lập trình, nhưng lại hiện hữu trong thế giới thật. Tôi rất quan tâm đến trạng thái tài khoản ngân hàng của tôi và mỗi khi nạp hay rút tiền, tôi đều cần trạng thái đó được update một cách chính xác.
Như vậy, với giả thiết là “trạng thái tồn tại trong thế giới thực”, các ngôn ngữ lập trình cung cấp điều gì để giải quyết điều đó?
-
Lập trình hướng đối tượng chủ trương : giấu trạng thái khỏi các lập trình viên - trạng thái luôn được giấu và chỉ có thể được xem bằng các hàm có khả năng truy cập.
-
Các ngôn ngữ lập trình truyền thống (C, Pascal) chủ trương : việc trạng thái có thể được nhìn thấy hay không, được điều khiển bằng scope rules của ngôn ngữ.
-
Các ngôn ngữ lập trình khai báo thuần túy chủ trương : trạng thái không tồn tại. Trong các ngôn ngữ này, trạng thái global của hệ thống được nhập và xuất bởi toàn bộ các hàm số.
Những cơ chế như monad (trong lập trình hàm số) hay DCGs (trong ngôn ngữ logic) được dùng để giấu trạng thái khỏi các lập trình viên sao cho họ có thể lập trình “như thể trạng thái không ảnh hưởng gì” nhưng khi cần, họ vẫn có thể truy cập vào tất cả các trạng thái của hệ thống.
Che giấu trạng thái khỏi các lập tình viên - cách mà các ngôn ngữ lập trình hướng đối tượng chọn là cách tồi nhất có thể. Thay vì công khai trạng thái và tìm cách tối giản sự phiền nhiễu của trạng thái, nó lại giấu trạng thái đi.
Tại sao sự hướng đối tượng lại trở nên phổ biến?
- Lí do 1 : Nó được coi là dễ học
- Lí do 2 : Nó được coi là giúp code dễ tái sử dụng hơn
- Lí do 3 : Nó được lăng xê nhiều
- Lí do 4 : Nó đã tạo ra một mảng công nghệ phần mềm mới
Với lí do 1 và 2, tôi chưa từng thấy có một chứng cứ xác thực nào. Có vẻ lí do thứ 3 và thứ 4 là động lực giúp nó phát triển hơn là về công nghệ.
Một ngôn ngữ tệ đến mức phải sinh ra cả một mảng công nghệ mới để sửa chữa những vấn đề do nó tự sinh ra, thì đó chính là một miếng mồi béo cho những kẻ muốn làm tiền rồi.
Đó chính là động lực thật sự phía sau khái niệm lập trình hướng đối tượng.
Nguồn : http://harmful.cat-v.org/software/OO_programming/why_oo_sucks
All rights reserved