Chào các bạn.
Ngắn gọn thì sau khi thi Ruby Silver 2.1 và nắm được syntax của Ruby, mình quyết tâm setup mục tiêu lấy Ruby Gold để nắm sâu hơn về OOP. Vì vậy có cái memo kiêm blog này.
Môi trường
Các biến global đặc biệt đã được định nghĩa
Đây là kiến thức kì quái mà mình không thể tiếp thu nổi và cũng chưa phải dùng bao giờ 😂😂😂😂 Phần này mình thấy quá kinh dị để nêu ra nên xin phép để link để các bạn đọc thêm ở đây
https://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/globalvars.html
Còn trong phạm vi của đề thi(hay bỏ văn vở đi là “học tủ”) thì chúng ta chỉ cần nhớ tới $0
là tên của file hiện tại đang chạy code, còn $n
với n là 1,2,3,…. là phần tử thứ n khớp với điều kiện đã cho. Các biến này hay được hỏi kèm với regex trong Ruby nên phần này cũng sẽ hơi lai regex 1 chút.
%r|(http://www(.)(.*)/)|=~"http://www.abc.com/"
$0# => tên file
$1# => "http://www.abc.com/"
$2# => "."
$3# => "abc.com"
$4# => nil
command line option
List đầy đủ ở đây: https://ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/options.html
Nhưng trong các option này cái được nhắc đến và giải thích trong sách cụ thể nhất là -l
và -d
.
-l
sẽ chỉ định path nào load library. Khi dùng-l
, các code ruby có liên quan khác bao gồm$LOAD_PATH
,require
vàload
-d
là chế độ debug
Các thư viện bổ sung
JSON và YAML
- Có 1 đoạn JSON như sau
require'json'
json =<<JSON
{
"price":100,
"order_code":200,
"order_date":"2018/09/20",
"tax":0.8
}
JSON
Khi đó, JSON.load
và JSON.parse
cùng cho 1 kết quả như nhau
JSON.load json # => {"price"=>100, "order_code"=>200, "order_date"=>"2018/09/20", "tax"=>0.8}JSON.parse json # => {"price"=>100, "order_code"=>200, "order_date"=>"2018/09/20", "tax"=>0.8}
- Với YAML, chúng ta cũng sẽ có Hash tương tự nhưng chỉ với
load
require'yaml'
yaml =<<YAML
sum: 510,
orders:
- 260
- 250
YAML
object =YAML.load # => {"sum"=>510, "orders"=>[260, 250]}
YAML.load
sẽ convert abc: def
thành {"abc"=>"def"}
(Hash) và - 260n-250
thành [260,250]
(mảng)
- Ngược lại, muốn convert từ Hash sang JSON và YAML
- Với JSON, ta có
hash.to_json
vàJSON.dump(hash)
- Với YAML, ta có
hash.to_yaml
- Với JSON, ta có
freeze
!!!!
freeze
sẽ khoá cứng lại 1 đối tượng, không cho phép các phương thức ghi đè lên đối tượng đó
s ="ab"
s.freeze
s.concat("c")# => RuntimeError: can't modify frozen String
Ví dụ trên là concat đã là chỉnh sửa s
từ trong ra nên lỗi
Tuy nhiên, nếu dùng dấu +
thì hiểu như thể s
được chỉnh sửa từ ngoài vào nên sẽ thành abc
s +"c"# => "abc"
clone và dup
Nhân có freeze
thì cũng phải nhắc đến clone
và dup
clone
và dup
đều là các method dùng để tạo 1 đối tượng giống hệt đối tượng khác, nhưng có khác nhau:
clone
sẽ copy nguyên trạng tháifreeze
,taint
vàsingleton
của đối tượngdup
không copy các trạng thái ấy
Lưu ý cả clone
và dup
đều là copy nông, không phải deep copy(hay tiếng Nhật là 参照先/sanshousaki. Xin lỗi các bạn vì phải note như này. Đây là câu lý thuyết)
Ví dụ minh họa:
original =%w(apple orange banana)
copy = original.clone
original.map(&:object_id)# [23506500, 23506488, 23506476]
copy.map(&:object_id)# [23506500, 23506488, 23506476]
Forwarder
require'forwardable'classRecordCollection
attr_accessor :recordsextendForwardable
def_delegator :@records,:[],:record_numberend
Thư viện tiêu chuẩn
Thời gian
Ở level Silver, chúng ta đã được làm phép tính cộng thời gian như là 1 biến thời gian cộng hay trừ với 1 số sẽ cho 1 bước nhảy du hành tới tương lai hoặc quá khứ Vậy nếu thời gian trừ lẫn nhau thì sao?
- Cùng Date sẽ cho kiểu Rational
- Cùng Time sẽ cho kiểu Float
- Cùng DateTime sẽ cho kiểu Rational
Ví dụ
d =Date.today -Date.new(2015,10,1)
p d.class# => Rational
d =Time.today -Time.new(2015,10,1)
p d.class# => Float
Ở đây Date
và DateTime
là trừ chéo được do cùng thư viện chứ Date
hoặc DateTime
không trừ chéo được với Time
đâu nhe các bợn
Cú pháp
Block, proc và lambda
1 chút lời nói đầu: đây là các câu rất hay bị hỏi nếu phỏng vấn cho các vị trí code Ruby ở 1 số công ty. Nên bài này phần cú pháp, block-proc-lambda sẽ được đặt vị trí trang trọng nhất
Block
Cú pháp Block
1.upto 5do|x| puts x end# đúng1.upto(5)do|x| puts x end# cũng đúng1.upto(5){|x| puts x }# đúng, nhưng ghi nhớ là bỏ dấu ngoặc đơn không chạy được đâu các bạn nha
yield
yield
là method dùng để gọi 1 block ra để dùng lại
deftag(name)
puts "<#{name}>#{yield}</#{name}>"end
tag(:p){"Hello World"}# => <p>Hello World</p>
Proc
Proc thực ra cũng không khác Block lắm. Như kiểu Block có tên để truyền tham số ấy
Proc.call
Nếu như block dùng yield
thì ở Proc ta sẽ dùng Proc.call
deftag(name,&block)
puts "<#{name}>#{block.call}</#{name}>"end
tag(:p){"Hello World"}# => <p>Hello World</p>
Lambda
Chút hoài niệm: Lần đầu tiên mình dùng lambda tử tế là để viết scope SQL cho các model. Động tới phần này bỗng kỉ niệm cũ ùa về lâng lâng ghê.
Cách để định nghĩa lambda
hi = lambda do|x|
puts "Hello, #{x}."end
hi.call("World")
hi =->(x){ puts "Hello, #{x}."}
hi.call("World")
Và Lambda cũng dùng method call
như Proc
Bài toán
- Để
tag(:p) {"Hello World"}
ra kết quả<p>Hello World</p>
thì 2 đoạn code sau đúng:
deftag(name)
puts "<#{name}>#{yield}</#{name}>"end
và
deftag(name,&block)
puts "<#{name}>#{block.call}</#{name}>"end
Hash và Array
Cái này ở Silver rồi mà sao giờ lên Gold trông nó LẠ LẮM.
Ở level Gold, chúng ta sẽ được giới thiệu tới các kiến thức liên quan tới mảng và hash được truyền như tham số hay biến số kiểu như:
*args
có kiểu dữ liệu là Array.**args
có kiểu dữ liệu là Hash.
1 số bài tập ví dụ:
Bài 1
x,*y =*[0,1,2]
p x, y
Ở đây, cụm *[0, 1, 2]
tương đương với 0, 1, 2
luôn nên ta có x
là kiểu đơn tương ứng phần tử đầu tiên của dãy, còn *y
là kiểu array thì tương ứng với các phần tử còn lại. Nên kết quả trả ra là 0 [1, 2]
Bài 2
defhoge# điền gì vào sau hoge
puts "#{x},#{y},#{params[:z]}"end
hoge x:1, z:3# => output: 1,2,3
Đáp án là (x: , y: 2, **params)
vì với #{x}
được truyền bằng 1 thì cú pháp định nghĩa sẽ là x:
, y ở đây không được truyền vào nhưng lại có output bằng 2 nên định nghĩa luôn y: 2
, còn #{params[:z]}
thì tham số truyền vào hàm phải là params
, và z: 3
là kiểu Hash nên sẽ phải là **params
Method inject
inject
hay reduce
là method dùng để kết hợp các phần tử có kiểu dữ liệu enum
thông qua các toán tử
# Sum some numbers(5..10).reduce(:+)#=> 45# Same using a block and inject(5..10).inject {|sum, n| sum + n }#=> 45
Như vậy chúng ta có thể bổ sung các cặp method tương đương: map
– collect
, select
– find_all
, detect
– find
và inject
– reduce
.
Ví dụ: để có mảng [1, 4, 9]
từ mảng [1, 2, 3]
[1,2,3].map{|x| x **2}# cách 1[1,2,3].collect{|x| x **2}# cách 2[1,2,3].inject([]){|x, y| x << y **2}# cách 3[1,2,3].reduce([]){|x, y| x << y **2}# cách 4
Kiểu dữ liệu số
- Lấy giá trị
4/5
, ta có
p 4/5# => 0
p 4.0/5# => 0.8
p 4/5.0# => 0.8
p 4/5r # => 4/5
- Phép toán với các kiểu số:
a =1.0+1
a = a +(1/2r)
a = a +(1+2i)
Dòng 1: Integer
với Float
sẽ cho kiểu dữ liệu là Float
Dòng 2: Float
với Rational
sẽ cho kiểu dữ liệu là Float
Dòng 3: Float
với Complex
sẽ cho kiểu dữ liệu là Complex
➡️ ồ, vậy ra trong Ruby, Complex
là vua của các số cũng như IT là vua của các ngà… à mà thôi
catch-throw
Cú pháp sẽ như sau
catch ObjectdothrowObjectend
Ví dụ
a, b = catch(:exit)dofor x in1..10for y in1..10throw(:exit)if x + y ==10endendend
puts a, b # => 1, 9
Tại đây, khi xét x=1
, chạy tới y=9
thì code dừng. Do đó, a và b là 1 và 9.
begin-raise-rescue-ensure
Đây là phần cũng tương tự như ở Silver thôi. Nhưng sẽ có thêm ensure
là chắc chắn sẽ in ra code và SystemExit
không phải là lỗi mà là thoát hệ thống
begin
exit
rescueRuntimeError
puts "RuntimeError"rescueSystemExit
puts "SystemExit"ensure
puts "End"end# => SystemExit# => End
DATA và __END__
Ruby hay được quảng cáo rầm rộ về metaprogramming, tức là có khả năng biến chương trình khác thành input data. Vâng, đây là ví dụ rõ ràng nhất: Lấy chính code Ruby ra làm input đầu vào
whileDATA.gets
puts $_if$_=~/Ruby/end
__END__
Java programming
Ruby programming
C programming
Kết quả trả về là Ruby programming
. Input data sẽ bắt đều từ DATA
với __END__
module_eval
Đây cũng là 1 cách metaprogramming trong Ruby
moduleAB=42deff21endendA.module_eval(<<-CODE)defself.f
p BendCODEB=15A.f =>B
alias
Từ khoá alias
sẽ cho phép ta gọi 1 method A như 1 method B khác. Khi đó, gọi B sẽ có toàn bộ các xử lý của A.
defmethod
puts "Hello World"endalias old_method method
defmethod
old_method
puts "Hello, Ruby World"end
method
Kết quả trả ra sẽ có cả Hello World
và Hello, Ruby World
.
sort
Với Sort, có 2 điều cần chú ý:
- Khi định nghĩa method sort và quy tắc sort của Ruby, ta dùng
<=>
classCompany
attr_reader :id
attr_accessor :namedefinitialize id, name
@id= id
@name= name
enddefto_s"#{id}:#{name}"enddef<=> other
self.id <=> other.id
endend
companies =[]
companies <<Company.new(2,'Liberyfish')
companies <<Company.new(3,'Freefish')
companies <<Company.new(1,'Freedomfish')
companies.sort
companies.eachdo|e|
puts e
end# => 2:Liberyfish# => 3:Freefish# => 1:Freedomfish
Ở trường hợp trên, sort
không mang tính destructive
nên mảng được giữ nguyên
Tuy nhiên, câu chuyện với sort!
sẽ là khác
1:Freedomfish
2:Liberyfish
3:Freefish
Để sắp xếp theo thứ tự lớn dần theo giá trị, ta dùng self.id <=> other.id
. Còn thứ tự nhỏ dần sẽ là other.id <=> self.id
- Có thể thay thế toán tử
<=>
bằng-
classCompany
attr_reader :id
attr_accessor :namedefinitialize id, name
@id= id
@name= name
enddefto_s"#{id}:#{name}"enddef<=> other
self.id <=> other.id
endend
OOP trong Ruby
Đây là cái phần mình muốn ôn lại nhất trong lần đặt mục tiêu thi lần này. OOP có 4 đặc tính: Tính đóng gói, tính kế thừa, tính đa hình và tính trừu tượng. Chúng ta sẽ cùng xem 4 đặc tính đó được thể hiện thế nào trong Ruby
ancestors
Đây là khi 1 class của Ruby gọi các cụ ra gánh còng lưng =)))
Giỡn chút chơi chứ nghiêm túc ra, đây chính là 1 đặc điểm ở Ruby thể hiện tính kế thừa trong OOP. Chúng ta sẽ thấy được 1 class được kế thừa từ những class nào.
moduleFooendclassBarincludeFooendclassBaz<Bar
p ancestors
p included_modules
p superclass
end# => [Baz, Bar, Foo, Object, Kernel, BasicObject]# => [Foo, Kernel]# => Bar
Ở đây chúng ta có 3 method:
ancestors
sẽ trả về các class, superclass và các module được dùng theo thứ tự cái đằng trước kế thừa cái đằng sau, bắt đầu từ class con.included_modules
trả về các module được sử dụng. Mặc định tất cả các class đều import moduleKernel
superclass
sẽ trả ra class cha của class hiện tại. Ví dụ như class cha của Baz là Barsuperclass
của Bar làObject
superclass
của Object làBasicObject
Từ khoá super
Đây là 1 người quen cũ ở level Silver nhưng trong lúc học mình đã không ghi lại trên Viblo.
Đây cũng là đặc điểm ở Ruby thể hiện tính kế thừa trong OOP. super
cho phép gọi lại xử lý của 1 method ở class Cha có cùng tên với class con.
classAnimaldefname
name ="Animal"endendclassCat<Animaldefnamesuper+": Cat"endend
cat =Cat.new
puts cat.name
# "Animal: Cat"
Ở đây, tại cat.name
, super + ": Cat"
sẽ tương đương name = "Animal" + ": Cat"
Ngoài ra, với việc 1 class con định nghĩa lại 1 method được kế thừa từ class cha ở đây cũng chính là thể hiện tính đa hình.
private, public và protected
Nhiều Tính kế thừa quá đúng là hơi chán. Thế nên rất may chúng ta có 1 ví dụ về tính đóng gói
private
, public
và protected
thể hiện tính đóng gói trong Ruby.
-
private
sẽ chỉ cho gọi method ở trong class kể từ lúc định nghĩa -
protected
cũng tương tự nhưprivate
, nhưng cho phép 1 số class khác kế thừa từ class định nghĩa truy cập thêm(bao gồm cả chính class ấy, kí hiệu làself
) -
public
là cho phép method đó được truy cập ở mọi nơi.
Cách dùng: chỉ cần thêm keyword muốn dùng vào và đặt ở cuối class các method muốn đóng gói. Với public
thì không cần từ khoá
classAdeffoo
bar
endprivatedefbar
p 'FooBar'endendA.new.foo # => FooBar
Best practice: Thứ tự đặt public
➡️ protected
➡️ private
Chú ý 1:
Đây là đoạn code sẽ xảy ra lỗi
classAdeffooself.bar
endprivatedefbar
p 'FooBar'endendA.new.foo # => NoMethodError
Khi có self
, các từ khoá dùng được để chạy success sẽ là public
, protected
hoặc không điền gì.
Chú ý 2:
Có thể định nghĩa lại tính đóng của method ở class con
classAprivatedefbar
p 'FooBar'endendclassB<Apublic:barendB.new.bar # => "FooBar"
undef
undef
sẽ hủy định nghĩa của 1 method trong 1 class
classRootdefm
puts "Root"endendclassA<Rootdefm
puts "A"endendclassB<Adefm
puts "Root"endundef m
endB.new.m # => NoMethodErrorA.new.m # => A
Refine và using
Đây cũng là 1 ví dụ thể hiện tính đa hình của OOP mà Ruby có.
Khi sử dụng đến refine
1 Module trong Ruby thì từ khoá đi theo cặp sẽ là using
classCdefhoge
puts "A"endendmoduleM
refine Cdodefhoge
puts "B"endendend
c =C.new
c.hoge
using M
c.hoge
# =># A# B
attr_accessor, attr_writer & attr_reader
Lại người quen cũ ở Silver này các bạn Và ở đây chúng ta sẽ nhớ
attr_accessor
cho cả quyền đọc lẫn ghiattr_writter
chỉ cho quyền ghiattr_reader
chỉ cho quyền đọc
Do vậy, 3 đoạn code sau xử lý giống nhau
attr_accessor: x
attr_reader: x
attr_writter: x
defx@xenddefx=(x)@x= x
end
Biến instance, biến class
Cần chú ý ở đây là với các biến class @@
, giá trị ở tất cả các class sẽ chung. Ví dụ
classA@@val=1endclassB<A@@val=2endA.class_variable_get(:@@val)# => 2
Bài tập ví dụ:
classS@@val=0definitialize@@val+=1endendclassC<Sclass<<C@@val+=1enddefinitialize@@val+=1superendendC.new# => 3C.new# => 5S.new# => 6S.new# => 7
p C.class_variable_get(:@@val)# => 7
Instance method và class method
Chúng ta có đoạn code định nghĩa như sau:
classSayHellodefself.from_the_class"Hello, from a class method"enddeffrom_an_instance"Hello, from an instance method"endend
Ở đây, class method chính là from_the_class
còn instance method là from_an_instance
Sự khác biệt sẽ ở dưới
>> SayHello.from_the_class
=> "Hello, from a class method"
>> SayHello.from_an_instance
=> undefined method `from_an_instance' for SayHello:Class
>> hello = SayHello.new
>> hello.from_the_class
=> undefined method `from_the_class' for #<SayHello:0x0000557920dac930>
>> hello.from_an_instance
=> "Hello, from an instance method"
instance_variable_get
classFoodefinitialize@foo=1endend
obj =Foo.new
p obj.instance_variable_get("@foo")#=> 1
p obj.instance_variable_get(:@foo)#=> 1
p obj.instance_variable_get(:@bar)
const
const_missing
Đây là 1 method có trong module, chỉ việc lôi ra định nghĩa lại như code dưới đây
classObjectX="X"defself.const_missing a
p "#{a}"endendY# => "Y"
const trong class
Cũng như tình huống trên, nếu ta định nghĩa lại const_missing
trong Object
thì có thể lôi const ra dùng trực tiếp. Nhưng nếu ta gọi 1 class khác như Foo
thì cần yêu cầu gọi thêm class trước khi gọi hằng số như Foo::Y
classFooX="X"defself.const_missing a
p "#{a}"endendFoo::Y# => "Y"
Include, extend, prepend
- Khi dùng
include
, method trong module sẽ trở thành instance method:
moduleMdeffooself.classendendclassCincludeMend
p C.new.foo # => C
prepend
: Tác dụng sẽ như include nhưng sẽ có sự đảo thứ tự kế thừa
moduleFooendclassBarprependFooend
p Bar.ancestors
# => [Foo, Bar, Object, Kernel, BasicObject]
extend
1 module ở trong class sẽ biến method trong module đó thành class method ở trong class được thừa kế
moduleMdefhoge
puts "hoge"endendclassAextendMendA.hoge # => M
define_method
Chức năng cũng tương tự như alias
bên trên
classFoodeffoo() p :fooenddefine_method(:bar, instance_method(:foo))endFoo.new.bar # => :foo
method ở Top Level
Method này rất thân thuộc với mọi người
deftalk
puts "Hello"end
Và nó tương đương với
classObjectprivatedeftalk
puts "Hello"endend
Ảo diệu ko :v
Thật vậy chúng ta đều phải có phép thử.
deftalk
puts "Hello"end
talk # => "Hello"Object.new.talk # => private method `talk' called for #<Object:....> (NoMethodError)
và
classObjectprivatedeftalk
puts "Hello"endend
talk # => "Hello"Object.new.talk # => private method `talk' called for #<Object:....> (NoMethodError)
Như vậy theo Ruby, các method được định nghĩa đều là private method trong class Object
Design pattern: Singleton
Singleton design pattern là 1 pattern được sinh ra nhằm làm cho đối tượng được khởi tạo từ 1 class là độc nhất. Các bạn có thể đọc kĩ hơn tại: https://www.tutorialspoint.com/design_pattern/singleton_pattern.htm
module Singleton
Khi include module Singleton
vào trong 1 class, ta sẽ gọi method instance của class và sau đó là method
require'singleton'classMessageincludeSingletondefmorning'Hi, good morning!'endend
p Message.instance.morning # => 'Hi, good morning!'
p Message.new.morning # => Lỗi nha các bạn
p Message.morning # => Lỗi nha các bạn
singleton_class
Trả về Singleton class của 1 object
classCend
p C.singleton_class # => #<Class:C>
p C.singleton_class.singleton_class.singleton_class.singleton_class # => #<Class:#<Class:#<Class:#<Class:C>>>>
Code mẫu luyện tập
Phần này được sinh ra sau lần đầu mình thi trượt. Ở trong đề thi đã xuất hiện các phần sử dụng đoạn code như ở trong sách luyện nhưng hỏi khác phần và khi đó 1 số câu mình đã bị tủ đè
Code 1
p ("aaaaaa".."zzzzzz").lazy.select {|e| e.end_with?("f")}.take(3).force
Trong đề thi sẽ dùng các công thức toán nhưng cứ tạm lấy câu này
- Kết quả câu lệnh trên: 3 phần tử thỏa mãn điều kiện trong block là
["aaaaaf", "aaaabf", "aaaacf"]
- Chắc chắn phải gọi
lazy
vì với số phần tử"aaaaaa".."zzzzzz"
sẽ vô cùng lớn, có thể time out hệ thống - Chỗ
select
có thể thay bằngfind_all
(Ruby Silver) take(3).force
có thể thay bằngfirst(3)
- Nếu bỏ force thì kết quả sẽ thành
#<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: "aaaaaa".."zzzzzz">:select>:take(3)>
Code 2:
classObjectprivatedeffoo
p 'Hello, world!'endenddefbar
foo
end
Tổng kết
Well, ngoài mục tiêu OOP của mình có vẻ phần nào đã đạt được thì mình cũng học được khá nhiều kiến thức về Ruby mà có khả năng support được cho các mục đích khác như viết serverless function thông qua IO, Fiber hay là Thread. Cảm ơn các bạn đã đọc bài
Nguồn: viblo.asia