Tôi đã viết các chương trình máy tính bằng ngôn ngữ Hướng đối tượng trong nhiều năm. Ngôn ngữ đầu tiên tôi sử dụng là C++, sau đó là Smalltalk, cuối cùng là .NET và Java. Tôi rất nhiệt tình tận dụng các tính năng Kế thừa, Đóng gói và Đa hình, đây là ba nguyên tắc chính của kiểu lập trình này. Tôi rất mong có thể sử dụng lại mã và hưởng lợi từ kinh nghiệm của những người đi trước tôi trong lĩnh vực mới và thú vị này. Tuy nhiên, tôi sớm nhận ra rằng mọi thứ không đơn giản như tôi mong đợi.
Thừa kế là thứ đầu tiên bị mất hoặc từ bỏ
Lúc đầu, có vẻ như ưu điểm chính của Lập trình hướng đối tượng là tính kế thừa. Nó có ý nghĩa khi chúng ta xem xét các ví dụ về cách các hình dạng khác nhau có liên quan với nhau, thường được sử dụng để giải thích khái niệm này cho những người mới tìm hiểu về nó.
Tôi rất hào hứng với ý tưởng tái sử dụng mọi thứ đến nỗi tôi ngay lập tức ra ngoài và chia sẻ nó với những người khác.
Vấn đề rừng khỉ chuối
Tôi cảm thấy quyết tâm và có động lực để giải quyết các vấn đề của mình, vì vậy tôi bắt đầu tạo hệ thống phân cấp lớp và viết mã. Tôi rất hào hứng khi sử dụng khái niệm tái sử dụng bằng cách lấy một lớp hiện có từ một dự án trước đó.
Tôi nghĩ rằng nó sẽ dễ dàng, nhưng tôi sớm nhận ra rằng tôi cần lớp cha, cha của cha và tất cả cha của các đối tượng được chứa. Bất chấp khó khăn, tôi vẫn quyết tâm tiếp tục và cuối cùng, tôi đã có thể khiến mọi thứ hoạt động hiệu quả và tôi cảm thấy mình đã đạt được thành tựu.
Có một câu nói rất hay của Joe Armstrong , người tạo ra Erlang :
Vấn đề với các ngôn ngữ hướng đối tượng là chúng có tất cả môi trường ẩn mà chúng mang theo bên mình. Bạn muốn có một quả chuối nhưng thứ bạn nhận được là một con khỉ đột đang ôm quả chuối và toàn bộ khu rừng.
Giải pháp đi rừng của khỉ chuối
Tôi có thể giải quyết vấn đề này bằng cách không làm cho hệ thống phân cấp quá phức tạp. Tuy nhiên, nếu kế thừa là cách tốt nhất để sử dụng lại mã, thì bất kỳ hạn chế nào tôi đặt lên nó sẽ làm giảm lợi thế của việc sử dụng lại mã.
Đúng không?
Lập trình viên hướng đối tượng tin tưởng vào lợi ích của cách tiếp cận này nên làm gì?
Câu trả lời là chứa và ủy quyền.
Tôi sẽ giải thích điều này sau.
Vấn đề kim cương
Cuối cùng, vấn đề khó khăn và có thể bất khả thi này sẽ xuất hiện.
Hầu hết các ngôn ngữ lập trình hướng đối tượng không cho phép bạn sử dụng cùng một tên phương thức cho hai tác vụ khác nhau, mặc dù làm như vậy sẽ hợp lý. Vậy tại sao các ngôn ngữ hướng đối tượng lại khó hỗ trợ điều này?
Chà, hãy tưởng tượng đoạn mã giả sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token class-name">Class</span> <span class="token class-name">PoweredDevice</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token class-name">Class</span> <span class="token class-name">Scanner</span> inherits from <span class="token class-name">PoweredDevice</span> <span class="token punctuation">{</span> function <span class="token function">start</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token class-name">Class</span> <span class="token class-name">Printer</span> inherits from <span class="token class-name">PoweredDevice</span> <span class="token punctuation">{</span> function <span class="token function">start</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token class-name">Class</span> <span class="token class-name">Copier</span> inherits from <span class="token class-name">Scanner</span> <span class="token punctuation">,</span> <span class="token class-name">Printer</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> |
Lớp Copier cần quyết định chức năng bắt đầu mà nó sẽ sử dụng, chức năng từ lớp Máy quét hoặc chức năng từ lớp Máy in, vì nó không thể sử dụng cả hai.
Giải pháp kim cương
Câu trả lời rất dễ: đừng làm những gì bạn đang cố gắng làm. Hầu hết các ngôn ngữ hướng đối tượng không cho phép nó. Nếu bạn cần mô hình hóa nó, bạn nên sử dụng ngăn chặn và ủy quyền.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token class-name">Class</span> <span class="token class-name">PoweredDevice</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token class-name">Class</span> <span class="token class-name">Scanner</span> inherits from <span class="token class-name">PoweredDevice</span> <span class="token punctuation">{</span> function <span class="token function">start</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token class-name">Class</span> <span class="token class-name">Printer</span> inherits from <span class="token class-name">PoweredDevice</span> <span class="token punctuation">{</span> function <span class="token function">start</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token class-name">Class</span> <span class="token class-name">Copier</span> <span class="token punctuation">{</span> <span class="token class-name">Scanner</span> scanner <span class="token class-name">Printer</span> printer function <span class="token function">start</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> printer <span class="token punctuation">.</span> <span class="token function">start</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Lớp Copier hiện có cả Máy in và Máy quét bên trong nó. Nó đang sử dụng lớp Máy in để thực hiện chức năng bắt đầu, nhưng thay vào đó, nó có thể dễ dàng sử dụng lớp Máy quét. Điều này cho thấy Kế thừa không phải lúc nào cũng là cách tốt nhất để giải quyết vấn đề.
Vấn đề lớp cơ sở mong manh
Tôi đã làm cho cấu trúc dữ liệu của mình đơn giản hơn và tránh mọi vòng lặp, nhưng rồi một ngày, mã của tôi ngừng hoạt động mặc dù tôi không thay đổi nó. Tôi nhận ra rằng có điều gì đó đã thay đổi nhưng không có trong mã của tôi. Nó nằm trong lớp mà tôi được thừa hưởng. Tôi đã bối rối về việc thay đổi trong lớp cơ sở có thể phá vỡ mã của tôi như thế nào. Để giải thích, hãy tưởng tượng một lớp cơ sở được viết bằng Java trông như thế này…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token keyword">import</span> <span class="token namespace">java <span class="token punctuation">.</span> util <span class="token punctuation">.</span></span> <span class="token class-name">ArrayList</span> <span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Array</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token class-name">ArrayList</span> <span class="token generics"><span class="token punctuation"><</span> <span class="token class-name">Object</span> <span class="token punctuation">></span></span> a <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span> <span class="token generics"><span class="token punctuation"><</span> <span class="token class-name">Object</span> <span class="token punctuation">></span></span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">add</span> <span class="token punctuation">(</span> <span class="token class-name">Object</span> element <span class="token punctuation">)</span> <span class="token punctuation">{</span> a <span class="token punctuation">.</span> <span class="token function">add</span> <span class="token punctuation">(</span> element <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addAll</span> <span class="token punctuation">(</span> <span class="token class-name">Object</span> elements <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token punctuation">(</span> <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> i <span class="token operator"><</span> elements <span class="token punctuation">.</span> length <span class="token punctuation">;</span> <span class="token operator">++</span> i <span class="token punctuation">)</span> a <span class="token punctuation">.</span> <span class="token function">add</span> <span class="token punctuation">(</span> elements <span class="token punctuation">[</span> i <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// this line is going to be changed</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Cảnh báo: Hãy chú ý đến dòng mã đã được đánh dấu là nhận xét. Chúng tôi sẽ thực hiện các thay đổi đối với dòng này sau, điều này có thể gây ra sự cố. Lớp này có hai tính năng: add(), thêm một mục và addAll(), thêm nhiều mục bằng cách gọi hàm add().
Và đây là lớp Derived:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ArrayCount</span> <span class="token keyword">extends</span> <span class="token class-name">Array</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">add</span> <span class="token punctuation">(</span> <span class="token class-name">Object</span> element <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token function">add</span> <span class="token punctuation">(</span> element <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token operator">++</span> count <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addAll</span> <span class="token punctuation">(</span> <span class="token class-name">Object</span> elements <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token function">addAll</span> <span class="token punctuation">(</span> elements <span class="token punctuation">)</span> <span class="token punctuation">;</span> count <span class="token operator">+=</span> elements <span class="token punctuation">.</span> length <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Lớp ArrayCount là một loại lớp Array theo dõi xem có bao nhiêu phần tử trong mảng. Lớp Array có hai phương thức, add() và addAll(), dùng để thêm các phần tử vào mảng. Lớp ArrayCount cũng có hai phương thức này, nhưng nó cũng tăng số đếm mỗi khi một phần tử được thêm vào.
Chúng tôi đang thực hiện thay đổi mã trong lớp Cơ sở. Dòng mã đã được nhận xét hiện đang được thay đổi thành như sau.
1 2 3 4 5 6 | <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addAll</span> <span class="token punctuation">(</span> <span class="token class-name">Object</span> elements <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token punctuation">(</span> <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> i <span class="token operator"><</span> elements <span class="token punctuation">.</span> length <span class="token punctuation">;</span> <span class="token operator">++</span> i <span class="token punctuation">)</span> <span class="token function">add</span> <span class="token punctuation">(</span> elements <span class="token punctuation">[</span> i <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// this line was changed</span> <span class="token punctuation">}</span> |
Chủ sở hữu của lớp Cơ sở không biết về lớp Derived và những thay đổi của nó đối với hàm add(). Khi hàm addAll() được gọi, số lượng được tăng lên hai lần, một lần bởi add() của lớp Derived và một lần bởi số phần tử được thêm vào trong addAll() của lớp Derived. Điều này là bất ngờ và chủ sở hữu của lớp Derived sẽ ngạc nhiên.
ĐƯỢC ĐẾM HAI LẦN
Nếu lớp Dẫn xuất được tạo từ lớp Cơ sở, tác giả của lớp Dẫn xuất phải biết cách lớp Cơ sở được triển khai và phải được thông báo về bất kỳ thay đổi nào được thực hiện đối với lớp Cơ sở, vì những thay đổi này có thể khiến lớp Dẫn xuất bị hỏng theo những cách không ngờ tới. Đây là một vấn đề lớn có thể khiến trụ cột Kế thừa trở nên không ổn định.
Giải pháp lớp cơ sở mong manh
Sử dụng Chứa và Ủy quyền, chúng ta có thể chuyển từ lập trình Hộp trắng, nơi chúng ta phải xem xét việc triển khai lớp cơ sở, sang lập trình Hộp đen, nơi chúng ta không cần biết việc triển khai vì chúng ta không thể đưa mã vào lớp lớp cơ sở. Điều này giúp bạn tập trung vào giao diện dễ dàng hơn. Tuy nhiên, xu hướng này đáng lo ngại vì các ngôn ngữ Hướng đối tượng được thiết kế để giúp Kế thừa dễ dàng, nhưng Chứa và Ủy quyền lại không dễ thực hiện. Điều này sẽ khiến chúng ta đặt câu hỏi về sức mạnh của Phân loại thông qua Hệ thống phân cấp.
Vấn đề thứ bậc
Bất cứ khi nào tôi bắt đầu một công việc mới, tôi không chắc chắn về cách sắp xếp các tài liệu của mình, chẳng hạn như sổ tay nhân viên. Tôi có nên tạo một thư mục có tên “Tài liệu” và sau đó tạo một thư mục có tên “Công ty” bên trong đó không?
Hay tôi nên tạo một thư mục có tên “Công ty” và sau đó tạo một thư mục có tên “Tài liệu” bên trong đó?
Cả hai đều hoạt động, nhưng đó là cách đúng đắn để làm điều đó?
Ý tưởng về Phân cấp phân loại là có các danh mục chung (cha mẹ) và các danh mục cụ thể hơn (con) dựa trên các danh mục chung. Nhưng nếu có thể chuyển đổi danh mục cha và con, thì mô hình không hoạt động chính xác.
Giải pháp phân cấp
Trong thế giới thực, chúng ta thấy Hệ thống phân cấp ngăn chặn (hoặc Quyền sở hữu độc quyền) ở khắp mọi nơi, nhưng không phải là Hệ thống phân cấp phân loại.
Ví dụ: ngăn đựng tất đựng trong tủ quần áo, ngăn chứa đựng trong phòng ngủ, ngăn chứa đựng trong nhà ở. Các thư mục trên ổ cứng là một ví dụ khác về Hệ thống phân cấp ngăn chặn. Để phân loại mọi thứ, không quan trọng chúng ta đặt chúng ở đâu.
Ví dụ: chúng ta có thể đặt các tài liệu của công ty trong một thư mục Tài liệu hoặc một thư mục có tên là Nội dung.
Tôi dán nhãn tệp bằng các từ mô tả nó để tôi có thể dễ dàng tìm thấy nó sau này. Những từ tôi sử dụng là:
1 2 3 4 | Document Company Handbook |
Các thẻ có thể được liên kết với một tài liệu theo bất kỳ thứ tự nào và không có bất kỳ thứ bậc nào. Điều này có nghĩa là vấn đề về nhiều loại liên quan đến một tài liệu, được gọi là Vấn đề kim cương, đã được giải quyết. Điều này cũng có nghĩa là khái niệm Kế thừa không còn cần thiết nữa.
Tạm biệt, Người thừa kế.
Đóng gói là khái niệm thứ hai được hiểu hoặc chấp nhận
Lập trình hướng đối tượng có nhiều lợi ích và Đóng gói là một trong những lợi ích quan trọng nhất. Tính đóng gói có nghĩa là các biến trạng thái của một đối tượng được bảo vệ khỏi sự truy cập từ bên ngoài, vì vậy chúng ta không phải lo lắng về việc bất kỳ ai cũng có thể truy cập các biến toàn cục. Đóng gói là một cách tuyệt vời để giữ an toàn cho các biến của bạn. Đó là một tính năng đáng kinh ngạc và nên được tổ chức!
Vấn đề tham khảo
Khi truyền một Đối tượng cho một hàm, hàm sẽ không tự truyền Đối tượng mà thay vào đó sẽ truyền một tham chiếu hoặc con trỏ tới Đối tượng. Tham chiếu này có thể được lưu trữ trong một biến riêng tư được bảo vệ bởi Encapsulation. Tuy nhiên, Đối tượng không an toàn vì mã được gọi là hàm phải có tham chiếu đến Đối tượng để chuyển nó đến hàm.
Giải pháp tham khảo
Trình xây dựng sẽ cần tạo một bản sao của Đối tượng đã cho, nhưng không chỉ là một bản sao nông, nó cần phải là một bản sao sâu, nghĩa là nó cần sao chép mọi đối tượng bên trong Đối tượng đã cho và mọi đối tượng bên trong các đối tượng đó, v.v. . Điều này không hiệu quả lắm. Ngoài ra, không phải tất cả các đối tượng đều có thể được sao chép, vì một số trong số chúng có tài nguyên được liên kết với hệ điều hành, điều này khiến việc sao chép trở nên vô dụng hoặc không thể thực hiện được. Đây là một vấn đề mà tất cả các ngôn ngữ hướng đối tượng chính thống đều gặp phải. Điều này có nghĩa là không thể đóng gói.
Tạm biệt, Đóng gói.
Đa hình, Trụ cột thứ ba sụp đổ
Tính đa hình không quan trọng bằng hai yếu tố còn lại của lập trình Hướng đối tượng, giống như Larry Fine của Three Stooges. Nó vẫn hữu ích, nhưng bạn không cần ngôn ngữ Hướng đối tượng để có được nó. Các giao diện có thể cung cấp các lợi ích tương tự mà không cần lập trình Hướng đối tượng phức tạp hơn và chúng có thể được sử dụng để tạo các hành vi đa dạng hơn. Vì vậy, chúng ta đang nói lời tạm biệt với Đa hình hướng đối tượng và xin chào Đa hình dựa trên giao diện.
thất hứa
Lập trình hướng đối tượng hứa hẹn rất nhiều khi nó mới ra mắt, và nó vẫn còn hứa hẹn rất nhiều với những lập trình viên chưa có kinh nghiệm đang tìm hiểu về nó. Tôi đã mất một thời gian dài để nhận ra rằng lập trình hướng đối tượng không tuyệt vời như vẻ ngoài của nó. Tôi đã thiếu kinh nghiệm và tin tưởng, và tôi đã thất vọng.
Tạm biệt, Lập trình hướng đối tượng.
Vậy thì sao?
Xin chào, Lập trình chức năng . Rất vui được cộng tác với bạn trong vài năm qua.
Tôi không tin vào những lời hứa của bạn và tôi cần thấy bằng chứng rằng chúng là sự thật trước khi tôi tin chúng.
Điều này có nghĩa là nếu bạn từng bị tổn thương hoặc thất vọng trong quá khứ, bạn sẽ thận trọng hơn trong tương lai.
Bạn hiểu.