【问题标题】:Exposing a List<domain objects> via an IList<interface>通过 IList<interface> 公开 List<domain objects>
【发布时间】:2013-09-01 17:10:25
【问题描述】:

我有一个List&lt;T&gt; 类型的私有成员,其中T 是丰富的域对象。我的应用程序中的域包含许多需要 T 的方法。

我只想公开IList&lt;IT&gt; 类型的公共属性,其中T : ITIT 占用空间小,由 DTO 实现。

由于我无法将List&lt;T&gt; 转换为IList&lt;IT&gt;,因此我在属性声明中使用List&lt;T&gt;.ConvertAll

有没有更好的方法来做到这一点?更快、更优雅?

编辑以提供更多细节

T 是一个基类,存在许多派生类,每个派生类都有许多不同的风格(在运行时加载的配置)。表示层中的用户可以添加/更改/删除任何配置的这些派生类的任何实例。实例也可以由用户定向地相互链接,但是有一些复杂的规则来管理允许的链接;有些在编译时已知,有些仅在运行时已知(基于配置)。有些实例可能是双联的,有些是交联的,有些是单向的,有些是单向的,有些则完全没有。

为此,T 包含任何此类链接的有效目标列表。表示层以图形方式突出显示这些有效目标,如果目标不在有效列表中,则不允许链接。当新创建、更改或删除任何实例时,每个实例的 ValidTargets 列表都需要重新评估,并且可能会发生变化。

T 类上还有很多其他成员和方法,工厂和服务类期望对其进行操作。有些行为与上面的示例非常相似。这些都不应该暴露在程序集之外。

【问题讨论】:

  • 它需要是可变的吗?
  • Stefan 正如@SLaks 所暗示的那样。这取决于您希望客户实际使用该列表做什么。他们是否允许添加或从中删除?它是否在多个客户端之间共享?它本身是否代表一个领域概念(例如命令、查询……)?等等。这取决于您的用例。
  • 如果你的列表只读的,你可以使用IReadOnlyList
  • 是的,它确实需要是可变的。

标签: c# list domain-driven-design ilist convertall


【解决方案1】:

假设你可以做这样的事情:

// NOT REAL CODE
public interface IMyInterface { } 
public class MyRealClass : IMyInterface { } 

...

public class Domain
{
    private List<MyRealClass> myList;

    public IList<IMyInterface> Interfaces { get { return (IList<IMyInterface>)this.myList; } }
}

如何阻止 Domain 类的用户这样做?

public class MyFakeClass : IMyInterface { } 

...

domain.Interfaces.Add(new MyFakeClass());

显然这会有问题,因为myList 实际上是List&lt;MyRealClass&gt;,但编译器 不能确保只有MyRealClass 的实例被添加到列表中(它需要运行-时间检查)。换句话说,这种行为不是类型安全的,所以编译器不会允许的。

您可以公开IMyInterfaceIList / ICollection — 这是类型安全的,但不能确保只有您的MyRealClass 被添加到列表中。

public class Domain
{
    private List<IMyInterface> myList;

    public IList<IMyInterface> Interfaces { get { return this.myList; } }
}

或者,您可以公开IMyInterfaceIEnumerable(或IReadOnlyList,正如zmbq 所建议的那样)——因为类型参数是协变的(请参阅Covariance and Contravariance in Generics)。虽然这不允许您将新项目添加到集合中。

public class Domain
{
    private List<MyRealClass> myList;

    public IEnumerable<IMyInterface> Interfaces { get { return this.myList; } }
}

另一种解决方案是实现您自己的集合类,该集合类实际上实现了IList&lt;IMyInterface&gt;,但如果用户尝试插入除MyRealClass 之外的任何内容,则会引发异常。这不是一个特别优雅的解决方案,实际上,它与简单地暴露 IList&lt;MyRealClass&gt; 没有任何不同。

【讨论】:

  • 域中的一个内部工厂负责创建MyRealClass,所以我不怕收到MyFakeClass。真正的目标是隐藏内部 IP 并最小化 DTO 的大小。由于MyRealClass 有很多方法,我想避免让每个 DTO 为这些方法创建一个存根,因为接口需要一个。
  • 将此标记为答案。尽管我从未找到更快或更优雅的方法来解决通过 DTO 仅公开部分域的问题,但我认为您的回答解释了为什么您不能在两个不同类型的列表之间进行转换。
【解决方案2】:

如果我对您的理解正确,您应该可以使用 Linqs 的 select 方法将您的列表转换为 IList。所以你正在做类似的事情:

List<T> myList = ...
IList<IT> b = myList.Select(a=> a as IT).ToList();

【讨论】:

  • 这与myList.ConvertAll(a=&gt; a as IT) 相同。 SelectConvertAll 的惰性版本。
猜你喜欢
  • 1970-01-01
  • 2012-04-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-17
相关资源
最近更新 更多