Đồ thị tư duy lập trình hướng đối tượng
Hướng đến: quy trình, hàm, đối tượng
Lập trình hướng quy trình: Viết mã theo logic nghiệp vụ từ trên xuống!
Nhược điểm của lập trình hướng quy trình: Mỗi lần gọi đều phải viết lại, mã rất dài, không có khả năng tái sử dụng, mỗi khi thêm chức năng mới phải sửa tất cả mã!
Vậy làm thế nào để giải quyết những nhược điểm trên? Hàm đã ra đời.
Lập trình hướng hàm: Đóng gói mã chức năng vào hàm, sau này không cần viết lại, chỉ cần gọi hàm!
Giải thích hàm: Hàm có thể hiểu là một khối chức năng, bạn chia một chức năng lớn thành các khối nhỏ, khi cần sử dụng chức năng nào thì gọi khối chức năng đó!
Hàm có thể hiểu là: khối Lego, cho bạn từng khối, bạn có thể sử dụng các khối này để tạo nên bất cứ thứ gì bạn muốn!
Hàm có thể gọi hàm! Chức năng chính là nối các hàm lại với nhau, gọi chúng! Hàm không thể tự thực thi nếu bạn không gọi nó!
Lập trình hướng đối tượng: Tái sử dụng mã đơn giản hơn!
Lập trình hướng đối tượng là gì? Đó là tất cả mã được thực hiện thông qua lớp và đối tượng!
Tạo lớp và đối tượng
Lập trình hướng đối tượng (Object Oriented Programming, OOP)
Java và C# chỉ hỗ trợ lập trình hướng đối tượng, trong khi Python linh hoạt hơn, hỗ trợ cả lập trình hướng đối tượng và lập trình hàm
Tất cả các ngôn ngữ đều có đặc tính: Đóng gói, Kế thừa, Đa hình (đa hình trong Python không thể hiện tốt)
Lập trình hướng đối tượng là một phương thức lập trình: Thực hiện thông qua "lớp" và "đối tượng", do đó, lập trình hướng đối tượng thực chất là sử dụng "lớp" và "đối tượng".
Xem hai ví dụ:
Ví dụ 1:
## Cách 1
# Hàm
def Chao():
print 'Chào'
def XinChao(ten):
print 'Tôi là %s' %ten
Chao()
XinChao('Tuan')
# Cách 2
# Tạo lớp
class Nguoi:
def Chao(self):
print 'Chào'
def XinChao(self, ten):
print 'Tôi là %s' %ten
# Tạo đối tượng obj từ lớp Nguoi
obj = Nguoi()
obj.Chao() # Thực thi phương thức Chao
obj.XinChao('Tuan') # Thực thi phương thức XinChao
Cách nào đơn giản hơn? Chắc chắn là cách đầu tiên, tại sao? Không phải lớp sẽ đơn giản hơn sao? Vì chúng ta chưa sử dụng được các tính năng của lớp!
Trước khi học được đóng gói và kế thừa, cách 1 đơn giản hơn!**Ví dụ 2:**Dựa trên cơ sở dữ liệu:Các thao tác phổ biến với cơ sở dữ liệu: Thêm, Xóa, Sửa, TìmThêm, Xóa, Sửa, Tìm có thể viết thành bốn phương thức không?
def Tim():
def Sua():
def Xoa():
def Them()
# Giả sử tôi muốn thêm một giá trị, trong hàm Them có cần:
# Kết nối cơ sở dữ liệu hostname, port, username, password, dbname
# Tôi có cần lưu nhiều thứ này để kết nối không? Sau khi kết nối có cần mở cơ sở dữ liệu không?
# Kết nối cơ sở dữ liệu hostname, port, username, password, dbname
# Mở
# Thao tác
# Đóng
Vậy thêm có cần không? Sửa có cần không? Tìm có cần không? Cả hai đều cần, vậy xem cách lập trình hàm thực tế:
def Tim(hostname, port, username, password, dbname, dieu_kien):
# Kết nối cơ sở dữ liệu hostname, port, username, password, dbname
# Mở
# Thao tác
# Đóng
def Sua(hostname, port, username, password, dbname, dieu_kien):
# Kết nối cơ sở dữ liệu hostname, port, username, password, dbname
# Mở
# Thao tác
# Đóng
def Xoa(hostname, port, username, password, dbname, dieu_kien):
# Kết nối cơ sở dữ liệu hostname, port, username, password, dbname
# Mở
# Thao tác
# Đóng
def Them(hostname, port, username, password, dbname, dieu_kien)
# Kết nối cơ sở dữ liệu hostname, port, username, password, dbname
# Mở
# Thao tác
# Đóng
Mỗi hàm đều chứa một số trường! Bạn tham số hóa chúng, có cần thêm vào tham số không? Khi thực thi, bạn có cần truyền tham số vào không?
Tim (hostname, port, username, password, dbname, [11,22,33,44])
Sua (hostname, port, username, password, dbname, [11,22,33,44])
Xoa(hostname, port, username, password, dbname, [11,22,33,44])
Them(hostname, port, username, password, dbname, [11,22,33,44])
Mỗi lần thao tác có cần viết lại không?
Cách trên là sử dụng hàm, vậy xem ví dụ dưới đây sử dụng lập trình hướng đối tượng:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
class QuanLyCSDL(object):
def __init__(self, hostname, port, username, password, dbname): #__init__ là phương thức khởi tạo, thực thi khi khởi tạo
self.hostname = hostname
self.port = port
self.username = username
self.password = password
self.dbname = dbname
def Tim(self):
# Kết nối cơ sở dữ liệu self.hostname, self.port, self.username, self.password, self.dbname
# Mở
# Thao tác
# Đóng
def Sua(self):
# Kết nối cơ sở dữ liệu self.hostname, self.port, self.username, self.password, self.dbname
# Mở
# Thao tác
# Đóng
def Them(self):
# Kết nối cơ sở dữ liệu self.hostname, self.port, self.username, self.password, self.dbname
# Mở
# Thao tác
# Đóng
def Xoa(self):
# Kết nối cơ sở dữ liệu self.hostname, self.port, self.username, self.password, self.dbname
# Mở
# Thao tác
# Đóng
obj1 = QuanLyCSDL('db.server.com', 3306, 'admin', 'password123', 'mydatabase')
obj1.Tim()
obj1.Sua()
obj1.Them()
obj1.Xoa()
Bây giờ khi gọi phương thức trong lớp, tôi có cần truyền tham số không? Không cần nữa, đặc điểm chính của lập trình hướng đối tượng trong Python là đóng gói!
Vậy các tham số này ở đâu?
obj1 = QuanLyCSDL('db.server.com', 3306, 'admin', 'password123', 'mydatabase') Khi khởi tạo, tôi đã truyền các tham số này cho lớp, chúng được đóng gói trong self
Như hình dưới đây:
Ví dụ: Khi obj1 gọi phương thức Them, trong đối tượng obj1 không chỉ lưu các tham số của đối tượng, mà còn có một tham số ẩn gọi là "con trỏ đối tượng lớp",
Chức năng của nó là chỉ cho đối tượng biết nơi tìm phương thức! Hãy tưởng tượng nếu không có con trỏ đối tượng lớp này, đối tượng có biết nó được tạo từ mẫu nào không? Không biết!
Và đối tượng không có phương thức, phương thức được lưu trong lớp! Như hình trên, mỗi phương thức đều có: self, nghĩa là: obj1 gọi tức là def Them(obj1) !!!
So với lập trình hàm, tôi có cần truyền một loạt tham số mỗi lần gọi không?
Sau khi xem xét các ví dụ trên, bạn sẽ nghĩ: Nếu một số phương thức sử dụng chung một nhóm biến, nếu sử dụng lập trình hàm, mỗi lần gọi phương thức đều phải truyền các tham số này!
Lúc này hãy nghĩ đến sử dụng lập trình hướng đối tượng: Đóng gói các biến này vào đối tượng, mỗi lần gọi chỉ cần truyền đối tượng này!
Ví dụ 3:
Nếu có một trò chơi, tôi muốn tạo nhân vật bằng hàm thì sao? Có cần viết nhiều hàm không? Rất phức tạp, xem mã của tôi!
#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
Định nghĩa: Mẫu và chức năng cơ bản của nhân vật trong game
'''
import random
class NhanVatGame(object):
def __init__(self, ten, nghe_nghiep, tan_cong=0, mau=0, toc_do=0): #Hàm khởi tạo, tên và nghề nghiệp
self.ten = ten # Định nghĩa trường thông thường
self.nghe_nghiep = nghe_nghiep # Định nghĩa trường thông thường
self.tan_cong = tan_cong
self.mau = mau
self.toc_do = toc_do
def nang_cap(self):
self.mau = self.mau + 1000
self.tan_cong = self.tan_cong + 1000
self.toc_do = self.toc_do + 1000
print "\033[32;1mGọi hỗ trợ, máu hiện tại: %s, tấn công hiện tại: %s, tốc độ hiện tại: %s" % (self.mau, self.tan_cong, self.toc_do)
def tang_tan_cong(self):
self.tan_cong = self.tan_cong +300
print "\033[32;1mSức tấn công hiện tại của bạn là %s\033[0m" % self.tan_cong
def hoi_phuc(self):
self.mau = self.mau + 300
print "\033[32;1mMáu hiện tại của bạn là %s\033[0m" % self.mau
def chi_tiet(self):
"""Ghi chú: Thông tin chi tiết của đối tượng hiện tại"""
temp = "Nhân vật: %s ; Nghề nghiệp: %s ; Sức tấn công: %s ; Máu: %s ; Tốc độ: %s" % (self.ten, self.nghe_nghiep, self.tan_cong, self.mau, self.toc_do)
print temp
Khi tạo một nhân vật:
obj1 = NhanVatGame('Tên nhân vật','Nghề nghiệp',Sức tấn công,'Máu','Tốc độ')
Bây giờ xem! Bây giờ tôi đã tạo một nhân vật thực sự, nó có tên, nghề nghiệp, tấn công, máu, tốc độ của riêng nó. Nếu viết bằng hàm, có chết không!
Tóm tắt: Tạo động một thứ gì đó, khi tạo phải dựa trên một mẫu, khi bạn tạo động một thứ thuộc về một loại nào đó, hãy sử dụng lập trình hướng đối tượng!
Kế thừa
Kế thừa rất đơn giản, ví dụ, bây giờ tôi có một mẫu nhân vật, tôi có thể tạo các nhân vật khác nhau dựa trên mẫu này, bây giờ tôi có một yêu cầu, tôi muốn viết riêng một mẫu chiến binh
Những thứ trong lớp tôi viết ở trên, nhiều thứ trùng lặp có thể sử dụng, tôi có cần viết lại không? Python cung cấp một phương pháp đơn giản "kế thừa"!
Trước đây đã nói Python hướng đối tượng giúp bạn tái sử dụng mã tốt hơn!
#!/usr/bin/env python
#-*- coding:utf-8 -*-
class NhanVatGame(object):
def __init__(self, ten, nghe_nghiep, tan_cong=0, mau=0, toc_do=0): #Hàm khởi tạo, tên và nghề nghiệp
self.ten = ten # Định nghĩa trường thông thường
self.nghe_nghiep = nghe_nghiep # Định nghĩa trường thông thường
self.tan_cong = tan_cong
self.mau = mau
self.toc_do = toc_do
def nang_cap(self):
self.mau = self.mau + 1000
self.tan_cong = self.tan_cong + 1000
self.toc_do = self.toc_do + 1000
print "\033[32;1mGọi hỗ trợ, máu hiện tại: %s, tấn công hiện tại: %s, tốc độ hiện tại: %s" % (self.mau, self.tan_cong, self.toc_do)
def tang_tan_cong(self):
self.tan_cong = self.tan_cong +300
print "\033[32;1mSức tấn công hiện tại của bạn là %s\033[0m" % self.tan_cong
def hoi_phuc(self):
self.mau = self.mau + 300
print "\033[32;1mMáu hiện tại của bạn là %s\033[0m" % self.mau
def chi_tiet(self):
"""Ghi chú: Thông tin chi tiết của đối tượng hiện tại"""
temp = "Nhân vật: %s ; Nghề nghiệp: %s ; Sức tấn công: %s ; Máu: %s ; Tốc độ: %s" % (self.ten, self.nghe_nghiep, self.tan_cong, self.mau, self.toc_do)
print temp
class ChienBinh(NhanVatGame):
def su_dung_vu_khi(self):
self.tan_cong = self.tan_cong + 1000
print "\033[32;1mCầm vũ khí, tấn công +1000, sức tấn công hiện tại của bạn là: %s \033[0m" % self.tan_cong
Trong lớp thứ hai: ChienBinh, tôi không đặt trường tấn_công, nó đến từ đâu! class ChienBinh(NhanVatGame): Ở đây kế thừa các trường và phương thức của lớp cơ sở! Như sau:
Tôi đã tạo một nhân vật từ lớp chiến binh, nhưng tôi không có add_attack() ở đây, nó đến từ đâu? Đó là kế thừa, vậy đó là kế thừa!
Ở đây cần đề cập một điểm, "lớp cổ điển", "lớp mới", và đa kế thừa của nó! (Lớp Python có thể kế thừa nhiều lớp, Java và C# chỉ có thể kế thừa một lớp)
Lớp mới có (object) ở trên, có sự khác biệt gì! Lớp mới, có một số sửa đổi và thêm chức năng mới, sau này nên sử dụng "lớp mới"
Cần đề cập một điểm: Trong trường hợp đa kế thừa:
Dù là kế thừa trực tiếp hay gián tiếp lớp mới, lớp của bạn cũng là lớp mới!
Khi lớp là lớp cổ điển, trong trường hợp đa kế thừa, sẽ tìm theo phương thức ưu tiên độ sâu Khi lớp là lớp mới, trong trường hợp đa kế thừa, sẽ tìm theo phương thức ưu tiên độ rộng
Lớp cổ điển: Đầu tiên tìm trong lớp A, nếu lớp A không có, tiếp tục tìm trong lớp B, nếu lớp B không có, tiếp tục tìm trong lớp D, nếu lớp D không có, tiếp tục tìm trong lớp C, nếu vẫn không tìm thấy, báo lỗi
Lớp mới: Đầu tiên tìm trong lớp A, nếu lớp A không có, tiếp tục tìm trong lớp B, nếu lớp B không có, tiếp tục tìm trong lớp C, nếu lớp C không có, tiếp tục tìm trong lớp D, nếu vẫn không tìm thấy, báo lỗi
Lưu ý: Trong quá trình tìm kiếm trên, một khi tìm thấy, quá trình tìm kiếm sẽ ngay lập tức dừng lại, không tiếp tục tìm nữa
Vậy vấn đề là, nếu trong kế thừa có "lớp cổ điển" và "lớp mới", cái nào ưu tiên? Xem mã và hình dưới đây:
class D(object):
def bar(self):
print 'D.bar'
class C(D):
def bar(self):
print 'C.bar'
class B(D):
pass
class A(B,C):
pass
a = A()
a.bar()
Chỉ trong trường hợp trên là ưu tiên độ rộng!
Tóm tắt
Lập trình hướng đối tượng là một phương thức lập trình, phương thức lập trình này được thực hiện dựa trên việc sử dụng "lớp" và "đối tượng" Lớp là một mẫu, mẫu đóng gói nhiều "hàm" để sử dụng Đối tượng, thực thể được tạo từ mẫu (tức: đối tượng), thực thể được sử dụng để gọi các hàm được đóng gói trong lớp Ba đặc tính chính của lập trình hướng đối tượng: Đóng gói, Kế thừa và Đa hình (đa hình trong Python không thể hiện tốt)
Lập trình hàm và Lập trình hướng đối tượng nên chọn loại nào? Sử dụng trong trường hợp nào?
Đối với lập trình viên C# và Java, không có vấn đề này, vì hai ngôn ngữ này chỉ hỗ trợ lập trình hướng đối tượng (không hỗ trợ lập trình hàm). Đối với Python và PHP và các ngôn ngữ khác, hỗ trợ cả hai phương thức lập trình, và lập trình hàm có thể thực hiện được, lập trình hướng đối tượng cũng có thể thực hiện; và lập trình hướng đối tượng có thể thực hiện được, lập trình hàm không thể (lập trình hàm không thể thực hiện được tính năng đóng gói của lập trình hướng đối tượng).
Vì vậy, trong phát triển Python, thường sử dụng hoàn toàn lập trình hướng đối tượng hoặc kết hợp lập trình hướng đối tượng và lập trình hàm