Hãy cùng Quản Trị Mạng tìm hiểu về cách học ngôn ngữ lập trình Ruby từ con số 0 trong bài viết này nhé!
- Hướng dẫn cách đơn giản và hiệu quả nhất để viết code dễ đọc
- Tại sao bạn nên học ngôn ngữ lập trình Python?
- 12 thủ thuật vô cùng hữu ích dành cho lập trình viên JavaScript
Vì sao bạn nên học ngôn ngữ lập trình Ruby?
Đối với tôi - tác giả bài viết, thứ nhất Ruby là một ngôn ngữ đẹp. Bạn sẽ thấy việc viết code trong Ruby rất mượt mà và tự nhiên.
Thứ hai, một trong những nguyên nhân khiến cho Ruby nổi tiếng nằm ở Rails: một framework được dùng bởi Twitter, Basecamp, Airbnb, Github và nhiều công ty khác.
“Ruby có ngoại hình đơn giản, nhưng tâm hồn bên trong phức tạp giống như con người vậy.” – Matz, cha đẻ của ngôn ngữ lập trình Ruby.
Giới thiệu/Nguồn gốc
Ruby là “một ngôn ngữ lập trình open source (mã nguồn mở) vô cùng mạnh mẽ với sự đơn giản mà hiệu quả. Với các cú pháp thanh lịch giúp cho việc đọc và viết vô cùng dễ dàng”.
Hãy bắt đầu với những điều cơ bản về nền tảng của Ruby!
Variables (Các biến)
Bạn có thể hiểu đơn giản rằng biến là một từ chứa một giá trị. Chỉ đơn giản vậy thôi!
Trong Ruby, rất dễ để định nghĩa một biến và đặt cho nó một giá trị. Giả sử bạn muốn cho giá trị là 1 vào một biến với tên gọi là one
. Hãy làm nó:
one = 1
Điều đó thật đơn giản phải không? Bạn chỉ cần gán giá trị 1 cho một biến được gọi là one
.
two = 2
some_number = 10000
Bạn có thể gán một giá trị bất kỳ đến biến nào mà bạn muốn. Trong ví dụ trên, biến two
chứa giá trị số nguyên là 2 và some_number
có giá trị 10.000.
Bên cạnh các số nguyên, chúng ta cũng có thể sử dụng các phép toán luận booleans (true / false), chuỗi, ký hiệu, float và các kiểu dữ liệu khác.
booleans
true_boolean = true
false_boolean = false
# string
my_name = "Leandro Tk"
# symbol
a_symbol = :my_symbol
# float
book_price = 15.80
Câu lệnh có điều kiện: Dòng điều khiển
Câu lệnh có điều kiện sẽ có nhiệm vụ đánh giá tính đúng/sai của một lệnh. Nếu kết quả là đúng (True), thì nội dung bên trong sẽ được xử lý. Ví dụ:
if true
puts "Hello Ruby If"
end
if 2 > 1
puts "2 is greater than 1"
end
2 lớn hơn 1 nên dòng chữ “2 is greater than 1” sẽ được hiện ra.
Câu lệnh else
sẽ được thực hiện khi ta gặp kết quả False (Sai):
if 1 > 2
puts "1 is greater than 2"
elsif 2 > 1
puts "1 is not greater than 2"
else
puts "1 is equal to 2"
end
1 không lớn hơn 2 nên code trong lệnh else
sẽ được xử lý.
Ngoài ra, bạn cũng có thể dùng tới lệnh elsif
, vốn kết hợp cả 2 loại trên. Ví dụ:
if 1 > 2
puts "1 is greater than 2"
elsif 2 > 1
puts "1 is not greater than 2"
else
puts "1 is equal to 2"
end
Tôi thích dùng câu lệnh if
sau khi code đã được thực hiện:
def hey_ho?
true
end
puts "let’s go" if hey_ho?
Nhìn rất đẹp mà lại ngắn gọn. Đó là thế mạnh của ngôn ngữ lập trình Ruby.
Looping/Iterator (Vòng lặp)
Trong ngôn ngữ lập trình Ruby, chúng ta có thể lặp lại bằng nhiều hình thức khác nhau. Tôi sẽ nói về 3 vòng lặp: while, for và each.
- Vòng lặp While: Miễn là kết quả của statement vẫn là true (đúng), nội dung code nằm trong statement sẽ luôn được thực hiện. Như vậy, code sẽ in và hiển thị từ 1 tới 10 như trong ví dụ dưới đây:
num = 1
while num <= 10
puts num
num += 1
end
- Vòng lặp For: Với statement được cho ra, nội dung code sẽ được thực hiện cho đến khi thỏa mãn yêu cầu của statement đó. Bạn truyền biến num đến block và câu lệnh
for
sẽ lặp lại nó cho bạn. Mã này sẽ in giống như khi code từ 1 đến 10:
for num in 1...10
puts num
end
- Vòng lặp Each: Tôi thực sự thích vòng lặp
each
. Đối với một mảng (array) các giá trị, nó sẽ lặp lại từng giá trị một.
[1, 2, 3, 4, 5].each do |num|
puts num
end
Điểm khác biệt giữa For và Each là Each
thực hiện chính xác từng giá trị được cho; trong khi For
sẽ xuất hiện những giá trị không mong muốn nằm ngoài yêu cầu.
# for vs each
# for looping
for num in 1...5
puts num
end
puts num # => 5
# each iterator
[1, 2, 3, 4, 5].each do |num|
puts num
end
puts num # => undefined local variable or method `n' for main:Object (NameError)
Array (Mảng): Collection/List/Data Structure
Giả sử bạn muốn lưu integer (số nguyên) 1 vào một biến. Nhưng giờ, bạn muốn lưu 2 và rùi 3,4,5…. Vậy có cách nào để lưu những integer bạn muốn mà không phải làm theo cách thủ công không? Ruby sẽ cung cấp giải pháp cho bạn.
Array (mảng) là một collection dùng để lưu trữ một danh sách các giá trị (như các integer này). Vì vậy, hãy sử dụng nó.
my_integers = [1, 2, 3, 4, 5]
Rất đơn giản, chúng ta tạo ra một array và lưu nó vào my_integer.
Bạn sẽ tự hỏi: “Làm cách nào để lấy giá trị từ array đó?”. Trong Arrays có một khái niệm gọi là Index. Bắt đầu với index 0 và cứ thế tăng dần lên.
Sử dụng cú pháp Ruby rất đơn giản để hiểu:
my_integers = [5, 7, 1, 3, 4]
print my_integers[0] # 5
print my_integers[1] # 7
print my_integers[4] # 4
Giờ nếu bạn muốn lưu strings thay vì integers, như danh sách tên họ hàng của bạn chẳng hạn:
relatives_names = [
"Toshiaki",
"Juliana",
"Yuji",
"Bruno",
"Kaio"
]
print relatives_names[4] # Kaio
Không có gì khác biệt, ngoài trừ giờ ta dùng chữ thay cho số. Thật tuyệt!
Chúng ta đã học được chỉ số mảng hoạt động như thế nào. Giờ hãy thêm các phần tử vào cấu trúc dữ liệu mảng (các item vào danh sách).
Phương pháp thường thấy dùng để thêm giá trị vào array là push và <<.
Push cực kỳ đơn giản. Bạn chỉ cần thông qua các phần tử (The Effective Engineer) như tham số push:
bookshelf = []
bookshelf.push("The Effective Engineer")
bookshelf.push("The 4 hours work week")
print bookshelf[0] # The Effective Engineer
print bookshelf[1] # The 4 hours work week
Phương pháp << có chút khác biệt:
bookshelf = []
bookshelf << "Lean Startup"
bookshelf << "Zero to One"
print bookshelf[0] # Lean Startup
print bookshelf[1] # Zero to One
Bạn có thể đặt ra câu hỏi rằng: "Tại sao nó không sử dụng ký hiệu dấu chấm giống như các phương pháp khác? Làm thế nào có thể trở thành là một phương pháp?”. Một câu hỏi hay. Được viết như sau:
bookshelf << "Hooked"
Cũng chính là...
bookshelf.<<("Hooked")
Ngôn ngữ lập trình Ruby thật là tuyệt đúng không? Giờ hãy nói thêm về một loại Data Structure khác.
Hash: Key-Value Data Structure / Dictionary Collection
Chắc hẳn chúng ta đều biết arrays thực chất chính là array với số. Thế nhưng, nếu chúng ta dùng số thứ tự thì sao? Một số cấu trúc dữ liệu (data structure) có thể dùng số, string hoặc các dạng type khác. Hash data structure là một trong số đó.
Hash là một collection các cặp key-value:
hash_example = {
"key1" => "value1",
"key2" => "value2",
"key3" => "value3"
}
Trong đó, key sẽ ám chỉ index của value. Vậy chúng ta sẽ truy cập vào value của Hash như thế nào? Sẽ thông qua key.
Đây là một hash về tôi bằng cách sử dụng key là họ tên, nickname và sắc tộc:
hash_tk = {
"name" => "Leandro",
"nickname" => "Tk",
"nationality" => "Brazilian"
}
print "My name is #{hash_tk["name"]}" # My name is Leandro
print "But you can call me #{hash_tk["nickname"]}" # But you can call me Tk
print "And by the way I'm #{hash_tk["nationality"]}" # And by the way I'm Brazilian
Trong ví dụ trên, tôi cho in một cụm từ về mình bằng cách sử dụng tất cả các giá trị được lưu trong hash.
Một điều khá tuyệt của hash là chúng ta có thể dùng bất cứ thứ gì làm value. Tôi sẽ thêm key “age” và số tuổi của mình là (24) vào value.
hash_tk = {
"name" => "Leandro",
"nickname" => "Tk",
"nationality" => "Brazilian",
"age" => 24
}
print "My name is #{hash_tk["name"]}" # My name is Leandro
print "But you can call me #{hash_tk["nickname"]}" # But you can call me Tk
print "And by the way I'm #{hash_tk["age"]} and #{hash_tk["nationality"]}" # And by the way I'm 24 and Brazilian
Giờ ta sẽ thêm các nhân tố khác vào một hash. Key dẫn tới value chính là đặc điểm làm nên Hash nên việc thêm vào cũng sẽ theo quy luật như vậy.
hash_tk = {
"name" => "Leandro",
"nickname" => "Tk",
"nationality" => "Brazilian"
}
hash_tk["age"] = 24
print hash_tk # { "name" => "Leandro", "nickname" => "Tk", "nationality" => "Brazilian", "age" => 24 }
Chúng ta chỉ cần gán một giá trị vào hash. Bạn có thấy việc thêm một giá trị vào hash rất đơn giản phải không?
Iteration: Vòng lặp thông qua Data Structures
Việc lặp lại mảng rất đơn giản. Các nhà phát triển Ruby thường sử dụng vòng lặp each
. Hãy thực hiện nó:
bookshelf = [
"The Effective Engineer",
"The 4 hours work week",
"Zero to One",
"Lean Startup",
"Hooked"
]
bookshelf.each do |book|
puts book
end
Vòng lặp each
ở trên hoạt động bằng cách đi qua từng phần tử trong mảng như một tham số trong block. Trong ví dụ trên, chúng ta sẽ in ra từng yếu tố đó.
Với hash data structure, ta cũng có thể dùng vòng lặp each
để đi qua 2 tham số trong cùng một block: key và value. Đây là ví dụ:
hash = { "some_key" => "some_value" }
hash.each { |key, value| puts "#{key}: #{value}" } # some_key: some_value
Ta sẽ đặt tên cho hai tham số này là key và value cho khỏi bị nhầm.
hash_tk = {
"name" => "Leandro",
"nickname" => "Tk",
"nationality" => "Brazilian",
"age" => 24
}
hash_tk.each do |attribute, value|
puts "#{attribute}: #{value}"
end
Bạn có thể thấy chúng tôi sử dụng thuộc tính như một tham số cho hash key và nó đã hoạt động. Tuyệt quá!
Classes & Objects
Là một ngôn ngữ lập trình hướng đối tượng (object oriented programming), Ruby sử dụng các khái niệm về class và object (đối tượng).
"Class" là một cách để xác định các đối tượng. Trong thế giới thực có nhiều vật thể cùng loại giống như xe cộ, chó và xe đạp. Mỗi xe có các thành phần tương tự (bánh xe, cửa ra vào, động cơ).
“Objects” có 2 đặc điểm: dữ liệu và hành vi. Ví dụ: ô tô có dữ liệu về số bánh xe và số cửa. Chúng cũng có hành vi như tăng tốc và dừng lại.
Trong lập trình hướng đối tượng, chúng tôi gọi là dữ liệu "attributes" và hành vi "methods".
Dữ liệu = Attributes
Hành vi = Methods
Chế độ lập trình hướng đối tượng trong Ruby: On (Bật)
Đây là một cú pháp trong Ruby cho Class:
class Vehicle
end
Chúng tôi xác định Vehicle
với lệnh class và kết thúc bằng end. Thật dễ dàng!
Objects chính là đại diện cho class. Ta tạo ra instance bằng phương pháp .new.
vehicle = Vehicle.new
Tại đây, vehicle
là object (hay instance) của class: Vehicle
Class Vehicle
có 4 attributes: bánh xe, loại thùng tank, số ghế và vận tốc.
Hãy xác định class Vehicle của chúng tôi để nhận dữ liệu và tạo đối tượng cụ thể.
class Vehicle
def initialize(number_of_wheels, type_of_tank, seating_capacity, maximum_velocity)
@number_of_wheels = number_of_wheels
@type_of_tank = type_of_tank
@seating_capacity = seating_capacity
@maximum_velocity = maximum_velocity
end
end
Chúng ta đã sử dụng phương pháp initialize (khởi tạo). Với tên gọi khác là constructor bởi khi bạn tạo ra vehicle object, đồng thời cũng sẽ xác định các attributes của nó luôn.
Giả sử bạn rất thích chiếc Tesla Model S và muốn tạo một object như vậy. Bao gồm 4 bánh, xe điện, 5 chỗ và chạy được với vận tốc tối đa là 250km/giờ (155 mph). Hãy tạo object tesla_model_s!
tesla_model_s = Vehicle.new(4, 'electric', 5, 250)
4 wheels + electric tank + 5 seats + 250km/hour maximum speed = tesla_model_s.
(4 bánh + xe điện + 5 chỗ + tốc độ tối đa 250km/giờ = tesla_model_s.)
tesla_model_s
# => <Vehicle:0x0055d516903a08 @number_of_wheels=4, @type_of_tank="electric", @seating_capacity=5, @maximum_velocity=250>
Vậy là ta đã thiết lập lên thuộc tính Tesla. Nhưng truy cập vào bằng cách nào?
Chúng tôi gửi một thông báo tới đối tượng để hỏi về chúng. Chúng tôi gọi đó là một phương pháp (method). Đó là hành vi của đối tượng. Hãy thực hiện nó!
class Vehicle
def initialize(number_of_wheels, type_of_tank, seating_capacity, maximum_velocity)
@number_of_wheels = number_of_wheels
@type_of_tank = type_of_tank
@seating_capacity = seating_capacity
@maximum_velocity = maximum_velocity
end
def number_of_wheels
@number_of_wheels
end
def set_number_of_wheels=(number)
@number_of_wheels = number
end
End
Trong ví dụ trên, ta dùng hai cách number_of_wheels và set_number_of_wheels. Còn được biết tới là getter and setter. Đầu tiên, ta lấy một giá trị thuộc tính và sau đó, đặt một giá trị cho thuộc tính.
Trong Ruby, ta có thể làm điều đó mà không dùng tới những phương pháp trên với attr_reader, attr_writer và attr_accessor.
- attr_reader: Áp dụng phương pháp getter.
class Vehicle
attr_reader :number_of_wheels
def initialize(number_of_wheels, type_of_tank, seating_capacity, maximum_velocity)
@number_of_wheels = number_of_wheels
@type_of_tank = type_of_tank
@seating_capacity = seating_capacity
@maximum_velocity = maximum_velocity
end
end
tesla_model_s = Vehicle.new(4, 'electric', 5, 250)
tesla_model_s.number_of_wheels # => 4
- attr_writer: Áp dụng phương pháp setter.
class Vehicle
attr_writer :number_of_wheels
def initialize(number_of_wheels, type_of_tank, seating_capacity, maximum_velocity)
@number_of_wheels = number_of_wheels
@type_of_tank = type_of_tank
@seating_capacity = seating_capacity
@maximum_velocity = maximum_velocity
end
end
# number_of_wheels equals 4
tesla_model_s = Vehicle.new(4, 'electric', 5, 250)
tesla_model_s # => <Vehicle:0x0055644f55b820 @number_of_wheels=4, @type_of_tank="electric", @seating_capacity=5, @maximum_velocity=250>
# number_of_wheels equals 3
tesla_model_s.number_of_wheels = 3
tesla_model_s # => <Vehicle:0x0055644f55b820 @number_of_wheels=3, @type_of_tank="electric", @seating_capacity=5, @maximum_velocity=250>
- attr_accessor: Áp dụng cả 2 phương pháp trên.
class Vehicle
attr_accessor :number_of_wheels
def initialize(number_of_wheels, type_of_tank, seating_capacity, maximum_velocity)
@number_of_wheels = number_of_wheels
@type_of_tank = type_of_tank
@seating_capacity = seating_capacity
@maximum_velocity = maximum_velocity
end
end
# number_of_wheels equals 4
tesla_model_s = Vehicle.new(4, 'electric', 5, 250)
tesla_model_s.number_of_wheels # => 4
# number_of_wheels equals 3
tesla_model_s.number_of_wheels = 3
tesla_model_s.number_of_wheels # => 3
Giờ chúng ta đã học được cách lấy các giá trị thuộc tính; thực hiện các phương thức getter và setter; và sử dụng attr (reader, writer và accessor).
Bên cạnh đó, chúng ta cũng có thể sử dụng các phương pháp để làm những thứ khác - như phương pháp "make_noise". Hãy thử xem!
class Vehicle
def initialize(number_of_wheels, type_of_tank, seating_capacity, maximum_velocity)
@number_of_wheels = number_of_wheels
@type_of_tank = type_of_tank
@seating_capacity = seating_capacity
@maximum_velocity = maximum_velocity
end
def make_noise
"VRRRRUUUUM"
end
End
Khi chúng ta gọi phương thức này, nó chỉ trả về một string "VRRRRUUUUM".
v = Vehicle.new(4, 'gasoline', 5, 180)
v.make_noise # => "VRRRRUUUUM"
Encapsulation (Tính đóng gói): Ẩn thông tin
Encapsulation là cách giới hạn việc truy cập trực tiếp vào dữ liệu và phương pháp của đối tượng đồng thời tạo điều kiện cho hoạt động trên dữ liệu đó (phương pháp của đối tượng).
Trong lập trình hướng đối tượng, bên trong mỗi đối tượng có chứa dữ liệu thể hiện tình trạng hay thuộc tính của nó. Mỗi đối tượng được trang bị những hành vi (behavior) hay phương thứ (method) để thực hiện một số nhiệm vụ nhất định, nhằm thông báo hay làm thay đổi thuộc tính của chính nó. Đối tượng là sự kết hợp dữ liệu và thao tác trên dữ liệu đó thành một thể thống nhất. Sự kết hợp này gọi là sự đóng gói.
Nói cách khác, những thông tin bên trong sẽ được giấu kín, chỉ có đối tượng mới có thể tương tác với dữ liệu nội bộ.
Như vậy, trong ngôn ngữ lập trình Ruby, ta sẽ dùng một phương pháp để truy cập dữ liệu trực tiếp. Hãy xem ví dụ:
class Person
def initialize(name, age)
@name = name
@age = age
end
end
Như vậy, ta đã áp dụng class Person.
Chúng tôi thực hiện class Person này. Như những gì chúng ta đã học, để tạo ra đối tượng person, chúng ta sử dụng phương thức mới và vượt qua các tham số.
tk = Person.new("Leandro Tk", 24)
Vậy để truy cập được những thông tin này thì phải làm sao?
tk.name
> NoMethodError: undefined method `name' for #<Person:0x0055a750f4c520 @name="Leandro Tk", @age=24>
Không thể làm được! Chúng tôi đã không thực hiện phương thức tên (và tuổi).
Hãy nhớ: "Trong Ruby, chúng tôi sử dụng các phương pháp nào để truy cập trực tiếp dữ liệu?" Để truy cập vào tên và độ tuổi tk
chúng ta cần thực hiện các phương pháp trên class Person của chúng ta.
Giờ chúng tôi có thể trực tiếp truy cập thông tin này. Với sự đóng gói, chúng ta có thể đảm bảo rằng đối tượng (tk trong trường hợp này) chỉ được phép truy cập tên và tuổi. Các đại diện nội bộ của đối tượng được ẩn từ bên ngoài.
Rõ ràng là không được. Lý do bởi chúng ta phải dùng phương thức truy cập trực tiếp như sau:
class Person
def initialize(name, age)
@name = name
@age = age
end
def name
@name
end
def age
@age
end
end
Inheritance (Kế thừa): Hành vi và Đặc điểm
Một số các objects nhất định đều có những điểm chung với nhau: Hành vi và đặc điểm.
Ví dụ, tôi thừa hưởng một số đặc điểm và hành vi từ cha tôi - như đôi mắt và mái tóc. Cùng với các hành vi như sự thiếu kiên nhẫn và hướng nội.
Trong lập trình hướng đối tượng, các class có thể thừa hưởng đặc điểm chung (dữ liệu) và hành vi (phương thức) từ một class khác.
Hãy xem ví dụ khác và thực hiện nó trong Ruby.
Hãy tưởng tượng về một chiếc xe hơi. Số lượng bánh xe, chỗ ngồi và vận tốc tối đa là tất cả các thuộc tính của một chiếc xe.
class Car
attr_accessor :number_of_wheels, :seating_capacity, :maximum_velocity
def initialize(number_of_wheels, seating_capacity, maximum_velocity)
@number_of_wheels = number_of_wheels
@seating_capacity = seating_capacity
@maximum_velocity = maximum_velocity
end
end
Class Car đã được thực hiện:
my_car = Car.new(4, 5, 250)
my_car.number_of_wheels # 4
my_car.seating_capacity # 5
my_car.maximum_velocity # 250
Trong Ruby, chúng ta dùng < operator
để chỉ định rằng một class được thừa kế từ class khác. Class ElectricCar sẽ kế thừa từ class Car.
class ElectricCar < Car
end
Đơn giản vậy đấy, ta không cần dùng thêm phương pháp nào bởi class này đã có sẵn thông tin được kế thừa kế từ Car.
tesla_model_s = ElectricCar.new(4, 5, 250)
tesla_model_s.number_of_wheels # 4
tesla_model_s.seating_capacity # 5
tesla_model_s.maximum_velocity # 250
Thật tuyệt vời!
Module: Hộp công cụ
Chúng ta có thể nghĩ đến một module như hộp công cụ có chứa một tập hợp các hằng số và phương thức.
Một ví dụ của Ruby module là Math. Chúng ta có thể truy cập PI không đổi:
Math::PI # > 3.141592653589793
Và phương pháp .sqrt:
Math.sqrt(9) # 3.0
Ngoài ra, chúng ta có thể áp dụng module của mình và dùng nó trong class. Ta có một class RunnerAthlete:
class RunnerAthlete
def initialize(name)
@name = name
end
end
Áp dụng module Skill vào để có phương thức average_speed.
module Skill
def average_speed
puts "My average speed is 20mph"
end
end
Làm sao thêm module vào class để nó có được hành vi này (average_speed)? Đơn giản là cứ ghi thẳng vào đó!
class RunnerAthlete
include Skill
def initialize(name)
@name = name
end
end
Bạn có thể nhìn thấy phần “include Skill”! Giờ bạn có thể dùng phương pháp này trong instance của class RunnerAthlete.
mohamed = RunnerAthlete.new("Mohamed Farah")
mohamed.average_speed # "My average speed is 20mph"
Tuy nhiên, bạn cần nhớ những điều sau:
- module có thể không có instances;
- module có thể không có subclass;
- module được xác định bằng module.
Chúc mừng! Bạn đã hoàn thành phần nội dung cần biết về ngôn ngữ lập trình Ruby! Nếu có bất kỳ câu hỏi nào, hãy cho chúng tôi biết ở phần bình luận bên dưới nhé!
Tác giả: TK
Tham khảo thêm một số bài viết:
- Mảng và đối tượng trong JavaScript giống như cuốn truyện và tờ báo!
- Hành trình chuyển nghề từ người mẫu thời trang sang kỹ sư phần mềm trong vòng 1 năm
- Nếu bạn muốn trở thành nhà khoa học dữ liệu, hãy học ngay 3 ngôn ngữ này!
Chúc các bạn vui vẻ!