4-5 Tạo class

4-5 Tạo class

Tại chương này chúng ta sẽ vừa tạo class vừa học cách định nghĩa class. Con Slime được chế tạo ra từ đây có máu và có khả năng tấn công đối phương là những con Slime khác

Định nghĩa class Slime

Rất tiếc trong Ruby thông thường thì chúng ta không được chuẩn bị class Slime hay class Dragon. Class Slime là một class không tồn tại nên tự nhiên chúng ta chạy [Slime.new] thì lệnh sẽ bị lỗi. Tuy nhiên tại Ruby thì chúng ta có thể tạo class một cách rất dễ dàng. Để tạo một class thì chúng ta chỉ cần làm như chương trình dưới đây.

class Tên class
end

Như vậy chúng ta thử tạo một class tên là Slime.

class Slime
end

Như vậy một class tên là Slime đã được tạo thành. Tạo class thì người ta gọi là định nghĩa class. Tên class thì chữ cái đầu nhất định phải bắt đầu bằng chữ in hoa. Từ chữ cái thứ 2 thì chúng ta có thể dùng chữ cái mình thích hay là dấu gạch dưới [_] tùy thích.

Chúng ta hãy thử tạo, lưu và chạy chương trình [monster_error.rb].

monster_error.rb

class Slime
end

monster = Slime.new
monster.hello	# => Error!

[Kết quả hiển thị]

slime.rb:5: underfined method 'hello' for #<Slime: 0x401c4f34> (NoMethodError)

Cho đến dòng thứ 4 thì đây vẫn là một thao tác đúng, từ dòng thứ 5 với lệnh [monster.hello] thì lỗi [NoMethodErrror] phát sinh. Lỗi này có nghĩa là "Lệnh hello trong class Slime không được định nghĩa". Dòng thứ 5 thì lỗi phát sinh nhưng dòng thứ 4 là

monster = Slime.new

thì vẫn hoàn toàn đúng nên chúng ta đã gọi ra được một object từ Slime class. Trông có vẻ như chúng ta đã định nghĩa ra một class tên Slime nhưng ngoài cái tên thì trong đó vẫn chưa có gì đặc biệt.

Định nghĩa các lệnh trong Class slime

Chúng ta hãy cùng định nghĩa những lệnh trong Slime class!

monster00.rb

class Slime
  def hello		# định nghĩa lệnh hello
end

monster = Slime.new
monster.hello	  # chạy lệnh hello đối với object

Như vậy, chúng ta đã định nghĩa được lệnh có trong class. Như thế những object có trong Slime class đã có thể nhận được lệnh tên là "hello". Chúng ta hãy thử chạy thử luôn xem sao.

[Kết quả hiển thị]

(Không có gì được hiển thị)

Khi chạy chương trình thì chương trình không bị lỗi, tuy nhiên cũng không có gì xảy ra. Đúng như vậy, vì nội dung trong lệnh hello là rỗng nên việc không có điều gì xảy ra là đương nhiên. Vậy chúng ta thử viết các xử lý cho lệnh hello nhé!

monster01.rb

class Slime
  def hello
    puts "Xin chào, tôi là Slime"
  end
end

monster = Slime.new
monster.hello

Chúng ta thử chạy thử [Kết quả hiển thị]

Xin chào, tôi là Slime

Chương trình đúng là đã chạy như chúng ta mong muốn. Chúng ta tạo ra object từ trong Slime class bằng [Slime.new], rồi đặt vào biến số [monster], rồi gọi ra lệnh hello đối với biến số đó.

Instance Method

class Slime
  def hello
    puts "Xin chào, tôi là Slime"
  end
end

Từ khi chúng ta viết chữ [class] cho đến [end] là chúng ta đã định nghĩa một class. Những lệnh định nghĩa có trong lúc định nghĩa class thì chúng ta gọi là Instance Method. Có nghĩa là lệnh [hello] này là một instance method.

Trong lúc định nghĩa một class thì chúng ta có thể thêm vào bao nhiêu instance method cũng được.

monster02.rb

class Slime
  def hello
    puts "Xin chào, tôi là Slime"
  end

  def bye
    puts "Bye
  end
end

monster = Slime.new
monster.hello
monster.bye

[Kết quả hiển thị]

Xin chào, tôi là Slime
Bye

Chúng ta có thể gọi instance method từ object.

object.method

Những lệnh được gọi ở đây là những lệnh được định nghĩa trong class.

Instance

[instance] có nghĩa là những object được tạo ra từ class. Trên thực tế Object để định hướng object có ý nghía rất rộng nhưng tại quyển sách này thì Object và instance là hai từ hoàn toàn giống nghĩa nhau.

Biến số của object

Lần này chúng ta sẽ mang máu cho con slime đã rạo ra. Như vậy chúng ta phải định nghĩa 2 thứ tại class.

class Slime
  def set_hp(a)
    @hp = a
  end

  def hp
    @hp
  end
end

monster = Slime.new
monster.set_hp 10
p monster.hp	# => 10

Chúng ta hãy tập trung vào bộ phận lệnh được gọi ra. [set_hp] là lệnh để thiết lập máu cho Object Slime. Và lệnh hp là lệnh yêu cầu trả lại kết quả máu mà chúng ta đã thiết lập cho object.

[@hp] chính là biến số. Tuy nhiên đây không phải là một biến số bình thường, những biến số có thêm [@] thì người ta gọi là biến số instance. Đối với biến số thông thường thì phạm vi hiệu lực chỉ là ở trong lệnh, nhưng đối với biến số thì phạm vi hiệu lực là ở trong chính object đó. Có nghĩa là nếu là object giống nau thì kể cả lệnh có khác nhau thì giá trị có thể chia sẻ. Biến số instance được bảo trì cố định trong mỗi object. Nếu là hai object cùng được tạo ra từ 1 class thì sẽ mang những giá trị [@hp] khác nhau, là những biến số khác nhau.

monster1 = Slime.new
monster2 = Slime.new
monster1.set_hp 10
monster2.set_hp 100
p monster1.hp	# => 10
p monster2.hp	# => 100

Như vậy con Slime của chúng ta đã có máu.

Lệnh sử dụng dấu "="

Ở trên để tạo máu cho slime thì chúng ta đã sử dụng lệnh

monster.set_hp 10

Tuy nhiên như thế này thì không mang phong cách Ruby. Nhất định các bạn hãy viết như sau

monster.hp = 10

Như vậy thì nó trở nên thông thường và dễ hiểu hơn đúng không. Để có thể dùng được [hp = ] như trên thì chúng ta cần định nghĩa lệnh như dưới đây.

class Slime
  def hp = (a)
    @hp = a
  end

  def hp
    @hp
  end
end

Như vậy thì chúng ta đã định nghĩa xong lệnh [hp = ], và để gọi lệnh [hp=] thì chúng ta gọi như sau.

monster.hp= 10

Tên lệnh có thêm dấu = này là một trường hợp khá đặc biệt. Khi gọi ra thì ngay trước đấu [=], chúng ta có thể để một khoảng trống.

monster.hp = 10

Ruby thật là một ngôn ngữ chi ly đúng không? Việc cho thêm dấu cách vào đây hay không đó chính là sở thích của cá nhân. Trong cuốn sách này thì đã thống nhất cách viết trước đó sẽ thêm một dấu cách. Tuy nhiên, lệnh [hp] và lệnh [hp=] được định nghĩa cả hai nên khi tính toán lại số máu thì mọi người hãy cố gắng viết được như dưới đây.

monster.hp += 5

Ngoài ra chúng ta cũng có thể dùng các kí hiệu khác như -= *= hay /=.

Lệnh initialize

monster = Slime.new
p monster.hp	# => nill

Nếu không có gái trị nào thêm vào giá trị biến số thay thế thì sẽ trả lại câu trả lời là [nil]. Tuy nhiên, nếu không có máu thì con Slime không thể sống được. Vậy nên các bạn hãy cố gắng trong giây phút con Slime được sinh ra thì chúng ta hãy thay thế ngay giá trị cho máu. Nếu chúng ta định nghĩa lệnh[ơinitialize] thì nó sẽ chạy được xử lý thêm thông tin ngay.

class Slime
  def initialize
    @hp = 10
  end
  (lược)
monster = Slime.new
p monster.hp		# =>10

Trong class Slime thì đã có thêm lệnh initialiaze. [initialize] là một lệnh rất đặc biệt, khi object đang được chế tạo thì lệnh sẽ được gọi luôn.

def initialize
  @hp = 10
rnd

Tại đây, giá trị ban đầu Hp của con Slime là 10.

monster = Slime.new
p monster.hp	# => 10

Tạo lệnh attack

Hiện nay, con Slime đã mang máu, và máu đã được thêm vào khi con Slime được tạo ra. Chuẩn bị đã xong, chúng ta thử thêm lệnh tấn công như tiếp theo đây.

class Slime
	(lược)
  def attack(target)
    target.hp -= 5
  end
end

monster1 = Slime.new
monster2 = Slime.new
p monster2.hp	# => 10

monster1.attack monster2
p monster2.hp	# => 5

monster1.attack monster2
p monster2.hp	#=> 5

Chúng ta đã thêm lệnh [attack] mới vào. Lệnh [attack] lấy argument là tấn công object Slime đối phương, và từ đó làm ảnh hưởng đến đối phương.

def attack(target)
  target.hp -= 5
end

Object nhận như một argument được thay thế bằng biến số [target]. Tiếp theo, trong lệnh [attack] thì máu của con Slime mà biến số [target] đang đại diện cho trừ dần đi 5 điểm cho mỗi lần attack. Có nghĩa là lệnh là lệnh sẽ trừ đi 5 điểm máu của đối phương.

Sử dụng Access

Vậy, cho đến hiện tại chúng ta đã làm cho con Slime mang máu theo người. Nhưng cũng giống như HP, trường hợp chúng ta mang giá trị cho object rồi từ bên ngoài làm ảnh hưởng đến giá trị đó sẽ phát sinh không ít. Lệnh để truy cập những thông số mà object đang mang như HP thì người ta gọi đó là Access.

class Slime
  def hp
    @hp
  end
end

Tại Ruby, chúng ta được chuẩn bị phương pháp để định nghĩa lệnh truy cập một cách rất dễ dàng. Chúng ta có thể viết như sau.

class Slime
  attr_reader :hp
end

Nếu viết như vậy, thì cũng giống như chúng ta viết lệnh [hp] để trả lại giá trị [@hp]. Nên [attr_reader] được nói là chức năng định nghĩa lệnh trả lại biến instance.

Để định nghĩa cho biến instance thay thế cho giá trị nào đó, chúng ta sử dụng [attr_writer].

class Slime
  attr_writer :hp
end

Như vậy thì lệnh [hp=] được định nghĩa. Cso nghĩa là thao tác trên có ý nghĩa y hệt thao tác dưới đây.

class Slime
  def hp = (a)
    @hp = a
  end
end

Hơn nữa, khi chúng ta muốn một lần chạy cả hai lệnh [attr_reader] và [attr_writer] cùng một lần thì có lệnh [attr_accessor].

class Slime
  attr_accessor :hp
end

Như vậy sẽ có ý nghĩa giống y hệt như chúng ta viết chương trình sau.

class Slime
  attr_writer :hp
  attr_reader :hp
end

Như vậy chúng ta có thể đơn giản định nghĩa để có thể truy cập vào.

Nếu sử dụng access như ở trên thì ngoài HP thì chúng ta có thể thêm vào những thông số khác một cách đơn giản.

class Slime
  attr_accessor :hp
  attr_accessor :mp
end

Mặt khác, phần code ở trên thì chúng ta có thể viết lại như sau.

class Slime
  attr_accessor :hp, :mp
end

Như trên chúng ta có thể viết những kí hiệu đại diện cho các biến và phân cách với nhau bằng dấu [,] để có thể đồng thời access vào những thông số khác nhau. Như ví dụ dưới đây là cách sử dụng. Chúng ta tạo những biến như [@hp] [@mp] [@str] [@agi] để làm thông số cho slime. Rồi chúng ta chạy lệnh [display_status] để xác nhận nội dung của những biến số.

monster03.rb

class Slime
  attr_accessor :hp, :mp
  attr_accessor :str, :agi

  def display_status
    puts "HP         #{@hp}"
    puts "MP         #{@mp}"
    puts "Lực        #{@str}"
    puts "Độ nhanh   #{@agi}"
  end
end

monster = Slime.new
monster.hp = 10
monster.mp = 0
monster.str = 7
monster.agi = 5
monster.display_status

[Kết quả hiển thị]

HP          10
MP          0
Lực         7
Độ nhanh    5

Access vào instance method

Những lệnh được định nghĩa trong class thì chúng ta cũng có thể trực tiếp gọi lệnh đó trong class giống nó.

class Slime
  attr_accessor :hp, :mp

  def display_status
    puts "HP     #{hp}"
    puts "MP     #{mp}"
  end
end

Tại Slime Class, dựa vào lệnh truy cập mà những lệnh như [mp] hay [hp] được định nghĩa. Những lệnh này này được lệnh [display_status] gọi ra.

Self

Trong instance thì chúng ta có thể dùng lệnh để gọi lệnh, nhưng trong những trường hợp những lệnh dùng dấu [=] như [hp=] thì chũng ta phải chú ý khi dùng.

class Slime
  attr_accessor :hp, :mp

  def setup_params
    hp = 10			#Thêm vào biến local chứ không phải biến @hp
    self.hp = 10	# Gọi lệnh hp=, rồi đặt cho biến @hp
  end
end

Trong lệnh [setup_params] chúng ta đã thêm vào 2 công thức để thay thế giá trị. Nếu như cách viết ở trên thì không phải chúng ta gọi lệnh [hp=] mà chỉ là thay thế giá trị mới cho biến local [hp]. Chỉ là chúng ta đã thay thế giá trị 10 cho biến số [hp] thôi.

hp = 10

Trong trường hợp lệnh [hp=] có trong lệnh [setup_params] thì chúng ta thêm thành tố [self.] đằng trước.

self.hp = 10

[self] là người nhận chỉ bản thân mình trong một object. Thêm [self.] để chỉ định người nhận thì chúng ta có thể gọi lệnh [hp=]. Có ý định thay vào biến [@hp] mà quên không thêm [self.] vào thì kết quả sẽ không như mong muốn nên mọi người hãy chú ý.