简体   繁体   English

是迭代类持有的列表的模式(动态类型的面向对象语言)

[英]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)?如果我有一个包含一个或多个列表的类,是否允许其他类获取这些列表(使用 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?或者为该列表实现doXyzList/eachXyzList类型方法,传递一个函数并在该对象包含的列表的每个元素上调用该函数?

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 .我写了一个程序,做了很多这样的事情,我讨厌传递所有这些列表,有时用A类中A方法调用B类中A方法来返回C类中包含的列表。 B contains a C or multiple C 's. B包含一个C或多个C

( note this question is about dynamically typed OO languages languages like ruby or smalltalk) 注意这个问题是关于动态类型的面向对象语言,比如 ruby​​ 或 smalltalk)

ex.前任。 (that came up in my program): (出现在我的程序中):

on a Person class containing scheduling preferences and a scheduler class needing to access them.在包含调度首选项的Person类和需要访问它们的scheduler类上。

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.对于 for-each 循环,两者最常耦合在一起。 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: Person、调度首选项和调度程序的示例,调度程序可以为可能的调度时间提供外部迭代器:

   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. getSchedulePreferred() 返回一个谓词,该谓词从给定人员首选的所有可用时间表中选择时间表。

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.比如说,如果这个人只想要 6 月份的时间表,那么今年剩余时间的所有时间表都将被浪费地重复。

Accessors配件

Making the lists available via gtters can be beneficial when implementing functionality that is not intrinsic to the class.在实现非类固有的功能时,通过 gtters 使列表可用可能是有益的。 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 (eg 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 (eg const in C++) or wrapping the list in an immutable wrapper.如果列表作为外部遍历的 getter 提供,则实现起来很简单,如果以某种已知顺序提供列表(例如,如果 Order 具有getSortedItems()方法)则非常简单。这里的复杂性是管理列表的可变性,尽管许多语言直接支持禁用突变(例如 C++ 中的 const)或将列表包装在不可变包装器中。

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, eg 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.在这种特殊情况下,我更喜欢 getter,因为它允许调度程序更好地控制如何使用调度首选项,而不是通过函子“强制馈送”它们。

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.然后只需调用doSomeOperation() ,它将在内部进行任何它想要的迭代,而不会暴露您的类型的内部数据结构。

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.而不是公开项目列表以使getTotal位于 ShoppingCart 外部。

If that's not possible or is very impractical, at the very least only provide non-modifiable views of those lists.如果这是不可能的或非常不切实际,至少只提供这些列表的不可修改的视图。

I (personally) dislike "doXList/eachXList" and list.for_each(Action) functions.我(个人)不喜欢“doXList/eachXList”和list.for_each(Action)函数。
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. foreach(item in collection)通用语言特征是显而易见的和众所周知的,并且 - 在我看来 - 足够了。

It's a personal decision, and Eric Lippert speaks about it in this blog post .这是个人决定,Eric Lippert 在这篇博文中谈到了这一点 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.这篇文章是特定于 C# 的,但这些论点通常适用于语言,即使它们是根据 C# 和 C# 编译器来表述的。

Hope this helps希望这可以帮助

I think using a getter is nasty - it violates Principle of Least Knowledge.我认为使用 getter 很讨厌——它违反了最少知识原则。 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)像这样:(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:例如,如果Scheduler是具有逻辑的类, Person是具有集合的类,而SchedulePref是我宁愿拥有的集合:

 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.并且有一个具体Scheduler的子类,每个子类都有不同的算法。

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 )这就像接收块一样,但不是公开持有者( Person )的内部结构和逻辑( Scheduler )的内部结构,而是传递也封装在“逻辑类( 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.那可不是什么好事。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM