Kế thừa trong Python

<p style="text-align: justify;">Trong v&iacute; dụ ở b&agrave;i tr&ecirc;n, ch&uacute;ng ta đ&atilde; thấy rằng, c&oacute; những đối tượng c&oacute; kh&aacute; nhiều đặc t&iacute;nh giống nhau về trạng th&aacute;i thậm ch&iacute; cả về h&agrave;nh vi. Trong những trường hợp như vậy, việc khai b&aacute;o lại c&aacute;c đối tượng v&agrave; h&agrave;nh vi n&agrave;y trở l&ecirc;n tr&ugrave;ng lặp v&agrave; kh&ocirc;ng cần thiết. Trong những trường hợp như vậy, ch&uacute;ng ta sử dụng kh&aacute;i niệm kế thừa trong lập trinh hướng đối tượng để giảm bớt sự tr&ugrave;ng lặp trong m&atilde; nguồn. Python cũng như c&aacute;c ng&ocirc;n ngữ lập tr&igrave;nh hướng đối tượng kh&aacute;c cũng hỗ trợ thực thi t&iacute;nh kế thừa n&agrave;y. Trong b&agrave;i n&agrave;y ch&uacute;ng ta sẽ t&igrave;m hiểu sơ bộ về t&iacute;nh chất n&agrave;y v&agrave; c&aacute;ch thức thực hiện n&oacute; trong Python.</p> <h2 style="text-align: justify;">Kế thừa l&agrave; g&igrave;?</h2> <p style="text-align: justify;">T&iacute;nh kế thừa trong Python cho ph&eacute;p ch&uacute;ng ta x&aacute;c định một lớp B c&oacute; thể nhận lại (thừa kế) tất cả c&aacute;c thuộc t&iacute;nh từ một lớp A n&agrave;o đ&oacute; đ&atilde; được định nghĩa v&agrave; cho ph&eacute;p mở rộng th&ecirc;m nhiều thuộc t&iacute;nh kh&aacute;c trong lớp B đ&oacute;.</p> <p style="text-align: justify;">Kế thừa l&agrave; một t&iacute;nh năng mạnh mẽ trong lập tr&igrave;nh hướng đối tượng. N&oacute; cho ph&eacute;p định nghĩa một lớp mới với &iacute;t sự sửa đổi so với một lớp hiện c&oacute;. Lớp mới được gọi l&agrave; lớp dẫn xuất (hoặc lớp con) v&agrave; lớp m&agrave; n&oacute; kế thừa được gọi l&agrave; lớp cơ sở (hoặc lớp cha).</p> <p style="text-align: justify;"><strong><em>C&uacute; ph&aacute;p:</em></strong></p> <pre class="language-python"><code>class lớp_cha: Đoạn m&atilde; class lớp_con(lớp_cha): Đoạn m&atilde; 2</code></pre> <p style="text-align: justify;">Như ch&uacute;ng ta thấy ở c&uacute; ph&aacute;p tr&ecirc;n.</p> <ul style="text-align: justify;"> <li style="text-align: justify;">Việc định nghĩa lớp cha sẽ kh&ocirc;ng c&oacute; ảnh hưởng g&igrave; khi ch&uacute;ng ta định nghĩa lớp con</li> <li style="text-align: justify;">Lớp cha phải được định nghĩa trước khi thực hiện định nghĩa lớp con, nếu kh&ocirc;ng chương tr&igrave;nh sẽ kh&ocirc;ng hiểu n&oacute; kế thừa từ lớp n&agrave;o.</li> <li style="text-align: justify;">Khi định nghĩa lớp con ch&uacute;ng ta cần th&ecirc;m dấu ngoặc tr&ograve;n v&agrave; chỉ r&otilde; tham số ph&iacute;a trong l&agrave; lớp con đ&oacute; kế thừa từ lớp cha n&agrave;o. Điều n&agrave;y giống hệt c&uacute; ph&aacute;p định nghĩa h&agrave;m, chỉ kh&aacute;c từ kh&oacute;a class, v&agrave; ở đ&acirc;y ch&uacute;ng ta sẽ thực hiện định nghĩa một lớp thay v&igrave; h&agrave;m.</li> </ul> <p style="text-align: justify;">Lớp dẫn xuất kế thừa c&aacute;c t&iacute;nh năng từ lớp cơ sở v&agrave; c&aacute;c thuộc t&iacute;nh mới c&oacute; thể được th&ecirc;m v&agrave;o trong lớp dẫn xuất n&agrave;y. Điều n&agrave;y dẫn đến khả năng t&aacute;i sử dụng của đoạn m&atilde;.</p> <p style="text-align: justify;">Để chứng minh việc sử dụng kế thừa, ch&uacute;ng ta sẽ c&ugrave;ng lấy một v&iacute; dụ đơn giản.</p> <p style="text-align: justify;">V&iacute; dụ:</p> <pre class="language-python"><code>class SinhVien: def __init__(self, ID): self.ID = ID def in_thong_tin(self): print("ID của sinh vi&ecirc;n l&agrave;: ",self.ID)</code></pre> <p style="text-align: justify;">Lớp SinhVien n&agrave;y c&oacute; c&aacute;c thuộc t&iacute;nh dữ liệu để lưu trữ ID của sinh vi&ecirc;n. Phương thức in_thong_tin() được sử dụng để in ra m&atilde; số ID của sinh vi&ecirc;n.</p> <p style="text-align: justify;">B&acirc;y giờ, ch&uacute;ng ta tiến h&agrave;nh định nghĩa một lớp SinhVienY để chỉ sinh vi&ecirc;n của khối c&aacute;c trường Y dược. C&aacute;c sinh vi&ecirc;n n&agrave;y r&otilde; r&agrave;ng cũng c&oacute; thuộc t&iacute;nh ID để chỉ m&atilde; sinh vi&ecirc;n. Ch&uacute;ng ta sẽ kh&ocirc;ng cần khởi tạo lại ch&uacute;ng trong lớp SinhVienY n&agrave;y nữa, bởi ch&uacute;ng ta sẽ kế thừa n&oacute; từ lớp SinhVien ở tr&ecirc;n.</p> <pre class="language-python"><code>class SinhVien: def __init__(self, ID): self.ID = ID def in_thong_tin(self): print("ID của sinh vi&ecirc;n l&agrave;: ",self.ID) class SinhVienY(SinhVien): def __init__(self,ID,name): SinhVien.__init__(self,ID) self.name=name def in_thong_tin_2(self): print('Đ&acirc;y l&agrave; sinh vi&ecirc;n trường Y')</code></pre> <p style="text-align: justify;">Ở đ&acirc;y, ch&uacute;ng ta thực hiện việc kế thừa phương thức tạo của SinhVien sang SinhVienY bằng c&aacute;ch gọi đến lớp cha của n&oacute; k&egrave;m theo dấu chấm v&agrave; t&ecirc;n h&agrave;m tạo: SinhVien.__init__(). Khi đ&oacute; SinhVienY sẽ kế thừa h&agrave;m tạo của SinhVien cho ph&eacute;p khởi tạo tham số ID. Tuy nhi&ecirc;n SinhVienY c&ograve;n muốn khởi tạo th&ecirc;m cả thuộc t&iacute;nh t&ecirc;n nữa, do đ&oacute; n&oacute; thực hiện g&aacute;n th&ecirc;m name v&agrave;o h&agrave;m tạo.</p> <p style="text-align: justify;">Ngo&agrave;i ra, ch&uacute;ng ta c&ograve;n muốn c&aacute;c đối tượng SinhVienY thực hiện một h&agrave;nh vi in ra chuỗi kh&aacute;c so với c&aacute;c đối tượng SinhVien n&oacute;i chung. Do đ&oacute;, ch&uacute;ng ta khai b&aacute;o một phương thức mới in_thong_tin2() để đưa ra th&ocirc;ng tin về sinh vi&ecirc;n của trường Y. Ta sẽ sử dụng c&aacute;c phương thức trong c&aacute;c lớp như sau.</p> <pre class="language-python"><code>class SinhVien: def __init__(self, ID): self.ID = ID def in_thong_tin(self): print("ID của sinh vi&ecirc;n l&agrave;: ",self.ID) class SinhVienY(SinhVien): def __init__(self,ID,name): SinhVien.__init__(self,ID) self.name=name def in_thong_tin_2(self): print('Đ&acirc;y l&agrave; sinh vi&ecirc;n trường Y') svy=SinhVienY(200,"T&ugrave;ng") svy.in_thong_tin() svy.in_thong_tin_2()</code></pre> <p style="text-align: justify;">Kết quả:</p> <pre class="language-markup"><code>ID của sinh vi&ecirc;n l&agrave;: 200 Đ&acirc;y l&agrave; sinh vi&ecirc;n trường Y</code></pre> <p style="text-align: justify;">Ch&uacute;ng ta c&oacute; thể thấy rằng mặc d&ugrave; ch&uacute;ng ta kh&ocirc;ng định nghĩa c&aacute;c phương thức như in_thong_tin() cho lớp SinhVienY, tuy nhi&ecirc;n, ch&uacute;ng ta vẫn c&oacute; thể sử dụng n&oacute; do n&oacute; đ&atilde; được kế thừa từ lớp cha l&agrave; l&agrave; lớp SinhVien.</p> <p style="text-align: justify;">Khi một thuộc t&iacute;nh hoặc một phương thức kh&ocirc;ng được t&igrave;m thấy trong lớp con, th&igrave; chương tr&igrave;nh sẽ thực hiện t&igrave;m kiếm n&oacute; trong lớp cơ sở. Nếu lớp cơ sở c&oacute; thuộc t&iacute;nh hoặc phương thức n&agrave;y, th&igrave; n&oacute; sẽ thực thi như lớp cơ sở.</p> <h2 style="text-align: justify;">Kế thừa trong b&agrave;i to&aacute;n quản l&yacute; kh&oacute;a học</h2> <p style="text-align: justify;">B&acirc;y giờ ch&uacute;ng ta quay lại b&agrave;i to&aacute;n chương tr&igrave;nh quản l&yacute; kh&oacute;a học ở b&agrave;i trước, ch&uacute;ng ta thấy rằng h&agrave;m tạo trong lớp Student v&agrave; Lecturer l&agrave; gần giống nhau về mặt định nghĩa. Do đ&oacute;, để r&uacute;t gọn đoạn m&atilde; định nghĩa 2 lớp n&agrave;y, ch&uacute;ng ta sẽ sử dụng kh&aacute;i niệm kế thừa trong Python.</p> <p style="text-align: justify;">Tuy nhi&ecirc;n, mọi chuyện ở đ&acirc;y phức tạp hơn v&iacute; dụ ở phần tr&ecirc;n đ&ocirc;i ch&uacute;t. Đ&oacute; l&agrave; b&agrave;i to&aacute;n ai kế thừa ai? Student kế thừa Lecturer hay Lecturer kế thừa Student???</p> <p style="text-align: justify;">Phức tạp phải kh&ocirc;ng?</p> <p style="text-align: justify;">B&acirc;y giờ ch&uacute;ng ta sẽ bắt đầu ph&acirc;n t&iacute;ch lại kh&aacute;i niệm kế thừa nh&eacute;. Ch&uacute;ng ta thấy rằng lớp con sẽ c&oacute; to&agrave;n bộ c&aacute;c đặc t&iacute;nh của lớp cha. Vậy th&igrave; Student kh&ocirc;ng thể kế thừa Lecturer được v&igrave; Student kh&ocirc;ng c&oacute; thuộc t&iacute;nh bank_account tức l&agrave; n&oacute; kh&ocirc;ng lấy thuộc t&iacute;nh n&agrave;y về m&igrave;nh l&agrave;m g&igrave; cả. Điều ngược lại cũng kh&ocirc;ng được, do Lecturer cũng kh&ocirc;ng c&oacute; thuộc t&iacute;nh id v&agrave; điểm v&agrave; Lecturer cũng kh&ocirc;ng muốn lấy c&aacute;c thuộc t&iacute;nh n&agrave;y về m&igrave;nh !!!. Tất nhi&ecirc;n, ch&uacute;ng ta c&oacute; thể cho c&aacute;c lớp lấy "bừa" về nhưng điều đ&oacute; l&agrave; kh&ocirc;ng cần thiết v&agrave; g&acirc;y kh&oacute; hiểu cũng như dễ nhầm lẫn khi sử dụng sau n&agrave;y!</p> <p style="text-align: justify;">Vậy th&igrave; 2 lớp n&agrave;y kh&ocirc;ng thể kế thừa nhau được. Vậy, ch&uacute;ng ta cần l&agrave;m như thế n&agrave;o?</p> <p style="text-align: justify;">Rất đơn giản, ch&uacute;ng ta sẽ định nghĩa một lớp mới l&agrave; lớp Person l&agrave; lớp cơ sở chung của 2 lớp Student v&agrave; Lecturer với c&aacute;c thuộc t&iacute;nh chung l&agrave; giao của c&aacute;c thuộc t&iacute;nh của hai lớp n&agrave;y.</p> <p style="text-align: justify;">V&agrave; khi đ&oacute;, 2 lớp Student, Lecturer sẽ kế thừa từ lớp Person n&agrave;y như sau:</p> <pre class="language-python" tabindex="0"><code>class Person: def __init__(self, first_name,last_name,age,email): self.first_name = first_name self.last_name = last_name self.age = age self.email = email def print_info(self): print(self.first_name +" " +self.last_name +" is " + str(self.age) +" years old.") class Lecturer(Person): def __init__(self,f_name,l_name,age,email,bank_account): super().__init__(f_name,l_name,age,email) self.bank_account = bank_account class Student(Person): def __init__(self,f_name,l_name,age,email,student_id, grade=-1): super().__init__(f_name,l_name,age,email) self.student_id = student_id self.grade=grade std=Student("Nam", "Nguyễn", "21", "namnguyen@tek4.vn", "1234") std.print_info()</code></pre> <p style="text-align: justify;">Như vậy, ch&uacute;ng ta đ&atilde; thực hiện tại cấu tr&uacute;c lại đoạn m&atilde; ở b&agrave;i trước để n&oacute; ngắn gọn hơn v&agrave; chức năng của chương tr&igrave;nh vẫn được giữ nguy&ecirc;n.</p> <p style="text-align: justify;">Ch&uacute; &yacute; rằng ở đ&acirc;y ch&uacute;ng ta đ&atilde; sử dụng phương thức super() để thay thế cho việc gọi đến t&ecirc;n của lớp Person. Điều n&agrave;y kh&aacute;c với v&iacute; dụ tr&ecirc;n. Ch&uacute;ng ta ho&agrave;n to&agrave;n c&oacute; thể l&agrave;m như v&iacute; dụ tr&ecirc;n, tuy nhi&ecirc;n khi ch&uacute;ng ta kh&ocirc;ng chắc lớp cha của lớp con l&agrave; g&igrave;, ch&uacute;ng ta c&oacute; thể t&igrave;m n&oacute; th&ocirc;ng qua phương thức super() n&agrave;y.</p> <p style="text-align: justify;">Trong chương tr&igrave;nh tr&ecirc;n, đ&ocirc;i khi ta muốn in những th&ocirc;ng tin kh&aacute;c nhau cho c&aacute;c đối tượng Lecturer v&agrave; Student. Để l&agrave;m như vậy, ch&uacute;ng ta sẽ cần định nghĩa th&ecirc;m c&aacute;c h&agrave;m in ra m&agrave;n h&igrave;nh ri&ecirc;ng biệt cho c&aacute;c lớp n&agrave;y. V&iacute; dụ:</p> <pre class="language-python"><code>class Lecturer(Person): def __init__(self,f_name,l_name,age,email,bank_account): super().__init__(f_name,l_name,age,email) self.bank_account = bank_account def print_Lecturer(self): print(self.first_name +" " +self.last_name +" is " + str(self.age) +" years old " + self.email + self.bank_account) class Student(Person): def __init__(self,f_name,l_name,age,email,student_id, grade=-1): super().__init__(f_name,l_name,age,email) self.student_id = student_id self.grade=grade def print_Student(self): print(self.first_name +" " +self.last_name +" is " + str(self.age) +" years old " + "Email: " + self.email + "ID: " + self.student_id + "Grade:" + str(self.grade)) std=Student("Nam", "Nguyễn", "21", "namnguyen@tek4.vn", "1234") std.print_info() std.print_Student()</code></pre> <p style="text-align: justify;">Kết quả:</p> <pre class="language-markup"><code>Nam Nguyễn is 21 years old. Nam Nguyễn is 21 years old Email: namnguyen@tek4.vnID: 1234Grade:-1</code></pre> <p style="text-align: justify;">Đến đ&acirc;y, c&oacute; thể bạn đ&atilde; nhận ra một điều kh&aacute; bất tiện phải kh&ocirc;ng ạ?</p> <p style="text-align: justify;">Đ&uacute;ng vậy, với một đối tượng c&oacute; qu&aacute; nhiều phương thức in v&agrave; chỉ sai kh&aacute;c nhau c&oacute; v&agrave;i tham số. Điều n&agrave;y sẽ g&acirc;y kh&oacute; khăn khi ch&uacute;ng ta để biết được ch&uacute;ng ta n&ecirc;n gọi phương thức n&agrave;o. V&agrave; cũng g&acirc;y kh&oacute; hiểu khi đọc lại chương tr&igrave;nh. Trong trường hợp n&agrave;y, Python cũng cung cấp hai kh&aacute;i niệm kh&aacute; quan trọng trong lập tr&igrave;nh hướng đối tượng để giải quyết đ&oacute; l&agrave; t&iacute;nh đa h&igrave;nh v&agrave; nạp chồng phương thức.</p>