Được chỉnh sửa ngày 16/12/2022.
Chào mừng các bạn đến với bài học Java số 15, bài học về Hướng đối tượng. Bài học này nằm trong chuỗi bài học lập trình ngôn ngữ Java của Yellow Code Books.
Mình cảm nhận như thời gian vừa qua rất là dài khi mà chúng ta từng bước làm quen với ngôn ngữ Java một cách… không hướng tối tượng gì ráo. Như các bạn cũng đã biết, ngay từ những bài học Java đầu tiên, mình cũng có nói rằng ngôn ngữ này được sinh ra để chúng ta suy nghĩ theo hướng đối tượng. Vậy mà chúng ta cũng đã học tập một cách trôi chảy theo lối suy nghĩ cũ rồi đấy thôi, mặc dù thỉnh thoảng chúng ta vẫn xen vào đâu đó vài kiến thức hướng đối tượng. Điều này chứng tỏ rằng hướng đối tượng là cái gì đó rất gần gũi và dễ dàng tiếp cận.
Vậy thì hướng đối tượng là gì? Suy nghĩ theo hướng cũ và suy nghĩ theo hướng đối tượng khác nhau như thế nào, chúng có bổ trợ cho nhau không? Tại sao lại phải hướng đối tượng? Vân vân và mây mây… Tất cả những câu hỏi này sẽ được làm sáng tỏ dần trong loạt bài tiếp theo của Yellow Code Books nhé. Còn bài hôm nay, do chúng ta mới đặt chân vào ngưỡng cửa của hướng đối tượng, sẽ rất tốt khi cùng nhau dạo qua các khái niệm và các so sánh trước khi đi vào từng bài học cụ thể.
Các Hướng Tư Duy Trong Lập Trình
Lập trình hướng-đối-tượng hay hướng-cái-gì đi nữa thì cũng chỉ là một giải pháp, một tư duy trong lập trình mà thôi. Nó không cao siêu hay phức tạp gì. Nó cũng không phụ thuộc vào ngôn ngữ lập trình Java hay gì cả. Nó chỉ là tập hợp các phương pháp và nguyên tắc để giúp bạn suy nghĩ và tổ chức vấn đề, từ đó giúp làm ra một chương trình phức tạp và to lớn một cách dễ dàng hơn.
Và dĩ nhiên với việc suy nghĩ theo một hướng lập trình nào đó, thì ngôn ngữ lập trình cũng phải hỗ trợ lập trình viên các cú pháp theo các nguyên tắc hướng đó. Chính vì vậy chúng ta mới có các ngôn ngữ lập trình hướng đối tượng hay không hướng đối tượng. Như ngôn ngữ Java mà các bạn đang tìm hiểu là một ngôn ngữ hướng đối tượng.
Có thể nói là có rất nhiều tài liệu đã mô tả cụ thể về thế nào là lập trình theo hướng đối tượng và thế nào là lập trình không hướng đối tượng. Nhưng với một vài tài liệu mà mình biết, chắc chắn là rất khó để các bạn hiểu sự khác nhau này. Vậy với các bài học của mình thì mình mời các bạn tiếp cận các hướng lập trình theo một cách hoàn toàn khác, đó là, chúng ta hãy cùng xem qua một ví dụ đơn giản, và cùng nhau thay đổi tư duy dần dần để từng bước khám phá cách thức xây dựng một ứng dụng theo hướng cũ và hướng đối tượng sẽ ra sao nhé.
Xây Dựng Ứng Dụng Theo Hướng “Cũ”
Giả sử có yêu cầu rằng bạn hãy viết một chương trình kêu người dùng nhập vào Bán kính của một Hình tròn, sau đó chương trình sẽ in ra console Chu vi và Diện tích của Hình tròn đó.
Nếu bạn bắt tay vào code nhanh sườn của chương trình, thì nó sẽ trông như thế này.
public static void main(String[] args) { // Kêu người dùng nhập vào Bán kính Hình tròn System.out.print("Hãy nhập vào Bán kính Hình tròn: "); Scanner scanner = new Scanner(System.in); float r = scanner.nextFloat(); // Tính Chu vi Hình tròn ở đây // ... // Tính Diện tích Hình tròn ở đây // ... // Xuất kết quả Chu vi và Diện tích của Hình tròn ra console System.out.println("Chu vi Hình tròn: "); System.out.println("Diện tích Hình tròn: "); }
Với cách tiếp cận từ Bài học số 1 đến giờ, rất dễ để bạn bắt tay vào hoàn thành chương trình này đúng không nào. Để tính Chu vi và Diện tích của Hình tròn, chúng ta chỉ cần áp dụng kiến thức toán học vào mà thôi, bạn xem code hoàn chỉnh như sau.
public static void main(String[] args) { // Khai báo số PI là hằng số final float PI = 3.14f; // Kêu người dùng nhập vào Bán kính Hình tròn System.out.print("Hãy nhập vào Bán kính Hình tròn: "); Scanner scanner = new Scanner(System.in); float r = scanner.nextFloat(); // Tính Chu vi Hình tròn ở đây float cv = 2*PI*r; // Tính Diện tích Hình tròn ở đây float dt = PI*r*r; // Xuất kết quả Chu vi và Diện tích của Hình tròn ra console System.out.println("Chu vi Hình tròn: " + cv); System.out.println("Diện tích Hình tròn: " + dt); }
Ôi có khó gì đâu! Cách tư duy như chúng ta làm với ví dụ này người ta gọi là tư duy theo hướng thủ tục (POP – Procedure Oriented Programming), hay có thể gọi là hướng cấu trúc. Bởi vì chúng ta tư duy theo kiểu chia nhỏ yêu cầu ra thành các “thủ tục”, các thủ tục ở đây là các hành động có thể có của một chương trình. Chẳng hạn như với ví dụ trên chúng ta có các thủ tục tính Chu vi Hình tròn, tính Diện tích Hình tròn. Bạn cũng có thể tách từng thủ tục này thành các phương thức (chúng ta sẽ học đến phương thức sau) cho chương trình dễ nhìn hơn kiểu như sau.
// Khai báo số PI là hằng số static final float PI = 3.14f; public static void main(String[] args) { // Kêu người dùng nhập vào Bán kính Hình tròn System.out.print("Hãy nhập vào Bán kính Hình tròn: "); Scanner scanner = new Scanner(System.in); float r = scanner.nextFloat(); // Chu vi Hình tròn float cv = chuViHinhTron(r); // Diện tích Hình tròn float dt = dienTichHinhTron(r); // Xuất kết quả Chu vi và Diện tích của Hình tròn ra console System.out.println("Chu vi Hình tròn: " + cv); System.out.println("Diện tích Hình tròn: " + dt); } public static float chuViHinhTron(float r) { return 2 * PI * r; } public static float dienTichHinhTron(float r) { return PI * r * r; }
Câu chuyện chưa dừng lại ở đây. Giả sử yêu cầu được nâng tầm lên. Đòi hỏi chương trình hỗ trợ thêm các phương thức liên quan đến Hình trụ. Cụ thể là nhập thêm chiều cao Hình trụ rồi sau đó in ra thêm Thể tích Hình trụ (Hình trụ này có Bán kính mặt tròn bằng với Bán kính Hình tròn trên kia).
Vậy là với tư duy hướng thủ tục, bạn cũng sẽ kêu dễ, và… Boom! Code mới ra đời với một số thủ tục mới.
// Khai báo số PI là hằng số static final float PI = 3.14f; public static void main(String[] args) { // Kêu người dùng nhập vào Bán kính Hình tròn System.out.print("Hãy nhập vào Bán kính Hình tròn: "); Scanner scanner = new Scanner(System.in); float r = scanner.nextFloat(); // Kêu người dùng nhập vào Chiều cao Hình trụ System.out.print("Hãy nhập vào Chiều cao Hình trụ: "); float h = scanner.nextFloat(); // Chu vi Hình tròn float cv = chuViHinhTron(r); // Diện tích Hình tròn float dt = dienTichHinhTron(r); // Thể tích Hình trụ float ttHinhTru = theTichHinhTru(dt, h); // Xuất kết quả Chu vi và Diện tích của Hình tròn, Thể tích Hình trụ ra console System.out.println("Chu vi Hình tròn: " + cv); System.out.println("Diện tích Hình tròn: " + dt); System.out.println("Thể tích Hình trụ: " + ttHinhTru); } public static float chuViHinhTron(float r) { return 2 * PI * r; } public static float dienTichHinhTron(float r) { return PI * r * r; } public static float theTichHinhTru(float dt, float h) { return dt * h; }
Cũng may là các hàm tính Chu vi, Diện tích, Thể tích trong ví dụ này rất ngắn. Nhưng bạn có thể thấy nếu yêu cầu ngày một phát sinh, như đòi hỏi tính thêm Chu vi hay Diện tích hay Thể tích cho các Hình vuông, Hình bình hành, Hình cầu,… thì các thủ tục trong chương trình của bạn sẽ phình ra đến mức nào, làm cho chương trình càng khó quản lý nữa. Do đó cần phải có một kiểu tư duy mới khiến cho việc quản lý code được dễ dàng hơn, thậm chí có thể rút ngắn thời gian code thông qua việc tận dụng lại các thủ tục tính toán nữa. Vậy là từ mong muốn đó, tư duy hướng đối tượng ra đời.
Có nhiều tài liệu cho rằng, phương thức suy nghĩ theo tư duy hướng thủ tục này là tư duy kiểu top-down, tức là theo kiểu từ trên-xuống, hay còn có thể hiểu là tư duy theo kiểu tổng quát đến cụ thể. Vậy là sao? Tức là bạn sẽ nhìn tổng thể yêu cầu của chương trình trước, như ví dụ bạn thấy ngay yêu cầu là tính Chu vi và Diện tích hình tròn, bạn sẽ xây dựng các thủ tục tính toán đó trước. Sau khi tiếp cận cái tổng thể đó, thì bạn thấy cần phải viết thêm các thủ tục nhỏ hơn để mà dễ quản lý, chẳng hạn bạn muốn thêm các thủ tục tính Bình phương, Thập phương của một số hạng. Nếu còn có thể phân chia thủ tục nhỏ đó thành các thủ tục nhỏ hơn nữa thì cứ phân chia. Đó chính là tư duy kiểu top-down.
Xây Dựng Ứng Dụng Theo Hướng Đối Tượng
Hướng đối tượng (OOP – Object Oriented Programming). Cách tư duy mới này sẽ không hướng đến các thủ tục nữa, mà là hướng đến các đối tượng. Ngoài thực tế thì các đối tượng sẽ là các thực thể hay sự vật mà chúng ta có thể cầm nắm, có thể gọi tên được, và còn có cả trạng thái và hành vi. Áp dụng vào trong chương trình phần mềm thì khái niệm đối tượng vẫn y như vậy.
Chẳng hạn với ví dụ viết chương trình tính Chu vi và Diện tích Hình tròn trên đây. Thì đối tượng mà ta cần quan tâm đó chính là Hình tròn. Vậy làm sao để giải quyết yêu cầu? Như bạn đã biết, đối tượng được sinh ra trong lập trình (hay ngoài đời thực) đều có những trạng thái (đặc điểm) và hành vi (hành động) nhất định. Như ở đây Hình tròn có đặc điểm là độ lớn của nó, chính là giá trị Bán kính, còn hành động của nó chính là các phép tính Chu vi và Diện tích của bản thân mình. Thậm chí bạn có thể xây dựng cho Hình tròn tự nó phải có trách nhiệm kêu người dùng nhập vào Bán kính và lưu lại. Rồi trách nhiệm tự nó in kết quả Chu vi và Diện tích ra console luôn. Khỏe!!! Như mình minh họa bằng code dưới đây, bạn không cần phải biết chính xác code này là như nào đâu, chỉ cần biết ý tưởng của hướng đối tượng như thế nào là được.
public static void main(String[] args) { // Khai báo Đối tượng Hình tròn và ra lệnh cho đối tượng này thực hiện // các yêu cầu HinhTron hinhTron = new HinhTron(); // Hình tròn tự kêu người dùng nhập Bán kính hinhTron.nhapBanKinh(); // Hình tròn tự nó biết cách tính Chu vi hinhTron.tinhChuVi(); // Hình tròn tự nó biết cách tính Diện tích hinhTron.tinhDienTich(); // Hình tròn tự in kết quả ra console hinhTron.inChuVi(); hinhTron.inDienTich(); }
Bạn có nhận ra sự gọn gàng và dễ quản lý trong code không? Mình rất thích nhìn thấy code như thế này.
Khác biệt sẽ rất lớn khi mà các thủ tục tính Chu vi, tính Diện tích là các thủ tục lớn và phức tạp. Khi đó các thủ tục của Đối tượng nào sẽ được giao cho Đối tượng đó, tức là bạn xây dựng các thủ tục đặc thù đó bên trong các Đối tượng (bạn sẽ được biết cách xây dựng này sau), và khi cần đến các thủ tục nào, chúng ta chỉ cần gọi đến thủ tục thông qua đối tượng đã được khai báo, như đối tượng HinhTron trên đây.
Với việc bạn chỉ định Hình tròn phải có các thủ tục của nó. Thì việc thêm vào một Hình trụ cũng chỉ là việc khai báo thêm một đối tượng mới, với các thủ tục mới tương xứng. Ngoài ra thì đối tượng Hình trụ còn có các đặc tính và thủ tục giống như Hình tròn, do đó chúng ta hoàn toàn có thể dùng lại được các thủ tục đã khai báo bên Hình tròn. Người ta gọi việc dùng lại các thủ tục này là tính kế thừa trong hướng đối tượng, thông qua việc chỉ định Hình trụ là con của Hình tròn (chúng ta sẽ xem xét mối quan hệ kế thừa này ở các bài học sau). Việc Hình trụ là con Hình tròn khiến nó có được các trạng thái và hành vi mà cha nó là Hình tròn đã định nghĩa, nên việc quản lý và sử dụng lại code rất dễ dàng. Về sau nếu như xuất hiện thêm Hình cầu, Hình abc, hay Hình xyz gì đó, thì chúng ta hoàn toàn có thể tạo các đối tượng và chúng sẽ có mối quan hệ nào đó với nhau, kèm các thủ tục đặc trưng hay kế thừa của nhau.
Mình cũng ví dụ bằng code cho mong muốn được tính Thể tích Hình trụ như sau.
public static void main(String[] args) { // Khai báo Đối tượng Hình trụ // Và ra lệnh cho đối tượng này thực hiện // các yêu cầu HinhTru hinhTru = new HinhTru(); // Hình trụ tự kêu người dùng nhập Bán kính // Thực chất bên trong thủ tục này Hình trụ gọi đến // phương thức nhập bán kính // mà cha của nó là Hình tròn đã khai báo hinhTru.nhapBanKinh(); // Hình trụ tự kêu người dùng nhập Chiều cao hinhTru.nhapChieuCao(); // Hình trụ tự nó biết cách tính Chu vi // Cũng dùng đến phương thức // tính Chu vi mà cha nó là // HinhTron đã khai báo hinhTru.tinhChuVi(); // Hình trụ tự nó biết cách tính Diện tích // Cũng dùng đến phương thức // tính Diện tích mà cha nó là // Hình tròn đã khai báo hinhTru.tinhDienTich(); // Hình trụ tự nó biết cách tính Thể tích hinhTru.tinhTheTich(); // Hình trụ tự in kết quả ra console hinhTru.inChuVi(); // Có gọi đến phương thức in kết quả của cha nó là Hình tròn hinhTru.inDienTich(); // Có gọi đến phương thức in kết quả của cha nó là Hình tròn hinhTru.inTheTich(); }
Nếu hướng thủ tục trên kia là tư duy theo kiểu top-bottom. Thì việc hướng đối tượng này, với tiếp cận đầu tiên đến từng đối tượng như Hình tròn, Hình trụ như ví dụ trên, sau đó ta mới xây dựng các thủ tục cho nó, thêm các mối quan hệ, chỉnh sửa các vai trò cho từng đối tượng khi mà yêu cầu hệ thống càng ngày càng phức tạp. Cách suy nghĩ này người ta gọi là tư duy kiểu bottom-up, tức là theo kiểu từ dưới lên, hay còn có thể hiểu là từ cụ thể đến tổng quát.
Vậy thôi! Tư duy theo hướng đối tượng cốt lõi là thế thôi. Bạn đã thấy thích lập trình theo hướng đối tượng rồi đúng không nào. Không thích cũng phải thích nhé, vì nếu không có hướng đối tượng, thì các ứng dụng lớn sau này bạn không thể xây dựng nổi đâu. Trên đây chỉ là ví dụ vắn tắt về hướng đối tượng thôi, mọi thứ râu ria và thế mạnh của nó chúng ta sẽ dần dần nói đến ở các bài học sau nữa nhé.
Vậy Tóm Lại, Tư Duy Theo Hướng Đối Tượng Là Như Thế Nào?
Như bạn đã làm quen trên đây, mục này mình chốt lại thôi. Đó là, đã gọi là hướng đối tượng, tức là mong muốn bạn luôn suy nghĩ mọi thứ theo đối tượng.
Chẳng hạn với yêu cầu xây dựng phần mềm quản lý Sinh viên, thì Trường học, Lớp học, Sinh viên, Giáo viên, thậm chí Bàn ghế, Máy chiếu, Môn học,… đều có thể trở thành đối tượng để chúng ta vận dụng trong việc quản lý. Miễn là đối tượng đó có xuất hiện bên trong ứng dụng của chúng ta.
Hay với yêu cầu xây dựng phần mềm quản lý Tour (như TourNote bên bài học Android), thì Ghi chú, Chủ đề, Tài khoản, Hành trình,… đều là các đối tượng mà bạn có thể tổ chức.
Kết Luận
Bài học hôm nay chỉ dừng lại ở chỗ điểm qua các cách tư duy, giúp bạn có trong đầu một ý niệm về các hướng trong lập trình. Để rồi các bài học sau chúng ta từng bước làm cụ thể các ý niệm này lên thành một kiến thức hoàn chỉnh. Có một điều cuối cùng mình cần lưu ý là, lập trình hướng đối tượng không phải một kiểu lập trình mới hoàn toàn giúp thay thế cho hướng thủ tục ngày xưa. Mà hướng đối tượng ra đời nhằm mang lại cho bạn một công cụ mới để xây dựng ứng dụng được nhanh hơn. Bạn buộc phải sử dụng hướng đối tượng để xây dựng sản phẩm, nhưng bạn không buộc phải từ bỏ tư duy hướng thủ tục. Bên trong hướng đối tượng có hướng thủ tục, tất cả chúng góp phần tạo thành một chương trình hoàn chỉnh có cấu trúc chặt chẽ, dễ đọc, dễ bảo trì, dễ nâng cấp.
Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:
– Đánh giá 5 sao ở mỗi bài viết nếu thấy thích.
– Comment bên dưới mỗi bài viết nếu có thắc mắc.
– Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
– Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.
– Ủng hộ blog theo hướng dẫn ở thanh bên phải để blog ngày càng phát triển hơn.
Bài Kế Tiếp
Chúng ta sẽ cùng nói đến hai khái niệm cơ bản nhất trong lập trình hướng đối tượng, đó là Class và Object.
Hay lắm ad ạ
bài giảng rất hay và dễ hiểu