2

如果我有一个包含一个或多个列表的类,是否允许其他类获取这些列表(使用 getter)更好?doXyzList/eachXyzList或者为该列表实现一个类型方法,传递一个函数并在该对象包含的列表的每个元素上调用该函数?

我写了一个程序,做了很多这样的事情,我讨厌传递所有这些列表,有时使用类中的方法A调用类中的方法B来返回包含在类中的列表CB包含一个C或多个C's。

注意这个问题是关于动态类型的 OO 语言,如 ruby​​ 或 smalltalk)

前任。(出现在我的程序中):

Person包含调度首选项的scheduler类和需要访问它们的类上。

4

6 回答 6

4

没有单一的答案 - 设计是关于兼顾优先级、权衡和妥协,以达到在您的情况下运作良好的东西。我将简要介绍使用函子、访问器和完全封装的相对优点和缺点。

函子

使用仿函数很方便,并且避免了样板迭代。这还允许您将列表中每个项目的执行内容与执行时完全分开。在 for-each 循环中,两者最常耦合在一起。仿函数不起作用的地方是,如果您需要对多个列表执行操作,无论是来自同一个对象,还是来自多个对象,或者您只需要列表的几个元素。仿函数的使用限制了执行顺序 - 项目必须按照提供者迭代的顺序使用。函子无法控制。这可能是一种祝福,也可能是一种诅咒。

以人、调度偏好和调度器为例,调度器可以为可能的调度时间提供一个外部迭代器:

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

getSchedulePreferred() 返回一个谓词,该谓词从给定人员首选的所有可用时间表中选择时间表。

遍历所有计划可能不是实现这一点的最有效方式。比如说,如果这个人只想要 6 月份的时间表,那么今年剩余时间的所有时间表都将被浪费地迭代。

访问器

在实现非类固有的功能时,通过 gtters 使列表可用是有益的。例如,给定两个订单,找出它们共有的项目。如果列表作为外部遍历的 getter 提供,这很容易实现,如果列表以某种已知的顺序提供(例如,如果 Order 有getSortedItems()方法)则非常简单。这里的复杂性是管理列表的可变性,尽管许多语言直接支持禁用突变(例如 C++ 中的 const)或将列表包装在不可变包装器中。

例如,该人可以公开日程偏好列表,然后由日程安排程序直接使用。调度程序有机会“聪明”地了解如何应用偏好,例如,它可以构建对数据库的查询,以根据个人偏好获取匹配的调度。

封装

第三种选择是质疑是否需要外部访问。对象都是属性而没有行为是贫血域模型的一个症状。但是不要为了避免这种反模式而努力将行为放在域对象中 - 行为应该是该对象职责的自然部分。

我认为这不适用于这里 - 人员、调度员和调度偏好显然履行不同的角色并有明确的职责。在一个实体上添加一个尝试从另一个实体计算数据的方法将是不必要的紧密耦合。

概括

在这种特殊情况下,我更喜欢 getter,因为它允许调度程序更好地控制调度首选项的使用方式,而不是通过仿函数“强制输入”它们。

于 2010-06-08T19:18:39.503 回答
3

最好都不做。如果可能的话,封装这些列表,并将操作本身移动到类中。然后只需调用doSomeOperation()which 将在内部进行迭代,而不会暴露您类型的内部数据结构。

例如,这样做通常是更好的设计:

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;
   }
}

比将要getTotal在 ShoppingCart 外部制作的项目列表公开。

如果这不可能或非常不切实际,至少只提供这些列表的不可修改视图。

于 2010-06-08T14:00:11.673 回答
0

我(个人)不喜欢“doXList/eachXList”和list.for_each(Action)函数。
根据定义,这些方法会产生副作用,并且应该标记副作用。

大致的语言特征foreach(item in collection)是显而易见的和众所周知的,并且 - 在我看来 - 足够了。

这是个人决定,Eric Lippert 在这篇博文中谈到了这一点。这篇文章是特定于 C# 的,但这些论点通常适用于语言,即使它们是根据 C# 和 C# 编译器来表述的。

希望这可以帮助

于 2010-06-08T13:57:06.397 回答
0

我认为使用吸气剂很讨厌 - 它违反了最少知识原则。所以函子在这方面更好,但我认为它还可以更灵活。您可以获取一个策略对象,并将列表传递给它,以便它对其进行转换 - 而不是获取一个函数并迭代列表并在其上运行函数。

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); }
于 2010-06-13T12:21:08.180 回答
0

你的问题是:

是迭代列表的模式吗

我认为只有一个简单的模式叫做“迭代器”,不是吗?

像这样:(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;
        }
    }
}
于 2010-06-14T20:21:30.073 回答
0

我认为将带有逻辑的对象传递给容器类并让它调用该方法会更好。

例如,如果Scheduler是具有逻辑Person的类,具有集合的类,以及SchedulePref我宁愿拥有的集合:

 Person {
    - preferences: Preference[]

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

在客户端

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

并且有一个具体的子类,Scheduler每个子类都有不同的算法。

这就像接收块,但不是暴露持有者的内部(Person)和逻辑的内部(Scheduler),而是传递也封装在“逻辑类(Scheduler) ”中的函数

想想这个。如果你生病了,你会吃药,在内部,你可能会认为它在你的内部起作用(可能是,在内部你知道一些药必须进入肺部而不是肝脏等。)

另一种方式是:把你所有的器官给我,然后你在体外对它们做点什么。那不是什么好事。

于 2010-06-14T21:01:30.070 回答