Tổng quan lập trình hướng đối tượng
Hướng đối tượng bỏ túi,  Nhập môn đạo dev

Bốn tính chất cần lưu tâm khi lập trình hướng đối tượng – OOP Concepts

Khi nói đến các tính chất của lập trình hướng đối tượng. Anh em lập trình có thể liệt kê ra cả bốn đặc tính ngay lập tức. Đó là tính đóng gói, tính kế thừa, tính đa hình và tính trừu tượng. Đối với một số anh em, lý thuyết phần này khá khó hiểu và khó nhớ. Ở phần 2 của series “Hướng đối tượng bỏ túi”. Mình sẽ hệ thống lại kiến thức về bốn đặc tính quan trọng khi lập trình hướng đối tượng.

Đặt vấn đề

Để nội dung của bài viết liền mạch hơn, mình sẽ đặt vấn đề trước. Mọi tính chất của hướng đối tượng sẽ được mình lồng ghép và xây đắp từ vấn đề này.

Tiếp nối phần 1 của series. Sau khi thiết kế ra chiếc xe chỉ chạy được trên … NetBeans. Mình đã được sếp thăng chức, chuyển sang làm nhân viên vườn thú. Với người thông minh và tài giỏi như mình, việc này dễ như tìm đường vào tim crush vậy. Suốt ngày chỉ có cho ăn, đếm thú, tối đến lại lùa vào chuồng. Dù thời gian bận không nhiều nhưng mình vẫn rãnh để mô phỏng sở thú này trên máy tính. Vậy mình nên thiết kế như nào anh em nhỉ?

It’s time to be a [email protected]

Encapsulation – Tính bao đóng

Tính bao đóng là gì?

Encapsulation means that a group of related properties, methods, and other members are treated as a single unit or object.

Tính chất đầu tiên chúng ta cần nhớ chính là tính bao đóng (đóng gói). Tính bao đóng yêu cầu thực hiện gom những thứ liên quan lại thành một đơn vị hoặc đối tượng, Class. Đồng thời ẩn những dữ liệu nhạy cảm khỏi khả năng truy xuất của người dùng.

Tính bao đóng có thể đạt được bằng cách sử dụng Access ModifierGetter - Setter. (xem lại định nghĩa ở phần 1)

Phân tích và áp dụng tính bao đóng

Tính chất này cũng dễ hiểu thôi, chúng ta sẽ bắt đầu với con Vịt nhé. Vậy là giờ chúng ta cần gom mấy thứ có liên quan lại thành một class, đặt tên là Duck. Để xem nào, nó là loài là vịt nè, phải có tên riêng nữa. Nó có thể ăn, bơi rồi kêu cak cak cak. 🦆 Nếu như thế thì tên loài sẽ không cho phép bên ngoài thay đổi, còn tên riêng con vịt có thể đặt sao cũng được. Sau đó là định nghĩa các phương thức đại diện cho hành động ăn, bơi, kêu của con vịt.

Như thế là chúng ta đã định nghĩa được con vịt trông như thế nào rồi. Việc còn lại là viết hàm main và chạy thử thôi. Demo ngôn ngữ Java anh em cho mình xin khất. Ai cần hãy comment trong bài viết, mình sẽ bổ sung sau.


Inheritance – Tính kế thừa

Tính kế thừa cần được hiểu như thế nào?

Inheritance describes the ability to create new classes based on an existing class.

Đây là tính chất dễ hiểu nhất của hướng đối tượng. Nó cho phép chúng ta định nghĩa một class mới dựa trên một class đã định nghĩa trước đó. Nói cách khác là class con kế thừa lại những đặc tính đã có của class cha.

Tính kế thừa có ba cấp độ:

  1. Base class inheritance: Kế thừa toàn bộ từ lớp cơ sở.
  2. Abstract class inheritance: Kế thừa từ lớp abstract. Gọi là kế thừa một phần vì phải định nghĩa lại những hàm abstract.
  3. Interface inheritance: Kế thừa khuôn mẫu, phải định nghĩa rất cả những gì interface yêu cầu.

Tuy nhiên, để đảm bảo tính kế thừa, ta cần chú ý khá nhiều thứ:

  • Lớp kế thừa được sử dụng những thành phần được cho phép của lớp cơ sở quy định bở Access Modifier (public, protected, v.v…).
  • Nói cho chuẩn: Kế thừa class gọi là extends và kế thừa interface gọi là implements. Java sử dụng 2 từ khóa đó để thực hiện kế thừa. Đối với C#, chỉ cần dùng dấu : cho cả class và interface.
  • Từ khóa this: Đại diện cho lớp chứa cục code hiện tại.
  • base class đại diện cho lớp cơ sở, lớp cha. Ngôn ngữ Java sử dụng phương thức super(), C# sử dụng base. Đối với C++ là Google.com.
  • Kế thừa 1 cấp & kế thừa nhiều cấp.
  • Lớp kế thừa có thể ép kiểu về lớp cơ sở mà không làm mất tính đúng đắn của chương trình.
  • Hầu hết các ngôn ngữ không cho phép đa kế thừa class. Tránh việc 2 class kế thừa có cùng các thuộc tính giống nhau, nhưng có thể đã implement khác nhau.
  • Có thể đa kế thừa Interface.

Phân tích và xây dựng sở thú dựa trên tính kế thừa

Nếu như chúng ta xây dựng sở thú chỉ dựa vào tính bao đóng như ở ví dụ 1. Thì nghĩa là cứ 10 loài ta phải tạo 10 class, sau đó định nghĩa lại thuộc tính, hành động một lần nữa. Nếu đặc điểm chung giữa chúng có điểm cần thay đổi lại phải sửa thủ công 10 class khác nhau. Rất là mất thời gian, công sức và hơi khổ râm. Đó là lý do chúng ta phải áp dụng tính kế thừa.

inheritance-oop-demo
Chúng ta sẽ thiết kế lại các class như thế này

Lúc này, chúng ta cần gom những đặc tính chung của động vật vào một lớp gọi là Animal. Sau đó muốn tạo ra loài mới, ta chỉ cần kế thừa từ lớp động vật là nó đã có những đặc tính của động vật rồi. Chương trình của chúng ta cần nâng cấp như sau:

Lúc này, class Duck cần kế thừa hay nói đúng hơn là mở rộng (extends) class Animal. Và nó được tinh gọn như sau:

Khi cần định nghĩa một loài mới, ta chỉ cần tạo class mới, kế thừa class Animal, sau đó thêm các thuộc tính của riêng loài mới là xong. Chúng ta sẽ định nghĩa thêm loài khỉ cho sở thú.

Sau khi định nghĩa 3 class trên, anh em tạo hàm main để chạy thử và xem kết quả:

Vậy là chúng ta đã áp dụng tính kế thừa cho sở thú rồi đấy. Bạn thử dự đoán kết quả in ra màn hình như thế nào nhé. 😇


Polymorphism – Tính đa hình

Định nghĩa tính đa hình

Polymorphism means that you can have multiple classes that can be used interchangeably, even though each class implements the same properties or methods in different ways.

Ngay trong từ đa hình nó đã mang ý nghĩa là một thứ gì đó mang nhiều hình thái khác nhau. Trong lập trình hướng đối tượng. Nếu tính kế thừa cho phép ta thừa hưởng một phương thức từ một class. Thì tính đa hình cho phép ta triển khai lại phương thức đó theo những cách khác nhau. Việc này dẫn đến việc các class kế thừa từ chung một class cha có thể được sử dụng thay thế cho nhau mà không làm ảnh hưởng đến tính đúng đắn của chương trình.

Tính đa hình có thể đạt được bằng cách sử dụng:

  • Method Overloading: Nạp chồng
  • Method Overriding: Ghi đè
Overloading & Overriding
Overloading & Overriding

Method Overloading – Nạp chồng phương thức

Method Overloading cho phép triển khai cùng một tính năng với nhiều loại tham số khác nhau. Nạp chồng được gọi là compiletime polymorphism.

Ví dụ phương thức cho vịt ăn ngoài việc cho ăn mặc định, đôi lúc ta cần cho cho biết thêm loại thức ăn, số lượng, thời gian, v.v… .

Phương thức nạp chồng phải cùng têncùng kiểu trả vềthỏa mãn một trong các điều kiện sau:

  1. Khác số lượng tham số truyền vào (parameters).
  2. Khác kiểu dữ liệu của các tham số truyền vào.
  3. Khác thứ tự của tham số truyền vào.

Method Overriding – Ghi đè phương thức

Method Overriding là một phương pháp cho phép lớp kế thừa tái định nghĩa một phương thức đã định nghĩa ở lớp cha. Ghi đè được gọi là runtime polymorphism.

Ghi đè phương thức phải thỏa mãn ba điều kiện sau:

  1. Phải có quan hệ kế thừa giữa hai class.
  2. Cùng tên và cùng kiểu trả về (hoặc sub-type).
  3. Cùng các tham số truyền vào.

Thực tế, ghi đè phương thức của Java và C# có nhiều điểm khác nhau. Ngày trước còn tay mơ mình rất hay nhầm lẫn mấy vấn đề này.

Java Method Overriding

Đối với Java, chỉ cần định nghĩa phương thức ghi đè trong class con thì nó được ngầm hiểu là phương thức ghi đè. Từ khóa @Override có dùng hay không cũng được, nhưng anh em nên dùng để lúc đọc code đỡ lú.

Ví dụ: Ghi đè phương thức Sound() của con Vịt.

C# Method Overriding

Đối với C#, khi ghi đè phương thức, ta bắt buộc phải sử dụng từ khóa virtual cho phương thức của lớp cha. Và sử dụng từ khóa override khi định nghĩa phương thức ghi đè. Nếu không, compiler sẽ quăng cho bạn một cái warning bảo là this method bị hide gì đó. Khi đó, ta cần dùng từ khóa new để định nghĩa phương thức đó là Method Hiding. Đây không phải là bug, đây là cơ chế sẽ được đề cập ở bài viết Method Hidding.

Ví dụ C# Method Overriding mình đính kèm vào code trừu tượng bên dưới luôn nha.


Data Abstraction – Trừu tượng hóa dữ liệu

Trước khi bắt đầu, cần thẳng thắn với nhau rằng “Không có tính trừu trượng trong OOP, chỉ có Data Abstraction”. Các bài viết trên mạng đang ra rả rằng Hướng đối tượng có bốn tính chất. Như vậy là không đúng bản chất. Trong các giai đoạn phát triển phần mềm, Data Abstraction nằm trong giai đoạn thiết kế. Còn OOP nằm ở giai đoạn triển khai. Do đó, nó phải đáp ứng được các yêu cầu nghiệp vụ, là tầng trung gian kết nối business logic với các kiến trúc phần mềm. Data Abstraction là mục tiêu mà lập trình hướng đến. OOP sử dụng các object, class, interface, và ba tính chất đóng gói, kế thừa, đa hình cũng để đạt đến trạng thái Abstraction. Đó cũng là lý do mình giới thiệu Data Abstraction sau khi đã nói về những thứ khác của OOP.

Nghe có vẻ trừu tượng, nhưng nghĩ lại thì rất trừu tượng 🤨

Data Abstraction means hiding the unnecessary details from type consumers.

Trừu tượng hóa dữ liệu nghĩa là che giấu những thành phần không cần thiết khỏi người dùng. Các bạn tránh nhầm lần với việc “ẩn những dữ liệu nhạy cảm khỏi khả năng truy xuất của người dùng” của tính đóng gói. Điều này cho phép người dùng có thể triển khai những logic phức tạp dựa trên một lớp trừu tượng có sẵn mà không cần quan tâm bên trong thực sự làm gì.

Trừu tượng có thể đạt được bằng cách sử dụng:

  • Abstract class
  • Interface

Phân tích và trừu tượng hóa sở thú

Ở ba phần trên, chúng ta đã lần lượt xây dựng những thứ nền tảng nhất. Sở thú đã có thể đi vào hoạt động bình thường. Lần này, ta sẽ nâng cấp bằng cách trừu tượng hóa chúng. Làm sao khi sở thú thuê nhân viên mới, họ vẫn có thể cho thú ăn, ngủ, chạy mà không cần quan tâm cụ thể công việc đó phải làm gì.

Animal abstraction class diagram
Animal abstraction class diagram

Interface – Chia tách các chức năng thành các Interface

Mindset của interface là quy định một số chức năng cho đối tượng. Nói đơn giản là interface báo cho người dùng biết class đang được sử dụng có thể làm gì. Thực tế, các interface có sẵn trong Java thường được đặt kiểu: Runnable, Enumerable,… đại diện cho khả năng mà class đó có thể phục vụ. Ở bài viết này, chúng ta sẽ tạo 3 interface là IAnimal, IRunnable, ISwimmable đại diện cho loài vật và khả năng di chuyển của chúng.

Abstract class – Implement một số chức năng

Trong thực tế, động vật chỉ là một khái niệm, không phải là con vật cụ thể nên được gọi là trừu tượng. Chúng ta bắt đầu xây dựng lớp trừu tượng Animal. Trong đó có các property và implement sẵn một vài chức năng. Lúc này chúng ta không thể tạo object kiểu Animal ani = new Animal(); được mà nó sẽ được dùng để đại diện cho các object khác kế thừa từ nó.

Ví dụ: Animal ani = new Duck("Donal") hoặc Animal ani = new Monkey("Wukong");

Hoặc vjp pro hơn: IList<IAnimal> zoo = new List<IAnimal>(); Sau đó zoo.Add(new Duck("Donal"));

Tạo lớp Duck và Monkey

Viết hàm Main để tạo sở thú

Chúng ta sẽ tạo một danh sách các con vật và cho chúng ăn mà không cần quan tâm chúng là con gì.

Và đây là kết quả khi chạy hàm main trên. Kết quả có giống những gì bạn dự đoán không? Nếu không, trả lời câu hỏi vì sao nhé!


Chính vì thế, mình không đặt tiêu đề bài viết là “Bốn tính chất của hướng đối tượng” mà đổi thành “Bốn tính chất cần lưu tâm khi học…”.

Tổng kết

Đây là bài viết thứ 2 của series Hướng đối tượng bỏ túi. Và cũng là quan trọng nhất trong ba bài. Ở phần này, chúng ta đã nói về bốn tính chất của lập trình hường đối tượng. Có thể các bạn không để ý, mình sắp xếp theo thứ tự tính đóng gói, kế thừa, đa hình và trừu tượng là vì tính chất về sau yêu cầu nắm tính chất trước mới ứng dụng được.

Series hướng đối tượng bỏ túi – OOP in basic:

  1. Các khái niệm căn bản trong lập trình hướng đối tượng.
  2. Bốn tính chất cần lưu tâm khi lập trình hướng đối tượng.
  3. Nguyên tắc S.O.L.I.D – Cảnh giới tối thượng của hướng đối tượng.

Bài viết nặng lý thuyết quá phải không anh em 😅 Để tổng hợp và viết bài này mình cũng mất mấy ngày lận. Cũng không thể tránh khỏi sai sót. Nếu anh em phát hiện chỗ nào không ổn hoặc giải thích khó hiểu. Hãy comment bên dưới để mình chỉnh sửa lại phù hợp hơn nhen. Cuối cùng, mình đính kèm bức ảnh tổng quan của 3 bài viết về hướng đối tượng.

Tổng quan lập trình hướng đối tượng
Tổng quan lập trình hướng đối tượng

Refs

Difference between Compile-time and Run-time Polymorphism in JavaMethod Hiding in C#Method Overriding in JavaC# | Method OverridingDifference between Method Overriding and Method Hiding in C#Abstraction is not a principle of Object-Oriented Programming

Chào bạn, mình là lập trình viên, yêu thiên nhiên và ghiền chụp ảnh. Blog này được tạo nên để lưu giữ những trải nghiệm cũng như ghi chú các kiến thức mình học được. Hy vọng bạn sẽ tìm thấy gì đó hay ho ở đây. 🌸

5 8 votes
Article Rating
Subscribe
Notify of
guest

1 Comment
Most Voted
Newest Oldest
Inline Feedbacks
View all comments