[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Composition: Build objects from other objects

Thufir Hawat

11/17/2007 4:49:00 AM

<http://www.javaworld.com/javaworld/jw-06-2001/jw-0608-java10... has
a discussion of composition versus inheritance which I'm trying to apply
to ruby. For discussions sake, I'll use the classes discussed in the
above linke: Car, Vehicle and Engine. Car inherits from Vehicle, of
course.

Let's say that Engine is something like:




class Engine

attr_accessor :type


def initialize ()
@type = "generic engine"
end

def vroom ()
puts "vroooooom"
end
end


Vehicle would be something like:


class Vehicle

attr_accessor :engine

def initialize ()
@engine = Engine.new
end
end


class Car < Vehicle

def initialize ()
super()
end
end


then from IRB for instance:

vw_bug = Car.new



then can you make the vw_bug go "vroooooom" and so forth? What would be
the usual way to do this in ruby? When a car is instantiated, should an
engine object get passed to the initializer method?



thanks,

Thufir


20 Answers

Raul Raul

11/17/2007 5:37:00 AM

0

Thufir wrote:
>
> then can you make the vw_bug go "vroooooom" and so forth? What would be
> the usual way to do this in ruby? When a car is instantiated, should an
> engine object get passed to the initializer method?
>
> thanks,
>
> Thufir

I do not like (at first view) the idea of this design; it leads to, as
Konrad has correctly observed, to start the car by saying:

vw_bug.engine.vroom()

Should the car user know that he has to tell the car to ask the engine
to rev up? Uhm..

Another take is the following; let's use a module Engine:

module Engine
attr_accessor :type

def initialize
@type = "generic engine"
end

def vroom
puts "vroooooom"
end
end

class Vehicle
include Engine

def initialize
super
end
end

class Car < Vehicle
def initialize
super
end
end

vw_bug = Car.new
vw_bug.vroom

I am not crazy of the 'Engine' Module, but I definitely like to ask the
VW to wroom, without worrying about what is inside,

Raul

--
Posted via http://www.ruby-....

Thufir Hawat

11/17/2007 6:32:00 AM

0

On Sat, 17 Nov 2007 14:36:50 +0900, Raul Parolari wrote:


> I do not like (at first view) the idea of this design; it leads to, as
> Konrad has correctly observed, to start the car by saying:
>
> vw_bug.engine.vroom()



Fair enough. This is pretty much how Java would do it, and, correct me
if I'm wrong, Smalltalk would probably do similarly.

Put another way: there must be a non-contrived case of composition in
Ruby? Yes?



thanks,


Thufir


Raul Raul

11/17/2007 9:38:00 AM

0

Thufir wrote:

> Put another way: there must be a non-contrived case of composition in
> Ruby? Yes?

I do not really see this as a contrived example of composition; the only
thing that I did not like in the solution we examined is the name
'Engine' for a module (for a principle given by D.Black of 'adjectives
for Modules, and nouns for Classes'..). But I think that the advantage
of a Module in this case trounces the naming principle (so far; perhaps
if design continued..).

For your question about existing examples of 'Composition': is this the
eternal OO issue about 'to have' vs 'to be', and the need for objects to
'contain' other objects?

I can only mention the only Ruby application that I looked at: Rails
(not as an user, but internally, to study how Ruby can be used). In
Rails definitely we find objects which 'contain' other objects (I will
however use the name 'reference' from now on, as I detest the word
'containment'); for example:
- the Dispatcher references the objects request, response, and the
Controller class name
- the Controller (besides request, response received from the
Dispatcher) creates an Anonymous Class View and then it instantiates it;
so it references the instance view (which allows cool tricks for
instance variables passing).

However, reading Rails, this is NOT what strikes the reader (what is the
big deal about objects referencing other objects?), but rather the
explosive combination of INHERITANCE + MIXIN + METAPROGRAMMING to glue
the system together; for example, what modules ActionController::Helpers
and ClassMethods do on behalf of the Controller (and View).

Here is where Ruby shows facets that other languages cannot even dream
of (and I doubt that we should worry about mimicking with Ruby what
others are doing..); but I am getting off theme.

I hope others will give you more useful examples,
good luck

Raul
--
Posted via http://www.ruby-....

Rick DeNatale

11/17/2007 1:31:00 PM

0

On Nov 17, 2007 4:38 AM, Raul Parolari <raulparolari@gmail.com> wrote:
> Thufir wrote:
>
> > Put another way: there must be a non-contrived case of composition in
> > Ruby? Yes?
>
> I do not really see this as a contrived example of composition; the only
> thing that I did not like in the solution we examined is the name
> 'Engine' for a module (for a principle given by D.Black of 'adjectives
> for Modules, and nouns for Classes'..). But I think that the advantage
> of a Module in this case trounces the naming principle (so far; perhaps
> if design continued..).

I think there are two issues here. First , how to model the
relationship between car and engine, and second what the interface to
the resulting car should be.

Including an Engine in a car using a module seems wrong to me.

Modules add behavior to an object. That, I think, is the heart of
David's analogy of Modules as adjectives, the Enumerable module adds
enumerability to an object, the Comparable module adds
comparability...

Cars don't behave like engines, they use the engine internally to
provide their power source. Modeling the engine as a separate class
allows different models of cars with different types of engines,
likely created using some form of Factory pattern.

vw_bug.engine.vroom violates the 'law' of Demeter, which says that
it's bad style to reach through one object to get at another. The
classic example is modeling a paperboy collecting money from a
customer.

Bad: the paper boy shouldn't be grabbing money from the customer's wallet.

class PaperBoy
def collect_from(customer)
receive_payment(customer, customer.wallet.remove(2.dollars))
end
end

instead of
class Paperboy
def collect_from(customer)
receive_payment(customer, customer.pay(2.dollars))
end
end

So I think that to start the car it should be something like

vw_bug.start

And the start method would cause the engine to start, producing Vrooom
as a side effect, depending on the particular engine which was
installed by the factory.

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denh...

ruby-talk

11/17/2007 1:40:00 PM

0

On Sat, Nov 17, 2007 at 03:32:03PM +0900, Thufir wrote:
> On Sat, 17 Nov 2007 14:36:50 +0900, Raul Parolari wrote:
>
> Fair enough. This is pretty much how Java would do it, and, correct me
> if I'm wrong, Smalltalk would probably do similarly.
>
> Put another way: there must be a non-contrived case of composition in
> Ruby? Yes?
>

What are you looking for? Composition is basically accomplished by
instance variables, even for less contrived cases. As for the
interface, to say TMTOWTDI is an understatement.

require 'forwardable'
class Engine
def vroom
puts "vroom"
end
end
class MustangEngine
def vroom
puts "rumble"
end
end
class Vehicle
extend Forwardable
def_delegator :@engine, :vroom
attr_accessor :engine
def initialize(engine)
@engine=engine
end
end

v=Vehicle.new(Engine.new)
v.vroom
v.engine=MustangEngine.new
v.vroom


As far as comparison to Java, this example uses duck typing. As long as it vrooms, you can use it as an engine for your vehicle.

Jeremy McAnally

11/17/2007 2:13:00 PM

0

Doesn't seeking to mix a method in like that totally break
encapsulation though (and possibly offend our good friend Demeter)?
Why does the whole Car need to know anything about what the Engine is
doing except that it's doing what it needs to be doing and give it
facilities to do what is needed?

I know you're going for composition here, but maybe I'm not seeing the
benefit of it or this is a bad example?

--Jeremy

On Nov 17, 2007 1:32 AM, Thufir <hawat.thufir@gmail.com> wrote:
> On Sat, 17 Nov 2007 14:36:50 +0900, Raul Parolari wrote:
>
>
> > I do not like (at first view) the idea of this design; it leads to, as
> > Konrad has correctly observed, to start the car by saying:
> >
> > vw_bug.engine.vroom()
>
>
>
> Fair enough. This is pretty much how Java would do it, and, correct me
> if I'm wrong, Smalltalk would probably do similarly.
>
> Put another way: there must be a non-contrived case of composition in
> Ruby? Yes?
>
>
>
> thanks,
>
>
> Thufir
>
>
>



--
http://www.jeremymca...

My books:
Ruby in Practice
http://www.manning.com...

My free Ruby e-book
http://www.humblelittlerub...

My blogs:
http://www.mrneigh...
http://www.rubyinpra...

Thufir Hawat

11/17/2007 8:02:00 PM

0

On Sat, 17 Nov 2007 22:40:20 +0900, Liam wrote:


> v=Vehicle.new(Engine.new)
> v.vroom
> v.engine=MustangEngine.new
> v.vroom
>
>
> As far as comparison to Java, this example uses duck typing. As long as
> it vrooms, you can use it as an engine for your vehicle.

Hehe, fair enough. Ok, enough Java, it was just an entry point (although
I would be interested to hear how this is addressed in other OOP oriented
languages). What stands out about the above is:

1.) I don't follow all of the syntax, some of it's over my head (which
is certainly ok).

2.) The engine is being instantiated *outside* of the class definition.
I was assuming that the engine would be created in the vehicle's
initialize method. The above technique is to specify the engine when the
car is "manufactured" by passing a new instance of Engine into the new
Vehicle instance.



-Thufir


Raul Raul

11/17/2007 9:20:00 PM

0

Rick Denatale wrote:

> Cars don't behave like engines, they use the engine internally to
> provide their power source.

You are right; the word 'behave' vs 'use' is the key. I could not resist
letting the 'vroom' reach the Engine without any intervention from
Vehicle; and I let expediency trounce principle. Your eloquent point is
taken.

> vw_bug.engine.vroom violates the 'law' of Demeter, which says that
> it's bad style to reach through one object to get at another
> So I think that to start the car it should be something like
> ..
> vw_bug.start
>
> And the start method would cause the engine to start, producing Vrooom
> as a side effect, depending on the particular engine which was
> installed by the factory.

Here we disagree; not on (what I prefer to call) the "principle of least
knowledge", but on the example at hand. Removing the 'vroom' method from
the user is a bad application of that principle; customers do not always
'vroom' to start the car, but to assert something (that I hope I don't
need to explain). It is enough to watch a city intersection for a few
minutes at a traffic light to see that. Removing the 'vroom' is not an
option.

[In fact, I liked the module Engine, exactly because of that: it removed
the need to put knowledge of 'vroom' in the Vehicle! but enough on
trying to justify my sin].

I agree that the problem 'vw_bug.engine.vroom' is that it breaks
encapsulation.
It seems that we are left with having to put a method in Vehicle to
'vroom', just to transmit the command to the engine..

============

Liam wrote:

> require 'forwardable'
> ..
> class Vehicle
> extend Forwardable
> def_delegator :@engine, :vroom
> attr_accessor :engine
> def initialize(engine)
> @engine=engine
> end
> end
>
> v=Vehicle.new(Engine.new)
> v.vroom
> v.engine=MustangEngine.new
> v.vroom

> As far as comparison to Java, this example uses duck typing. As long as
> it vrooms, you can use it as an engine for your vehicle.

Interesting: this solves the problem of explicitly writing a 'vroom'
method in Vehicle just to pass it to the Engine. I am not totally
comfortable with the thought of Car 'delegating to' (as opposed to
'having') an Engine, but it is very elegant.

=========

Personally, I learnt/conclude this (in view of some vagueness in other
comments that seem to say the Composition is not needed):

1) Thufir was right (aside on the details for how to 'vroom'), in seeing
the Engine as a Composition problem for Vehicle. There is an instance
variable @engine in Vehicle! (shame on me for attempting to remove it
:-).

2) we can use 'Delegation' to avoid silly 'bridge methods' in Vehicle,
to allow the user reach the Engine, when needed.

Thanks!

Raul


--
Posted via http://www.ruby-....

Pat Maddox

11/17/2007 10:08:00 PM

0

On Nov 16, 2007 9:36 PM, Raul Parolari <raulparolari@gmail.com> wrote:
> Thufir wrote:
> >
> > then can you make the vw_bug go "vroooooom" and so forth? What would be
> > the usual way to do this in ruby? When a car is instantiated, should an
> > engine object get passed to the initializer method?
> >
> > thanks,
> >
> > Thufir
>
> I do not like (at first view) the idea of this design; it leads to, as
> Konrad has correctly observed, to start the car by saying:
>
> vw_bug.engine.vroom()
>
> Should the car user know that he has to tell the car to ask the engine
> to rev up? Uhm..
>
> Another take is the following; let's use a module Engine:
>
> module Engine
> attr_accessor :type
>
> def initialize
> @type = "generic engine"
> end
>
> def vroom
> puts "vroooooom"
> end
> end
>
> class Vehicle
> include Engine
>
> def initialize
> super
> end
> end
>
> class Car < Vehicle
> def initialize
> super
> end
> end
>
> vw_bug = Car.new
> vw_bug.vroom
>
> I am not crazy of the 'Engine' Module, but I definitely like to ask the
> VW to wroom, without worrying about what is inside,
>
> Raul
>
> --
> Posted via http://www.ruby-....
>
>


I think this is a pretty bad approach. Consider the following example
(keep in mind I know nothing about cars):

module Engine
attr_reader :engine

def initialize
@engine = :generic
end

def vroom
puts "vrooooooom"
end
end

module Transmission
attr_reader :transmission

def initialize
@transmission = :silky_smooth
end

def vroom
puts "smooth like butta"
end
end

class Car
include Engine
include Transmission

def initialize
super
end
end

By "composing" it with modules like that, we end up with a seriously
broken car - one with no engine! Clearly this approach won't work.
Part of it is due to how you set stuff up in initialize...but anyway,
the point is that you don't really use modules for this kind of
composition.

A far better approach would be to have different engine and
transmission objects.

class Engine
def vroom
"vrooooooom"
end
end

class Transmission
def vroom
"smooth like butta"
end
end

class Car
def initialize(engine, transmission)
@engine = engine
@transmission = transmission
end

def vroom
"we go #{@engine.vroom}, #{@transmission.vroom}"
end
end

c = Car.new Engine.new, Transmission.new
c.vroom # => "we go vrooooooom, smooth like butta"

if you want to create a car with its parts already initialized, you
can create a factory method:

def Car.basic
self.new Engine.new, Transmission.new
end

The basic idea is that you don't really want to make the car object
responsible for a bunch of stuff...you want a bunch of little objects
that know how to do one thing well.

Pat

ruby-talk

11/17/2007 10:41:00 PM

0

On Sun, Nov 18, 2007 at 05:02:12AM +0900, Thufir wrote:
>
> Hehe, fair enough. Ok, enough Java, it was just an entry point (although
> I would be interested to hear how this is addressed in other OOP oriented
> languages). What stands out about the above is:
>
> 1.) I don't follow all of the syntax, some of it's over my head (which
> is certainly ok).

It makes use of fowardable[1] from the standard library.

>
> 2.) The engine is being instantiated *outside* of the class definition.
> I was assuming that the engine would be created in the vehicle's
> initialize method. The above technique is to specify the engine when the
> car is "manufactured" by passing a new instance of Engine into the new
> Vehicle instance.

This is more of a design decision. Providing outside access allows for
inversion of control. Perhaps you would prefer to use a factory for
your car?

class Factory
def self.build_car
Car.new(Engine.new)
end
end
class Mechanic
def soup_up(car)
car.engine=MustangEngine.new
end
end

falcon=Factory.build_car
car.vroom
joe=Mechanic.new
joe.soup_up falcon
car.vroom

The interface is of course rather arbitrary. Take a look at logr4[2]
for some interesting composition.


[1] http://ruby-doc.org/stdlib/libdoc/forwardable/rdoc/files/forwardab...
[2] http://log4r.source...