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

    Собираем капли дальше (1, 2, 3). В этот раз узнаем о реализации ООП в Руби.

    Классы, объекты, методы


    Вместо того, чтобы углубляться дальше в синтаксис Ruby, отставим циклы, типы, модули и др. — мы вернёмся к ним позже. Сейчас же мы посмотрим, как создавать классы, объекты и методы.

    Как и в других объектных языках объекты определяются классом. Например этот класс определяет собак (о_0):

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

    Ну а чем вам собаки — не класс? ;) Смотрим код. Определение класса начинается с ключевого слова class и названия самого класса, которое обязано должно начинаться с большой буквы. В классе определен метод set_name (def...end), который будет называть каждый объект-собаку. Он берёт входящий аргумент aName и назначает его значение переменной @myname.

    Переменные, начинающиеся с @, — это переменные экземпляра класса. Важно то, что значение такой переменной доступно только внутри экземпляра, к которому она принадлежит. Создадим два объекта (две конкретные собаки) с помощью метода new:

    moya = Dog.new 
    tvoya = Dog.new

    Напоминаю и запоминаем: класс называем только с большой буквы, объект — только со строчной. Используем метод set_name, чтобы назвать собак:

    moya.set_name( 'Lassie' ) 
    tvoya.set_name( 'Rex' ) 

    Как теперь вывести клички собак? Мы не можем «вытащить» переменную @name из объекта, так как внутренние данные объекта известны только ему (см. выше). Это основной принцип ООП: данные объекта приватны; это называется скрытие данных и является частью принципа инкапсуляции.

    Для решения этой задачки просто добавим новый метод в класс, который будет выводить переменную:

    def get_name 
       return @myname 
    end

    Слово return необзательно, Руби возвратит последнее полученное значение, однако писать его — хорошая привычка.

    Заставим собаку лаять (для этого напишем еще один метод) и соберём всё в кучу:

    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 Cat
       attr_accessor :name, :age, :gender, :color
    end
    class Dog
       attr_accessor :name, :age, :gender, :color
    end
    class Snake
       attr_accessor :name, :age, :gender, :color, :length
    end

    Рассмотрим вторую строку. Она предоставляет три атрибута для класса Cat. Каждая кошка имеет свою кличку, возраст, пол и окрас — и код создает эти атрибуты. _accessor означает «сделайте эти атрибуты доступными для создания и изменения».

    Создадим объект и применим атрибуты:

    cat_object = Cat.new
    cat_object.name = "Pussy"
    ........................
    puts cat_object.name


    Наследование


    Посмотрев на последний код с объявлением трёх классов, легко заметить, что они предельно похожи. Лишь у змеи дополнительно определена длина. Вспомним принципы программирования на Ruby из первой капли. Один из них: DRY (Don't repeat yourself), который означает, что мы должны писать необходимый код только один раз, а у нас сплошные повторения. Нам просто необходимо обратить внимание на одну из лучших возможностей ООП: наследование.

    Наследование позволяет разным классам соотноситься друг с другом и группироваться по схожестям в структуре. В нашем примере коты, собаки, змеи — всё это питомцы (pets). Так почему бы нам не создать класс Pet, который будет «родительским» для остальных, которые, в свою очередь, будут наледовать общие для всех питомцев свойства? Пишем:

    class Pet
       attr_accessor :name, :age, :gender, :color
    end
    class Cat < Pet
    end
    class Dog < Pet
    end
    class Snake < Pet
       attr_accessor :length
    end

    Принцип и правила объявления наследованных классов, думаю, понятны, теперь попробуйте создать объекты, задать параметры и вывести результат. Заметим, что более простые классы стоят выше сложных в иерархии классов.

    Эпилог


    В прошлой капле мы с вами выяснили, что такое ООП, в этой — научились применять знания в Ruby, узнали несколько замудренных слов, познакомились с переменными экземпляра и атрибутами класса. Еще одна капля в наш стакан ;)

    Благодарю Хью Колинборна и Питера Купера за замечательные примеры для наших капель. Коментарии и замечания как всегда принимаются! До встречи!
    Share post

    Similar posts

    Comments 66

      +3
      Спасибо за оперативность статей, ненавижу ждать следующих глав/частей/и т.д.
      Желаю не потерять боевой заряд, и писать больше/лучше/чаще, ибо полезно, познавательно, и читаем =)
        +1
        Главное, что хоть кто-то читает :)
          +2
          Читает, читает ;-)
            +2
            Угу, читаем :)
          0
          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
          puts(dog1.get_name)
          puts(dog1.gav)

          выводит:
          nil
          r-r-r-r!

          То есть одна из самых трудноотлавливаемых ошибок (неиницализированная переменная) в Ruby вообще за ошибку, или хотя бы достойной предупреждения, не считается?
          Странно, что даже не получил никаких предупреждений, просто
            0
            Ой, а я уже исправлять ринулся :))) Да, на самом деле просто nil
              0
              Выполнил пример, получил предупреждение в IDE (Netbeas).
              А вы в консоли выполняли? Версия руби 1.8?
                0
                Точно так, консоль ruby 1.8.7 (irb тоже не выдал предупреждений)

                P.S. сейчас вот поковырялся с параметрам. если вызывать ruby -w file.rb то выдает предпреждение
              0
              >Возможность иметь несколько классов, содержащих одноименные методы, называется полиморфизм.

              Вообще понятие полиморфизма намного шире, главное в нем, по-моему, что возможны следующая операция (псевдокод. как на Ryby написать, вроде еще «не проходили» массивы и итераторы по ним

              pets = [new Dog, new Cat]
              foreach pets as pet
              {
              print pet.get_name
              }
                +2
                >как на Ryby написать

                pets = [Dog.new, Cat.new]
                pets.each {|pet| puts pet.name}

                вот так ;)
                  0
                  В принципе если бы подумал и попробовал выполнить свой код, то догадался бы :)
                    0
                    или
                    pets.each do |pet| puts pet.name end

                    обожаю руби за возможность решать задачу кучей способов^_^
                    0
                    Я как Java программист могу сказать вам по этому поводу, что это называется рефлексией) По крайней мере в Java. Полиморфизм поидее это:

                    Полиморфи́зм (в языках программирования) — взаимозаменяемость объектов с одинаковым интерфейсом. (с) Википедия. Так же полиморфизм возможет если объекты предки расриряют общий суперкласс.

                    Хотя в динамических языках программирования вполне может быть и по другому)
                    +2
                    ИМХО, транслит в названиях переменных, функции, метода, класса — дурная привычка.

                    moya = Dog.new
                    tvoya = Dog.new

                    коробит знающих англ. язык и ничего не означает для не владеющих им.

                    moya.skazi-gav-gromko — нехорошо, имхо.
                      0
                      OK, будем писать по-английски
                        0
                        :) да я так, побурчал. Однако, спасибо за обратную связь, с вниманием читаю ваши статьи. Пример с собакой не из открытой книжки (ruby open book) по руби?
                          0
                          Эта собака с переменными из The Book of Ruby — во многих книгах ООП показывают на животных и собаках в частности :)
                            +1
                            зря вы просто побурчали, писать транслитом идентификаторы в коде это просто невообразимый пипец.

                            Вот жутчайший пример:
                            ru.wikibooks.org/wiki/Введение_в_язык_Scheme_для_школьников
                              0
                              От первых строк глаза выпучились о_О как
                              Это ужасно.
                                0
                                Ven lärnoy püki votik, vödastok plösenon fikulis.
                          0
                          спасибо за очередную статью)
                          с нетерпением жду следующую каплю.
                            0
                            Отлично, слежу за всеми выпусками, так как параллельно уже почти прочел книжечку по Руби, всё четко и очень доступно, продолжайте. Была бы сила, плюсанул…
                              0
                              Шустрый ты парнишка. Я слаживаю твои капли в стакан ;)
                                0
                                ИМХО логичнее было бы использовать конструктор

                                class Dog
                                def initialize(dogName)
                                @name = dogName
                                end

                                def name
                                puts @name
                                end
                                end

                                тогда можно написать
                                rex = Dog.new(«rex»)
                                rex.name
                                  0
                                  За конструктор обеими руками «за», особенно раз руби по дефолту позволяет не инициализировать переменные, но вот переменные с присваиванием только в конструкторе как-то не тру, имхо. Больше на константы похожи, чем на переменные :)
                                    0
                                    само собой. просто я не стал весь класс переписывать полностью, а только показал пример на использование конструктора.
                                    вот более полная версия

                                    class Dog
                                    def initialize(dogName)
                                    @name = dogName
                                    end

                                    def getName
                                    puts @name
                                    end

                                    attr_accessor :name
                                    end

                                    rex = Dog.new(«rex»)
                                    rex.getName
                                    >>rex
                                    rex.name = «milo»
                                    puts rex.name
                                    >>milo
                                      0
                                      Просто думаю многие ничего не знают о руби, кроме того, что написано в «каплях», и не зная, что, например, можно создавать методы вида name= и про аксессоры типа ридеров и райтеров (о чем я узнал из коментов :) ) первый ваш код выглядит каким-то ущербным :) в том смысле, что кажется, что отказавшись от attr_accesor мы теряем возможность писать rex.name = 'rex' и будем вынуждены пользоваться rex.set_name('rex');
                                        0
                                        кажется, что отказавшись от attr_accesor мы теряем возможность писать rex.name = 'rex' и будем вынуждены пользоваться rex.set_name('rex');
                                        так и есть
                                          0
                                          Наверное не правильно выразился, имел в виду, что казалось будто attr_accessor единственный способ писать a.name=«rex»
                                        +3
                                        > def getName

                                        лишний метод-алиас, поскольку attr_accessor :name создаст геттер (ридер) для @name;

                                        Вообще, свои геттеры (ридеры) и сеттеры (райтеры) лучше описывать, когда установка/чтение проперти более сложное, нежели простое «верни прямое значение», «установи прямое значение». Для примитивных же reader'ов и writter'ов (и для их обобщающих accessor'ов) достаточно соответствующих классовых методов.

                                        def a
                                        #какие-то сложные вычисления возвращающие некоторое значение, а не просто «примитивный „return“
                                        end

                                        def a=
                                        #тоже могут быть сложные вычисление перед установкой проверти, а не просто „примитивный =“
                                        end

                                        attr_reader :b # только для чтения (будет создан метод def b @b end)
                                        attr_writer :c # только для записи (будет создан метод def c=(val) @c = val end)
                                        attr_accessor :d # объединение двух предыдущих „примитивных ридера и райтера“
                                          0
                                          def a=(value)
                                            0
                                            > (value)

                                            ну, ествественно, писал просто «на коленке», торопился :)
                                      0
                                      def initialize(dogName) — экто конструтор класса получается?
                                        0
                                        конструктор здесь — метод initialize. он вызывается когда мы создаем новый экземпляр класса (Dog.new). Конструктор может быть без параметров, а может с параметрами, как в данном случае. можно определять несколько конструкторов, напр. с параметрами и без, с разным числом параметров и пр.
                                        class Dog

                                        def initialize #без параметров
                                        @name = «rex» #по умолчанию собаку зовут Рекс
                                        end

                                        def initialize(dogName) #но если нам не нравится
                                        @name = dogName #мы можем назвать ее и по-другому
                                        end

                                        <...>

                                        end
                                          0
                                          Еще пример полиморфизма :)

                                          А вопрос такой есть: как определяется конструктор — по имени initialize или по тому, что он первый описан, или я еще чего-то не доглядел?
                                            0
                                            по имени метода initialize, положение его в коде класса насколько я знаю не важно
                                            0
                                            > можно определять несколько конструкторов, напр. с параметрами и без, с разным числом параметров и пр.

                                            Это что-то из мира С++ и Java. В Ruby второй метод initialize просто-напросто перезапишет первый initialize. В итоге, без параметров Вы не вызовите метод. А параметры по умолчанию можно в самом initialize передавать:

                                            class A
                                            
                                              def initialize(a=10)
                                                @a = a
                                              end
                                            
                                            end
                                            
                                            a = A.new # @a = 10
                                            b = A.new(20) # @a = 20


                                            Можно и так:

                                            def initialize(a=nil)
                                              @a = a || 10
                                            end
                                              0
                                              хм, действительно… прошу прощения, попутал что-то
                                                0
                                                нда. Только начинает нравиться, как что-нибудь всплывает. То ++ нет, то конструктор только один
                                                  0
                                                  Отсутствие оверлоадинга методов — это ограничение динамических языков.
                                                  Хотите два конструктора — используйте переменное число параметров.

                                                  class Test
                                                      def initialize *args
                                                          if args.size == 1
                                                              puts "first constructor"
                                                          elsif args.size == 2
                                                              puts "second constructor"
                                                          else
                                                              raise "Required 1 or 2 parameters"
                                                          end
                                                      end
                                                  end
                                                  Test.new("asdf") #=> first constructor"
                                                  Test.new("asdf","asdf") #=> "second constructor"
                                                  Test.new() #=> `initialize': Required 1 or 2 parameters (RuntimeError)
                                                  

                                                  Такой же код для перегрузки методов.
                                                    0
                                                    Я понимаю — выкрутиться можно. Это хорошо.
                                                    Но вот смотрите например в C#.

                                                      class SimpleParser
                                                      {
                                                        public SimpleParser()
                                                        {
                                                        }

                                                        public SimpleParser(String fileName)
                                                        {
                                                        }

                                                        public SimpleParser(Stream stream)
                                                        {
                                                        }

                                                        public SimpleParser(BinaryReader reader)
                                                        {
                                                        }

                                                      }


                                                    * This source code was highlighted with Source Code Highlighter.


                                                    При компиляции в месте создания объекта SimpleParser будет сразу сгенерирован вызов того конструктора, который нужен в зависимости от переданного (или не переданного вовсе) параметра. И в рантайме уже анализировать ничего не нужно. А это скорость

                                                      0
                                                      > А это скорость

                                                      А статические системы в любом случае быстрее динамических. С другой стороны, статические не обладают такой гибкостью, как динамические.
                                                        0
                                                        Руби, вроде как, язык интерпретируемый и по любому анализ в рантайме будет проходить, насколько я понимаю, что такое интерпретатор
                                                          0
                                                          Я просто надеюсь, что он предварительно какой-нибудь байт-код генерит.
                                                          В таком случае, будь в нем overload, проверка параметров проводилась бы один раз на этапе генерации байт-кода, а так при каждом создании объекта
                                                            0
                                                            Во-первых, в ветке Ruby 1.8 компилляции в байткод нет, только в 1.9.
                                                            Во-вторых, не в компиляции дело.
                                                            Ruby — это динамический язык, в динамических языках нет оверлоадинга.
                                                            И по скорости динамические языки всегда медленнее статических.
                                                            Если для ваших задач критична скорость — используйте тот же C#:)
                                                              0
                                                              если критична скорость то дотНЕТ вряд ли поможет…
                                                0
                                                Если более точно, что initialize — это инциализатор порожденного объекта (инстанса). Конструктор, именно в плане конструирования — это new (и то, new — это лишь оболочка для allocate (выделяет память) и initialize (инициализирует объект)).
                                            • UFO just landed and posted this here
                                                0
                                                Вот, спасибо! А только хотел спросить, как реализуется внутренне конструкции a.name и a.name=при использовании attr_acсesor (вернее последняя, с первой-то все понятно), можно ли как-то изменить поведение, например валидацию провести в «сеттере» или же attr_acсesor является аналогом public переменных класса в других языках и способа контроля (простого) в присваивании не существует. А так сразу все понятно стало :)
                                                  0
                                                  Да, поскольку, инстанс-переменные являются настоящими пропертями, то, лучше бы, показать, что get_name и set_name — это прерогатива того, что работает не с виртуальными пропертями. В Руби же для таких целей есть полноценные аксессроры (ридеры, райтеры): name и name= (который, как было сказано выше, создаются автоматом в «примитивном виде» (установить простое значение / вернуть простое значение) классовыми методами attr_accessor, attr_reader и attr_writer)
                                                  0
                                                  Благодарю! Я тоже весь в сомнениях был, а в книгах никаких подробностей :(
                                                  +1
                                                  есть еще, по аналогии с attr_accessor:

                                                  attr_reader
                                                  attr_writer
                                                    0
                                                    За статьи — большое спасибо.
                                                      0
                                                      или просто: attr
                                                      attr :asdf, true #тоже что и attr_accessor :asdf
                                                      attr :asdf, false #тожу что и attr_reader :asdf
                                                      –6
                                                      И всё таки мне кажется что руби не такой православный как питон.
                                                        0
                                                        и всётаки мне кажется что топик не об этом
                                                          –3
                                                          :0)
                                                          +3
                                                          А слабо такие же «капли» по питону писать? ;) А мы бы читали и то, и другое и сравнивали бы :)
                                                            +4
                                                            От заявлений о православности питона сразу хочеться холиварить:)
                                                              –1
                                                              :-р
                                                            +1
                                                            По рубишному соглашению, мультивордовые имена необходимо писать через underscore. @myname и особенно aName выбивают из колеи, делают код нечитаемым. Надо так: a_name, @my_name. Через капиталайзед, пишутся мультивордовые имена классов(e.g. MyClassName).

                                                            и вообще:
                                                            Table 2.1. Example variable and class names
                                                            ----------------------------------------------------------+---------------
                                                                                       Variables                      | Constants and
                                                                                                                      | Class Names
                                                            ----------------------------------------------------------+---------------
                                                            Local           | Global       | Instance     | Class     | 
                                                            ----------------+--------------+--------------+-----------|
                                                            name            | $debug       | @name        | @@total   | PI
                                                            fish_and_chips  | $CUSTOMER    | @point_1     | @@symtab  | FeetPerMile
                                                            x_axis          | $_           | @X           | @@N       | String
                                                            thx1138         | $plan9       | @_           | @@x_pos   | MyClass
                                                            _26             | $Global      | @plan9       | @@SINGLE  | JazzSong
                                                            --------------------------------------------------------------------------
                                                            
                                                            +1
                                                            Змейка в качестве питомца (pet), это сильно :)

                                                            Тем не менее, спасибо за статьи. Сам брался как-то за руби, но толи книги не те попались, то ли голова тогда не соображала, мне он показался какой-то излишне мудрёный. А тут смотрю — вполне себе красиво, мне нравится :)
                                                              0
                                                              Автор как бы намекает нам: python.
                                                            • UFO just landed and posted this here

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