OOP là gì?
Sau khi bước vào học lập trình một thời gian, chúng ta sẽ tiếp cận với một khái niệm mới và sẽ gây lú với nhiều người đó là OOP - Object Oriented Programming, hay tiếng việt chúng ta gọi là lập trình hướng đối tượng. Nó là một tư duy lập trình với những quy ước hay ý tưởng nhất định giúp cho các chương trình có cấu trúc rõ ràng, dễ bảo trì và mở rộng hơn, chứ nó không phải là một công nghệ của một ngôn ngữ lập trình cụ thể.
Hiện tại đã có rất nhiều bài viết về OOP nhưng chúng ta sẽ thấy phần lớn khá là hàn lâm, trong khi ta nên liên hệ một chút ra thực tế thì sẽ khiến ta hiểu bản chất của chúng hơn rất nhiều. Bài viết này mình muốn chia sẻ kiến thức mình học được, tổng hợp được và đóng góp cho cộng đồng những gì chọn lọc nhất có thể.
Tại sao OOP ?
Đúng rồi đấy, ta nên hiểu theo đúng nghĩa đen của nó - hướng đến một đối tượng cụ thể. Sau khi hiểu về mẫu hình lập trình này, bạn sẽ thấy rằng nếu không có chúng thì chương trình của chúng ta thực sự rất rắc rối đấy. Khi mới học lập trình, code của chúng ta sẽ khá bừa bãi, bạ đâu code đấy mặc dù ta đã sắp xếp theo nhiều module, chia ra hàm hiếc riêng biệt nhưng khi chương trình càng ngày càng lớn hơn thì ta sẽ thấy nó phát sinh ngày càng nhiều vấn đề.
Ví dụ bạn code 1 project mà không theo quy chuẩn nào thì sau một thời gian không động tới liệu chúng ta có thể tiếp tục mở rộng, chỉnh sửa chúng trơn tru luôn không hay lại mất một thời gian tìm hiểu lại, hoặc khi đưa code cho người khác, liệu họ có thể nhìn ra những gì ta đã làm không? Vì vậy OOP sẽ khiến mọi thứ ngăn nắp hơn, theo quy chuẩn chung và tất nhiên sẽ kéo theo các lợi ích khác, vậy chúng có những gì?
Class và Object
Hãy liên hệ sang thực tế, ta sẽ thấy rằng mỗi chúng ta sẽ có những đặc trưng, hành vi riêng và chung. Ví dụ mình và bạn sẽ có những thứ chung như: 2 chân, 2 tay, đau thì khóc, vui thì cười, hoặc những thứ riêng như: mình có chỉ số IQ là 123, bạn có chỉ số IQ 124, ... . Nếu mỗi lần muốn tạo một đối tượng nào đấy mà ta code lần lượt từ đầu đến cuối, thêm từng thuộc tính, hành vi cho nó thì sẽ phải là tốn rất nhiều thời gian, vì vậy mà ta cần một cái khuôn chung, đó là class Bạn cứ hiểu rằng nó như một khôn mẫu và mỗi lần tạo một object (đối tượng) từ cái khuôn đấy thì mọi sản phẩm đó đều thừa hưởng hết những gì mà class đấy có. Cùng xem ví dụ sau đây.
Class này tượng trưng cho dòng xe Porsche Panamera, và khi mỗi lần 1 người chọn mua 1 chiếc xe, họ có thể có các lựa chọn: Màu, đường kính bánh xe, loại đồng hồ và loại da ghế, những thứ này ta gọi là attribute (thuộc tính). Ngoài ra ta có thể thấy có thêm một method makeCarNoise(), nó là một hành vi của chiếc xe, mô tả tiếng xe kêu "Vroom Vroom!" mỗi khi khởi động xe. Vì vậy mỗi chiếc xe được sản xuất ra thì ta gọi nó là một object (đối tượng). Chung quy lại, class nó sẽ giống như một cái khuôn mẫu chung và mỗi khi ta tạo ra một đối tượng từ nó, thì nó sẽ có các thuộc tính cụ thể của riêng nó, mỗi ông đều có thể mua một chiếc xe, nhưng ông A thích đồng hồ cơ, ông B lại thích đồng hồ hiện đại, ông C thích bánh 21 inches, ông D thích bánh 19 inches, ... .
Ngoài những thứ trên, ta sẽ thấy được các method khác đó là getter và setter. Vậy chúng là gì? Các bạn có thể thấy các attribute của class đều được định nghĩa private, vì vậy chúng chỉ được sử dụng trong khuôn khổ class đó, nên ta sẽ cần những public method để đọc và xét giá trị cho các attributes đấy. Và đây là quy ước chung nhé chứ không phải mình bịa ra đâu, và chúng chính là encapsulation - một trong 4 đặc tính của OOP.
4 đặc tính của OOP
Encapsulation - Tính đóng gói
Như đã vừa đề cập ở trên ở trên, ta thấy các attributes đều phải được định nghĩa private, và ta có thể tưởng tượng được chúng đã được "gói ghém" lại trong một class. Và chỉ những method trong class đó mới được trực tiếp sử dụng những thuộc tính đó (cụ thể là getter và setter trong ví dụ này). Nhưng tại sao lại như vậy? Không phải là ta có getter và setter rồi thì khác gì public mà viết thế cho dài dòng hơn sao?
Mới đầu chắc hẳn ai cũng có câu hỏi đó, điều đó là rất tốt. Rõ ràng là trong vài bài toán nhỏ ta để public cho các thuộc tính thì nó cũng chả ảnh hưởng gì mấy, nhưng đôi lúc có những ràng buộc gì thì sao. Cùng nhìn lại ví dụ trên kia, nếu một khách hàng chọn đường kính bánh xe mà không có sẵn trong nhà máy thì sao, khi đó ta phải sử lý thông qua method setter chứ không thể để chúng bị set một cách vô tội vạ được.
Ngoài ra sau này khi mà học sâu hơn, các bạn có thể thấy một số thuộc tính có thể chỉ cho phép đọc hoặc chỉ cho phép ghi, khi đó ta chỉ cần 1 trong 2 getter hoặc setter, hoặc có những dữ liệu chỉ sử dụng trong class đấy thôi chỉ chả cần getter hay setter. Nếu như trong một class có thuộc tính mà có thể sử dụng như public, nhưng lại có thuộc tính cần private thì chúng ta vẫn nên cho tất cả chúng là private, sau đó thêm getter, setter, constructor và các method khác. Như vậy sẽ khiến chúng được gọn gàng, đóng gói cẩn thận.
Inheritance - Tính kế thừa
Hãy giả sử rằng bây giờ thị trường Mỹ yêu cầu Porsche làm một dòng Panamera riêng cho thị trường họ, nhưng thêm các tính năng khác là định vị xe, rèm cửa, và một hành vi khác cho xe là báo động. Nếu như chúng ta xây dựng mọi thứ lại từ đầu thì sẽ mất nhiều thời gian, đôi lúc còn bỏ sót thứ gì đấy. Vì vậy mà tính kế thừa được ra đời - nó cho phép một class được thừa hưởng các thuộc tính và hành vi từ class khác, khiến code của chúng ta dễ dàng bảo trì, tái sử dụng hơn.
Từ ví dụ trên, ta sẽ tạo một class kế thừa như sau:
Như bạn thấy thì một class con thừa sẻ thừa hưởng hết những gì class cha có, và có thể tùy biến theo của thằng con qua @Override trên kia, xe ở Mỹ nó không thích kêu "Wroom Wroom" nữa nên nó đổi sang "Br Br" là được, còn nó thêm gì của riêng nó thì tự nó thêm như phương thức còi báo động alarmSiren(), hay các thuộc tính khác. Vậy ta đã thấy rằng tính kế thừa này khiến code của chúng ta dễ tái sử dụng, bảo trì hơn. Tại sao lại dễ bảo trì hơn? tại vì nếu như ông Porsche này muốn thêm sửa gì cho tất cả các dòng Panamera thì ổng chỉ cần sửa trong class PorschePanamera là xong.
Polymorphism - tính đa hình
Tính đa hình cho phép các đối tượng cùng kiểu có thể có các hành vi khác nhau. Nó cho phép ta định nghĩa các phương thức có cùng tên nhưng có các hành vi khác nhau cho các lớp con khác nhau. Hãy giả sử bây giờ lại đến Ấn Độ bắt Porsche tạo cho họ một dòng Panamera riêng cho họ, nhưng chỉ thay đổi tiếng của nó thôi:
Bây giờ, tại một triển lãm xe có 1 chiếc Panamera bản quốc tế, 1 chiếc bản Mỹ, 1 chiếc bản Ấn và cả 3 chiếc khởi động cùng lúc:
Vậy là bạn thấy đấy, 3 chiếc xe cùng có một hành động là khởi động xe, nhưng chúng lại cho ra các kết quả khác nhau. Ta sẽ sang một ví dụ khác của tính đa hình:
Abstraction - tính trừu tượng
Tính chất này sẽ giúp chúng ta chỉ xây dựng những gì bề ngoài nó có chứ chẳng quan tâm bên trong nó hoạt động như thế nào, chúng ta sử dụng trừu tượng để giảm sự phức tạp của chương trình và tạo ra một cấu trúc rõ ràng. Tính trừu tượng được thể hiện thông qua các abstract class, interface và abstract method . Nói như vậy thực ra vẫn hơi mơ hồ nên ta sẽ đến ví dụ.
Như các bạn thấy, ở đây chúng ta có abstract class Shape, có các abstract methods là area() và perimeter() và chúng hoàn toàn không có thân, vì vậy chúng ta mới nói rằng tính trừu tượng tức là nó chỉ quan tâm tới bề ngoài mà chẳng biết nó hoạt động như thế nào cả. Bởi vì trong ví dụ này, nếu ta sinh ra các class con extends abstract class Shape thì khi đó ta mới quan tâm tới phần bên trong của nó, ví dụ ta có thêm class biểu trưng cho hình thoi, hình chữ nhật, hình vuông và tất nhiên chúng có các công thức tính chu vi và diện tích khác nhau rồi, vậy nên khi đó ta mới tính tới cách hoạt động của chúng, trong khi abstract chỉ biết rằng mỗi hình đề có chu vi và diện tích.
Nếu như mà chúng ta định nghĩa luôn từ class cha thì chỉ có if else đến ch** mà thôi :U . Trong khi nếu không dùng abstract method thì khi ta xây dựng một class con nào đấy mà ta lại quên một hành vi thì khi đó fix bug rất mệt, vì vậy mà khi extends từ abstract class, hay implement từ interface thì ta đều bắt buộc Override lại hết abstract method.
Ngoài ra, chúng ta cũng nên phân biệt giữa interface và abstract class, chúng được sử dụng trong những hoàn cảnh khác nhau nhưng đều có chung ý tưởng về tính trừu tượng.
Tóm lại, chúng ta có 4 tính chất chính của OOP như ở trên và chúng ta cũng nên tìm hiểu nhiều nguồn khác nhau để hiểu hơn về chúng, tất nhiên phải có chọn lọc bởi hiện nay thông tin quá nhiều và đôi khi nguồn mơ hồ sẽ khiến ta hiểu nhầm. Thank you all !