Статьи

Классы как объекты и получатели сообщений

Классы являются специальными объектами: это единственный вид объектов, который обладает способностью порождать новые объекты (экземпляры). Тем не менее, они являются объектами. Когда вы создаете класс, такой как Ticket, вы можете отправлять ему сообщения, добавлять к нему методы, передавать его другим объектам в качестве аргумента метода и, как правило, делать с ним все, что вы делаете с другим объектом. Как и другие объекты, классы могут быть созданы более чем одним способом.

Создание объектов класса

Эта статья основана на главе 3 из «Обоснованного рубииста» Дэвида Блэка , которая будет опубликована в январе 2009 года. Она воспроизводится здесь с разрешения Manning Publications . Книги раннего доступа Manning и электронные книги продаются исключительно через Manning. Посетите страницу книги для получения дополнительной информации

[img_assist | nid = 6662 | title = | desc = | link = url | url = http: //www.manning.com/black2/ | align = right | width = 146 | height = 182] Каждый класс — объект, человек , Ticket — это экземпляр класса с именем Class. Как вы уже видели, вы можете создать объект класса с помощью специальной формулы ключевого слова class:

class Ticket 

# your code here

end

Эта формула является специальным предложением Ruby — способом создать красивый, легко доступный блок определения класса. Но вы также можете создать класс так же, как и большинство других объектов, отправив сообщение, новое для объекта класса Class:

my_class = Class.new 

В этом случае переменной my_class назначается новый объект класса. Class.new точно соответствует другим вызовам конструктора, таким как Object.new и Ticket.new. Когда вы создаете класс Class, вы создаете класс. Этот класс, в свою очередь, может создавать свои собственные экземпляры:

instance_of_my_class = my_class.new

Объекты класса обычно представлены константами (например, Ticket или Object). В сценарии, приведенном в предыдущем
примере, объект класса связан с обычной переменной (my_class). Вызов метода new отправляет сообщение new в класс через эту переменную.

СОВЕТ: ОПРЕДЕЛЕНИЕ МЕТОДОВ ИНСТАНЦИИ В СВЯЗИ С CLASS.NEW

Если вы хотите создать анонимный класс с использованием Class.new, а также хотите добавить к нему методы экземпляра во время его создания, вы можете сделать это, добавив блок кода после вызова new. Блок кода — это фрагмент кода, который вы предоставляете как часть вызова метода, который может быть выполнен из самого метода. Вот небольшой пример Class.new с блоком:

c = Class.new do 
def say_hello
puts "Hello!"
end
end

Если вы сейчас создадите экземпляр класса (с помощью c.new), вы сможете вызвать метод say_hello для этого
экземпляра.

И да, здесь есть парадокс ….

Парадокс класса / объекта «курица и яйцо»

Класс Class является экземпляром самого себя; то есть это объект класса. И это еще не все. Помните класс
Object? Ну, Object это класс … но классы это объекты. Итак, Объект — это объект. И класс это класс. И
Object — это класс, а Class — это объект.

Что было первым? Как может быть создан класс Class, если класс Object уже не существует? Но как может
существовать класс Object (или любой другой класс), пока не будет класса Class, экземпляры которого могут быть?

Лучший способ справиться с этим парадоксом, по крайней мере, на данный момент, это игнорировать его. Руби должен сделать кое-что из этого «курица или яйцо», чтобы запустить систему классов и объектов, а затем округлость и парадоксы не имеют значения. В процессе программирования вам просто нужно знать, что классы являются объектами, экземплярами класса, называемого Class.

(Если вы хотите вкратце узнать, как это работает, это выглядит так: у каждого объекта есть внутренняя запись того, к какому классу он относится, и внутренняя запись внутри объекта Class указывает обратно на Class.) Классы — это объекты и объекты. получать сообщения и выполнять методы. Как именно происходит
процесс вызова метода в случае объектов класса?

Как объекты класса вызывают методы

Когда вы отправляете сообщение объекту класса, оно выглядит так:

Ticket.some_message

Или, если вы находитесь внутри тела определения класса, а класс играет роль объекта self по умолчанию, это выглядит так:

class Ticket 
some_message # such as "attr_accessor"!

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

  1. из их класса
  2. от суперкласса и более ранних предков своего класса
  3. из собственного хранилища одноэлементных методов («talk» в def obj.talk)

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

Экземпляры класса могут вызывать методы, которые определены как методы экземпляра в своем классе. Например, Ticket является экземпляром Class, а Class определяет метод экземпляра с именем new. Вот почему мы можем написать:

Ticket.new

Это заботится о сценарии 1. Теперь, сценарий 2. Суперклассом Class является Module. Таким образом, экземпляры класса имеют доступ к методам экземпляров, определенным в модуле; среди них семейство методов attr_accessor. Вот почему мы можем написать:

class Ticket 
attr_reader :venue, :date
attr_accessor :price

с этими вызовами методов, идущими непосредственно к объекту класса Ticket, который играет роль объекта self по умолчанию в момент выполнения вызовов. Это оставляет только сценарий 3: вызов одноэлементного метода объекта класса.

Одноэлементный метод под любым другим именем …

Вот пример. Допустим, мы создали наш класс Ticket. На этом этапе Ticket — это не только класс, из которого могут возникать объекты (экземпляры билетов). Билет (класс) также является самостоятельным объектом. Как мы сделали с другими объектами, давайте добавим к нему одноэлементный метод.

Наш метод скажет нам, какой билет из списка объектов билета является самым дорогим. Здесь есть некоторый код черного ящика. Не беспокойтесь о деталях; Основная идея заключается в том, что операция max_by найдет билет, цена которого самая высокая:

def Ticket.most_expensive(*tickets) 
tickets.max_by(&:price)
end

Теперь мы можем использовать метод Ticket.most_exорого, чтобы сказать нам, какой из нескольких билетов самый дорогой. (Мы избегаем иметь два билета с одинаковой ценой, потому что наш метод не справляется с этой ситуацией).

th = Ticket.new("Town Hall","11/12/13") 
cc = Ticket.new("Convention Center","12/13/14/")
fg = Ticket.new("Fairgrounds", "13/14/15/")

th.price = 12.55
cc.price = 10.00
fg.price = 18.00

highest = Ticket.most_expensive(th,cc,fg)

puts "The highest-priced ticket is the one for #{highest.venue}."

Выход из этого примера:

The highest-priced ticket is the one for Fairgrounds.

Метод most_exорого определяется непосредственно в объекте класса Ticket в стиле синглтон-метода.
Метод одноэлементный , определенный на объект класса обычно называют как метод класса класса , на котором она определена. Идея метода класса заключается в том, что вы отправляете сообщение объекту, который является классом, а не одному из экземпляров класса. Сообщение most_exорого направляется в класс Ticket, а не в конкретный билет.

Почему вы хотите это сделать? Разве это не портит основной порядок: создание объектов билетов и отправка сообщений этим объектам?

Когда и зачем писать метод класса

Методы класса служат цели. Некоторые операции, относящиеся к классу, не могут выполняться отдельными экземплярами этого класса. Новый метод является отличным примером. Мы называем Ticket.new, потому что, пока мы не создали отдельный билет, мы не можем отправлять ему сообщения! Кроме того, работа по порождению нового объекта логически принадлежит классу. Бессмысленно, когда экземпляры Ticket порождают друг друга. Однако имеет смысл централизовать процесс создания экземпляра как действие класса Ticket.

Другой похожий случай — встроенный метод Ruby File.open — метод, который открывает файл для чтения и / или
записи. Операция открытия немного похожа на новую; он инициирует ввод и / или вывод файла и возвращает объект File. Это
имеет смысл для открытого быть методом класса File: вы запрашиваете создание отдельного объекта из класса. Класс действует как отправная точка для объектов, которые он создает.

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

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

Преобразование Конвертер

Давайте преобразуем конвертер в класс конвертера, добавив два метода класса для конвертации в обоих направлениях. Мы сделаем версию с плавающей запятой. (Использование 9.0 вместо 9 приведет к получению результатов с плавающей запятой.)

class Temperature 

def Temperature.c2f(f)
f * 9.0 / 5 + 32
end

def Temperature.f2c(c)
(c - 32) * 5 / 9.0
end
end

И давайте попробуем это:

puts Temperature.c2f(100) #A 

#A 212.0

Идея состоит в том, что у нас есть вспомогательные методы, связанные с температурой — методы, относящиеся к температуре как к концепции, но не к конкретной температуре. Класс Температуры — хороший выбор объекта для владения этими методами. Мы могли бы полюбоваться и иметь экземпляры Температуры, которые знали, были ли они C или F и могли бы преобразовать себя; но, практически говоря, наличие класса Температуры с методами класса для выполнения преобразований является адекватным и приемлемым дизайном. (Еще лучше, поскольку нам вообще не нужны экземпляры Temperature, было бы использовать модуль, который похож на класс, но не может создавать экземпляры.)

Методы класса и методы экземпляра принципиально не отличаются друг от друга; все они являются методами, и их выполнение всегда инициируется отправкой сообщения объекту. Просто объект, получающий сообщение, может быть объектом класса. Тем не менее, есть различия и важные моменты, о которых нужно помнить, когда вы начинаете писать методы на разных уровнях.

Методы класса против методов экземпляра

Определив Ticket.most_exорого, мы определили метод, к которому мы можем обращаться через объект класса
Ticket, но не через его экземпляры. Отдельные объекты билета (экземпляры класса Ticket) не имеют этого
метода. Вы можете проверить это легко. Попробуйте добавить это в код, где переменная fg ссылается на объект Ticket (для события на ярмарочной площади):

puts "Testing the response of a ticket instance...." 

wrong = fg.most_expensive

Вы получаете сообщение об ошибке, потому что у fg нет метода с именем most_exорого. Класс fg — Ticket — имеет
такой метод. Но fg, который является экземпляром Ticket, этого не делает. Помнить:

  • Классы являются объектами.
  • Экземпляры классов тоже являются объектами.
  • Объект класса (например, Ticket) имеет свои собственные методы, свое собственное состояние, свою собственную идентичность. Он не делится этими вещами с экземплярами самого себя. Отправка сообщения в Ticket — это не то же самое, что отправка сообщения в fg или cc или любой другой экземпляр Ticket. 

If you ever get tangled up over what’s a class method and what’s an instance method, you can usually sort out the confusion by going back to these three principles. Meanwhile, now that we’ve got class and instance methods on the radar, a word about a couple of notational conventions is in order.

A Note On Method Notation

In writing about and referring to Ruby methods (outside of code, that is), it’s customary to refer to instance
methods by naming the class (or module, as the case may be) in which they’re defined, followed by a hash mark (#) and the name of the method; and to refer to class methods with a similar construct but using a period instead of the hash mark. Sometimes you’ll see a double colon (::) instead of a period in the class-method case. Here are some examples of this notation:

Notation Method referred to
Ticket#price The instance method price in the class Ticket
Ticket.most_expensive The class method most_expensive, in the class Ticket
Ticket::most_expensive Another way to refer to the class method most_expensive

From now on, when you see this notation (in this book or elsewhere), you’ll know what it means. (The second example—class-method reference using a dot—looks the same as a call to the method, but you’ll know from the context whether it’s a method call or a reference to the method in a discussion.)

The term “class method”: more trouble than it’s worth?

Ruby lets objects have singleton methods, and classes are objects. So when you do def
Ticket.most_expensive, you’re basically creating a singleton method for Ticket. On the calling side,
when you see a method called on a class object—like Ticket.new—you can’t tell just by looking whether you’re dealing with a singleton method defined directly on this class (def Ticket.new) or an instance method of the class Class.

Just to make it even more fun, the class Class has both a class method version of new and an instance method version; the former is called when you write Class.new, and the latter when you write Ticket.new. Unless, of course, you override it by defining new for Ticket yourself….

Admittedly, new is a particularly thorny case. But in general, the term “class method” is not necessarily a great fit for Ruby. It’s a concept shared with other object-oriented languages, but in those languages there’s a greater difference between class methods and instance methods. In Ruby, when you send a message to a class object, you can’t tell where and how the corresponding method was defined.

So there’s a fuzzy meaning to “class method,” and a sharp meaning. Fuzzily, any method that gets called directly on a Class object is a class method. Sharply, a class method is defined, not just called, directly on a Class object. You’ll hear it used both these ways, and as long as you’re aware of the underlying engineering and can make the sharp distinctions when you need to, you’ll be fine.