Переменные экземпляра класса Ruby и наследование - PullRequest
18 голосов
/ 02 мая 2010

У меня есть класс Ruby с именем LibraryItem. Я хочу связать с каждым экземпляром этого класса массив атрибутов. Этот массив длинный и выглядит примерно так:

['title', 'authors', 'location', ...]

Обратите внимание, что эти атрибуты не должны быть методами, это просто список атрибутов, которые LibraryItem имеет.

Далее я хочу создать подкласс LibraryItem с именем LibraryBook, который имеет массив атрибутов, который включает в себя все атрибуты LibraryItem, но также будет включать в себя и многие другие.

В конечном итоге мне понадобятся несколько подклассов LibraryItem, каждый со своей версией массива @attributes, но каждый из которых будет добавлен к LibraryItem @attributes (например, LibraryBook, LibraryDVD, LibraryMap и т. д.).

Итак, вот моя попытка:

class LibraryItem < Object
  class << self; attr_accessor :attributes; end
  @attributes = ['title', 'authors', 'location',]
end

class LibraryBook < LibraryItem
  @attributes.push('ISBN', 'pages')
end

Это не работает. Я получаю ошибку

undefined method `push' for nil:NilClass

Если бы это работало, я бы хотел что-то вроде этого

puts LibraryItem.attributes 
puts LibraryBook.attributes

для вывода

['title', 'authors', 'location']
['title', 'authors', 'location', 'ISBN', 'pages']

(добавлено 2 мая 2010 г.) Одним из решений этого является создание @attributes простой переменной экземпляра, а затем добавление новых атрибутов для LibraryBoot в методе initialize (это было предложено demas в одном из ответов).

Хотя это, безусловно, сработает (и на самом деле это то, чем я занимался все время), я не доволен этим, поскольку он неоптимален: почему эти неизменяемые массивы должны создаваться при каждом создании объекта

Что я действительно хочу, так это иметь переменные класса, которые могут наследоваться от родительского класса, но при изменении в дочернем классе не изменяются в родительском классе.

Ответы [ 9 ]

10 голосов
/ 29 ноября 2012

Другим решением было бы использовать унаследованный хук:

class LibraryItem < Object
  class << self
    attr_accessor :attributes
    def inherit_attributes(attrs)
      @attributes ||= []
      @attributes.concat attrs
    end

    def inherited(sublass)
      sublass.inherit_attributes(@attributes)
    end
  end
  @attributes = ['title', 'authors', 'location',]
end

class LibraryBook < LibraryItem
  @attributes.push('ISBN', 'pages')
end
9 голосов
/ 12 августа 2010

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

class Foo
    ATTRS = ['title', 'authors', 'location']
    def attributes
        ATTRS
    end
end

class Bar < Foo
    ATTRS = ['ISBN', 'pages']
    def attributes
        super + ATTRS
    end
end

Вы вручную реализуете метод чтения (вместо того, чтобы позволить attr_accessor создать его для вас), который маскирует внутреннее имя массива.В своем подклассе вы просто вызываете функцию чтения класса предка, добавляете дополнительные поля, связанные с дочерним классом, и возвращаете это вызывающей стороне.Для пользователя это выглядит как переменная-член только для чтения с именем attributes, которая имеет дополнительные значения в подклассе.

5 голосов
/ 02 мая 2010

Так же, как версия:

class LibraryItem < Object
  def initialize
    @attributes = ['one', 'two'];
  end
end

class LibraryBook < LibraryItem
  def initialize
   super
   @attributes.push('three')
 end
end

b = LibraryBook.new
4 голосов
/ 12 августа 2010

Из любопытства, будет ли что-то подобное работать?

class Foo
  ATTRIBUTES = ['title','authors','location']
end

class Bar < Foo
  ATTRIBUTES |= ['ISBN', 'pages']
end

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

> Foo::ATTRIBUTES
=> ['title','authors','location'] 
> Bar::ATTRIBUTES
=> ['title','authors','location', 'ISBN', 'pages'] 
3 голосов
/ 27 февраля 2012

Чтобы расширить ответ @Nick Vanderbilt, используя active_support, вы сделаете это, и это именно то, что мне нужно для этой функции. Вот полный пример:

require 'active_support/core_ext'

class Foo
  class_attribute :attributes
  self.attributes = ['title','authors','location']
end

class Bar < Foo
  self.attributes = Foo.attributes + ['ISBN', 'pages']
end

puts Foo.attributes.inspect #=> ["title", "authors", "location"]
puts Bar.attributes.inspect #=> ["title", "authors", "location", "ISBN", "pages"]

Жаль, что для ruby ​​так трудно добиться этого, не нуждаясь в библиотеке для этого. Это единственное, что я скучаю по питону. И в моем случае я не против зависимости от гема active_support.

3 голосов
/ 12 августа 2010

ActiveSupport имеет метод class_attribute на краю рельсов.

2 голосов
/ 02 мая 2010

В библиотеке LibraryBook переменная @attributes - это новая независимая переменная, переменная экземпляра объекта LibraryBook, поэтому она не инициализирована, и вы получаете сообщение об ошибке «неопределенный метод ... для nil»
Вы должны инициализировать его по списку атрибутов LibraryItem перед использованием

class LibraryBook < LibraryItem
  @attributes = LibraryItem::attributes + ['ISBN', 'pages']
end
0 голосов
/ 12 апреля 2012

Это для строк (что угодно), а не для массивов, но ...

class A
  def self.a
    @a || superclass.a rescue nil
  end

  def self.a=(value)
    @a = value
  end

  self.a = %w( apple banana chimp )
end

class B < A
end

class C < B
  self.a += %w( dromedary elephant )
end

class D < A
  self.a = %w( pi e golden_ratio )
end



irb(main):001:0> require 'test2'
=> true
irb(main):002:0> A.a
=> ["apple", "banana", "chimp"]
irb(main):003:0> B.a
=> ["apple", "banana", "chimp"]
irb(main):004:0> C.a
=> ["apple", "banana", "chimp", "dromedary", "elephant"]
irb(main):005:0> D.a
=> ["pi", "e", "golden_ratio"]
irb(main):006:0> A.a = %w( 7 ) 
=> ["7"]
irb(main):007:0> A.a
=> ["7"]
irb(main):008:0> B.a
=> ["7"]
irb(main):009:0> C.a = nil
=> nil
irb(main):010:0> C.a
=> ["7"]
0 голосов
/ 02 мая 2010

Вы можете сделать это, используя CINSTANTS. Нет проверки, хотя.

class LibraryItem < Object
  class << self; attr_accessor :attributes; end
  ATTRIBUTES = ['title', 'authors', 'location',]
end

class LibraryBook < LibraryItem
  ATTRIBUTES .push('ISBN', 'pages']
end
...