Mục lục bài họcĐang ở d08-b1
← IB Computer Science
0/32 bài đã học xong
Chương 8 · Option: Object-Oriented Programming and Databases · Bài 1/4 của chương · bài 29/32 của IB Computer Science

Object-Oriented Programming

Lập trình hướng đối tượng
← Mục lục bài học
Lý thuyết · English

Classes and objects. A class is a template describing the data (attributes) and the operations (methods) that a kind of thing has. An object is a particular instance created from that template, with its own values. A constructor is the method that runs when an object is created and puts it into a valid initial state.

Encapsulation. Attributes are declared private and access is provided through public methods. The purpose is not secrecy but control: because every change to the data must pass through a method, the class can validate input and maintain its own invariants — a bank account can refuse a withdrawal that would create a negative balance, which is impossible to guarantee if any code may write the field directly. Encapsulation also allows the internal representation to be replaced without affecting any external code, because callers depend only on the public interface.

Inheritance. A subclass acquires the attributes and methods of its superclass and adds or changes what it needs. The relationship is valid only when the statement subclass IS A superclass is true — an hourly employee is an employee. Where that is false, inheritance is the wrong tool and composition, in which one object holds another as an attribute, should be used instead. Over-using inheritance produces deep hierarchies in which a change near the root breaks distant subclasses.

Polymorphism. A subclass may override a method of its superclass with its own version. When code holds a reference typed as the superclass and calls that method, the version belonging to the object's actual class runs. The practical value is decisive: a loop that pays every employee needs no knowledge of employee types, and adding a new type later requires writing one new subclass and changing nothing in the loop. Do not confuse this with overloading, which merely means several methods share a name but take different parameters, and is resolved when the program is compiled.

Design quality. A good design has high cohesion — each class does one clearly identifiable job — and low coupling — classes interact through narrow, well-defined interfaces rather than reaching into each other's data. These two properties are what make an object-oriented program easier to test, to change and to divide among several developers; without them the paradigm delivers none of its promised benefits.

Giải thích tiếng Việt

Lớp và đối tượng. Lớp là một khuôn mẫu mô tả dữ liệu, tức các thuộc tính, và các thao tác, tức các phương thức, mà một loại sự vật có. Đối tượng là một thể hiện cụ thể được tạo ra từ khuôn mẫu đó, mang các giá trị của riêng nó. Hàm khởi tạo là phương thức chạy khi đối tượng được tạo, đưa nó về một trạng thái ban đầu hợp lệ.

Đóng gói. Các thuộc tính được khai báo riêng tư và việc truy cập được cung cấp qua các phương thức công khai. Mục đích không phải là giữ bí mật mà là kiểm soát: vì mọi thay đổi tới dữ liệu đều phải đi qua một phương thức, lớp có thể kiểm tra tính hợp lệ của dữ liệu đầu vào và duy trì các bất biến của chính nó — một tài khoản ngân hàng có thể từ chối một lệnh rút làm số dư âm, điều không thể bảo đảm được nếu bất kỳ đoạn mã nào cũng ghi trực tiếp vào trường dữ liệu. Đóng gói còn cho phép thay hoàn toàn cách biểu diễn bên trong mà không ảnh hưởng tới đoạn mã bên ngoài nào, vì bên gọi chỉ phụ thuộc vào giao diện công khai.

Kế thừa. Lớp con nhận các thuộc tính và phương thức của lớp cha rồi bổ sung hoặc thay đổi phần nó cần. Quan hệ này chỉ hợp lệ khi phát biểu lớp con LÀ MỘT lớp cha là đúng — một nhân viên theo giờ là một nhân viên. Ở nơi phát biểu đó sai, kế thừa là công cụ sai và phải dùng kết hợp, trong đó một đối tượng giữ một đối tượng khác làm thuộc tính. Lạm dụng kế thừa tạo ra các cây phân cấp sâu, trong đó một thay đổi gần gốc làm hỏng các lớp con ở xa.

Đa hình. Lớp con có thể ghi đè một phương thức của lớp cha bằng phiên bản của riêng nó. Khi đoạn mã giữ một tham chiếu có kiểu là lớp cha và gọi phương thức đó, phiên bản thuộc lớp thật của đối tượng sẽ được chạy. Giá trị thực tiễn là quyết định: một vòng lặp trả lương cho mọi nhân viên không cần biết gì về các loại nhân viên, và việc thêm một loại mới về sau chỉ đòi viết thêm một lớp con mà không phải sửa gì trong vòng lặp. Đừng nhầm điều này với nạp chồng, vốn chỉ có nghĩa là nhiều phương thức dùng chung một tên nhưng nhận tham số khác nhau, và được phân giải ngay khi biên dịch.

Chất lượng thiết kế. Một thiết kế tốt có độ gắn kết cao — mỗi lớp làm đúng một việc nhận diện được rõ ràng — và độ ràng buộc thấp — các lớp tương tác qua những giao diện hẹp và được định nghĩa rõ, chứ không thò tay vào dữ liệu của nhau. Chính hai tính chất này mới làm một chương trình hướng đối tượng dễ kiểm thử, dễ thay đổi và dễ chia cho nhiều người cùng làm; thiếu chúng thì mô hình này không mang lại lợi ích nào như nó hứa hẹn.

SƠ ĐỒ LỚP UML — ĐÓNG GÓI, KẾ THỪA VÀ ĐA HÌNH NhanVien - maNV : String - luongCoBan : double + tinhLuong() : double + getMaNV() : String dấu TRỪ = riêng tư dữ liệu bị che dấu CỘNG = công khai giao diện ra ngoài tam giác RỖNG = kế thừa NhanVienGio - soGio : int + tinhLuong() : double GHI ĐÈ — công thức riêng NhanVienHoaHong - doanhSo : double + tinhLuong() : double GHI ĐÈ — công thức riêng ĐA HÌNH: gọi tinhLuong() trên một danh sách NhanVien — mỗi đối tượng chạy phiên bản của LỚP THẬT của nó Thêm một loại nhân viên mới KHÔNG phải sửa đoạn mã duyệt danh sách — đó là giá trị thật của đa hình
Ba khái niệm cốt lõi của lập trình hướng đối tượng đều hiện ra trên sơ đồ này, và điểm số đến từ việc giải thích chúng LÀM GÌ chứ không phải định nghĩa chúng. Đóng gói hiện ra ở các dấu trừ và dấu cộng. Dấu trừ đánh dấu thành phần riêng tư: dữ liệu bị che khỏi bên ngoài. Dấu cộng đánh dấu thành phần công khai: đó là giao diện mà phần còn lại của chương trình được phép dùng. Lợi ích không phải là bí mật mà là kiểm soát: mọi thay đổi tới dữ liệu phải đi qua các phương thức của lớp, nên lớp có thể kiểm tra tính hợp lệ và duy trì các bất biến của mình. Nó cũng cho phép đổi hoàn toàn cách lưu trữ bên trong mà không ảnh hưởng tới bất kỳ đoạn mã nào bên ngoài. Kế thừa hiện ra ở mũi tên đầu tam giác rỗng chỉ lên lớp cha. Hai lớp con nhận toàn bộ thuộc tính và phương thức của lớp NhanVien mà không phải viết lại, rồi bổ sung phần riêng của mình. Quan hệ này chỉ đúng khi phát biểu lớp con LÀ MỘT lớp cha có nghĩa — nhân viên theo giờ là một nhân viên. Nếu phát biểu đó không đúng thì không được dùng kế thừa. Đa hình là dải tím ở dưới và là khái niệm có giá trị thực tế lớn nhất. Cả hai lớp con đều ghi đè phương thức tinhLuong bằng công thức riêng. Khi chương trình duyệt một danh sách NhanVien và gọi tinhLuong trên từng phần tử, mỗi đối tượng chạy phiên bản thuộc lớp thật của nó, dù đoạn mã duyệt không hề biết đó là loại nhân viên nào. Giá trị nằm ở chỗ: thêm một loại nhân viên mới chỉ cần viết thêm một lớp con, và không phải sửa một dòng nào trong đoạn mã duyệt danh sách. Đây là câu trả lời chuẩn cho câu hỏi vì sao lập trình hướng đối tượng dễ mở rộng hơn.
Ví dụ — Vì sao đóng gói không phải là giữ bí mật

Một lớp TaiKhoan có thuộc tính soDu. Hãy so sánh hai thiết kế: cho soDu là công khai, và cho soDu là riêng tư với các phương thức napTien, rutTien và getSoDu. Nêu ba hệ quả cụ thể của lựa chọn thứ hai.

Sau đó giải thích vì sao câu "đóng gói để người khác không nhìn thấy dữ liệu" là một cách hiểu sai.

Giải.

Hệ quả 1 — lớp có thể bảo đảm các bất biến của chính nó. Nếu soDu là công khai, bất kỳ đoạn mã nào ở bất kỳ đâu trong chương trình cũng gán được một giá trị âm, hoặc gán một giá trị vô nghĩa, và lớp TaiKhoan không có cách nào ngăn cản. Khi soDu là riêng tư và mọi thay đổi phải đi qua rutTien, phương thức đó kiểm tra được rằng số tiền rút không vượt số dư và từ chối lệnh không hợp lệ. Bất biến số dư không bao giờ âm trở thành một tính chất được bảo đảm bởi chính lớp, chứ không phải một quy ước mà mọi lập trình viên phải nhớ tuân thủ.

Hệ quả 2 — lỗi được định vị ở một chỗ duy nhất. Nếu một ngày phát hiện có tài khoản mang số dư âm, với thiết kế công khai thì nghi phạm là toàn bộ chương trình, và phải rà mọi dòng có gán vào soDu. Với thiết kế riêng tư, chỉ có đúng hai phương thức thay đổi được giá trị đó, nên phạm vi tìm lỗi thu hẹp từ hàng nghìn dòng xuống vài chục dòng.

Hệ quả 3 — cách biểu diễn bên trong có thể thay hoàn toàn. Giả sử về sau hệ thống cần lưu số dư dưới dạng số nguyên đơn vị xu để tránh sai số của số thực, hoặc cần tính số dư từ một lịch sử giao dịch thay vì lưu trực tiếp. Với thiết kế riêng tư, chỉ phần bên trong lớp thay đổi; mọi đoạn mã gọi getSoDu và rutTien vẫn chạy y nguyên. Với thiết kế công khai, mọi nơi từng chạm vào soDu đều phải sửa.

Vì sao cách hiểu "để giữ bí mật" là sai. Dữ liệu không hề được giấu khỏi ai — phương thức getSoDu công khai cho phép đọc nó, và mã nguồn thì ai đọc cũng thấy. Điều đóng gói thực sự làm là kiểm soát ĐƯỜNG ĐI tới dữ liệu, chứ không phải che dữ liệu. Nói cách khác, nó không trả lời câu hỏi ai được biết mà trả lời câu hỏi thay đổi được thực hiện qua đâu, và chính điều đó cho phép lớp kiểm tra, ghi nhật ký, và tự bảo vệ tính đúng đắn của mình.

Ví dụ — Khi nào kế thừa là công cụ sai

Một lập trình viên viết lớp DanhSachXepChong kế thừa từ lớp DanhSach, lý do là ngăn xếp cần dùng lại các thao tác lưu trữ của danh sách. Hãy chỉ ra vấn đề và đề xuất thiết kế đúng.

Giải.

Kiểm tra bằng phát biểu LÀ MỘT. Kế thừa chỉ hợp lệ khi câu "lớp con là một lớp cha" đúng ở mọi ngữ cảnh mà lớp cha được dùng. Ở đây câu đó SAI: một ngăn xếp không phải là một danh sách. Ngăn xếp có một hợp đồng rất hẹp — chỉ được thêm và lấy ở đỉnh — trong khi danh sách cho phép chèn và xoá ở vị trí bất kỳ.

Hậu quả cụ thể của việc kế thừa sai. Vì lớp con nhận toàn bộ phương thức công khai của lớp cha, người dùng DanhSachXepChong nay gọi được các phương thức chèn vào giữa và xoá ở giữa mà lớp DanhSach cung cấp. Điều đó phá vỡ chính bất biến làm nên ngăn xếp: sau một lệnh chèn vào giữa, cấu trúc không còn tuân theo nguyên tắc vào sau ra trước, và mọi đoạn mã dựa vào tính chất đó đều sai một cách âm thầm. Tệ hơn, lỗi không xuất hiện ở lớp ngăn xếp mà xuất hiện ở nơi khác, rất xa nguyên nhân.

Vấn đề thứ hai — ràng buộc quá chặt. Lớp con phụ thuộc vào chi tiết cài đặt của lớp cha. Nếu về sau lớp DanhSach đổi cách lưu trữ hoặc đổi hành vi của một phương thức, DanhSachXepChong có thể hỏng mà không có dấu hiệu báo trước ở mã nguồn của chính nó.

Thiết kế đúng — dùng KẾT HỢP thay cho kế thừa. Lớp NganXep chứa một đối tượng DanhSach làm thuộc tính riêng tư, và chỉ công bố ba phương thức đẩy vào, lấy ra và kiểm tra rỗng. Bên trong, mỗi phương thức đó gọi thao tác tương ứng của danh sách. Lợi ích: người dùng NganXep chỉ gọi được ba thao tác hợp lệ, nên bất biến vào sau ra trước được bảo đảm; và có thể thay hoàn toàn phần lưu trữ bên trong — chuyển từ danh sách sang mảng chẳng hạn — mà không ai bên ngoài bị ảnh hưởng.

Nguyên tắc rút ra để trả lời mọi câu hỏi dạng này. Dùng kế thừa khi phát biểu LÀ MỘT đúng và khi lớp con có thể thay thế lớp cha ở mọi nơi mà không phá vỡ điều gì. Dùng kết hợp khi quan hệ thực chất là CÓ MỘT hoặc khi chỉ muốn tái sử dụng phần cài đặt. Việc tái sử dụng mã, tự nó, chưa bao giờ là lý do đủ để kế thừa.

Bẫy hay mất điểm — Bẫy 1 — giải thích đóng gói là "giấu dữ liệu khỏi người khác". Dữ liệu vẫn đọc được qua phương thức công khai. Cái đóng gói làm là KIỂM SOÁT đường đi tới dữ liệu, nhờ đó lớp kiểm tra được tính hợp lệ và thay được cách biểu diễn bên trong.
Bẫy 2 — dùng kế thừa chỉ để tái sử dụng mã. Kế thừa chỉ hợp lệ khi phát biểu LÀ MỘT đúng; nếu không thì lớp con thừa hưởng cả những phương thức phá vỡ bất biến của nó. Khi chỉ muốn tái sử dụng cài đặt, hãy dùng KẾT HỢP.
Bẫy 3 — nhầm đa hình với nạp chồng. Đa hình là ghi đè, được phân giải lúc CHẠY theo lớp thật của đối tượng, và giá trị của nó là cho phép thêm loại mới mà không sửa mã cũ. Nạp chồng chỉ là nhiều phương thức trùng tên nhưng khác tham số, phân giải lúc BIÊN DỊCH.
Phải nhớ — Chốt bài 1. Bốn thứ mang vào phòng thi. Một, đóng gói để kiểm soát chứ không để giấu, kèm ba hệ quả cụ thể: bảo đảm bất biến, thu hẹp phạm vi tìm lỗi, và cho phép thay cách biểu diễn bên trong. Hai, phát biểu LÀ MỘT là tiêu chí duy nhất để dùng kế thừa; tái sử dụng mã thì dùng kết hợp. Ba, giá trị của đa hình là thêm một loại mới chỉ cần viết thêm một lớp con mà không sửa mã cũ — nêu đúng câu này là lấy trọn điểm. Bốn, độ gắn kết cao và độ ràng buộc thấp là hai tiêu chí đánh giá mọi thiết kế hướng đối tượng.

Đọc xong rồi — làm thử ngay

Bài tập của chương Option: Object-Oriented Programming and Databases gồm 14 câu trắc nghiệm và 5 đề tự luận. Đáp án hiện ngay khi chọn, miễn phí.

Làm bài tập chương →