2010-01-28 43 views
10

Trong Ruby, giả sử tôi có một lớp Foo để cho phép tôi lập danh mục bộ sưu tập Foos lớn của mình. Đó là một đạo luật cơ bản của tự nhiên rằng tất cả Foos là màu xanh lá cây và hình cầu, vì vậy tôi đã xác định phương pháp lớp học như sau:Ủy quyền các phương thức thể hiện cho phương thức lớp

class Foo 
    def self.colour 
    "green" 
    end 

    def self.is_spherical? 
    true 
    end 
end 

này cho phép tôi làm

Foo.colour # "green" 

nhưng không

my_foo = Foo.new 
my_foo.colour # Error! 

mặc dù thực tế là my_foo rõ ràng là màu xanh lục.

Rõ ràng, tôi có thể xác định phương pháp thể hiện colour gọi self.class.colour, nhưng điều đó trở nên khó sử dụng nếu tôi có nhiều đặc điểm cơ bản như vậy. Tôi cũng có thể làm điều đó bằng cách xác định method_missing để thử lớp cho bất kỳ phương pháp còn thiếu nào, nhưng tôi không rõ liệu đây có phải là việc tôi nên làm hay hack xấu xí hay cách thực hiện an toàn (đặc biệt là tôi thực sự là dưới ActiveRecord trong Rails, điều mà tôi hiểu là một số thông tin thú vị của Clever với method_missing).

Bạn sẽ đề xuất điều gì?

+1

Nếu không muốn tranh luận về colo (u) r của cuộc đua xe đạp, bạn nên sử dụng tiếng Anh Mỹ bằng mã, thay vì tiếng Anh thịnh vượng chung. –

+0

Đây là mã cá nhân của riêng tôi; những người duy nhất có thể thấy đó là tôi, và một vài người bạn cũng đến từ Anh. Tôi sẽ sử dụng mã người Mỹ ở nơi làm việc nếu phong cách nhà ở yêu cầu nó - tôi sẽ không sử dụng nó theo cách riêng của mình. – Chowlett

+0

@ Chowlett, đó chắc chắn là lựa chọn của bạn. Nhưng bạn vẫn sẽ kết hợp chúng, ví dụ: khi bạn truy cập '@ colour' trong phương thức' initialize'. – ecoologic

Trả lời

2

Bạn có thể xác định một cơ sở passthrough:

module Passthrough 
    def passthrough(*methods) 
    methods.each do |method| 
     ## make sure the argument is the right type. 
     raise ArgumentError if ! method.is_a?(Symbol) 
     method_str = method.to_s 
     self.class_eval("def #{method_str}(*args) ; self.class.#{method_str}(*args) ; end") 
    end 
    end 
end 

class Foo 
    extend Passthrough 

    def self::colour ; "green" ; end 
    def self::is_spherical? ; true ; end 
    passthrough :colour, :is_spherical? 
end 

f = Foo.new 
puts(f.colour) 
puts(Foo.colour) 

tôi không thường như sử dụng eval, nhưng nó nên được khá an toàn, ở đây.

+0

Đây cũng là một ứng cử viên thực sự mạnh mẽ cho chính xác những gì tôi cần. – Chowlett

+0

Điều này sẽ đối phó với thừa kế tốt hơn nhiều so với câu trả lời khác mà tôi đã đăng. –

+0

Đúng. Nó đơn giản, tôi hiểu những gì nó đang làm, nó xử lý thừa kế, và nó sẽ xảy ra để trông rất giống như Rails-phong cách tờ khai (has_many vv)! – Chowlett

4

Bạn có thể sử dụng một mô-đun:

module FooProperties 
    def colour ; "green" ; end 
    def is_spherical? ; true ; end 
end 

class Foo 
    extend FooProperties 
    include FooProperties 
end 

Một chút xấu xí, nhưng tốt hơn so với sử dụng method_missing. Tôi sẽ cố gắng đưa các tùy chọn khác vào các câu trả lời khác ...

+0

Tuyệt. Điều này có thể xử lý thừa kế không? Đó là, nếu Thing và Bar kế thừa từ Foo, và Mọi thứ luôn "nặng", trong khi Bars luôn "nhẹ"; Tôi có cần các phần tử ThingProperties và BarProperties riêng biệt không, hoặc tôi có thể chuyển nó thành FooProperties bằng cách nào đó không? – Chowlett

1

Điều này nghe có vẻ giống như một cảnh sát, nhưng trong thực tế hiếm khi cần phải làm điều này, khi bạn có thể gọi Foo.color dễ dàng. Ngoại lệ là nếu bạn có nhiều lớp với phương thức màu được xác định. @var có thể là một trong nhiều lớp và bạn muốn hiển thị màu bất kể.

Khi đó là trường hợp, tôi muốn tự hỏi mình nơi bạn đang sử dụng phương pháp nhiều hơn - trên lớp học hoặc trên mô hình? Nó gần như luôn luôn là một hoặc khác, và không có gì sai với làm cho nó một phương pháp dụ mặc dù nó dự kiến ​​sẽ giống nhau trên tất cả các trường hợp.

Trong trường hợp hiếm hoi mà bạn muốn phương pháp "callable" của cả hai, bạn có thể làm @ var.class.color (mà không cần tạo một phương pháp đặc biệt) hoặc tạo ra một phương pháp đặc biệt như vậy:

def màu self.class.color end

Tôi chắc chắn tránh được giải pháp catch-all (method_missing), vì nó lý do bạn thực sự xem xét cách sử dụng của từng phương thức và liệu nó thuộc về cấp lớp hay thể hiện.

4

Từ quan điểm thiết kế, tôi cho rằng, mặc dù câu trả lời là giống nhau cho tất cả Foos, màu sắc và hình cầu? là các thuộc tính của các cá thể của Foo và vì vậy nên được định nghĩa là các phương thức thể hiện chứ không phải là các phương thức lớp.

Tuy nhiên, tôi có thể thấy một số trường hợp bạn muốn hành vi này, ví dụ: khi bạn có Bars trong hệ thống của mình, tất cả đều có màu xanh dương và bạn được chuyển một lớp ở đâu đó trong mã của bạn và muốn biết màu nào sẽ là trước khi bạn gọi new trên lớp.

Ngoài ra, bạn chính xác rằng ActiveRecord sử dụng rộng rãi method_missing ví dụ: đối với các công cụ tìm động vì vậy nếu bạn đi xuống tuyến đường đó, bạn sẽ cần phải đảm bảo rằng method_missing của bạn được gọi là một từ superclass nếu nó xác định rằng tên phương thức không phải là tên mà nó có thể tự xử lý.

+0

Bạn hoàn toàn đúng, chúng là tài sản của cá thể, chứ không phải của lớp - nhưng bạn đã đóng đinh tình huống tôi cần hàm này; Tôi cần đưa ra quyết định trong mã của mình khi tôi có Lớp trong tay, trước khi tôi gọi mới. – Chowlett

3

Tôi nghĩ rằng cách tốt nhất để làm điều này là sử dụng the Dwemthy's array method.

tôi sẽ xem xét nó lên và điền vào chi tiết, nhưng đây là bộ xương

EDIT: Yay! Đang làm việc!

class Object 
    # class where singleton methods for an object are stored 
    def metaclass 
    class<<self;self;end 
    end 
    def metaclass_eval &block 
    metaclass.instance_eval &block 
    end 
end 
module Defaults 
    def self.included(klass, defaults = []) 
    klass.metaclass_eval do 
     define_method(:add_default) do |attr_name| 
     # first, define getters and setters for the instances 
     # i.e <class>.new.<attr_name> and <class>.new.<attr_name>= 
     attr_accessor attr_name 

     # open the class's class 
     metaclass_eval do 
      # now define our getter and setters for the class 
      # i.e. <class>.<attr_name> and <class>.<attr_name>= 
      attr_accessor attr_name 
     end 

     # add to our list of defaults 
     defaults << attr_name 
     end 
     define_method(:inherited) do |subclass| 
     # make sure any defaults added to the child are stored with the child 
     # not with the parent 
     Defaults.included(subclass, defaults.dup) 
     defaults.each do |attr_name| 
      # copy the parent's current default values 
      subclass.instance_variable_set "@#{attr_name}", self.send(attr_name) 
     end 
     end 
    end 
    klass.class_eval do 
     # define an initialize method that grabs the defaults from the class to 
     # set up the initial values for those attributes 
     define_method(:initialize) do 
     defaults.each do |attr_name| 
      instance_variable_set "@#{attr_name}", self.class.send(attr_name) 
     end 
     end 
    end 
    end 
end 
class Foo 
    include Defaults 

    add_default :color 
    # you can use the setter 
    # (without `self.` it would think `color` was a local variable, 
    # not an instance method) 
    self.color = "green" 

    add_default :is_spherical 
    # or the class instance variable directly 
    @is_spherical = true 
end 

Foo.color #=> "green" 
foo1 = Foo.new 

Foo.color = "blue" 
Foo.color #=> "blue" 
foo2 = Foo.new 

foo1.color #=> "green" 
foo2.color #=> "blue" 

class Bar < Foo 
    add_defaults :texture 
    @texture = "rough" 

    # be sure to call the original initialize when overwriting it 
    alias :load_defaults :initialize 
    def initialize 
    load_defaults 
    @color = += " (default value)" 
    end 
end 

Bar.color #=> "blue" 
Bar.texture #=> "rough" 
Bar.new.color #=> "blue (default value)" 

Bar.color = "red" 
Bar.color #=> "red" 
Foo.color #=> "blue" 
+0

Đây có thể chỉ là những gì tôi đang tìm kiếm ... Mong được cập nhật chi tiết. – Chowlett

+0

@Chris: Bản cập nhật chi tiết được thực hiện. Hãy cho tôi biết nếu bạn gặp phải bất kỳ vấn đề nào với nó. – rampion

+0

hiện tại màu không được thừa kế (vì nó được lưu trữ trong một biến cá thể của lớp), nhưng bạn có thể sửa đổi điều này để các giá trị mặc định hiện tại được kế thừa khi một lớp được phân lớp bằng cách ghi đè Thing.inherited – rampion

21

Các Forwardable mô-đun mà đi kèm với Ruby sẽ làm điều này độc đáo:

#!/usr/bin/ruby1.8 

require 'forwardable' 

class Foo 

    extend Forwardable 

    def self.color 
    "green" 
    end 

    def_delegator self, :color 

    def self.is_spherical? 
    true 
    end 

    def_delegator self, :is_spherical? 

end 

p Foo.color    # "green" 
p Foo.is_spherical?  # true 
p Foo.new.color   # "green" 
p Foo.new.is_spherical? # true 
+1

Hơi ngắn hơn: 'def_delegator self,: color' –

+0

@Martin Carpenter, Tất nhiên! Đã chỉnh sửa và cảm ơn. –

+5

Lưu ý rằng nếu bạn cung cấp cho 'def_delegator' một biểu tượng cho đối số đầu tiên của nó, nó sẽ đánh giá sau đó. Vì vậy, nếu bạn phân lớp 'Foo' với' FooChild' và bạn muốn các cá thể của FooChild ủy nhiệm cho 'FooChild' và không phải' Foo', hãy sử dụng 'def delegator: self,: colour' để giữ' self' khỏi bị đánh giá là 'Foo '. Nó thực sự dường như là một eval: 'def_delegator:" đặt 'hi'; self "' sẽ có tác dụng tương tự nhưng sẽ in ra tiêu chuẩn. –

2

Bạn cũng có thể làm điều này:

def self.color your_args; your_expression end 

define_method :color, &method(:color) 
5

Nếu đó là đồng bằng Ruby sau đó sử dụng Forwardable là câu trả lời đúng

Trong trường hợp đó là Rails tôi đã sử dụng delegate, ví dụ

class Foo 
    delegate :colour, to: :class 

    def self.colour 
    "green" 
    end 
end 

irb(main):012:0> my_foo = Foo.new 
=> #<Foo:0x007f9913110d60> 
irb(main):013:0> my_foo.colour 
=> "green" 
Các vấn đề liên quan