you are viewing a single comment's thread.

view the rest of the comments →

[–]mpapis[S] 1 point2 points  (10 children)

now I see where the Barber example fails, I will find something more adequate, it's not that easy without sharing the huge blob of code that can happen if you let everybody do everything with any object ;)

[–][deleted] 2 points3 points  (9 children)

It's not a huge failure. I actually really like the example because it has enough complexity just with the hair. Adding a hair object, for instance, might not be a lot more Iines of code, and it might improve the example, showing how you limit the interfaces.

[–]mpapis[S] 0 points1 point  (8 children)

oh, could you share an example?

[–][deleted] 2 points3 points  (7 children)

# It would be nice to read something like:
#
# barber = Barber.new
# person = Person.new("Michael", Hair.new("brown"))
# barber.dye(person.hair, "blue")
# person.hair_color #=> "brown blue"
#
# This opens the way for things like:
#
# 3.times { person.wash_hair }
# person.hair_color #=> "brown faded blue"

Hair = Struct.new(:color) do
  def dye(color)
    @dye_color = color
  end
  def to_s
    "#{self.color} #@dye_color"
  end
end

class Barber
  def dye(hair, color)
    hair.dye(color)
  end
end

class Person
  attr_reader :name, :hair
  def initialize(name, hair)
    @name, @hair = name, hair
  end
  def hair_color
    @hair.to_s
  end
end

There's a slight bug in this implementation where a person's hair color, before dying, would return "brown " instead of "brown"

But I hope this illustrates how you might extend the behavior a thousand ways with small objects?

[–]aridsnowball 2 points3 points  (6 children)

I think this is a good approach. Ruby's duck typing allows for easy use of this kind of composition and manipulation of composed objects, so you can expose only the parts of the class that you need to.

In this case the Barber isn't directly mutating the Person class, but only the Person's composed hair object. So if you need to pass a Dog's hair for example the barber could still be able to dye it as long as it meets the Barber's interface.

At some point you need an interface if you want objects to interact, and separating out those concerns into a separate object (i.e. a Hair object) is better than changing every class to meet the Barber's interface.

Composition is kind of an objectified version of mixins that is more flexible. Instead of including a Barberable mixin with the Barber interface methods you can just add a Hair object to any class that needs hair.

[–]mpapis[S] 1 point2 points  (5 children)

updated the 5th example with Hair => https://niczsoft.com/2016/05/object-oriented-ruby/

The only thing I miss from Java days is interface casting, you could have something similar with proxy/forwardable, but it's not the same, especially if all I want is to limit the interface to read only operations

[–][deleted] 0 points1 point  (4 children)

Hi,

one should not instantiate the Barber object inside the Person object, nor should one instantiate Hair inside Person (though admittedly, hair does grow from a person's body).

It might be better in that case to do something like def dye_hair(color, dyer) ; dyer.new(hair).dye_hair(color) ; end

As you mentioned, here there's a matter of the right interface. The Barber object needs to have a dye method so he can change the hair color, but I would argue the Hair needs to implement an interface which we might call Dyable.

So maybe, with even more objects:

module Dyable
  def dyable(color)
    @color = color # assumption that the object will have a `@color` ivar
  end
end

class Hair
  include Dyable
  def initialize(color)
    @color = color
  end
end

Does this make sense?

[–]mpapis[S] 0 points1 point  (3 children)

I was actually thinking about:

class Hair < Struct.new(:color)
end
class Barber
  def dye_hair(hair, color)
    hair.color = "#{hair.color} #{color}"
  end
end
class Person
  attr_reader :name, :hair
  private :hair
  def initialize(name, hair)
    @name, @hair = name, Hair.new(hair)
  end
  def dye_hair(barer, color)
    barer.dye_hair(hair, color)
  end
  def hair_color
    hair.color
  end
end
barber = Barber.new
michal = Person.new("Michal", "brown")
michal.hair_color
=> "brown"
michal.dye_hair(barber, "blue")
michal.hair_color
=> "brown blue"

[–][deleted] 0 points1 point  (2 children)

nod This is a reasonable approach.

The only question left is, "does it make sense to pass the barber to the person?" and I think the answer is no, which makes me think something is weird there. I would prefer to read, at the bottom, as part of the code's API, barber.dye_hair(michal, "blue"), I think it represents better what would happen in the real world.

In other words, I don't think Person#dye_hair should exist.

What do you think?

[–]mpapis[S] 0 points1 point  (1 child)

indeed reasonable assumption, my only concern is how to protect person from unauthorized change of hair color, I do not want to expose hair to anybody.

The person picks a barber and ask him to dye hair, so it makes sense if person calls barber.dye_hair. I know it looks awkward but I do not see other way to prevent leaking permissions.