Consider a class that supports a number of configurable properties.
As a concrete example, take a class that represents an HTML link. It
requires that a “url” and link “content” be provided. It also supports two
optional attributes: “title” and “target”.
Link
url
content
?title
?target
I’ve seen a bunch of different ways of configuring such objects …
···
- SettingAttributes
The obvious way is just to create the instance, and then call
writer-methods to set attributes, e.g.
class Link
def initialize(url, content)
@url = url
@content = content
end
attr_accessor :url, :content, :link, :target
end
link = Link.new(url, "here")
link.title = "the whole story"
- OptionalArguments
You can have initialize() support optional (positional) arguments, e.g.
class Link
def initialize(url, content, title = nil, target = nil)
@url = url
@content = content
@title = title
@target = target
end
attr_accessor :url, :content, :link, :target
end
link = Link.new(url, "here", "the whole story")
but, this doesn’t scale well: if you have a bunch of optional attributes,
you have to start inserting “nil”:
link2 = Link.new(url, "here", nil, "helpFrame")
- OptionalArgumentMap
Another alternative is pass in a map of named optional args, e.g.
class Link
def initialize(url, content, args = {})
@url = url
@content = content
@title = args[:title]
@target = args[:target]
end
end
link = Link.new("http://", "here", {:title => "the whole story"})
helplink = Link.new("/help", "help" {:target => "helpFrame"})
As I understand it, there’s some syntax-sugar planned for Ruby-2.0, that
would make this a bit cleaner:
link = Link.new("http://", "here", title: "the whole story")
Right?
- YieldSelf
I’ve seen some people include a “yield self” in the initialize() method,
which allows you to do something like this:
class Link
def initialize(url, content)
@url = url
@content = content
yield self if block_given?
end
end
link = Link.new(url, "here") { |l|
l.title = "the whole story"
}
Hmmm. How does this help, exactly? I guess it could save you from having
to use a temp variable, in some cases.
- InstanceEval
Or, you can use instance_eval():
class Link
def initialize(url, content, &config)
@url = url
@content = content
instance_eval(&config) if (config)
end
end
link = Link.new("http://", "here") {
@title = "the whole story"
}
This is a nifty trick, though it does have the downside that you can set
arbitrary instance-variables in the block, breaking class encapsulation.
In Summary
Where am I going to with this? No idea. I guess I’m just wondering what
y’all consider to be the benefits/problems of each approach.
–
cheers, MikeW
“He smelled as if he had just eaten a mustard-coated camel …”
– Martin Amis, “London Fields”