【问题标题】:How to include properties via composition?如何通过组合包含属性?
【发布时间】:2018-03-27 11:01:15
【问题描述】:

在我第三次不得不重构当前项目中的继承链后,我在 Google 上搜索“继承很烂”,发现我遇到的问题并不少见,组合是推荐的替代解决方案。

我了解如何使用组合以函数的形式添加行为,但我在想出以相同方式添加属性的方法时遇到了问题。

假设我想为树节点建模。每个节点至少有两个属性:名称和描述。

class Node {
   public string Name { get; set; }
   public string Description { get; set; }
}

其他更具体的节点会继承这些属性,如下所示:

class StructuredNode : Node {
   public List<Node> Children { get; set; }
}

如何在不依赖继承和随之而来的问题的情况下实现属性代码的类似可重用性?
是否有这种设计模式,还是在这种情况下我必须使用继承?

提前致谢!

编辑: “组合优于继承”的例子:

【问题讨论】:

  • 添加了带有指针的编辑。
  • public List&lt;Node&gt; Children; 是一个字段,而不是一个属性(只是有点挑剔)
  • 改变了定义。
  • “不依赖继承的属性代码的类似可重用性” 我想到了装饰器模式。
  • 是的,我只是想举个例子,它似乎对属性来说超级流畅......

标签: c# oop inheritance design-patterns composition


【解决方案1】:

然后取决于类,你应该依赖子抽象(这也是使用组合的一部分)所以对于你的情况你应该这样做

public interface INode {
   string Name { get; set; }
   string Description { get; set; }
}

class Node : INode {
   public string Name { get; set; }
   public string Description { get; set; }
}

class StructuredNode : INode {
   public string Name { get; set; }
   public string Description { get; set; }
   public List<INode> Children { get; set; }
}

或者你也可以这样做

//this is something similar to decorator pattern.
class StructuredNode  {
   private readonly INode _node;
   public StructureNode(INode node)
   {
      _node = node;//now you can reuse function of any class which implements INode
   }

   public List<INode> Children { get; set; }
}

你以后也应该这样做

   List<Node> nodes = List<Node>();
   StructuredNode sNode = new StructuredNode();
   sNode.Children  = nodes; 

这是可能的,因为一切都基于抽象。现在所有实现都使用INode

在评论中建议您的其他解决方案是使用Decorator pattern。如果你只想扩展你的类而不修改它。

【讨论】:

  • 这通过重复所有类中的所有代码解决了我的继承缺乏可读性的问题。我喜欢。但这并不能解决问题,如果您发现新案例,您必须进行大量重构。例如,我刚刚发现我想要的节点仅用于构建我的视图模型,因此没有描述。
  • @RubenBohnet - 如果您想按原样使用其他类功能,我建议使用构造函数注入,我在回答中更新了...
【解决方案2】:

如何在不依赖继承和随之而来的问题的情况下归档属性代码的类似可重用性?

使用继承的替代方法是接口或组合。但是,特别是对于属性,您有点卡住了。

  • 接口不能像基类一样包含默认实现。因此,虽然您可以强制您的类使用正确的“组合属性结构”,但如果不在每个实现接口的类中实现可重用方法(或者你可以吗?休息后更多! em>)
  • Composition 在 C# 中根本不存在,您可以动态地将属性添加到类中(除非您对 Dictionary&lt;string,string&gt; 感到满意)。可能有一些人为的方法可以技术上让它发挥作用,但这不是一个好方法。

接口+扩展方法

这里可以使用扩展方法来替换您在继承的基类中找到的可重用逻辑。

这样做有一个缺点:您希望在扩展方法中访问的属性必须是接口协定的一部分并且可以公开访问。
除了这个缺点之外,它还能满足您的所有其他要求。


首先,一个基于继承的例子:

public class Property
{
    public string Name { get; set; }
    public string Value { get; set; }
}

public class PropertyComposedObject
{
    public List<Property> Properties { get; set; }

    public Property GetProperty(string name)
    {
        return this.Properties.SingleOrDefault(x => x.Name == name);
    }
}

public class Person : PropertyComposedObject
{

}

如果我们改用接口,我们将无法获得诸如共享GetNode(string) 方法之类的好处。您可以将其添加为接口的一部分,但随后每个实现类都将负责实现该方法(导致您在各处复制/粘贴相同的方法)。

没有扩展方法的接口示例:

public class Property
{
    public string Name { get; set; }
    public string Value { get; set; }
}

public interface IPropertyComposedObject
{
    List<Property> Properties { get; set; }

    Property GetProperty(string name);
}

public class Person : IPropertyComposedObject
{
    public List<Property> Properties { get; set; }

    public Property GetProperty(string name)
    {
        return this.Properties.SingleOrDefault(x => x.Name == name);
    }
}

但是扩展方法允许我们定义可重用方法一次,但仍然可以从每个实现接口的类中访问它:

public class Property
{
    public string Name { get; set; }
    public string Value { get; set; }
}

public interface IPropertyComposedObject
{
    List<Property> Properties { get; set; }
}

public class Person : IPropertyComposedObject
{
    public List<Property> Properties { get; set; }
}

public static class IPropertyComposedObjectExtensions
{
    public Property GetProperty(this IPropertyComposedObject obj, string name)
    {
        return obj.Properties.SingleOrDefault(x => x.Name == name);
    }
}

【讨论】:

  • 很好的答案!但我想我需要再读几遍,直到我真正明白它是否能解决我的问题。
  • @RubenBohnet:简单地尝试一下可能是最简单的;这就是为什么我给出了三个例子(继承、接口、接口+扩展)。复制/粘贴代码(包括您的 Node 类),然后尝试执行类似 var personAge = myPerson.GetNode("Age"); 的操作,您会发现它适用于所有三种情况,但只有第三个示例避免了 both 继承并在每个实现类中复制/粘贴相同的方法逻辑。
  • 谢谢。在我的项目中尝试后,我会回复你。很好奇它是否适用于我的 wpf 可视化!
  • @Fildor:我将Node 类作为单个属性,因此使用List&lt;Node&gt; 作为属性列表。如果 OP 的意图是将属性列表添加到 Node 类;那么我应该修改我的答案以使用 MyProperty 类而不是 Node 类。但剩下的答案还是一样的。 (重新阅读问题,我确实错误地认为Node 是属性的表示。我会更新答案。)
  • @MohammadaminKhayat 它如何降低可测试性?此外,虽然它确实偏离了 C# 通常的做事方式,但ECS 是一个设计良好的系统。无论您称它们为组件还是属性,都是命名的问题。检查给定实体是否具有给定组件会花费额外的开销,但它确实使您摆脱了过于严格的类型和继承。
【解决方案3】:

我尽量减少代码重复的尝试:

interface INodeProperties
{
    string Name { get; set; }
    string Description { get; set; }
}

class NodeProperties : INodeProperties
{
    public string Name { get; set; }
    public string Description { get; set; }
}

interface INode
{
     INodeProperties NodeProps { get; set; }
}

class Node : INode
{
     public INodeProperties NodeProps { get; set; } = new NodeProperties();
}

interface IStructuredNode
{
      List<Node> Children { get; set; }
}

class StructuredNode: INode, IStructuredNode
{
     public INodeProperties NodeProps { get; set; } = new NodeProperties();
     public List<Node> Children { get; set; }
}

缺点:再“跳”一次即可到达实际的属性... :(

【讨论】:

  • 我喜欢这种方法。我会继续朝这个方向思考。
  • @RubenBohnet 我刚刚想到:如果您的属性都是字符串,并且您需要您的节点真正灵活地处理它们,那么仅使用Dictionary&lt;string,string&gt; 怎么样?但我猜你想使用绑定,对吧?
  • 这不只是将List&lt;Node&gt; 包装在NodeProperties 类中吗?虽然没有真正改变List&lt;Node&gt; 在内部的工作方式?这似乎只是增加了一个冗余层。还是我错过了什么?
  • 不,不是列表。所有其他属性。我认为 OP 确实有一个相当长的通用属性列表。所以我的目标是在向节点添加子节点功能的同时尽量减少代码重复。
  • @Fildor 由于附加了一个 wpf 可视化,我宁愿不改变我的问题中暗示的视图模型如何向外部展示自己。如果没有其他帮助,我会继续你的思路!
【解决方案4】:

有一个INode接口,封装了常用属性。

这样你应该有自动属性,然后避免将逻辑放在属性的getter和setter中,因为你不能重用这个逻辑。

那么重复自动属性定义并不重要,也不影响可重用性。

如果需要属性变更通知,最好使用postsharp等拦截器库。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-08
    • 1970-01-01
    • 2021-04-14
    • 2011-09-17
    • 2019-10-01
    • 1970-01-01
    • 2013-01-03
    • 1970-01-01
    相关资源
    最近更新 更多