Strategy pattern / interface design arrangement

Discussion in 'Ruby' started by Daniel Waite, Aug 21, 2006.

  1. Daniel Waite

    Daniel Waite Guest

    Hi all. I've got a design question.

    Imagine you have a module, CreditCardProcessor. Inside
    CreditCardProcessor is another module called AuthorizeNet.
    CreditCardProcessor acts as an interface that AuthorizeNet must
    implement. (This is, from what I understand, a reasonably accurate
    implementation of the Strategy pattern (behavioral implementation behind
    a common interface). However, if you feel I could be more accurate,
    don't hesitate to say so. :)

    Now imagine we have a class named Acme that extends class Company.
    Company includes CreditCardProcessor and has an instance variable called
    @credit_card_processor.

    Now Acme has all the tools it needs to process cards. However, do we
    say...

    @company.process_credit_card(card) # A pass-through (convenience) method

    ... or...

    @company.credit_card_processor.process_card(card)

    If you were designing (or have designed) such an arrangement, which is
    your preferred method and why? (Or if you have an entirely different
    suggestion I'm all ears.)

    Thanks, much!

    - Daniel
     
    Daniel Waite, Aug 21, 2006
    #1
    1. Advertisements

  2. Daniel Waite

    snacktime Guest

    I would probably not put CreditCardProcessor inside Company. I would
    leave it as a stand alone module and call it when needed.
     
    snacktime, Aug 21, 2006
    #2
    1. Advertisements

  3. I find it odd that AuthorizeNet is a module and that it's contained in
    CreditCardProcessor which you said defines the interface. First, you
    don't have interfaces in Ruby (and you don't need them). Second,
    nesting the "interface" and classes that implement it seems weird to me.
    If we assume for the moment that we are using a language with
    interfaces then the main point of them is, that you can have arbitrary
    classes implement them. And these classes can reside in whatever
    package or namespace.
    Why does Company include CreditCardProcessor if it's just an interface?
    It always depends... I'd probably go with the second approach because
    that does less cluttering of class Company's public interface / signature.

    Kind regards

    robert
     
    Robert Klemme, Aug 21, 2006
    #3
  4. Daniel Waite

    Ilan Berci Guest

    Some of the GOF patterns don't translate to a dynamically typed language
    such as Ruby. With duck typing, there is no need to explicitly set the
    interface that the internal strategy component will adhere to and your
    implementation is free to swap strategy components just as you would in
    a statically typed language but with the added flexibility of not having
    to explicitly set the interface before hand..
     
    Ilan Berci, Aug 21, 2006
    #4
  5. Daniel Waite

    Daniel Waite Guest

    I realize Ruby doesn't explicitly support the concept of an interface
    via a keyword (convention over configuration anyway, right?). However,
    that doesn't mean I can't program inline with the _concept_ of an
    interface. The _concept_ of design by contract. It's those concepts I'm
    trying to utilize because I believe they're helpful.
    That's the hallmark of the Strategy pattern.
    Yes and no. Mostly no in my situation. The point of using an interface
    _in this case_ is to encapsulate a defined set of behaviors into many
    possible implementations. We may well add another payment processor to
    our arsenal later down the road. Provided the interface is strong enough
    I should be able to code to that interface and not be able to tell which
    implementation I'm using.
    To this end I'm not certain. Looking at my examples again I suppose it
    doesn't make much sense to include it into Company.

    CreditCardProcessor.process_credit_card(@company.credit_card, order)

    Would make more sense. Or does it? :(

    What's GOF?
    Not only is there no need, there's no way to explicitly set the
    interface in Ruby. But I want to act as though it's explicit, that it
    must be adhered to.
    Aye, I think I'm with you on this. Thanks! :)
     
    Daniel Waite, Aug 21, 2006
    #5
  6. Daniel Waite

    Kenosis Guest

    GOF = Gang of Four (the authors of the "original" Design Patterns"
    text.)
     
    Kenosis, Aug 21, 2006
    #6
  7. Daniel Waite

    Daniel Waite Guest

    Ah... I'm currently reading Kathy Sierra's Head First Design Patterns.
    Kinda sucks because it's written in and for Java programmers, so my
    translations are sometimes rough.

    Thanks for the heads up. :)
     
    Daniel Waite, Aug 21, 2006
    #7
  8. Yes, but Modules in Ruby provide implementation, so they don't really
    make good reifications of the concept.
    Here I have to politely disagree. The hallmark of the Strategy
    pattern is the provision of a configurable service object (which the
    GOF calls a Context) which clients can configure by giving it
    different ConcreteStrategy objects, and the separation of
    implementation of the Context and the various ConcreteStrategies,
    through the use of an interface definition called a Strategy.

    So one way of looking at the strategy in the pattern is that it
    identifies the species of duck that the context is looking for when
    the client configures it.

    But this can be done as documentation or commentary.
    But again, there's no real need or advantage to reify it in a Module.
    And if you populate the Module with 'unimplemented' methods, and
    require the ConcreteStrategies to include it, it actually obscures
    whether or not a given ConcreteStrategy really implements the
    Strategy.
    It's not a feature of the Strategy pattern, which seems to be about
    allowing a client to configure an internal implementation choice.
    Having the Context export the strategy interface to the client seems
    to be more related to Facade or Proxy, but not quite.

    GOF = [Erich_Gamma, Richard_Helm, Ralph_Johnson, John_Vlissides]

    These were the authors of the book "Design Patterns" which was first
    to really popularize the idea of patterns which was "discovered" by
    Ward Cunningham and Kent Beck in the writings of Architect (houses
    and towns) Christopher Alexander, and developed into a burgeoning
    movement including a cadre called the Hillside group which the book
    brought out into the open. http://c2.com/cgi/wiki?HistoryOfPatterns

    GOF.map { | author | author.native_language } => [[Swiss_German, C++],
    [Strine, C++], [American, Smalltalk], [American, C++]]

    I used to hang out fairly regularly with all four of them, and worked
    with all but Ralph at IBM, OTI or both. I used to kid GOF[2] that he
    didn't hold up his end of the stick as the lonely dynamic language
    guy. <G> Sadly I recently discovered that GOF[3] passed away about a
    year ago.

    With the 3-1 composition of the GOF as guys with an abstract-data type
    background, IMHO most of the patterns put too much emphasis on C++ish
    implementation artifacts such as type-implementation confusion, and
    templates (In fact in the implementation section on the Strategy
    pattern they suggest cases where you might use C++ templates to
    configure the Context rather than defining a Strategy interface).

    Had Ralph pushed a bit harder, the book might well have abstracted
    some of those concepts to duck types (Smalltalk had them, but I can't
    recall that we used THAT name, it was most often roles or something
    else http://talklikeaduck.denhaven2.com/articles/2006/07/26/my-favorite-duck-typing-story
    ), and folks might look on both the GOF patterns, and languages like
    Ruby and Smalltalk in a different light.
     
    Rick DeNatale, Aug 21, 2006
    #8
  9. Daniel Waite

    Phrogz Guest

    Perhaps read the article that you cite:
    http://talklikeaduck.denhaven2.com/articles/2006/07/26/my-favorite-duck-typing-story


    The short of it is: two different classes of objects are
    interchangeable if they both support the same (named) methods that code
    using those objects invoke. You don't need static-typing or
    compile-type checking. You don't need interfaces. You don't need
    contracts. You just need to have the methods available that you are
    going to call.
     
    Phrogz, Aug 22, 2006
    #9
  10. Daniel Waite

    Daniel Waite Guest

    Wow. Okay, your description of the Strategy pattern doesn't mean a lot
    to me without an example. (Sorry if that sounds harsh; it's not my
    intention.) Could you provide an example? Are such patterns even worth
    pursuing in Ruby?
    We're using a module now, actually. In the event another payment
    processor comes along do you feel it should simply be another module?
    (That's interesting, that a module provides implementation. I've not
    heard that said anywhere else, but maybe because it was considered
    obvious?)
    You're saying one class is given another, configurable class, which is
    configurable by giving a ConcreteStrategy object? Example? :(

    Confused, much! I suppose if I fall back on my earliest Ruby teachings,
    modules implement behavior (e.g. Enumerable) and classes mix-in that
    behavior. Gah... I'm reading three books and the information presented
    is frying my brain. (Head First Design Patterns, XP Explained and Object
    Thinking.)

    Anywho, thanks for all the help thus far. :)
     
    Daniel Waite, Aug 22, 2006
    #10
  11. Daniel Waite

    Daniel Waite Guest

    Thank you! That's what I needed to hear! :) :) :)

    I figured as much. From my experience with PHP 4, with weak OO support,
    you could still code in a manner similar to C++ or Java without all the
    rules _enforcing_ a particular paradigm.

    I think what you said can be summed up in: Code to an interface, not an
    implementation. Granted, that mentions one of the things you said you
    don't need, but here's my codified understanding of it:

    Implementation:

    @authorize_net = AuthorizeNet.new
    @authorize_net.process_card(card)

    Interface:

    @payment_processor = AuthorizeNet.new
    @payment_processor.process_card(card)

    That may be a weak example, but I think it illustrates the point. When
    the "physical" variable I'm using tells me more about the object that it
    should. E.g. "this is an AuthorizeNet object," versus "this is some kind
    of credit card processor."

    Thanks all!
     
    Daniel Waite, Aug 22, 2006
    #11
  12. Daniel Waite

    khaines Guest

    An example in real life.

    IOWA apps receive an Iowa::Request object that encapsulates all of the
    details about the request to be handled. Because different source
    environments for the request, such as mod_ruby, FCGI, Webrick, Mongrel,
    etc... themselves make the request information available in different
    ways, the Iowa::Request needs to know how to initialize itself if it is
    given an Apache::Request object from mod_ruby or a Mongrel request object.

    It's very ugly to have all of that platform specific code piled up in
    Iowa::Request, though.

    So, the solution that I settled on is to duck type.

    Iowa::Request has a method, new_request(). I waffled for a while about
    just overriding new(), but in the end, decided against it. Anyway, an
    instance of Iowa::Request is now never created. It simply serves as a
    superclass to define a common set of accessors and make some common code
    available to its subclasses, and to act as the factory to create the real
    request objects.

    Each of the subclasses implements the same API, but the
    Iowa::Requests::Apache class takes an Apache::Request to initialize
    itself, while the Iowa::Requests::WEBrick takes a webrick request object.
    Each subclass knows how to take care of it's own specific details.

    The Iowa::Request.new_request() method just provides a call to get a new
    request. No code except for the code in new_request() cares about the
    identity of the actual Request objects, and all it does is identify which
    request class is needed, require the code if it has not already been
    required, and then create an object of the class, which it returns.
    Nothing else cares what it is, so long as it has the expected methods.

    This seems like it might be a reasonable pattern for your credit card
    processors, and it is easy to implement.

    You have some method that you call to access or retrieve a processor.
    Each of the real credit card processor classes implement the same API, so
    none of the rest of your code has to care at all what they are. They are
    just a thingy that you feed card and purchase info to; some lights flash,
    there's some beeping and maybe a gear clack or two, and it spits back a
    response indicating what happened during the processing.

    cc_processor = CreditCardProcessor.new_processor(company)
    buyers.each {|b| b.purchases.each {|p| cc_processor.process(b,p)}}


    Kirk Haines
     
    khaines, Aug 22, 2006
    #12
  13. These requirements are sufficiently important that we give them a
    There is a lot of power in identifying a cohesive set of type
    requirements and giving them a name as it allows communication and
    thought to move up a level so we can think of the requirements as a
    single entity rather than thinking repsonds_to? :this and
    responds_to? :that. This is much the same as using iterators rather
    than explicit loops in our code.

    A group of type requirements that is cohesive and is given a name
    also increases the likelihood that code from various sources will be
    written that both conforms to and works with types conforming to the
    requirements. A nice example of this is the large number of types in
    Ruby which work with Enumerable by implementing each and possibly
    having members that implement <=>.

    I believe it would be of benefit to the Ruby community to name more
    of these cohesive sets of type requirements and give them informal
    definitions as they are identified. STL provides examples of many
    sets of type requirements, although STL concepts do not fit the Ruby
    Way very well at all and of course would need to change quite
    significantly in order to feel natural in Ruby. The collection
    concepts and function object concepts in particular are very well
    thought out and could be very applicable in more clearly defining
    some common "duck types" in Ruby.
    Although interestingly they are likely to be a part of the language
    in the future. There are a variety of Technical Reports published
    that are being considered by the standards committee, thus
    illustrating how the C++ approach is different from Ruby as there is
    a strong desire in the C++ community to remove the freedom and find a
    way to statically check duck types.

    Matthew
     
    Matthew Johnson, Aug 22, 2006
    #13
  14. Yes, but you have to adjust the implementation to the language at hand.
    I really don't understand why you bring in modules here and seem to keep
    insisting on them. Each strategy should be its own class IMHO. You
    can, if you want to have some form of reuse or general behavior, give
    them all a common superclass. But that's it.
    See the Wikipedia page. "Context" in the UML diagram is your Company,
    "Strategy" is your Processor base class (if you have it at all).
    I guess *this* is your real problem: you need some more understanding -
    once you get that all will be clear.

    Kind regards

    robert
     
    Robert Klemme, Aug 22, 2006
    #14
  15. I haven't seen anyone mention it, and I think it's important to know...
    The name 'Duck Typing' comes from the saying "if it walks like a duck
    and quacks like a duck, it must be a duck."
    (http://peak.telecommunity.com/DevCenter/MonkeyTyping)

    In short, if a type/class/whatever has all the methods of a duck (that
    you intend to use), it's a duck and can be used as a duck would be used,
    regardless of what's actually under the feathers.

    In other words, TCPSocket doesn't just inherit from Socket, Object,
    etc... It IS a Socket, it IS an Object, etc.
     
    William Crawford, Aug 22, 2006
    #15
  16. While this is true, it's not a particularly good example to explain duck typing.

    Duck typing is not about what an object "is", but what it can be used
    for. Whether an object can be used for something depends on who is
    using it.

    Let's look at DT's example from the Pickaxe. Cutting the code down to
    the bare essentials we've got:

    def csv_from_row(op, row)
    #code to compute res from row
    op << res << CRLF
    end

    Now what kind of duck does op need to be? Simply an object which
    accumulates objects via the << method.

    His first use was:

    result = ""
    query.each_row {|row| csv_from_row(result, row)
    http.write result

    This works, Strings meet the requirements of the op parameter, but
    they also have other characteristics. The important one here is the
    subtle effect on GC. For a more detailed analysis of this see why's
    article http://whytheluckystiff.net/articles/theFullyUpturnedBin.html

    So let's try to find another object which we can put in the role of
    the op parameter. Arrays also can be used. So we have

    result = []
    query.each_row {|row| csv_from_row(result, row)
    http.write result

    However we've now got a little problem in that although an array works
    as the op parameter, http.write probably won't like it. But that's
    easy to fix:

    http.write result.join

    Back when I was younger, and object[-oriented] programming was fairly
    new, my friends and I spent a lot of time philosophizing about types
    and objects. Although I don't remember talking about ducks, we did
    talk alot about the theater. The most common word we used to use for
    what we now call a duck type was a role.

    So when we duck type, we're in a position similar to a director
    casting for a role in a play. Sometimes, we find a great actor, but
    we need to subtly adapt the script to fit his style. That's what's
    happening in DT's pickaxe example.

    Another way of looking at this, is that duck typing is akin to the way
    the American President is chosen. An assessment is performed on the
    candidates, and then the country and world adapts.

    Contrast this to the way things are done in picking the next British
    monarch, or the type systems of C++ or Java. In both cases one has to
    be born into the role, having the right genes via inheritance. Of
    course in the first case, who gets the job doesn't matter all that
    much anymore.
     
    Rick DeNatale, Aug 22, 2006
    #16
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.