Глава друга

Визначення класів, створення об'єктів та швидкий огляд їхньої роботи…

Досі ми використовували “стандартні” об'єкти Ruby, такі як цифри і рядки. Тепер розглянемо створення власних нових типів об'єктів. Як і в більшості об’єктно орієнтованих мов програмування, в Ruby об'єкт визначається класом. Клас подібний до креслення, з якого все будується. Це приклад дуже простого класу:

class MyClass

end

А тут я створюю об'єкт нашого простого класу:

object = MyClass.new

Я небагато можу зробити з нашим об'єктом хоча б тому, що нічого не запрограмував в класі MyClass, з якого він був створений.

object_class.rb:

Насправді, якщо ви створите порожній клас як от MyClass, об'єкти створені від нього — не будуть зовсім безкорисними. Усі класи в Ruby автоматично успадковують функціональність від класу Object. Тож, наш об'єкт ob може використовувати методи класу Object, наприклад такі, як .class (виводить назву класу, якому належить наш клас):

puts ob.class    #=> виведе: “MyClass”

Щоб зробити MyClass трохи кориснішим, потрібно додати в нього один або декілька методів. У цьому прикладі, що був коротко згаданий у попередній главі, я додав метод, який називається saysomething:

class MyClass
  def saysomething
    puts("Привіт")
  end
end

Тепер, якщо я створю об'єкт класу MyClass, я зможу викликати метод saysomething цього об'єкта і вивести слово "Привіт":

ob = MyClass.new
ob.saysomething

Екземпляри і змінні екземплярів класів

Створимо кілька корисних об'єктів. Дім не повинен бути без собаки, тож давайте створимо клас Dog:

class Dog
  def set_name(aName)
    @myname = aName
  end
end

Зверніть увагу, що визначення класу починається з ключового слова class (в нижньому регістрі). Після нього — назва класу, що повинна починатися з великої літери. Мій клас Dog містить один метод set_name. Він приймає вхідний аргумент aName. В тілі методу set_name значення aName присвоюється змінній екземпляра @myname.

Змінні, що починаються з символу @ є змінними екземпляра (instance variables). Це означає, що вони належать індивідуальним об'єктам класу або “екземплярам” класу. Оголошувати змінні заздалегідь не обов’язково.

Я можу створити екземпляр класу Dog, тобто “об'єкт собака”, викликавши метод new. Тут я створюю два об'єкти класу Dog тобто “дві собаки” (пам'ятайте, що імена класів починаються з великої літери, а імена об'єктів складаються з літер нижнього регістру):

mydog = Dog.new
yourdog = Dog.new

Зараз ці дві собаки не мають імен. Тож наступне, що я повинен зробити, щоб задати їм імена — це викликати метод set_name:

mydog.set_name('Фідо')
yourdog.set_name('Бензо')

Задавши імена собакам, мені потрібно мати спосіб дізнати їх пізніше. У кожної собаки повинно бути власне ім'я, тому давайте створимо метод get_name:

def get_name
  return @myname
end

Слово return тут необов'язковим. В Ruby методи завжди повертають значення останнього обчисленого виразу. Але для більшої ясності (і для уникнення несподіваних результатів від методів складніших ніж цей) нам слід виробити звичку явно вказувати return, коли ми хочемо повернути якесь значення. Давайте задамо поведінку нашій собаці, наприклад, навчимо її відгукуватися. Нижче визначений завершений клас Dog:

dogs_and_cats.rb:

class Dog
  def set_name(aName)
    @myname = aName
  end

  def get_name
    return @myname
  end

  def talk
    return 'woof!'
  end
end

Тепер ми можемо створити собаку, задати їй ім'я, дізнатися її ім'я і попросити відгукнутися:

mydog = Dog.new
mydog.set_name('Фідо')
puts mydog.get_name
puts mydog.talk

Для різноманітності та практики — можете створити файл dogs_and_cats.rb і задати в ньому поведінку не тільки собаці, а й коту, створивши відповідно новий клас Cat. Клас Cat відрізняється від класу Dog лише тим, що метод talk має повертати 'miaow' замість 'woof'.

Ця програма містить помилку. Об’єкт someotherdog ніколи не матиме значення, яке присвоюється у його змінну @name. На щастя, Ruby не підведе нас, якщо ми спробуємо вивести ім’я цього собаки. Замість цього, вона виведе nil. Ми скоро поглянемо на простий спосіб, який допоможе уникнути подібних помилок у майбутньому.

Конструктор - new і initialize

treasure.rb:

Зараз розглянемо ще один приклад класу. Відкрийте файл treasure.rb. Ми створимо пригодницьку гру з двома класами: Thing (Річ) і Treasure (Скарб). Клас Thing дуже схожий на клас Dog з нашої попередньої програми (крім того, що він не вміє гавкати).

Однак, у класу Treasure є кілька цікавих особливостей. По-перше, в нього немає таких методів як get_name і set_name. Замість цього, у ньому визначений метод initiaize, який приймає два аргументи, а їх значення присвоюються змінним екземпляра @name і @description:

def initialize(aName, aDescription)
  @name        = aName
  @description = aDescription
end

Якщо клас містить метод initialize, то він буде автоматично викликаний при створенні нового об'єкта, методом new. Використовувати метод initialize для задання значень змінним екземпляра — хороша ідея. Вона має дві явні переваги у порівнянні з використанням методів накшталт set_name для встановлення значення кожній зі змінних екземпляру. По–перше, складний клас може містити велику кількість змінних екземпляру і ви можете встановити всім значення лише одним методом initialize, а не багатьма set–методами. По-друге, якщо змінним автоматично задається значення в момент створення об'єкта — у вас не буде змінної екземпляру, якій ви забули встановити значення.

Зауважте: Не рекомендується реалізовувати свою власну версію методу new.

І нарешті, я створив метод to_s щоб повернути об'єкт класу Treasure у вигляді рядка. Назва to_s вибрана не просто так, така сама назва використовується і в стандартній ієрархії класів Ruby. Насправді, to_s метод визначається для самого класу Object, який є кінцевим предком всіх інших класів в Ruby. Перевизначаючи метод to_s, я додав нову поведінку, яка більше підходить класу Treasure, ніж метод за замовчуванням.

Інспектування об'єктів

До речі, зверніть увагу також, що я “заглянув в середину” Treasure об'єкта t1, використовуючи метод inspect:

t1.inspect

Метод inspect визначений для всіх об'єктів Ruby. Він повертає рядок, що містить зручний для читання опис об'єкта. В даному випадку, він показує щось на зразок цього:

#<Treasure:0x28962f8 @description="ельфійська зброя, викована золотом", @name="Меч">

Перше в цьому рядку це клас, якому належить об'єкт класу Treasure. За ним йде число, яке у вас може відрізнятися від написаного вище - це внутрішній ідентифікатор в Ruby для даного конкретного об'єкта. Останніми показані змінні нашого екземпляра та їх значення.

p.rb:

class Treasure
  def initialize(aName, aDescription)
    @name        = aName
    @description = aDescription
  end

  def to_s # перезаписує метод to_s за замовчуванням
    "Скарб #{@name} — це #{@description}\n"
  end
end

a = "привіт"
b = 123
c = Treasure.new("кільце", "блискуча золота річ")

p(a)
p(b)
p(c)

Ruby надає метод p для перевірки і відображення об'єктів.

p(anobject)

to_s.rb:

# Показуємо різні об'єкти у вигляді рядка використовуючи метод `to_s`

class Treasure
  def initialize(aName, aDescription)
    @name = aName
    @description  = aDescription
  end
  # Тут ми не перевизначатимемо метод `to_s`
  # тому об'єкти цього класу використовуватимуть стандартний метод `to_s`
end

t = Treasure.new("Меч", "чудова ельфійська зброя")
print("Class.to_s: ")
puts(Class.to_s)
print("Object.to_s: ")
puts(Object.to_s)
print("String.to_s: ")
puts(String.to_s)
print("100.to_s: ")
puts(100.to_s)
print("Treasure.to_s: ")
puts(Treasure.to_s)
print("t.to_s: ")
puts(t.to_s)
print("t.inspect: ")
puts(t.inspect)

Щоб побачити, як метод to_s може використовуватися різними об'єктами і перевірити як екземпляр класу Treasure буде перетворений в рядок, якщо не перевизначати метод to_s, випробуйте програму to_s.rb.

Ви побачите, що, під час виклику методу to_s на таких класах, як Class, Object, String і Treasure, метод просто поверне їхні імена. А якщо ми його викличемо на об'єкті, як наприклад t екземпляр класу Treasure то він поверне той же ідентифікатор, що повертає метод inspect.

treasure.rb:

class Thing
  def set_name(aName)
    @name = aName
  end

  def get_name
    return @name
  end
end

class Treasure
  def initialize(aName, aDescription)
    @name        = aName
    @description = aDescription
  end

  def to_s # перезаписує метод to_s за замовчуванням
    "Скарб #{@name} — це #{@description}\n"
  end
end

thing1 = Thing.new
thing1.set_name("Прекрасна річ")
puts thing1.get_name

t1 = Treasure.new("Меч", "ельфійська зброя, викована золотом")
t2 = Treasure.new("Кільце", "магічне кільце великої влади")
puts t1.to_s
puts t2.to_s
# Метод inspect дозволяє вам заглянути всередину об’єкта
puts "Інспектуємо 1й скарб: #{t1.inspect}"

Дивлячись на цей код, можна побачити, що місцями він повторюється. Зрештою, чому маючи клас Thing, який містить змінну @name і клас Treasure, який також містить цю саму змінну, кожна з них ініціалізуються окремо один від одного? Це здається логічним, якщо розглядати клас Treasure (Скарб) як тип Thing (Річ). Якби я хотів розвинути цю програму до справжньої пригодницької гри, інші об'єкти, такі як “кімнати” і “зброя” були б іншим типом класу Think. Думаю, пора почати роботу над правильною побудовою ієрархії класів. Спробуємо це зробити у наступній главі.

results matching ""

    No results matching ""