Java 如何处理复合模式中的添加、删除功能?

Java 如何处理复合模式中的添加、删除功能?,java,oop,design-patterns,composite,Java,Oop,Design Patterns,Composite,复合模式对于处理部分-整体层次结构非常有用。它有一个用于组件的接口Leaf和Composite都提供了组件接口的实现 对于Leaf类中的Add(Component c)、Remove(Component c)和GetChild(int position)方法,应该实现什么 我可以让方法什么都不做,或者抛出一个异常,比如:operationnotsupportedbyleafafexception但这样做将打破利斯科夫替换原则。处理这些方法的最佳方法是什么 编辑:另一种方法是在合成中移动这些方法。

复合模式
对于处理部分-整体层次结构非常有用。它有一个用于
组件的接口
Leaf
Composite
都提供了
组件
接口的实现

对于
Leaf
类中的
Add(Component c)
Remove(Component c)
GetChild(int position)
方法,应该实现什么

我可以让方法什么都不做,或者抛出一个异常,比如:
operationnotsupportedbyleafafexception
但这样做将打破利斯科夫替换原则。处理这些方法的最佳方法是什么

编辑:另一种方法是在合成中移动这些方法。它将是暴露的最顶层接口,即组件。当需要调用
add
remove
操作时,在Composite中移动方法需要显式转换,这同样违反了良好的设计原则


有两种方法可以解决这个问题

  • 抛出
    操作NotSupportedByLeafException
    。如果这一点被打破,你可以进行辩论。我个人认为应该避免使用它,但有时它是最好的解决方案(例如,请参阅Java的不可变列表)。LSP原则旨在帮助您编写质量更好的代码。每个系统都有疣,这可能是其中之一

  • 将添加和删除移动到复合实体()。这是您通常在视图库中看到的内容。视图可以是一块文本,也可以是一个包含许多其他视图的复杂布局


  • 当然,这取决于您的设计目标是什么。如果您的目标是树/图在任何时间点都可以修改,那么叶实际上可以成为父节点。在这种情况下,可以在组件中定义层次结构相关的方法

    另一种方法(尽管我不知道这是否适用于您的用例)是使用以下两种想法:

    • 使结构不可变
    • 结构与功能分离
    通过使结构不可变,我们可以将所有图形构造推给构造函数。这样我们就避开了你提到的类型转换问题,也使整个事情更容易推理

    通过将结构与功能分离,我们根本不需要向客户端发布结构信息,而是提供我们想要提供的功能。有了这个,我们可以保留利斯科夫、德米特和其他OO的东西

    这就是它的样子:

    公共接口节点{
    //这里我们提供了“功能”,但我们没有
    //发布“结构”。这是我编的
    //不知道你的用例。
    作废流程(消费者付费消费者);
    }
    公共类叶实现节点{
    专用有效载荷;
    公共导线(有效载荷){
    这个有效载荷=有效载荷;
    }
    @凌驾
    公共作废流程(消费者付费消费者){
    payloadConsumer.accept(有效载荷);
    }
    }
    公共类ParentNode实现节点{
    私人名单儿童;
    公共父节点(节点…子节点){
    this.children=asList(children);
    }
    //这里我们递归地实现处理。
    //所有的孩子都能根据他们的语意做出反应,
    //而不是假设任何超出我们所知的结构。
    @凌驾
    公共作废流程(消费者付费消费者){
    children.forEach(child->child.process(payloadConsumer));
    }
    }
    
    当然,您可以根据要表示的逻辑类型定义自己的
    节点
    类型。您可以定义多个操作,而不仅仅是我编写的一个
    process()
    方法。然后,您可以像这样将所有这些连接在一起:

    节点图=新的父节点(
    新父节点(新叶(p1)、新叶(p2)),
    新特辑af(a,b,c)//随便什么
    );
    
    #2。您不能在叶子中添加或删除任何内容--请将代码移动到复合文件中。@cyroxis,是的,第二个选项也是一种方法,我将编辑我的问题,将其也包括在内。但它也有一个问题,组件接口将被公开,为了调用添加、删除操作,需要显式类型转换,这又违反了良好的设计原则。@nits.kk为什么需要显式类型转换?这不是模式所必需的(可能是您的设计所必需的,如果需要,请更新您的问题,并提供更多详细信息)。即使如此,您也会经常发现好的设计原则可能会相互冲突。不是每个问题都有一个干净的解决方案,有时你会选择最不混乱的。我修改了这个问题。需要显式类型转换,因为它将是最顶部的接口组件,将被公开,复合模式旨在表示整个零件层次结构,并以类似的方式处理叶和复合结构……听起来,您计划使用复合结构的方式可能涉及
    铸造
    未报告操作异常
    ,在这两种情况下,如果您做错了什么,您都会出错。示例:Android使用findViewById之后的强制转换,但对于渲染和布局,对于父级来说,子级是叶结构还是复合结构并不重要。由于它的使用方式,以及早期(视图绑定)和后期(视图操作)的错误,它工作得非常好。我喜欢在Composite的cunstuctor中添加叶子的想法。但通常在基于ui的用例中,我们可能有例如TextView、Button、Label(每种类型为leaf)和其他视图,如LinearLayout、RelativeLayout(每种类型为Composite),在这种情况下,leaf永远不会变为Composite,但在运行时,可以将leaf和composites添加到父复合中或从父复合中移除。因此,构造函数方法将不起作用,这是一个很好的例子!出于好奇,你为什么称它为
    消费者
    而不是
    访问者
    ?这只是因为
    java。