Article:
4 đặc thù của lập trình hướng đối tượng
5231
ngocdaothanh.myopenid.com 172Updated 6 months ago |
Với lập trình hướng đối tượng (OOP), có 2 câu hỏi thực dụng:
- Nó có những đặc thù gì?
- Khi học ngôn ngữ OOP nào đó, làm sao để kiểm tra xem ta đã nắm bắt được ngôn ngữ này hay chưa, bằng cách diễn tả các đặc thù này bằng cú pháp của ngôn ngữ này?
![]()
Trả lời câu hỏi 1
OOP có 4 đặc thù chính (còn nhiều cái khác, nên không phải ai cũng đồng ý coi 4 cái dưới đây là chính), hầu hết ngôn ngữ OOP nào cũng đều có cách để diễn tả:
- Tính đóng gói: Có thể gói dữ liệu (data/biến) và mã chương trình (code/phương thức) thành một cục gọi là lớp (class) để dễ quản lí. Các class có liên quan lại có thể được đóng gói vào cùng module/package. Trong cục này thường data rất rối rắm, không tiện cho người không có trách nhiệm truy cập trực tiếp, nên thường ta che dấu data đi, chỉ để lòi phương thức ra ngoài. Ví dụ hàng xóm sang mượn búa, thay vì bảo hàng xóm cứ tự nhiên vào lục lọi, ta sẽ bảo: "Ấy bác ngồi chơi để tôi bảo cháu lấy cho". Ngôn ngữ Ruby "phát xít" đến nỗi dấu tiệt data, cấm truy cập từ bên ngoài, phải thông qua phương thức nào đó thì mới truy cập được data bên trong.
- Tính trừu tượng: Tinh túy của OOP là truyền message. Có nghĩa các object độc lập với nhau và khi muốn liên lạc cho nhau sẽ phải thông qua message (gọi method). Để cái này gọi được cái kia thì cái này phải biết interface của cái kia. Ngành CNTT sau nhiều thử nghiệm với OOP đã đúc kết được kinh nghiệm khi thiết kế chương trình theo hướng OOP: Code to interface, not to implementation.
- Tính thừa kế: Nếu cha đã có phương thức m, thì con khỏi phải định nghĩa lại m, giúp chương trình ngắn gọn lại nhiều. Việc thừa kế này ngoài giúp con tái sử dụng code của cha, còn cho phép con thừa kế interface của cha. Có thể nói tinh túy của thừa kế chính là thừa kế interface chứ không phải là tái sử dụng code. Điều này phản ánh kinh nghiệm khi thiết kế là nên ưu tiên composition hơn inheritance, tránh làm cho cây thừa kế quá sâu, quá phức tạp. Chẳng phải Java thắng C++ là vì Java chỉ hỗ trợ đơn thừa kế còn C++ hỗ đa thừa kế, nên chuơng trình Java đơn giản dễ hiểu ít bug hơn chương trình C++ nhiều hay sao?
- Tính đa hình: Khi lớp B thừa kế từ lớp A, thì đối tượng của lớp B có thể coi là đối tượng của lớp A, vì B chứa nhiều thứ thừa kế từ A. Để dễ hiểu, ta lấy ngôn ngữ OOP có định kiểu là C++. Trong C++, con trỏ kiểu cha có thể dùng để trỏ đến đối tượng kiểu con. Như vậy khi khai báo "A *p" thì con trỏ p có thể trỏ đến bất kì đối tượng của lớp thừa kế từ A, ví dụ B. Tuy nhiên thông qua p chỉ có thể truy cập những thứ có ở lớp A, còn nếu B khai báo riêng cái gì đó, thì cần ép kiểu để truy cập. Thông thường, việc tái sử dụng code được thực hiện bằng cách code mới gọi lại code cũ. Tính đa hình độc đáo ở chỗ nó ngược lại: code cũ gọi code mới!
Để dễ nhớ tên của 4 cái trên, ta có mẹo: đóng gói dẫn đến trừu tượng dẫn đến thừa kế dẫn đến đa hình.
Trả lời câu hỏi 2
Giả sử đang học Ruby, một ngôn ngữ OOP từ đầu đến chân (cái gì trong Ruby cũng là đối tượng, kể cả con số 1 2 3). Nên làm bài tập nhỏ về các con vật sau:
- Tạo class Animal, trong có phương thức khởi tạo nhận tham số là tên con vật (<- tính đóng gói, gói biến chứa tên con vật và các hàm khác thành 1 cục)
- Class Animal, có phương thức talk rỗng (Ruby không có cách khai báo abstract class hoặc interface một cách tường minh như đa số ngôn ngữ OOP khác) (<- tính trừu tượng, cho biết tất cả các class kế thừa từ Animal sẽ có cùng interface là có phương thức talk)
- Tạo 2 lớp Cat và Dog kế thừa từ Animal (<- tính thừa kế)
- Chúng override lại phương thức talk (<- tính đa hình)
- Tạo lớp Zoo để quản lí nhiều Animal, có (1) phương thức add, remove để thêm, bớt đối tượng Animal hoặc con của nó (đối tượng của lớp thừa kế từ Animal), (2) phương thức talk để gọi talk của tất cả đối tượng nó quản lí. (<- tính đa hình)
Đây là bài tập gối đầu giường, khi được yêu cầu viết phải viết được ngay không ngắc ngứ.
# animal.rb
class Animal
attr_reader :name
def initialize(name)
@name = name
end
def talk
raise 'Animal is an abstract class'
end
end
class Cat < Animal
def talk
puts "Meow, my name is #{@name}"
end
end
class Dog < Animal
def talk
puts "Woof woof, my name is #{@name}"
end
end
class Zoo
attr_reader :animals
def initialize
@animals = []
end
def add(animal)
@animals << animal
end
def delete(animal)
@animals.delete(animal)
end
def talk
@animals.each { |a| a.talk }
end
end
# Some test
if __FILE__ == $0
z = Zoo.new
k = Cat.new('Kitty')
p = Dog.new('Pluto')
z.add(k)
z.add(p)
z.talk
z.delete(k)
z.talk
end
Nâng cao: duck typing
Ở trên là căn bản về OOP. Các ngôn ngữ OOP định kiểu động có thêm đặc thù độc đáo là "định kiểu kiểu con vịt". Phương pháp xác định kiểu kiểu con vịt phát biểu như sau: "nếu p đi như vịt nói như vịt, thì cứ coi nó là vịt". Nếu lớp A có phương thức m, mà hiện tại có thể gọi phương thức m từ đối tượng p bất kì nào đó, thì cứ coi p hiện có kiểu là A.
Đoạn mã trên lưu thành tập tin animal.rb. Để thử tính năng duck typing của Ruby, ta chạy thử tập tin duck.rb sau:
# duck.rb
require 'zoo'
class Duck
attr_reader :name
def initialize(name)
@name = name
end
def talk
puts "Duck, my name is #{@name}"
end
end
# Some test
if __FILE__ == $0
z = Zoo.new
animals = [Cat.new('Kitty'), Dog.new('Pluto'), Duck.new('Donald')]
animals.each { |a| z.add(a) }
z.talk
end
Chạy thử thấy ngon lành. Để ý sẽ thấy lớp Duck không thừa kế từ Animal, nhưng có phương thức talk, nên chỗ a.talk trong phương thức talk không văng lỗi.
Điều này có nghĩa ngôn ngữ động như Ruby không cần biết phương thức talk ở đâu ra (có do thừa kế hoặc do tự định nghĩa), chỉ cần có là gọi được, không văng lỗi.
tutorial
172
over 3 years ago
over 3 years ago
over 3 years ago
Updated over 2 years ago
over 2 years ago
about 1 year ago