Ruby - Hướng đối tượng
Ruby là một ngôn ngữ hướng đối tượng thuần túy và mọi thứ đều xuất hiện với Ruby dưới dạng một đối tượng. Mọi giá trị trong Ruby đều là một đối tượng, ngay cả những thứ nguyên thủy nhất: chuỗi, số và thậm chí là true và false. Ngay cả bản thân một lớp cũng là một đối tượng là một thể hiện của lớp Class . Chương này sẽ đưa bạn qua tất cả các chức năng chính liên quan đến Ruby hướng đối tượng.
Một lớp được sử dụng để chỉ định dạng của một đối tượng và nó kết hợp biểu diễn dữ liệu và các phương thức để thao tác dữ liệu đó thành một gói gọn gàng. Dữ liệu và phương thức trong một lớp được gọi là thành viên của lớp.
Định nghĩa lớp Ruby
Khi bạn xác định một lớp, bạn xác định một bản thiết kế cho một kiểu dữ liệu. Điều này không thực sự xác định bất kỳ dữ liệu nào, nhưng nó xác định ý nghĩa của tên lớp, nghĩa là, một đối tượng của lớp sẽ bao gồm những gì và những thao tác nào có thể được thực hiện trên một đối tượng như vậy.
Định nghĩa lớp bắt đầu bằng từ khóa class tiếp theo là class name và được phân định bằng một end. Ví dụ, chúng tôi đã định nghĩa lớp Box bằng cách sử dụng lớp từ khóa như sau:
class Box
code
end
Tên phải bắt đầu bằng một chữ cái viết hoa và theo quy ước, các tên chứa nhiều hơn một từ được chạy cùng với mỗi từ được viết hoa và không có ký tự phân cách (CamelCase).
Xác định các đối tượng Ruby
Một lớp cung cấp bản thiết kế cho các đối tượng, vì vậy về cơ bản một đối tượng được tạo ra từ một lớp. Chúng tôi khai báo các đối tượng của một lớp bằng cách sử dụngnewtừ khóa. Các câu lệnh sau khai báo hai đối tượng của lớp Box:
box1 = Box.new
box2 = Box.new
Phương thức khởi tạo
Các initialize method là một phương thức lớp Ruby tiêu chuẩn và hoạt động gần giống như constructorhoạt động trong các ngôn ngữ lập trình hướng đối tượng khác. Phương thức khởi tạo hữu ích khi bạn muốn khởi tạo một số biến lớp tại thời điểm tạo đối tượng. Phương thức này có thể có một danh sách các tham số và giống như bất kỳ phương thức ruby nào khác, nó sẽ có trướcdef từ khóa như hình bên dưới -
class Box
def initialize(w,h)
@width, @height = w, h
end
end
Các biến phiên bản
Các instance variableslà một loại thuộc tính của lớp và chúng trở thành thuộc tính của các đối tượng khi các đối tượng được tạo bằng cách sử dụng lớp. Mỗi thuộc tính của đối tượng được gán riêng lẻ và không chia sẻ giá trị với các đối tượng khác. Chúng được truy cập bằng toán tử @ trong lớp nhưng để truy cập chúng bên ngoài lớp, chúng ta sử dụngpublic phương pháp, được gọi là accessor methods. Nếu chúng ta sử dụng lớp đã xác định ở trênBox thì @width và @height là các biến thể hiện cho Box lớp.
class Box
def initialize(w,h)
# assign instance variables
@width, @height = w, h
end
end
Phương thức truy cập & setter
Để làm cho các biến khả dụng từ bên ngoài lớp, chúng phải được định nghĩa bên trong accessor methods, các phương thức truy cập này còn được gọi là phương thức getter. Ví dụ sau cho thấy việc sử dụng các phương thức truy cập:
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# accessor methods
def printWidth
@width
end
def printHeight
@height
end
end
# create an object
box = Box.new(10, 20)
# use accessor methods
x = box.printWidth()
y = box.printHeight()
puts "Width of the box is : #{x}"
puts "Height of the box is : #{y}"
Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:
Width of the box is : 10
Height of the box is : 20
Tương tự như các phương thức của trình truy cập, được sử dụng để truy cập giá trị của các biến, Ruby cung cấp một cách để đặt giá trị của các biến đó từ bên ngoài lớp bằng cách sử dụng setter methods, được định nghĩa như sau:
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# accessor methods
def getWidth
@width
end
def getHeight
@height
end
# setter methods
def setWidth=(value)
@width = value
end
def setHeight=(value)
@height = value
end
end
# create an object
box = Box.new(10, 20)
# use setter methods
box.setWidth = 30
box.setHeight = 50
# use accessor methods
x = box.getWidth()
y = box.getHeight()
puts "Width of the box is : #{x}"
puts "Height of the box is : #{y}"
Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:
Width of the box is : 30
Height of the box is : 50
Các phương thức thể hiện
Các instance methods cũng được định nghĩa theo cách tương tự như chúng tôi xác định bất kỳ phương pháp nào khác bằng cách sử dụng deftừ khóa và chúng chỉ có thể được sử dụng bằng một cá thể lớp như được hiển thị bên dưới. Chức năng của chúng không giới hạn trong việc truy cập các biến phiên bản, mà còn có thể làm được nhiều việc hơn theo yêu cầu của bạn.
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# instance method
def getArea
@width * @height
end
end
# create an object
box = Box.new(10, 20)
# call instance methods
a = box.getArea()
puts "Area of the box is : #{a}"
Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:
Area of the box is : 200
Các phương thức và biến của lớp
Các class variableslà một biến, được chia sẻ giữa tất cả các trường hợp của một lớp. Nói cách khác, có một thể hiện của biến và nó được truy cập bởi các thể hiện đối tượng. Các biến lớp được bắt đầu bằng hai ký tự @ (@@). Một biến lớp phải được khởi tạo trong định nghĩa lớp như hình dưới đây.
Một phương thức lớp được định nghĩa bằng cách sử dụng def self.methodname(), kết thúc bằng dấu phân cách cuối và sẽ được gọi bằng tên lớp là classname.methodname như thể hiện trong ví dụ sau:
#!/usr/bin/ruby -w
class Box
# Initialize our class variables
@@count = 0
def initialize(w,h)
# assign instance avriables
@width, @height = w, h
@@count += 1
end
def self.printCount()
puts "Box count is : #@@count"
end
end
# create two object
box1 = Box.new(10, 20)
box2 = Box.new(30, 100)
# call class method to print box count
Box.printCount()
Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:
Box count is : 2
Phương pháp to_s
Bất kỳ lớp nào bạn xác định phải có to_sphương thức thể hiện để trả về một biểu diễn chuỗi của đối tượng. Sau đây là một ví dụ đơn giản để đại diện cho một đối tượng Box theo chiều rộng và chiều cao:
#!/usr/bin/ruby -w
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# define to_s method
def to_s
"(w:#@width,h:#@height)" # string formatting of the object.
end
end
# create an object
box = Box.new(10, 20)
# to_s method will be called in reference of string automatically.
puts "String representation of box is : #{box}"
Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:
String representation of box is : (w:10,h:20)
Kiểm soát truy cập
Ruby cung cấp cho bạn ba cấp độ bảo vệ ở cấp phương thức cá thể, có thể là public, private, or protected. Ruby không áp dụng bất kỳ kiểm soát truy cập nào đối với các biến thể hiện và biến lớp.
Public Methods- Phương thức public có thể được gọi bởi bất kỳ ai. Các phương thức là công khai theo mặc định ngoại trừ khởi tạo, luôn là riêng tư.
Private Methods- Các phương thức riêng không thể được truy cập, hoặc thậm chí được xem từ bên ngoài lớp. Chỉ các phương thức của lớp mới có thể truy cập các thành viên riêng tư.
Protected Methods- Một phương thức được bảo vệ chỉ có thể được gọi bởi các đối tượng của lớp xác định và các lớp con của nó. Quyền truy cập được giữ trong gia đình.
Sau đây là một ví dụ đơn giản để hiển thị cú pháp của cả ba công cụ sửa đổi quyền truy cập:
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# instance method by default it is public
def getArea
getWidth() * getHeight
end
# define private accessor methods
def getWidth
@width
end
def getHeight
@height
end
# make them private
private :getWidth, :getHeight
# instance method to print area
def printArea
@area = getWidth() * getHeight
puts "Big box area is : #@area"
end
# make it protected
protected :printArea
end
# create an object
box = Box.new(10, 20)
# call instance methods
a = box.getArea()
puts "Area of the box is : #{a}"
# try to call protected or methods
box.printArea()
Khi đoạn mã trên được thực thi, nó tạo ra kết quả như sau. Ở đây, phương thức đầu tiên được gọi là thành công nhưng phương pháp thứ hai lại gặp sự cố.
Area of the box is : 200
test.rb:42: protected method `printArea' called for #
<Box:0xb7f11280 @height = 20, @width = 10> (NoMethodError)
Kế thừa giai cấp
Một trong những khái niệm quan trọng nhất trong lập trình hướng đối tượng là tính kế thừa. Tính kế thừa cho phép chúng ta xác định một lớp theo nghĩa của một lớp khác, điều này giúp tạo và duy trì một ứng dụng dễ dàng hơn.
Kế thừa cũng tạo cơ hội để sử dụng lại chức năng mã và thời gian thực hiện nhanh nhưng tiếc là Ruby không hỗ trợ nhiều cấp độ kế thừa nhưng Ruby hỗ trợ mixins. Mixin giống như một triển khai đa kế thừa chuyên biệt, trong đó chỉ phần giao diện được kế thừa.
Khi tạo một lớp, thay vì viết các thành viên dữ liệu hoàn toàn mới và các hàm thành viên, lập trình viên có thể chỉ định rằng lớp mới sẽ kế thừa các thành viên của một lớp hiện có. Lớp hiện có này được gọi làbase class or superclassvà lớp mới được gọi là derived class or sub-class.
Ruby cũng hỗ trợ khái niệm phân lớp, tức là, kế thừa và ví dụ sau giải thích khái niệm này. Cú pháp để mở rộng một lớp rất đơn giản. Chỉ cần thêm ký tự <và tên của lớp cha vào câu lệnh lớp của bạn. Ví dụ, sau đây xác định một lớp BigBox là một lớp con của Box :
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# instance method
def getArea
@width * @height
end
end
# define a subclass
class BigBox < Box
# add a new instance method
def printArea
@area = @width * @height
puts "Big box area is : #@area"
end
end
# create an object
box = BigBox.new(10, 20)
# print the area
box.printArea()
Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:
Big box area is : 200
Ghi đè phương thức
Mặc dù bạn có thể thêm chức năng mới trong một lớp dẫn xuất, nhưng đôi khi bạn muốn thay đổi hành vi của phương thức đã được xác định trong một lớp cha. Bạn có thể làm như vậy đơn giản bằng cách giữ nguyên tên phương thức và ghi đè chức năng của phương thức như được hiển thị bên dưới trong ví dụ:
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# instance method
def getArea
@width * @height
end
end
# define a subclass
class BigBox < Box
# change existing getArea method as follows
def getArea
@area = @width * @height
puts "Big box area is : #@area"
end
end
# create an object
box = BigBox.new(10, 20)
# print the area using overriden method.
box.getArea()
Người vận hành quá tải
Chúng tôi muốn toán tử + thực hiện phép cộng vectơ của hai đối tượng Hộp bằng cách sử dụng +, toán tử * để nhân chiều rộng và chiều cao của Hộp với một đại lượng vô hướng, và toán tử một ngôi để phủ định chiều rộng và chiều cao của Hộp. Đây là một phiên bản của lớp Box với các toán tử toán học được định nghĩa:
class Box
def initialize(w,h) # Initialize the width and height
@width,@height = w, h
end
def +(other) # Define + to do vector addition
Box.new(@width + other.width, @height + other.height)
end
def -@ # Define unary minus to negate width and height
Box.new(-@width, -@height)
end
def *(scalar) # To perform scalar multiplication
Box.new(@width*scalar, @height*scalar)
end
end
Vật thể đóng băng
Đôi khi, chúng ta muốn ngăn một đối tượng bị thay đổi. Phương thức đóng băng trong Object cho phép chúng ta làm điều này, biến một đối tượng thành một hằng số một cách hiệu quả. Mọi đối tượng có thể bị đóng băng bằng cách gọiObject.freeze. Một đối tượng cố định có thể không được sửa đổi: bạn không thể thay đổi các biến phiên bản của nó.
Bạn có thể kiểm tra xem một đối tượng nhất định đã bị đóng băng hoặc không sử dụng Object.frozen?phương thức này trả về true trong trường hợp đối tượng bị đóng băng, nếu không thì trả về giá trị false. Ví dụ sau làm rõ khái niệm -
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# accessor methods
def getWidth
@width
end
def getHeight
@height
end
# setter methods
def setWidth=(value)
@width = value
end
def setHeight=(value)
@height = value
end
end
# create an object
box = Box.new(10, 20)
# let us freez this object
box.freeze
if( box.frozen? )
puts "Box object is frozen object"
else
puts "Box object is normal object"
end
# now try using setter methods
box.setWidth = 30
box.setHeight = 50
# use accessor methods
x = box.getWidth()
y = box.getHeight()
puts "Width of the box is : #{x}"
puts "Height of the box is : #{y}"
Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:
Box object is frozen object
test.rb:20:in `setWidth=': can't modify frozen object (TypeError)
from test.rb:39
Hằng số lớp
Bạn có thể xác định một hằng số bên trong một lớp bằng cách gán giá trị số hoặc chuỗi trực tiếp cho một biến, được xác định mà không sử dụng @ hoặc @@. Theo quy ước, chúng tôi giữ các tên không đổi bằng chữ hoa.
Khi một hằng số được định nghĩa, bạn không thể thay đổi giá trị của nó nhưng bạn có thể truy cập trực tiếp vào một hằng số bên trong một lớp giống như một biến nhưng nếu bạn muốn truy cập một hằng số bên ngoài lớp thì bạn sẽ phải sử dụng classname::constant như thể hiện trong ví dụ dưới đây.
#!/usr/bin/ruby -w
# define a class
class Box
BOX_COMPANY = "TATA Inc"
BOXWEIGHT = 10
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# instance method
def getArea
@width * @height
end
end
# create an object
box = Box.new(10, 20)
# call instance methods
a = box.getArea()
puts "Area of the box is : #{a}"
puts Box::BOX_COMPANY
puts "Box weight is: #{Box::BOXWEIGHT}"
Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:
Area of the box is : 200
TATA Inc
Box weight is: 10
Hằng số lớp được kế thừa và có thể được ghi đè giống như các phương thức thể hiện.
Tạo đối tượng bằng cách sử dụng Allocate
Có thể có một tình huống khi bạn muốn tạo một đối tượng mà không gọi hàm tạo của nó initializetức là sử dụng phương thức mới, trong trường hợp đó bạn có thể gọi phân bổ , điều này sẽ tạo một đối tượng chưa được khởi tạo cho bạn như trong ví dụ sau:
#!/usr/bin/ruby -w
# define a class
class Box
attr_accessor :width, :height
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# instance method
def getArea
@width * @height
end
end
# create an object using new
box1 = Box.new(10, 20)
# create another object using allocate
box2 = Box.allocate
# call instance method using box1
a = box1.getArea()
puts "Area of the box is : #{a}"
# call instance method using box2
a = box2.getArea()
puts "Area of the box is : #{a}"
Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:
Area of the box is : 200
test.rb:14: warning: instance variable @width not initialized
test.rb:14: warning: instance variable @height not initialized
test.rb:14:in `getArea': undefined method `*'
for nil:NilClass (NoMethodError) from test.rb:29
Thông tin lớp học
Nếu các định nghĩa lớp là mã thực thi, điều này ngụ ý rằng chúng thực thi trong ngữ cảnh của một số đối tượng: self phải tham chiếu đến một cái gì đó. Hãy cùng tìm hiểu xem đó là gì.
#!/usr/bin/ruby -w
class Box
# print class information
puts "Type of self = #{self.type}"
puts "Name of self = #{self.name}"
end
Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:
Type of self = Class
Name of self = Box
Điều này có nghĩa là một định nghĩa lớp được thực thi với lớp đó là đối tượng hiện tại. Điều này có nghĩa là các phương thức trong siêu lớp và các lớp cha của nó sẽ khả dụng trong quá trình thực thi định nghĩa phương thức.