开发者

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

开发者 https://www.devze.com 2023-01-02 22:14 出处:网络
If I have a class that holds one or several lists, is it better to allow other classes to fetch those list开发者_运维问答s (with a getter)? Or to implement a doXyzList/eachXyzList type method for that

If I have a class that holds one or several lists, is it better to allow other classes to fetch those list开发者_运维问答s (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.


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.


It's better to do neither. Encapsulate those lists and instead move the operation itself inside the class, if possible. Then just call doSomeOperation() which will iterate however it wants internally, without exposing the inner data structures of your type.

For example, it's usually better design to do this:

public class ShoppingCart {
   private final List<CartItem> items;
   public double getTotal() {
      double total = 0;
      for ( CartItem item : items ) {
         total += item.getPrice() * item.getQuantity();
      }
      return total;
   }
}

Than to expose the list of items to make getTotal external to ShoppingCart.

If that's not possible or is very impractical, at the very least only provide non-modifiable views of those lists.


I think using a getter is nasty - it violates Principle of Least Knowledge. So the functor is better in this respect, but I think it could be more flexible still. You could take a strategy object, and pass the list to it so that it transforms it - rather than taking a function and iterating the list an running the function on it.

class A{

    List<B> bs;
    List<C> cs;

    // better than getter but could be better
    void transformBs(Functor f){
        for(B b : bs){
            f.transform(b);
        }
    }

    // more flexible
    void transformCs(Strategy s){
        s.transform(cs);
    }
}
interface Functor{ <T> void transform(T t);}
interface Strategy{ <T> void transform(List<T> list); }


You question is:

Is the a pattern for iterating over lists

I think there is just a simple pattern called "iterator", no?

Something like this: (example in Java)

public class ListHolder<T>
{
    private List<T> list = new ArrayList<T>();

    public Iterator<T> newIterator()
    {
        return new Iterator();
    }

    public class Iterator <T>
    {
        int index = 0;
        public T next()
        {
            return list.get(index++);
        }
        public boolean hasNext()
        {
            return list.size() > index;
        }
    }
}


I think it would be better to pass the object with the logic to the container class and let it invoke the method.

For instance, if the Scheduler is the class with the logic, Person the class with the collections, and SchedulePref the collection I would rather have:

 Person {
    - preferences: Preference[]

     + scheduleWith( scheduler: Scheduler ) {
            preferences.each ( pref: Preference ) {
                  scheduler.schedule( pref )
            }      
       }
  }

And in the client

 Client {
      main() {
          scheduler = SchoolScheduler.new
          person    = Person.withId( 123 )
          person.scheduleWith( scheduler )
      }
 }

And have a subclass for the concrete Scheduler having a different algorithm each.

It would be like receive the block, but instead of exposing the internals of the holder ( Person ) and the internals of the logic (the Scheduler) you pass the function also encapsulated in the "logic class ( the Scheduler )

Think about this. If you get sick you take a pill, and internally, you may think of it acting in your internals ( may be, internally you know some pill must go to the lungs and not to the liver etc. )

The other way it would be like: Give me all your organs and then you do something on them outside of the body. That's not something good.


I (personally) dislike "doXList/eachXList" and list.for_each(Action) functions.
By definition these methods perform side effects, and side effects should be flagged.

General language features along the lines of foreach(item in collection) are obvious and well known, and - in my opinion - sufficient.

It's a personal decision, and Eric Lippert speaks about it in this blog post. The post is C# specific, but the arguments apply to languages in general, even though they are phrased in terms of C# and the C# compiler.

Hope this helps

0

精彩评论

暂无评论...
验证码 换一张
取 消