Trong phần này chúng ta sẽ tìm hiểu về lập trình hướng đối tượng trong Python.
Lập trình hướng đối tượng (gọi tắt là OOP, từ chữ Anh ngữ object-oriented programming), là kĩ thuật lập trình hỗ trợ công nghệ đối tượng. OOP được xem là giúp tăng năng suất, đơn giản hóa độ phức tạp khi bảo trì cũng như mở rộng phần mềm bằng cách cho phép lập trình viên tập trung vào các đối tượng phần mềm ở bậc cao hơn. Ngoài ra, nhiều người còn cho rằng OOP dễ tiếp thu hơn cho những người mới học về lập trình hơn là các phương pháp trước đó (theo Wikipedia).
Trong OOP có các khái niệm cơ bản sau đây:
- Trừu tượng (Abstraction)
- Đa hình (Polymorphism)
- Đóng gói (Encapsulation)
- Kế thừa (Inheritance)
Các khái niệm trên giải thích theo wikipedia hay lấy trong sách rất khó hiểu nên mình sẽ làm ví dụ luôn và tới đoạn nào mình sẽ giải thích đoạn đó cho dễ hiểu.
Khái niệm đối tượng
Đối tượng là các thực thể của một lớp nào đó. Lớp là khuôn mẫu của các đối tượng. Ví dụ như list
, tuple
, dictionary
, string
, int
… là các lớp. Khi chúng ta khai báo biến thuộc các lớp này thì chúng là các đối tượng. Tất cả mọi thứ trong Python đều là đối tượng.
import sys def function(): pass print (type(1)) print (type("")) print (type([])) print (type({})) print (type(())) print (type(object)) print (type(function)) print (type(sys))
Trong ví dụ trên nhờ vào hàm type()
mà chúng ta biết được thực chất tất cả các kiểu dữ liệu và các module mà chúng ta đã học thực chất đều là các đối tượng.
<class 'int'> <class 'str'> <class 'list'> <class 'dict'> <class 'tuple'> <class 'type'> <class 'function'> <class 'module'>
Từ khóa class
Trong ví dụ trên, các lớp mà chúng ta kiểm tra bằng hàm type
là các lớp có sẵn trong Python. Khi chúng ta muốn định nghĩa một lớp cho riêng mình thì chúng ta dùng từ khóa class
. Một lớp có thể hiểu là một bản thiết kế để tạo ra một thực thể (mà chúng ta gọi là đối tượng). Một đối tượng là một thực thể được xây dựng từ một bản thiết kế đó (mà chúng ta gọi là lớp). Ví dụ chúng ta định nghĩa lớp Dog, thì chúng ta có các đối tượng là Huck, Lulu…. 😀
class First: pass fr = First() print (type(fr)) print (type(First))
Trong đoạn code trên chúng ta định nghĩa một lớp. Phần thân lớp tạm thời được để trống.
fr = First()
Ở dòng trên chúng ta xây dựng nên thực thể fr
từ bản thiết kế First.
Hay nói cách khác là chúng ta đã tạo ra đối tượng fr
từ lớp First
.
<class '__main__.First'> <class 'type'>
Bên trong một lớp, chúng ta có thể định nghĩa các biến và hàm, biến và hàm bên trong một lớp được gọi là thuộc tính và phương thức. Chẳng hạn chúng ta định nghĩa lớp Employee
(nhân viên), bên trong lớp này sẽ có thuộc tính Salary
(lương) và phương thức calculateSalary
(tính tiền lương).
Thuộc tính
Thuộc tính là biến nằm trong một lớp. Thuộc tính mô tả các đặc tính của một đối tượng. Trong Python có một phương thức đặc biệt gọi là __init__()
dùng để khởi tạo giá trị cho các thuộc tính của một đối tượng.
class Cat: def __init__(self, name): self.name = name missy = Cat('Missy') lucky = Cat('Lucky') print (missy.name) print (lucky.name)
Trong đoạn code trên, chúng ta định nghĩa lớp Cat.
Bên trong lớp này, chúng ta định nghĩa phương thức __init__().
Phương thức __init__()
là phương thức khởi tạo của tất cả các lớp, mỗi khi tạo một đối tượng phương thức này sẽ tự động được gọi.
def __init__(self, name):
Bất cứ phương thức nào của Python cũng đều phải có tham số đầu tiên là self
rồi mới đến các tham số khác.
self
thực ra chỉ là biến đối tượng đã gọi phương thức này mà thôi. Chẳng hạn trong ví dụ trên khi chúng ta gọi missy = Cat("Missy")
thì self
chính là missy.
self.name = name
Ở dòng trên chúng ta khởi tạo thuộc tính name
và gán giá trị cho nó.
missy = Cat('Missy') lucky = Cat('Lucky')
Trong hai dòng trên chúng ta dùng hàm Cat()
để tạo ra hai đối tượng lớp thuộc lớp Cat
.
Chúng ta không trực tiếp đưa tham số self vào mà chỉ đưa các tham số thứ 2 trở đi thôi, self
được tự động đưa vào bởi trình thông dịch. Trong ví dụ trên “Missy” và “Lucky” sẽ được gán cho thuộc tính name
trong lớp Cat
.
print (missy.name) print (lucky.name)
Trong hai dòng code trên chúng ta in ra giá trị của thuộc tính name
trong hai đối tượng Cat
. Để truy xuất đến thuộc tính của một đối tượng thì chúng ta ghi tên đối tượng, dấu chấm và tên thuộc tính.
Missy Lucky
Ngoài ra bạn có thể gán giá trị cho các thuộc tính ở bất cứ đâu sau phần định nghĩa lớp chứ không chỉ riêng bên trong phương thức khởi tạo.
class Dynamic: pass d = Dynamic() d.name = "Dynamic" print (d.name)
Trong ví dụ trên chúng ta định nghĩa lớp Dynamic.
d.name = "Dynamic"
Ở dòng này chúng ta khai báo và gán giá trị cho thuộc tính name
.
Dynamic
Một đặc điểm nữa trong Python bạn có thể định nghĩa các thuộc tính chung cho mọi đối tượng.
class Cat: species = 'mammal' def __init__(self, name, age): self.name = name self.age = age missy = Cat('Missy', 3) lucky = Cat('Lucky', 5) print (missy.name, missy.age) print (lucky.name, lucky.age) print (Cat.species) print (missy.__class__.species) print (lucky.species)
Trong ví dụ trên, chúng ta tạo ra hai đối tượng Cat
với thuộc tính name
và age,
hai thuộc tính này được khai báo bên trong phương thức __init__()
và được gọi là thuộc tính đối tượng. Ngoài ra hai đối tượng này còn có một thuộc tính chung là thuộc tính species
,
thuộc tính này được khai báo ở ngoài phương thức __init__()
và
được gọi là thuộc tính lớp. Thuộc tính lớp được chia sẻ chung cho mọi đối tượng của lớp đó, trong khi thuộc tính đối tượng chỉ dành riêng cho đối tượng. Nghĩa là khi bạn thay đổi giá trị của thuộc tính chia sẻ trong một đối tượng thì tất cả các thuộc tính chia sẻ đó trong các đối tượng khác cũng thay đổi theo.
print (Cat.species) print (missy.__class__.species)
Có hai cách để truy xuất thuộc tính lớp, thứ nhất là thông qua tên lớp, cách thứ hai là thông qua một thuộc tính đặc biệt nữa là thuộc tính __class__.
Missy 3 Lucky 5 mammal mammal mammal
Phương thức
Phương thức chẳng qua là các hàm, chỉ khác hàm bình thường ở chỗ chúng được định nghĩa bên trong một lớp. Các phương thức được sử dụng để thực hiện các công việc cụ thể. Phương thức là một thành phần quan trọng trong khái niệm Encapsulation (đóng gói). Chẳng hạn như bạn có một lớp tên là Network,
lớp này có phương thức connect()
dùng để kết nối đến các máy tính trong mạng LAN, thì bạn chỉ quan tâm rằng phương thức này làm công việc kết nối mạng chứ không quan tâm đến việc nó kết nối như thế nào.
class Circle: pi = 3.141592 def __init__(self, radius=1): self.radius = radius def area(self): return self.radius * self.radius * Circle.pi def setRadius(self, radius): self.radius = radius def getRadius(self): return self.radius c = Circle() c.setRadius(5) print (c.getRadius()) print (c.area())
Trong ví dụ trên, chúng ta định nghĩa lớp Circle
(hình tròn) và 3 phương thức mới.
def area(self): return self.radius * self.radius * Circle.pi
Phương thức area()
trả về diện tích hình tròn.
def setRadius(self, radius): self.radius = radius
Phương thức setRadius()
thiết lập giá trị cho thuộc tính radius
(bán kính).
def getRadius(self): return self.radius
Phương thức getRadius()
trả về lấy giá trị bán kính.
c.setRadius(5)
Chúng ta gọi các phương thức thông qua biến đối tượng.
5 78.5398
Kế thừa
Kế thừa là định nghĩa một lớp dựa trên một lớp đã được định nghĩa trước đó. Lớp kế thừa từ lớp khác được gọi là lớp dẫn xuất, lớp được các lớp khác kế thừa mình thì gọi là lớp cơ sở. Kế thừa trong lập trình hướng đối tượng cho phép chúng ta sử dụng lại mã nguồn và giảm độ phức tạp của chương trình. Lớp dẫn xuất có thể kế thừa hoặc mở rộng các tính năng của lớp cơ sở.
class Animal: def __init__(self): print ("Animal created") def whoAmI(self): print ("Animal") def eat(self): print ("Eating") class Dog(Animal): def __init__(self): Animal.__init__(self) print ("Dog created") def whoAmI(self): print ("Dog") def bark(self): print ("Woof!") d = Dog() d.whoAmI() d.eat() d.bark()
Trong ví dụ trên chúng ta định nghĩa hai lớp là là Animal và lớp Dog. Lớp Dog kế thừa từ lớp Animal, thừa hưởng một số phương thức của lớp Animal và có các phương thức của riêng nó. Ở trên lớp Dog kế thừa phương thức eat()
, kế thừa và thay đổi phương thức whoAmI()
, ngoài ra lớp Dog còn có phương thức của riêng nó là phương thức bark().
class Dog(Animal): def __init__(self): Animal.__init__(self) print ("Dog created")
Để kế thừa một lớp thì chúng ta đặt tên lớp đó bên trong cặp dấu ngoặc tròn ()
ngay phía sau phần định nghĩa tên lớp. Nếu bên trong lớp cơ sở đã định nghĩa phương thức __init__(),
chúng ta phải gọi lại phương thức __init__()
từ lớp cơ sở.
Animal created Dog created Dog Eating Woof!
Đa hình
Đa hình là bạn có thể sử dụng các toán tử hay các hàm trên nhiều kiểu dữ liệu khác nhau.
a = "alfa" b = (1, 2, 3, 4) c = ['o', 'm', 'e', 'g', 'a'] print (a[2]) print (b[1]) print (c[3])
Bản thân các hàm có sẵn trong Python cũng có tính chất đa hình. Chẳng hạn như hàm print()
mà chúng ta hay dùng, mỗi lần gọi hàm này bạn có thể đưa vào hầu hết các kiểu dữ liệu khác nhau.
f 2 g
Trong hướng đối tượng thì tính đa hình càng được phát huy tác dụng.
class Animal: def __init__(self, name=''): self.name = name def talk(self): pass class Cat(Animal): def talk(self): print ("Meow!") class Dog(Animal): def talk(self): print ("Woof!") a = Animal() a.talk() c = Cat("Missy") c.talk() d = Dog("Rocky") d.talk()
Trong ví dụ trên, chúng ta định nghĩa hai lớp chó (Dog) và mèo (Cat) kế thừa từ lớp Animal. Do đó cả hai lớp này đều kế thừa phương thức talk()
của lớp Animal, nhưng mỗi lớp lại in ra hai dòng text khác nhau.
Meow! Woof!
Các phương thức đặc biệt
Tất cả các lớp dù là có sẵn hay do chúng ta định nghĩa đều kế thừa từ một lớp gốc trong Python có tên là object
. Lớp này có sẵn một số phương thức và đương nhiên là các lớp do chúng ta định nghĩa đều kế thừa các phương thức này, ví dụ như phương thức __init__()
… Trong Python khi chúng ta gọi đến các hàm hay toán tử được xây dựng sẵn như print(), del
… chúng sẽ gọi đến các phương thức gốc của lớp object. Chính vì các lớp do chúng ta định nghĩa đều được kế thừa từ lớp object nên chúng ta cũng có thể dùng các hàm hay toán tử có sẵn trong Python với các lớp của chúng ta. Trong các ngôn ngữ như C++ thì tính chất này được gọi là quá tải toán tử (operator overloading).
class Book: def __init__(self, title, author, pages): print ("A book is created") self.title = title self.author = author self.pages = pages def __str__(self): return "Title:%s , author:%s, pages:%s " % \ (self.title, self.author, self.pages) def __len__(self): return self.pages def __del__(self): print ("A book is destroyed") book = Book("Inside Steve's Brain", "Leander Kahney", 304) print (book) print (len(book)) del book
Trong ví dụ trên, chúng ta định nghĩa lớp Book. Bên trong lớp này chúng ta định nghĩa 4 phương thức được kế thừa từ lớp object là __init__()
,__str__()
, __len__()
và __del__()
.
print (book)
Khi sử dụng hàm print()
, hàm này sẽ gọi đến phương thức __str__()
, do đó khi chúng ta kế thừa lại phương thức này, hàm print()
sẽ gọi đến phương thức __str__()
trong lớp Book của chúng ta.
print (len(book))
Tương tự, hàm len()
gọi đến phương thức __len__()
del book
Từ khóa del
gọi đến phương thức __del__()
và trên thực tế là phương thức này làm công việc hủy bỏ một đối tượng ra khỏi bộ nhớ. Nhưng ở đây chúng ta kế thừa phương thức này chỉ làm công việc là in ra một đoạn text.
Trong ví dụ dưới đây, chúng ta sẽ kế thừa các toán tử cộng và trừ.
class Vector: def __init__(self, data): self.data = data def __str__(self): return repr(self.data) def __add__(self, other): data = [] for j in range(len(self.data)): data.append(self.data[j] + other.data[j]) return Vector(data) def __sub__(self, other): data = [] for j in range(len(self.data)): data.append(self.data[j] - other.data[j]) return Vector(data) x = Vector([1, 2, 3]) y = Vector([3, 0, 2]) print (x + y) print (y - x)
def __add__(self, other): data = [] for j in range(len(self.data)): data.append(self.data[j] + other.data[j]) return Vector(data)
Chúng ta định nghĩa lớp Vector, lớp vector này kế thừa hai phương thức __add__()
và __sub__()
. phương thức __add__()
sẽ được gọi khi sử dụng toán tử + và phương thức __sub__()
sẽ được gọi khi sử dụng toán tử -.
[4, 2, 5] [2, -2, -1]