General Ruby OOP question

Hello,

I have a general question about OOP with Ruby. If I have 2 class: Home
and Person for instance, such as:

class Home
def initialize
@person1 = Person.new
@person2 = Person.new
# …
end

def open_a_window; end

end

class Person
def play_wii_game; end
end

Then, a home instance can include a lot of people. And that’s cool. And
a home instance can apply action on a person using a method just like:
@person2.play_wii_game

But if a person want to open a window in its home? Here, with a such
design it’s impossible, isn’t it.

So to allow this kind of action, I think we need to pass as parameter
self inside Home initialization process, becoming:

class Home
def initialize
@person1 = Person.new(self)
@person2 = Person.new(self)
# …
end

def open_a_window; end

end

class Person
def initialize(home)
@home = home
end

def open_a_window
@home.open_a_window
end

def play_wii_game; end
end

Now, a instantiated person can do it using open_a_window proxy method.
And any other proxifyable home’s methods.

But is it ethics? I mean, is that lawful under the principles of
object-oriented programming. Maybe this is so much power, to pass self
inside person instance inside its home… 'cause it’s almost as if there
was no class, no partitioning.

What do you think about this?
Thanks for any considerations.

On Jun 19, 9:56 am, “Paul A.” [email protected] wrote:

end
@person2.play_wii_game
def initialize
@home = home
And any other proxifyable home’s methods.

But is it ethics? I mean, is that lawful under the principles of
object-oriented programming. Maybe this is so much power, to pass self
inside person instance inside its home… 'cause it’s almost as if there
was no class, no partitioning.

What do you think about this?
Thanks for any considerations.

It’s fine. But why is Home creating people? Perhaps they should be
added to a home? In which case you could create a home and then a
person with a home and the person would be automatically added to that
home.

home = Home.new
person1 = Person.new(home1)

Or you could create both the home and person separately but when you
add the person to the home they will pick up a reference to it.

home = Home.new
person1 = Person.new
home << person1

In Home:

class Home

def <<(person)
  @persons << person
  person.home = self
end

~trans

On Sat, Jun 19, 2010 at 4:56 PM, Paul A. [email protected] wrote:

But is it ethics? I mean, is that lawful under the principles of
object-oriented programming. Maybe this is so much power, to pass self
inside person instance inside its home… 'cause it’s almost as if there
was no class, no partitioning.

There is nothing wrong from an OOP point of view with one object
containing a reference to another object. Even in the real world, it
makes sense that a person knows where their home is.

In some languages, like C++, this can introduce some typing and API
complications, but not in Ruby where methods are invoked by sending
messages to the objects.

The problems that you might have with this approach are related to
design requirements. For example, what if one person can, optionally,
have more than one home?

Ammar

On Sun, Jun 20, 2010 at 12:37 AM, Ammar A. [email protected]
wrote:

In some languages, like C++, this can introduce some typing and API
complications, but not in Ruby where methods are invoked by sending
messages to the objects.

The problems that you might have with this approach are related to
design requirements. For example, what if one person can, optionally,
have more than one home?

I think I’d model it somewhat like this (untested):

class Window
attr_accessor :open
end

class Home
attr_accessor :window

def initialize
@window = Window.new
end
end

class Person
attr_accessor :location

def enter(location)
self.location = location
end

def open_window
if location.window.respond_to?(:open=)
location.window.open = true
else
raise(“The location doesn’t have any windows you can open”)
end
end
end

guy = Person.new
home = Home.new
guy.enter(home)
guy.open_window

On Sat, Jun 19, 2010 at 8:56 AM, Paul A. [email protected] wrote:

end
a home instance can apply action on a person using a method just like:
@person1 = Person.new(self)
@home = home
And any other proxifyable home’s methods.

Plus, you get a nice little benefit in that the Person#open_a_window
method
can contain the conditionals for dealing with the edge case of when
there is
no home.

class Person
def open_a_window
@home.open_a_window if @home
end
def has_home?
!!@home
end
end

Then later you can say things like
person.open_a_window if person.hot?

rather than having to cart the exception logic all over your
implementaion
person.open_a_window if person.hot? && person.has_home?

Though, I suppose, it is a rather limited model in that your person may
be
at work, or at a friend’s home.

On 2010-06-19 09:20:26 -0700, Michael F. said:

def open_window
if location.window.respond_to?(:open=)
location.window.open = true
else
raise(“The location doesn’t have any windows you can open”)
end
end
end

This is a Law of Demeter violation. The home should be responsible for
opening its own windows.

This code makes assumptions about both the house and its windows on the
behalf of the person (that it has exactly one window, that windows are
opened by setting their ‘open’ accessor) and even then it deals with
these assumptions inconsistently.

  1. It checks for the behavior of the window without first checking for
    the existance of the window. If you really must program defensively, at
    least be consistent. Better still to not program defensively.

  2. It raises a misleading (or at least ambiguious) error. Far better to
    simply try it and let the interpreter tell you where you failed. Is a
    NoMethodError on a window object really any less informative? Given the
    unnecessarily complex code path (because of the Demeter violation), I
    would argue that it is more informative.

In any event, you shouldn’t be violating Demeter in the first place.
The following Law of Demeter violation better illustrates the problem:

class Person
attr_accessor :wallet
end

class Store
def accept_payment(person, amount)
person.wallet.amount -= amount
end
end

NB: I actually originally wrote it as person.wallet.withdraw(amount)
(because I instinctively hide state and expose behavior in cases like
these) and then realized that this is one less Demeter violation than
the original example code.

People should be allowed to make payments themselves. I don’t want the
store reaching into my wallet and just taking my money. I certainly
don’t want the store reaching into my wallet and then telling me how
much money I have when it’s done.

class Store
def accept_payment(person, amount)
person.pay(amount)
end
end

This lets me decide where I keep my money, how I pay and if I can pay at
all.

Furthermore, allowing assignment to the window’s ‘open’ accessor lets
client code do window.open = [1,2,3] and other such nonsense. A
window has exactly two states and its behavior should be modeled in a
way that enforces this relationship and keeps its state hidden behind
an interface, not by allowing client code to set its state in arbitrary
ways. Window#open and Window#close.

The home should delegate window opening and closing to its window. The
person should ask the home to close its window. Or the person should
know about the window, which makes more sense in the real world but
less sense in this OOP system.

Hopefully we can avoid making concrete OOP mistakes even while we’re
answering completely hypothetical OOP questions.

Also, can someone please write me a Person#sudo_make_me_a_sandwich!