Покорим Ruby вместе! Капля восьмая

    Давайте прямо сейчас перечитаем четвертую каплю, чтобы вспомнить о реализации ООП в Руби. Повторили? Идем дальше. В этой капле мы подрежем все образовавшиеся хвосты, связанные с объектно-ориентированным программированием на Руби.


    new и initialize


    Давайте взглянем еще раз на код из четвертой капли:

    class Dog    
    	def set_name( aName ) 
    		@myname = aName 
    	end 
    	
    	def get_name 
    		return @myname 
    	end 
    	
    	def gav 
    		return 'r-r-r-r!' 
    	end 
    end
    
    dog1 = Dog.new 
    dog1.set_name( 'Fido' ) 
    puts(dog1.get_name) 
    puts(dog1.gav)


    Похожая программа:

    class Dog
    	def initialize(name, age) 
    		@name = name
    		@age = age
    	end 
    	
    	def get_name 
    		return @name 
    	end
    	
    	def get_age 
    		return @age 
    	end 
    end
    
    d = Dog.new('Fido', 2)
    puts "My name is #{d.get_name} and I'm #{d.get_age}"


    Когда определяется новый класс (обычно используя class Name ... end), создается объект типа Class. Когда вызывается Name.new для создания нового объекта, вызывается метод экземляра new из Class, который в свою очередь активизирует allocate, чтобы выделить память для объекта перед тем, как будет вызван метод initialize нового объекта. Фазы конструкции и инициализации объекта отдельны и могут быть переписаны. Инициализация происходит через метод экземпляра initialize, конструкция — через new. initialize — не конструктор (спорно, конечно, но в разных источниках — разное мнение).

    Использование initialize имеет два явных преимущества над установкой переменных экземляра, используя методы типа set_name. Прежде всего сложный класс может содержать множество переменных экземпляра и все их можно объявить с помощью одной строки с initialize без необходимости писать методы для каждой. Также если все переменные объявляются во время создания объекта (как написано выше непосредственно после new вызывается именно initialize), у вас никогда не останется неопределенных переменных (nil).

    Еще проще


    В коде мы определили два метода (get_name и get_age), чтобы возвращать две переменные экземпляра. Так как это простая и часто используемая идиома, Руби предоставляет упрощение: attr_reader определит эти методы за нас:

    class Dog	
    	attr_reader :name, :age
    	def initialize(name, age) 
    		@name = name
    		@age = age
    	end 
    end
    
    d = Dog.new("Fido", 2)
    puts "My name is #{d.name} and I'm #{d.age}"


    Заметим, что в attr_reader используются symbol'ы (см. прошлую каплю) и то, как изменился запрос значения в выводе. attr_writer определит set-методы (set_name в первом листинге, например), а attr_accessor комбинирует возможности ридера и райтера (см. пример с зоопарком в четвертой капле).

    initialize + наследование


    В четвертой капле мы уже говорили о наследовании в «зоопарке», однако там мы использовали attr_accessor. Как же будет выглядеть наследование, если мы от них откажемся и вернемся к initialize и методам? Не намного сложнее:

    class Pet
    	def initialize(name, age) 
    		@name = name
    		@age = age
    	end
    	
    	def get_name 
    		return @name 
    	end
    	
    	def get_age 
    		return @age 
    	end 
    	
    end
    
    class Dog < Pet
    	def initialize(name, age)
    		@name = name
    		@age = age
    		super
    	end 
    end
    
    class Snake < Pet
    	def initialize(name, age, length) 
    		@name = name
    		@age = age
    		@length = length
    		super(name, age)
    	end 
    	
    	def get_length
    		return @length
    	end
    
    end
    
    d = Dog.new('Fido', 2)
    s = Snake.new('Lili', 2, 85)
    puts "Dog: My name is #{d.get_name} and I'm #{d.get_age}"
    puts "Snake: My name is #{s.get_name}, I'm #{s.get_age} & I'm #{s.get_length} cm"


    В этом примере мы отказались еще и от кошек :) Как всегда самый простой класс выше по иерархии. Единственное отличие — это ключевое слово super. Им обозначаются переменные, которые классы-потомки должны передать вышестоящему классу. Просто super передаст все переменные, super() — ни одной. Общие методы уходят в класс-родитель, у потомков остается только инициализация переменных и собственные методы.

    Методы объектов


    Даже новосозданный, пустой объект уже «откликается» на ряд методов. Выведем список этих методов кодом: puts d.methods.sort, где d — любой объект. Из всех стоит выделить object_id (у всех объектов Руби есть уникальный номер, который и выведет метод), class (выведет класс, которому принадлежит объект) и обратный instance_of? (вернет true, если объект принадлежит классу из параметра, например, puts 10.instance_of?(Fixnum))

    Вы можете узнать, может ли объект ответить на сообщение, которое вы хотите отправить ему с помощью метода respond_to?. Чаще всего он используется в условии:

    if d.respond_to?("gav") 
    	d.gav 
    else 
    	puts "Sorry, don't understand." 
    end


    Proc в Руби


    Блок (do ... end или {...}) объектом не является, но он может быть преобразован в объект класса Proc с помощью метода lambda. Активизирует блок метод call из Proc. Методы могут содержать в себе proc'и:

    def method proc 
    	puts 'Start of method' 
    	proc.call 
    	puts 'End of method' 
    end 
    
    say = lambda {puts 'Hello'} 
    
    method say


    Эпилог


    Еще немного полезной информации, чуть важной теории и много кода. Это была последняя подобная капля, обещаю ;) Дальше начнем кодить по-серьезному, а пока у вас есть время пробежаться глазами по всем каплям и собрать всю инфу в кучу. Комментарии ожидаемы!

    PS: Как видите, теперь все капли в одном блоге. Не забудьте подписаться на него — так следить за выпусками еще легче!

    Share post

    Similar posts

    Comments 33

      0
      Да… что то в 5 утра ни капли не лезет в голову. Спасибо за статью!
        +1
        А каково мне писалось? ;)
        0
        «Классы в Руби — объекты самого высшего класса..»

        То, что ты назвал высшем классом, принято называть метаклассом. Можно ли в руби создать свой метакласс и переопределить в нем метод new? В данном случае можно было бы просто создавать сингелтоны, например.
          +1
          Переопределить класс Class нельзя, а следовательно и метод new. Но метаклассы создавать можно.
          Посмотрите например эти статьи:
          whytheluckystiff.net/articles/seeingMetaclassesClearly.html
          vision-media.ca/resources/ruby/understanding-ruby-metaclasses
          reference.jumpingmonkey.org/programming_languages/ruby/ruby-metaprogramming.html
            –1
            Извиняюсь, я вас немного дезинформировал:)
            Class переопределить нельзя, а вот метод new в нем можно.
            Пример из документации:
               class Class
                  alias oldNew  new
                  def new(*args)
                    print "Creating a new ", self.name, "\n"
                    oldNew(*args)
                  end
                end
            
                class Name
                end
            
                n = Name.new
            
              0
              Почитаю статьи вечером. Я имел ввиду конструкцию, с которой я знаком из xotcl — ниже я написал её реализация на псевдо руби, как её я вижу, надеюсь станет понятнее:

              # мета класс сингелтон
              class Singelton < Class
              def new(*args)
              if (@instance==nil) @instance = base.new(args);
              @instance
              end
              end

              # использование метакласса Singelton
              Singelton God
              method Hi
              puts "Hello, i'm God"
              end
              end

              Теперь God.new - возвращает один и тот же объект
                0
                Этот паттерн уже входит в стандартную библиотеку и используется вот так:

                class God
                  include Singleton
                end
                
                God.instance
                


                А метод-синглтон — это совершенно другое. Это личный метод одного-единственного экземпляра, который недоступен во всех остальных экземплярах того же класса:

                class Penguin
                  def sing
                    # ...
                  end
                end
                
                memphis = Penguin.new
                mumble = Penguin.new
                
                def mumble.sing
                  fail
                end
                def mumble.dance
                  # ...
                end
                
                memphis.sing
                mumble.sing  #  => RuntimeError
                
                mumble.dance
                memphis.dance  # => NoMethodError
                
            0
            «return @length»
            в данном случае return не нужен.
            и вы уверены что super надо вызвать не в самом начале конструктора?
              0
              Все-таки правильно сказанно в статье, что initialize по настоящему конструктором не является. Конструктор — это метод new который выделяет память и создает объект, а затем вызывает метод initialize. initialize можно рассматривать как просто метод инициализации(что и отражено в его названии:)).
              А super в теле метода означает что мы просто вызываем аналогичный метод суперкласса, его можно использовать в любом методе, не только в initialize.
              Именно по этому мы не обязаны вызыват super в самом начале initialize, как в Java например.
                0
                	def initialize(name, age)
                		@name = name
                		@age = age
                		super
                	end 
                

                Хм, а если так, то почему такая конструкция не «обнилит» переменные? Ведь super вызывается без параметров, а значит (я так думаю :) ), что инициализатор базового класса Pet получит в своих парметрах nil и обнилит переменные. Просто проверить сейчас не могу

                  0
                  Если мы вызываем super без параметров, то ему автоматически придут те же параметры что и методу, его вызывающему.
                  Т.е. в данном случае вызов super аналогичен вызову super name, age, а не super nil, nil
                  Ну а вообще, да, пример какой-то странный:) Потому что, как вы правильно заметели — переменные затераються:
                  class Dog
                  	def initialize(name, age)
                  		@name = name+"  - dog"
                  		@age = age
                  		super
                  	end 
                  end
                  dog = Dog.new "Rex", 5
                  <pre>
                  В результате @name будет равно "Rex" а не "Rex - dog", потому как  Pet.initialize вызывается со значением "Rex".
                    0
                    и здесь тоже

                    def initialize(name, age, length)
                    @name = name
                    @age = age
                    @length = length
                    super(name, age)
                    end
                  +1
                  > Конструктор — это метод new который выделяет память и создает объект, а затем вызывает метод initialize

                  Конструктор — это allocate (можно его вызвать при создании, без инициализации), а new — всего лишь обертка для allocate + initialize

                  class A
                    def initialize
                      p self
                    end
                  end
                  
                  a = A.new
                  b = A.allocate # создаст объект, но без init'a
                0
                >Им обозначаются переменные, которые классы-потомки должны передать вышестоящему классу. Просто super передаст все переменные, super() — ни одной.

                Вот что-то не допер, что вообще значит «передать вышестоящему классу», вызвать его конструктор (который инициализатор) с этими переменными или что-то другое?
                  0
                  уже допер по ответу выше :)
                  +2
                  откуда вы эти get_, set_ взяли? не пишут так в ruby.
                    +1
                    «Настоящий программист может написать фортрановскую программу на любом языке».
                    +1
                    закатайте потом все капли в пдф, в колонтитул поместите ссылку, допустим, на свой блог и пустите в свободное плавание. Многие спасибо вам скажут
                      –1
                      А смысл? Получиться тот же викибукс про Ruby, только в pdf. Тут основной плюс что в коментариях подсказать могут.
                      0
                      Спасибо, интересно.

                      Было бы здорово выпустить еще теоретическую часть, меня например, очень интересуют особенности реализации ООП в Ruby. Очень было бы познавательно почитать сравнительную статью о, например, Java — Ruby с точки зрения реализации ООП. Может где-то есть такая статья? Не раскрыты темы интерфейсов, множественного наследования, области видимости и тд.

                      Я для себя открываю тут Ruby благодаря вашим статьям ) и вижу, что возможно это хорошая альтернатива Java для небольших проектов.
                      0
                      Извините, конечно, я руби не знаю. Может все так и есть, но ИМХО фраза:

                      «Классы в Руби — объекты самого высшего класса, каждый из них является экземпляром встроенного в Руби класса Class (тавтология, но понять нужно). Когда определяется новый класс (обычно используя class Name… end), создается объект типа Class.
                      »

                      некорректная. И звучать должно примерно так:

                      Класс в Руби является потомком встроенного в класса Class. Когда определяется новый класс, он автоматически становится наследником типа Class.

                      Или я чего то не понял?

                        0
                        правильнее назвать метакласс
                        sapr.mgsu.ru/biblio/ex-syst/Glava7/Index6.htm
                        • UFO just landed and posted this here
                            +2
                            Нет. В руби есть такая мантра для запоминания:
                            Class — это объект, а Object — это класс. :)
                            Новый класс становится наследником встроенного класса Object, при этом сам новый класс (не экземпляры этого класса, а сам класс, как тип) является экземпляром класса Class.
                              0
                              А, там еще и Object есть. Тогда понятно.
                            0
                            Что за ужас get_name и set_name? :( В Руби принят о так:
                            class Dog
                            def name
                            @name
                            end
                            def name=(str)
                            @name = str
                            end
                            end
                              0
                              Я бы даже сказал, что так:
                              class Dog
                                attr_accessor :name
                              end
                              
                                0
                                > Я бы даже сказал, что так:

                                только в случае примитивного аксессора
                                  0
                                  Ну я говорил о примерах из статьи :)
                                  А так всё верно, только в случае примитивного
                              0
                              Прикольная серия статей.
                              по-поводу девятой капли (надеюсь будет:)
                              Очень полезным считаю нераскрытую тему case-when в руби, операторы ===, <=>, примеси (mixin), отдельно даже полезно рассмотреть примешивание Enumerable.
                              Верю в Вас и в будущие капли.

                              Only users with full accounts can post comments. Log in, please.