Search code examples
design-patternsdictionarylistoopdynamic-typing

Is the a pattern for iterating over lists held by a class (dynamicly typed OO languages)


If I have a class that holds one or several lists, is it better to allow other classes to fetch those lists (with a getter)? Or to implement a doXyzList/eachXyzList type method for that list, passing a function and call that function on each element of the list contained by that object?

I wrote a program that did a ton of this and I hated passing around all these lists, sometimes with method in class A calling method in class B to return lists contained in class C. B contains a C or multiple C's.

(note this question is about dynamically typed OO languages languages like ruby or smalltalk)

ex. (that came up in my program):

on a Person class containing scheduling preferences and a scheduler class needing to access them.


Solution

  • There is no single answer - design is about juggling priorities, tradeoffs and comprimises, to arrive at something that works well in your sitluation. I'll briefly cover the relative merits and drawbacks of using functors, accessors and full encapsulation.

    Functors

    Using functors can be convenient, and avoids boilerplate iteration. This also allows you to cleanly separate what you are executing for each item in the list from when you execute it. With a for-each loop, the two are most often coupled together. Where functors don't work is if you need to perform an operation on multiple lists, either from the same object, or from multiple objects, or if you only need a few elements of the list. Use of functors constrains execution order - items must be used in the order iterated by the provider. The functor has no control. This can be a blessing, and also a curse.

    The example of Person, scheduling preferences and a Scheduler, the scheduler could provide an external iterator for possible schedule times:

       schedules = scheduler.schedules(person.getSchedulePreferred())
    

    The getSchedulePreferred() returns a predicate that selects the schedules from all those available that are preferred by the given person.

    Iterating across all schedules may not be the most efficient way of implementing this. Say, if the person only wants schedules in June, then all schedules for the rest of the year will be wastefully iterated.

    Accessors

    Making the lists available via gtters can be beneficial when implementing functionality that is not intrinsic to the class. For example, given two Orders, find the items that they have in common. This simple to implement if the lists are provided as getters for external traversal, and very simple if the lists are provide in some known order (e.g. if the Order has a getSortedItems() method.) The complexity here is managing mutability of the list, although many languages have direct support to disable mutation (e.g. const in C++) or wrapping the list in an immutable wrapper.

    For the example, the person could expose the list of schedule preferences, which are then used directly by the scheduler. The scheduler has the opportunity to be "smart" about how the preferences are applied, e.g. it could build a query to a datbase to fetch matching schedules based on the persons preferences.

    Encpasulation

    The third alternative is to question if external access is required. It's one symptom of an anemic domain model that objects are all properties and no behaviour. But don't strive to put behaviour in a domain object just to avoid this anti-pattern - the behavior should be a natural part of that object's responsibility.

    I don't think this applies here - person, scheduler and scheduling preference clearly fulfill different roles and have clear responsibilities. Adding a method on one entity that tries to compute data from another would be an unnecessary tight coupling.

    Summary

    In this particular case, my preference is for the getter, since it allows the scheduler more control over how the schedule preferences are used, rather than being "force-fed" them through a functor.