Forum: Ruby Ruby's take on S.O.L.I.D.

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Mike L. (Guest)
on 2008-11-03 21:56
I'm curious about everyone's take on Bob Martin's S.O.L.I.D. design
principles. They seem to be getting a lot of traction with statically
typed languages such as C# and Java but I haven't heard much buzz about
it in the Ruby community.

Any thoughts on how duck typing impacts the usefulness or necessity of
these design principles? For instance, I don't really hear much
regarding Liskov's Substitution Principle around Ruby programmers and am
interested in opinions as to why.

Regards,
Mike
Stefan R. (Guest)
on 2008-11-03 22:24
Mike L. wrote:
> I'm curious about everyone's take on Bob Martin's S.O.L.I.D. design
> principles. They seem to be getting a lot of traction with statically
> typed languages such as C# and Java but I haven't heard much buzz about
> it in the Ruby community.
>
> Any thoughts on how duck typing impacts the usefulness or necessity of
> these design principles? For instance, I don't really hear much
> regarding Liskov's Substitution Principle around Ruby programmers and am
> interested in opinions as to why.
>
> Regards,
> Mike

Do you have any links on it? Or do we have to google ourselfs? :)

Regards
Stefan
Mike L. (Guest)
on 2008-11-03 22:39
Stefan R. wrote:
> Mike L. wrote:
>> I'm curious about everyone's take on Bob Martin's S.O.L.I.D. design
>> principles. They seem to be getting a lot of traction with statically
>> typed languages such as C# and Java but I haven't heard much buzz about
>> it in the Ruby community.
>>
>> Any thoughts on how duck typing impacts the usefulness or necessity of
>> these design principles? For instance, I don't really hear much
>> regarding Liskov's Substitution Principle around Ruby programmers and am
>> interested in opinions as to why.
>>
>> Regards,
>> Mike
>
> Do you have any links on it? Or do we have to google ourselfs? :)
>
> Regards
> Stefan

Here are some good links I've been looking at.

http://www.lostechies.com/blogs/chad_myers/archive...

http://mmiika.wordpress.com/oo-design-principles/

Mike
Jeff C. (Guest)
on 2008-11-03 23:31
(Received via mailing list)
On Nov 3, 2:38 pm, Mike L. <removed_email_address@domain.invalid> wrote:
> >> interested in opinions as to why.
I was a SOLID developer in my C# days, but I don't feel it applies as
well to dynamic languages like Ruby.

The S, I still agree with: single responsibility per class.

The O is something I'm on the fence about.  Ruby allows you to open
classes and modify them, and I've seen how valuable this is when you
know what you're doing.

The LID (substitution, inteface granularity, dependency injection) are
just non-issues for me with Ruby.  I enjoy duck-typing so much more
than worrying about interface granularities, and I don't need to use
dependency injection anymore (which I really only used to make things
more testable, which I can do with Mocha nowadays anyway).

I'd be interested in other viewpoints on this, but I think SOLID is
great advice only for statically-typed languages and isn't quite as
important with a language like Ruby.

For me, the SOLID equivalent would go like this:

Single Responsibility per Class
Drive your development with unit tests
Have fun

...but "SDH" isn't as easily pronounced as SOLID. :-)

Jeff

www.purpleworkshops.com
Mike L. (Guest)
on 2008-11-05 00:47
Jeff C. wrote:
> On Nov 3, 2:38�pm, Mike L. <removed_email_address@domain.invalid> wrote:
>> >> interested in opinions as to why.
> I was a SOLID developer in my C# days, but I don't feel it applies as
> well to dynamic languages like Ruby.
>
> The S, I still agree with: single responsibility per class.
>
> The O is something I'm on the fence about.  Ruby allows you to open
> classes and modify them, and I've seen how valuable this is when you
> know what you're doing.
>
> The LID (substitution, inteface granularity, dependency injection) are
> just non-issues for me with Ruby.  I enjoy duck-typing so much more
> than worrying about interface granularities, and I don't need to use
> dependency injection anymore (which I really only used to make things
> more testable, which I can do with Mocha nowadays anyway).
>
> I'd be interested in other viewpoints on this, but I think SOLID is
> great advice only for statically-typed languages and isn't quite as
> important with a language like Ruby.
>
> For me, the SOLID equivalent would go like this:
>
> Single Responsibility per Class
> Drive your development with unit tests
> Have fun
>
> ...but "SDH" isn't as easily pronounced as SOLID. :-)
>
> Jeff
>
> www.purpleworkshops.com

Thanks for the feedback. I was wondering specifically about the LID part
and how important they were for a static vs duck type language. I've
been out of Java for some time and by default now think like a dynamic
language.

For example, the typical Liskov Substitution example you see in Java has
the case where you have a Rectangle class and a derived Square class.
The Rectangle has an independent width and height. They have to be the
same for the Square. Somewhere in the example they do something like
this:

Rectangle r = Rectangle.new( Square.new() );

Then they proceed to show that the methods for setting the width and
height for a Rectangle, behave like a Square and you get unexpected
results. While I understand the logic, I must confess this construct
feels really odd from a Ruby mindset. I'm trying to remember why this is
so important for statically typed language. Does it have to do with
something like collections or another mechanism?

Mike
Dean W. (Guest)
on 2008-11-05 01:38
(Received via mailing list)
I actually blogged about a few of them on the OM blog a few months ago.

http://blog.objectmentor.com/articles/2008/09/06/t...

http://blog.objectmentor.com/articles/2008/09/04/t...

I need to blog about the others, for completeness.

I think if you take them at face value, they don't fit well, because
they seem too oriented towards statically-typed languages. However, if
you look at the underlying reasons for their existence and their
intentions, then you can adapt them to ruby. For example, I think the
Open/Closed Principle adapts nicely to open types, as I blogged.

For another example, this is true even for the Interface Segregation
Principle (ISP), despite the fact that Ruby doesn't have Java-style
interfaces, per se. What it has, of course, are informal interfaces
required by the "duck type" ;)

Hope this helps.

dean

On Tue, Nov 4, 2008 at 4:45 PM, Mike L. <removed_email_address@domain.invalid>
wrote:
> > classes and modify them, and I've seen how valuable this is when you
> > important with a language like Ruby.
> >
> same for the Square. Somewhere in the example they do something like
>
> Mike
> --
> Posted via http://www.ruby-forum.com/.
>



--
Dean W.
http://www.objectmentor.com
http://www.polyglotprogramming.com
http://www.aspectprogramming.com
http://aquarium.rubyforge.org
http://www.contract4j.org
Robert K. (Guest)
on 2008-11-05 09:59
(Received via mailing list)
2008/11/3 Mike L. <removed_email_address@domain.invalid>:
> I'm curious about everyone's take on Bob Martin's S.O.L.I.D. design
> principles. They seem to be getting a lot of traction with statically
> typed languages such as C# and Java but I haven't heard much buzz about
> it in the Ruby community.
>
> Any thoughts on how duck typing impacts the usefulness or necessity of
> these design principles? For instance, I don't really hear much
> regarding Liskov's Substitution Principle around Ruby programmers and am
> interested in opinions as to why.

Probably we do it all the time - and not only with sub classes but
with completely unrelated classes (-> duck typing). :-)

IMHO you used an important word: "buzz".  These principles have their
value where applicable but should never be followed religiously. It
does makes sense though to reason about them and understand their
value so you can judge when to apply and when not.

Another reason for the observed lack of "buzz" might be that the Ruby
community generally seems to be more focused on getting things done
and not less interested in, err, theoretical debate than maybe other
communities. ;-) (Which, btw, does not mean that there are enough
people around here with brilliant ideas and excellent solutions.)

Just a few random thoughts...

Kind regards

robert
Karl von Laudermann (Guest)
on 2008-11-05 16:31
(Received via mailing list)
On Nov 4, 5:45 pm, Mike L. <removed_email_address@domain.invalid> wrote:
> For example, the typical Liskov Substitution example you see in Java has
> the case where you have a Rectangle class and a derived Square class.
> The Rectangle has an independent width and height. They have to be the
> same for the Square.

One guiding principle when designing a class heirarchy is that the
class with fewer data members should be the superclass, and the class
with more data members should be the subclass. This sometimes leads to
cases which are counterintuitive with respect to the way we think of
things in real life. For example, Rectangle should be a subclass of
Square, not the other way around. This is because the Square class
only needs one dimension member variable, e.g. "width", and the
Rectangle subclass needs to add a second one, e.g. "height".
Dean W. (Guest)
on 2008-11-05 18:13
(Received via mailing list)
On Wed, Nov 5, 2008 at 8:29 AM, Karl von Laudermann
<removed_email_address@domain.invalid> wrote:
> things in real life. For example, Rectangle should be a subclass of
> Square, not the other way around. This is because the Square class
> only needs one dimension member variable, e.g. "width", and the
> Rectangle subclass needs to add a second one, e.g. "height".
>

Interesting idea. However, you have to be careful with this. Is a
Rectangle always substitutable for a Square? If you have mutable
states, then the following test will fail:

class Square
  attr_accessor :height
  def initialize height
    @height = height
  end
  def area
    height * height
  end
end
class Rectangle < Square
  attr_accessor :width
  def initialize height, width
    super(height)
    @width = width
  end
  def area   # this override is where the trouble starts...
    height * width
  end
end

require 'rubygems'
require 'spec'

describe Square, "height vs. width" do
  it "should keep the height and width equal" do
    [Square.new(2), Rectangle.new(2,2)].each do |s|
      p s.inspect
      s.height = 4
      s.area.should == 16   # fails for Rectangle
    end
  end
end

Clients of Square have a "contract" with the class that the height ==
width, which affects the area. Rectangle doesn't have this
restriction. Usually, we think of subclasses as "narrowing" the range
of allowed behaviors.

I think it's great that you can nail down these details with
tests/specs!

dean
Pat M. (Guest)
on 2008-11-05 21:53
Dean W. wrote:
> On Wed, Nov 5, 2008 at 8:29 AM, Karl von Laudermann
> <removed_email_address@domain.invalid> wrote:
>> things in real life. For example, Rectangle should be a subclass of
>> Square, not the other way around. This is because the Square class
>> only needs one dimension member variable, e.g. "width", and the
>> Rectangle subclass needs to add a second one, e.g. "height".
>>
>
> Interesting idea. However, you have to be careful with this. Is a
> Rectangle always substitutable for a Square? If you have mutable
> states, then the following test will fail:
>
<snip> to make ruby-forum happy...
> Clients of Square have a "contract" with the class that the height ==
> width, which affects the area. Rectangle doesn't have this
> restriction. Usually, we think of subclasses as "narrowing" the range
> of allowed behaviors.
>
> I think it's great that you can nail down these details with
> tests/specs!
>
> dean

I think the answer is "it depends."  I would argue that the specs you
gave are great for verifying that the implementation of Square works,
but not so great at defining the contract.  The contract for calculating
area may be a bit fuzzy to spec out, but I think you can be happy by
saying that the area should be greater than one of the sides.  Now a
Rectangle is substitutable for a Square.

Pat
Dean W. (Guest)
on 2008-11-06 14:27
(Received via mailing list)
On Wed, Nov 5, 2008 at 1:51 PM, Pat M. <removed_email_address@domain.invalid> 
wrote:
>> Rectangle always substitutable for a Square? If you have mutable
>>
>> dean
>
> I think the answer is "it depends."  I would argue that the specs you
> gave are great for verifying that the implementation of Square works,
> but not so great at defining the contract.  The contract for calculating
> area may be a bit fuzzy to spec out, but I think you can be happy by
> saying that the area should be greater than one of the sides.  Now a
> Rectangle is substitutable for a Square.
>
> Pat

Right. That's why I said "clients of Square have a contract". It's an
interesting feature of Liskov substitution that it doesn't just
involve the types in isolation; it also involves users of the types.
There are certainly situations where the extra constraint on
"real-world" squares isn't an issue.
Curator1 (Guest)
on 2008-11-06 18:44
(Received via mailing list)
On Nov 5, 6:27 am, Karl von Laudermann <removed_email_address@domain.invalid>
wrote:
> On Nov 4, 5:45 pm, Mike L. <removed_email_address@domain.invalid> wrote:


> things in real life. For example, Rectangle should be a subclass of
> Square, not the other way around. This is because the Square class
> only needs one dimension member variable, e.g. "width", and the
> Rectangle subclass needs to add a second one, e.g. "height".

The guiding principle is wrong.  And it's an egregious error I keep
seeing repeated.  Lest a whole new generation of newbies follow this,
lets name the real principle involved in the design of superclasses:
abstraction based on common conceptual denominators, which in cs terms
means essential common datamembers and essential common behavior gets
aggregated to superclasses.  This has NOTHING to do with # of
datamembers.

Repeating: Superclasses are based on common conceptual denominators
(CCD)not on the number of data members.

That in many-to-most cases there are fewer datamembers in the
superclass is true, but not the guiding principle. And classically one
uses the hierarchy to enforce certain constraints based on those CCDs.
Also the simplicity of the example gives false security -- broadening
the problem in question regards general closed shapes.  For example.
if I were designing this, the hierarchy in Ruby psuedocode, to capture
the essential nature of the shapes, you have to encode that nature of
a closed polygon which means closure, no self intercept for any
enclosed area by arbitrary points.  THEN you encode regular polygon
behavior as sides of same length and then, if you need to, name
squares, pentagons, hexagons as regular polygons and rectangles and
similar as closed polygons.

Now purists will scream but "square ARE rectangles!".  Which is poor
thinking. The correct statement is "Squares and Rectangles are four-
sided closed shapes.  Squares have equal sides, rectangles do not."
As always, your *model* and its general power defines the flexibility
of your application.  Ruby pseudocode for clarity below.

class ClosedPolygon

@arrayPoints
@orientation
@anchor
def initialize(arrPoints)
    if NoSelfIntercept==true
        raise NotAClosedPolygonException
        end
    if MoreThanOneIntercept==true
        raise NotAProperPolygonException
end
def draw
end
def size
return @arrayPoints.length
end
def NoSelfIntercept
end
etc
end

class RegularPolygon < ClosedPolygon
def initialize(arrPoints)
     if checkSidesEqualLength(arrPoints)!=true
        raise NotRegularPolygonException
     end
     @arrayPoints=arrpoints
end
def checkSidesEqualLength
end
end

class Square < RegularPolygon
def initialize(arrPoints)
    super.initialize(arrPoints)
    if self.size!=4
       raise NotASquareException
       end
end
end

class Rectangle < ClosedPolygon  # See!!!  CCD's properly
differentiate
                                 # Square from Rectangle on Essential
characteristics
etc
end

which allows of course the proper definitions of things like

class Pentagon < RegularPolygon
etc
end

class IsoTriangle < ClosedPolygon
etc
end

class EquiTriangle < RegularPolygon
etc
end

which has little to do with datamembers (most are in the superclasses)
and all to do with the common conceptual denominators of abstraction
and where behavior is properly defined.

My best to all.
Mike L. (Guest)
on 2008-11-06 18:52
Dean W. wrote:
> On Wed, Nov 5, 2008 at 1:51 PM, Pat M. <removed_email_address@domain.invalid> wrote:
>>> Rectangle always substitutable for a Square? If you have mutable
>>>
>>> dean
>>
>> I think the answer is "it depends."  I would argue that the specs you
>> gave are great for verifying that the implementation of Square works,
>> but not so great at defining the contract.  The contract for calculating
>> area may be a bit fuzzy to spec out, but I think you can be happy by
>> saying that the area should be greater than one of the sides.  Now a
>> Rectangle is substitutable for a Square.
>>
>> Pat
>
> Right. That's why I said "clients of Square have a contract". It's an
> interesting feature of Liskov substitution that it doesn't just
> involve the types in isolation; it also involves users of the types.
> There are certainly situations where the extra constraint on
> "real-world" squares isn't an issue.

An interesting approach that I recently read about was that the "is-a"
relationship should really be thought of as a "behaves-as-a"
relationship where OO is concerned. In this case a square really doesn't
behave like a rectangle. In this sense, a square is not a rectangle.
Michael L. (Guest)
on 2008-11-06 21:36
(Received via mailing list)
On Thu, Nov 6, 2008 at 10:38 AM, Curator1 <removed_email_address@domain.invalid> 
wrote:

> Repeating: Superclasses are based on common conceptual denominators
> (CCD)not on the number of data members.

Agreed.

[following code sample shortened for clarity]
> end
>
> class Pentagon < RegularPolygon
> end
>
> class IsoTriangle < ClosedPolygon
> end
>
> class EquiTriangle < RegularPolygon
> end

Can we take the principal of common conceptual denominators further by
using modules in addition to superclasses?

class Rectangle < ClosedPolygon
end

class Square < Rectangle
  include RegularPolygon
  def initialize
     super()
     check_sides_are_equal_length #defined in RegularPolygon
end

-Michael L.
Curator1 (Guest)
on 2008-11-07 17:02
(Received via mailing list)
On Nov 6, 11:34 am, Michael L. <removed_email_address@domain.invalid> wrote:
> > class ClosedPolygon
>
> using modules in addition to superclasses?
>
> -Michael L.

Yes.  Modules offer all sorts of exciting possibilities if behavior
can be coded generically enough with the subclasses providing key
operations that enable the mixin. It's one aspect of Ruby that I'm
learning to love. Definitely superior to interfaces, as say in Java.
E.g. the relationship of Sets and Enumerable is one example.

I didn't mean to be pedantic in the prior post.  It's just I was
confronted recently with a complicated hierarchy where someone used
all sorts of principles to derive the hierarchy except the right one.
We were in Java and so the above came out in single-inheritance
style.  Your module comment is well-received.
Ken B. (Guest)
on 2008-11-09 17:40
(Received via mailing list)
On Mon, 03 Nov 2008 16:29:54 -0500, Jeff wrote:

>> >> regarding Liskov's Substitution Principle around Ruby programmers
>> >> and am interested in opinions as to why.
>
> I was a SOLID developer in my C# days, but I don't feel it applies as
> well to dynamic languages like Ruby.
>
> The S, I still agree with: single responsibility per class.
>
> The O is something I'm on the fence about.  Ruby allows you to open
> classes and modify them, and I've seen how valuable this is when you
> know what you're doing.

One could espouse a design school in Ruby that used open classes to
implement O, with rules such as "don't override existing methods when
reopening a class, only add new ones". But Ruby as a language doesn't
enforce this, and there are other kinds of changes that one could do for
O that are actually quite complicated in terms of coding. For example,
in
my SqlStatement gem, I monkeypatch DBI to accept an SQL statement object
as a query. It's probably an extension, but the facilities needed to do
it look a lot like modification.

I think Groovy's categories and optional type specifications make this
are constructs that would make O easier to enforce in a language.

But from what I see, the OpenClosedPrinciple was about refactorings, and
not just about class extension. And in this sense, it sucks, because you
can only implement the OpenClosedPrinciple if you have a perfect crystal
ball about what you'll need in the future. (See http://c2.com/cgi/wiki?
OpenClosedPrinciple which points out that the Xtreme Programming
principle of YouArentGonnaNeedIt is in total conflict, since that
recommends avoiding the crystal ball completely.)
This topic is locked and can not be replied to.