2

将包含对象作为参数传递给包含对象的方法是否是糟糕的设计,如下面的简化示例所示?

class A
{
    private B _containedObject;

    public A(B b)
    {
            _containedObject = b;
            //...
    }

    public void SomeMethod()
    {
            //...
            _containedObject.SomeMethod(this);
            //...
    }
}

class B
{
    public void SomeMethod(A a)
    {
            //do something with a
    }
}

Ps 上面的例子是简化的,只是为了说明包含关系以及将包含对象传递给被包含对象,本身并没有说明这样做的目的。尽管有目的,但请放心。

4

6 回答 6

2

在这个简单的例子中,它看起来像是一种双重分派。在这种情况下,我会将 A 作为 B 的方法参数传递。

void SomeMethod(A a)
{
    a.DoSomething(this);
}

然而,这只是一个例子。在其他时候,将对象作为 ctor 参数传递可能更有益。

于 2012-01-03T16:21:45.887 回答
1

不,这不是糟糕的设计,而且经常使用。但是,您必须小心这样做,以免导致内存泄漏。当您将容器对象传递给内部对象时,它将持有对父对象的引用,直到内部对象被删除。

这意味着如果你更新了一个 A,并且它更新了 ab,如果你让所有其他引用过期,A 仍然存在,因为 B 持有一个副本。

于 2012-01-03T16:19:19.560 回答
1

如果对象的父对象存在并且在构造对象时已知,并且该对象永远不会有任何其他父对象,那么让每个子对象在构造时保持对父对象的引用通常很有用。然后可以要求子对象做一些涉及其父对象的事情,而不必将父对象作为参数传递,因为子对象已经知道它的父对象是谁。

这个概念的一个问题是,根据类的作用,人们可能希望允许父类型的对象限制谁可以创建以它们为父对象的对象。如果父类型和子类型在同一个程序集中,这可以通过internal子构造函数上的修饰符轻松完成。如果希望允许父对象可能是在尚未编写的程序集中定义的类的可能性,但为此类父对象提供编译时保证,即未经他们的“同意”它们不会被“附加”,事情是有点棘手,但可以通过使用私有构造函数和公共工厂方法来相当好地完成:

公共接口 IAssuredParent<T>
static childClass createAttachedChild<T,U>(T parent) where T:IAssuredParent<U>
{...};

如果父类定义了P私有类XYZ并实现IAssuredParent<XYZ>了,那么它将是唯一可以访问类型的类XYZ,因此也是唯一可以成功调用createAttachedChild的类,该类具有一对可以容纳类型实际参数P并符合类型约束的类型参数。 .

于 2012-01-03T17:08:20.480 回答
1

有时您可能希望将包含对象作为参数传递给包含对象。正如其他海报所指出的那样,它被称为“双重调度”。Smalltalk 语言提供了一个经典的例子。

该示例是关于将整数和浮点数(或其他类型的数字)相加。在 C++ 或 Java 中,您的整数类将具有多态方法,例如:

Integer {
     add(Float) {...}
     add(Integer) {...}
     add(UserDefinedType) {...}
}

每个 add 方法都可以正确实现不同类型的加法。编译器使用类型信息来选择正确的方法。语句 Integer(6).add(Float(1.0)) 将调用 Integer::add(Float) 方法。

Smalltalk 与 C++ 和 Java 不同,它是动态类型的。Smalltalk 类只能有一个名为add的方法。

整数::添加(对象)

编译器不知道“对象”的类别。出于这个原因,Smalltalk 无法像 C++ 或 Java 那样实现添加。相反,Smalltalk 使用双重调度。它是这样工作的:

   Integer {
          add(Object o) {
              return o.addToInteger(this)
          }        
    }

其他数字类如 Float、Fraction 甚至 Integer 都实现了 addToInteger() 方法。

Float {
      addToInteger(Object o) {
          //Perform float + in math
          return result
     }
}

双分派解决了如何在动态类型语言中多态地实现人工操作的问题。但是还有一个更常见的用途:访问者模式。我会把它放在一个单独的答案中。

于 2012-01-04T05:33:41.907 回答
1

也许使用这种技术最常见的原因是访问者模式。我很难在网上找到我喜欢的示例,所以我将只使用 Wikipedia 文章中的类图。看看这张图:http: //upload.wikimedia.org/wikipedia/commons/5/59/VisitorPatternUML.png

现在想象一个接受访问者对象的汽车部件:

Engine myEngine;
CarElementPrintVistor myVisitor;
myEngine.accept(myVisitor);

但是在 accept() 方法中,汽车部分右转并将自身传递给访问者!

myVisitor.visit(this)

查看访问者设计模式以获取更多信息。

于 2012-01-04T05:50:42.053 回答
0

在我看来,为了改进它,这不是一个很好的设计,您可以声明对象 A 的接口并在如下之后声明您的 SomeMethod:

void SomeMethod(IMyInterface a)

那么您的 A 类将如下所示:

public class A : IMyInterface
{
  ....
}

所以这个方法会在实现中给出一个抽象。

一般来说,更好的设计==更少的关系。

于 2012-01-03T16:25:49.680 回答