如何构造两个对象,彼此作为参数/成员

时间:2021-12-09 17:11:47

I have two classes that each need an instance of each other to function. Ordinarily if an object needs another object to run, I like to pass it in the constructor. But I can't do that in this case, because one object has to be instantiated before the other, and so therefore the second object does not exist to be passed to the first object's constructor.

我有两个类,每个类都需要一个彼此的实例才能运行。通常,如果一个对象需要运行另一个对象,我喜欢在构造函数中传递它。但是在这种情况下我不能这样做,因为一个对象必须在另一个之前被实例化,因此第二个对象不存在被传递给第一个对象的构造函数。

I can resolve this by passing the first object to the second object's constructor, then calling a setter on the first object to pass the second object to it, but that seems a little clunky, and I'm wondering if there's a better way:

我可以通过将第一个对象传递给第二个对象的构造函数来解决这个问题,然后调用第一个对象上的setter将第二个对象传递给它,但这看起来有点笨重,我想知道是否有更好的方法:

backend = new Backend();
panel = new Panel(backend);
backend.setPanel();

I've never put any study into MVC; I suppose I'm dealing with a model here (the Backend), and a view or a controller (the Panel). Any insights here I can gain from MVC?

我从未对MVC进行任何研究;我想我在这里处理模型(后端),以及视图或控制器(Panel)。我可以从MVC获得任何见解吗?

6 个解决方案

#1


8  

It's time to take a look at MVC. :-) When you have a model-view-controller situation, the consensus is that the model shouldn't be aware of the view-controller (MVC often plays out as M-VC), but the view is invariably aware of the model.

现在是时候看看MVC了。 :-)当你有一个模型 - 视图 - 控制器的情况,一致的是模型不应该知道视图控制器(MVC经常扮演M-VC),但视图总是意识到模型。

If the model needs to tell the view something, it does so by notifying its listeners, of which it may have multiples. Your view should be one of them.

如果模型需要告诉视图某些东西,它通过通知其侦听器来实现,其中它可能具有倍数。你的观点应该是其中之一。

#2


2  

In a circular construction scenario I'd use a factory class/factory method. I would normally make the construction logic private to the factory (using friend construct, package level protection or similar), to en sure that no-one could construct instances without using the factory.

在循环施工场景中,我使用工厂类/工厂方法。我通常会将构造逻辑设置为工厂私有(使用友元构造,包级别保护或类似),以确保没有人可以在不使用工厂的情况下构造实例。

The use of setter/constructor is really a part of the contract between the two classes and the factory, so I'd just use whichever's convenient.

setter / constructor的使用实际上是两个类和工厂之间合同的一部分,所以我只使用方便的方法。

As has been pointed out, you really should try to find a non-circular solution.

正如已经指出的那样,你真的应该尝试找到一个非循环的解决方案。

#3


2  

First of all, contrary to what others has said here, there's no inherent problem with circular references. For example, an Order object would be expected to have a reference to the Customer object of the person who placed the Order. Similarly, it would be natural for the Customer object to have a list of Orders he has placed.

首先,与其他人在这里所说的相反,循环引用没有固有的问题。例如,Order对象应该具有对放置订单的人的Customer对象的引用。同样,Customer对象自然会有一个他所放置的订单列表。

In a refernce-based language (like Java or C#) there's no problem, at all. In a value-based language (like C++), you have to take care in designing them.

在基于引用的语言(如Java或C#)中,完全没有问题。在基于值的语言(如C ++)中,您必须注意设计它们。

That said, you design of:

那说,你的设计:

backend = new Backend();
panel = new Panel(backend);
backend.setPanel(panel);

It pretty much the only way to do it.

这几乎是唯一的方法。

#4


1  

It's better to avoid circular references. I would personally try to rethink my objects.

最好避免使用循环引用。我个人会尝试重新考虑我的对象。

#5


0  

panel = new Panel(backend);

You do this in this routine something like

你可以在这个例程中做到这一点

  Public Sub Panel(ByVal BackEnd as BackEnd)
        Me.MyBackEnd = BackEnd
        BackEnd.MyPanel = Me
  End Sub

You don't need BackEnd.SetPanel

您不需要BackEnd.SetPanel

It is better to use Proxies. A proxy links one object to another through raising a Event. The parent hands the child a proxy. When the child needs the parent it calls a GetRef method on the proxy. The proxy then raises a event which the parent uses to return itself to the proxy which then hands it to the child.

最好使用Proxies。代理通过引发事件将一个对象链接到另一个对象。父母将孩子交给代理人。当子需要父代时,它会在代理上调用GetRef方法。然后代理引发一个事件,父母用它将自己返回给代理,然后代理将其交给孩子。

The use of the Event/Delegate mechanism avoids any circular reference problems.

Event / Delegate机制的使用避免了任何循环引用问题。

So you have (assuming that the backend is the 'parent' here)

所以你有(假设后端是'父'这里)

  Public Sub Panel(ByVal BackEnd as BackEnd)
        Me.MyBackEnd = BackEnd.Proxy
        BackEnd.MyPanel = Me
  End Sub

  Public Property MyBackEnd() as BackEnd
     Set (ByVal Value as BackEnd)
        priBackEndProxy = BackEnd.Proxy
     End Set
     Get
        Return priBackEndProxy.GetRef
     End Get
  End Property

Here is a fuller discussion on the problem of circular references. Although it is focused on fixing it in Visual Basic 6.0.

以下是关于循环引用问题的更全面的讨论。虽然它专注于在Visual Basic 6.0中修复它。

Dynamic Memory Allocation

动态内存分配

Also another solution is aggregating Panel and BackEnd into another object. This is common if both elements are UI Controls and need to behave in a coordinated manner.

另一种解决方案是将Panel和BackEnd聚合到另一个对象中。如果两个元素都是UI控件并且需要以协调的方式运行,则这很常见。

Finally as far as MVC goes I recommend using a a Model View Presenter approach instead.

最后,就MVC而言,我建议使用Model View Presenter方法。

Basically you have your Form Implement a IPanelForm interface. It registers itself with a class called Panel which does all the UI logic. BackEnd should have events that Panel can hook into for when the model changes. Panel handles the event and updates the form through the IPanelForm interface.

基本上,您的表单实现了一个IPanelForm接口。它使用一个名为Panel的类来注册它,该类执行所有UI逻辑。 BackEnd应该具有Panel可以在模型更改时挂钩的事件。 Panel处理事件并通过IPanelForm接口更新表单。

  1. User clicks a button

    用户单击一个按钮

  2. The form passes to Panel that the user clicked a button

    表单传递给Panel,用户单击了一个按钮

  3. Panel handles the button and retrieves the data from the backend

    Panel处理按钮并从后端检索数据

  4. Panel formats the data.

    面板格式化数据。

  5. Panel uses IPanelForm Interface to show the data on the Form.

    Panel使用IPanelForm接口显示Form上的数据。

#6


0  

I've been delaying implementing the lessons learned here, giving me plenty of time to think about the exact right way to do it. As other people said, having a clear separation where the backend objects have listeners for when their properties change is definitely the way to go. Not only will it resolve the specific issue I was asking about in this question, it is going to make a lot of other bad design smells in this code look better. There are actually a lot of different Backend classes (going by the generic class names I used in my example), each with their own corresponding Panel class. And there's even a couple of places where some things can be moved around to separate other pairs of classes into Backend/Panel pairs following the same pattern and reducing a lot of passing junk around as parameters.

我一直拖延实施这里吸取的教训,给我足够的时间来考虑确切的正确方法。正如其他人所说的那样,在后端对象拥有监听器的时候,他们的属性发生了明显的分离,这绝对是可行的方法。它不仅会解决我在这个问题中提出的具体问题,而且会使这个代码中的许多其他糟糕的设计气味看起来更好。实际上有很多不同的Backend类(按照我在我的例子中使用的泛型类名称),每个类都有自己对应的Panel类。而且甚至有几个地方可以移动一些东西,将其他类对分成相同模式的后端/面板对,并减少大量的传递垃圾作为参数。

The rest of this answer is going to get language specific, as I am using Java.

这个答案的其余部分将使用特定语言,因为我正在使用Java。

I've not worried a whole lot about "JavaBeans," but I have found that following basic JavaBean conventions has been very helpful for me in the past: basically, using standard getters and setters for properties. Turns out there's a JavaBean convention I was unaware of which is really going to help here: bound properties. Bound properties are properties available through standard getters and setters which fire PropertyChangeEvents when they change. [I don't know for sure, but the JavaBeans standard may specify that all properties are supposed to be "bound properties." Not relevant to me, at this point. Be aware also that "standard" getters and setters can be very non-standard through the use of BeanInfo classes to define a JavaBean's exact interface, but I never use that, either.] (The main other JavaBean convention that I choose to follow or not as appropriate in each situation is a no-argument constructor; I'm already following it in this project because each of these Backend objects has to be serializable.)

我并不担心“JavaBeans”,但我发现以下基本的JavaBean约定对我来说非常有帮助:基本上,使用标准的getter和setter作为属性。原来有一个JavaBean约定我不知道哪个在这里真的会有所帮助:绑定属性。绑定属性是通过标准getter和setter提供的属性,它们在更改时触发PropertyChangeEvents。 [我不确定,但JavaBeans标准可能指定所有属性都应该是“绑定属性”。在这一点上与我无关。还要注意,通过使用BeanInfo类来定义JavaBean的确切接口,“标准”getter和setter可能非常非标准,但我也从不使用它。](我选择遵循的主要的其他JavaBean约定或在每种情况下都不合适的是无参数构造函数;我已经在这个项目中跟踪它了,因为每个后端对象都必须是可序列化的。)

I've found this blog entry, which was very helpful in cluing me into the bound properties/PropertyChangeEvents issue and helping me construct a plan for how I'm going to rework this code.

我找到了这个博客条目,这非常有助于我找到绑定属性/ PropertyChangeEvents问题,并帮助我构建一个计划,我将如何重做这段代码。

Right now all of my backend objects inherit from a common class called Model, which provides a couple of things every backend in this system needs including serialization support. I'm going to create an additional class JavaBean as a superclass of Model which will provide the PropertyChangeEvent support that I need, inherited by every Model. I'll update the setters in each Model to fire a PropertyChangeEvent when called. I may also have JavaBean inherited by a couple of classes which aren't technically Models in the same sense as these but which could also benefit from having other classes registered as listeners for them. The JavaBean class may not fully implement the JavaBean spec; as I've said, there are several details I don't care about. But it's good enough for this project. It sounds like I could get all this by inheriting from java.awt.Component, but these aren't components in any sense that I can justify, so I don't want to do that. (I also don't know what overhead it might entail.)

现在我的所有后端对象都继承自一个名为Model的公共类,它提供了系统中每个后端需要的一些东西,包括序列化支持。我将创建一个额外的类JavaBean作为Model的超类,它将提供我需要的PropertyChangeEvent支持,由每个Model继承。我将更新每个Model中的setter以在调用时触发PropertyChangeEvent。我也可能有几个类继承的JavaBean,这些类在技术上不是与这些类似的模型,但也可以从其他类注册为它们的监听器中受益。 JavaBean类可能无法完全实现JavaBean规范;正如我所说,有几个细节我不在乎。但这对这个项目来说已经足够了。听起来我可以通过继承java.awt.Component来获得所有这些,但这些不是任何意义上的组件,我可以证明这一点,所以我不想这样做。 (我也不知道它可能带来什么开销。)

Once every Model is a JavaBean, complete with PropertyChangeEvent support, I'll do a lot of code cleanup: Models that are currently keeping references to Panels will be updated and the Panels will register themselves as listeners. So much cleaner! The Model won't have to know (and shouldn't have known in the first place) what methods the Panel should call on itself when the property updates.

一旦每个Model都是一个JavaBean,完成了PropertyChangeEvent支持,我将做很多代码清理:当前保持对Panels的引用的模型将被更新,Panel将自己注册为监听器。太干净了!模型将不必知道(并且首先不应该知道)当属性更新时Panel应该调用哪些方法。

#1


8  

It's time to take a look at MVC. :-) When you have a model-view-controller situation, the consensus is that the model shouldn't be aware of the view-controller (MVC often plays out as M-VC), but the view is invariably aware of the model.

现在是时候看看MVC了。 :-)当你有一个模型 - 视图 - 控制器的情况,一致的是模型不应该知道视图控制器(MVC经常扮演M-VC),但视图总是意识到模型。

If the model needs to tell the view something, it does so by notifying its listeners, of which it may have multiples. Your view should be one of them.

如果模型需要告诉视图某些东西,它通过通知其侦听器来实现,其中它可能具有倍数。你的观点应该是其中之一。

#2


2  

In a circular construction scenario I'd use a factory class/factory method. I would normally make the construction logic private to the factory (using friend construct, package level protection or similar), to en sure that no-one could construct instances without using the factory.

在循环施工场景中,我使用工厂类/工厂方法。我通常会将构造逻辑设置为工厂私有(使用友元构造,包级别保护或类似),以确保没有人可以在不使用工厂的情况下构造实例。

The use of setter/constructor is really a part of the contract between the two classes and the factory, so I'd just use whichever's convenient.

setter / constructor的使用实际上是两个类和工厂之间合同的一部分,所以我只使用方便的方法。

As has been pointed out, you really should try to find a non-circular solution.

正如已经指出的那样,你真的应该尝试找到一个非循环的解决方案。

#3


2  

First of all, contrary to what others has said here, there's no inherent problem with circular references. For example, an Order object would be expected to have a reference to the Customer object of the person who placed the Order. Similarly, it would be natural for the Customer object to have a list of Orders he has placed.

首先,与其他人在这里所说的相反,循环引用没有固有的问题。例如,Order对象应该具有对放置订单的人的Customer对象的引用。同样,Customer对象自然会有一个他所放置的订单列表。

In a refernce-based language (like Java or C#) there's no problem, at all. In a value-based language (like C++), you have to take care in designing them.

在基于引用的语言(如Java或C#)中,完全没有问题。在基于值的语言(如C ++)中,您必须注意设计它们。

That said, you design of:

那说,你的设计:

backend = new Backend();
panel = new Panel(backend);
backend.setPanel(panel);

It pretty much the only way to do it.

这几乎是唯一的方法。

#4


1  

It's better to avoid circular references. I would personally try to rethink my objects.

最好避免使用循环引用。我个人会尝试重新考虑我的对象。

#5


0  

panel = new Panel(backend);

You do this in this routine something like

你可以在这个例程中做到这一点

  Public Sub Panel(ByVal BackEnd as BackEnd)
        Me.MyBackEnd = BackEnd
        BackEnd.MyPanel = Me
  End Sub

You don't need BackEnd.SetPanel

您不需要BackEnd.SetPanel

It is better to use Proxies. A proxy links one object to another through raising a Event. The parent hands the child a proxy. When the child needs the parent it calls a GetRef method on the proxy. The proxy then raises a event which the parent uses to return itself to the proxy which then hands it to the child.

最好使用Proxies。代理通过引发事件将一个对象链接到另一个对象。父母将孩子交给代理人。当子需要父代时,它会在代理上调用GetRef方法。然后代理引发一个事件,父母用它将自己返回给代理,然后代理将其交给孩子。

The use of the Event/Delegate mechanism avoids any circular reference problems.

Event / Delegate机制的使用避免了任何循环引用问题。

So you have (assuming that the backend is the 'parent' here)

所以你有(假设后端是'父'这里)

  Public Sub Panel(ByVal BackEnd as BackEnd)
        Me.MyBackEnd = BackEnd.Proxy
        BackEnd.MyPanel = Me
  End Sub

  Public Property MyBackEnd() as BackEnd
     Set (ByVal Value as BackEnd)
        priBackEndProxy = BackEnd.Proxy
     End Set
     Get
        Return priBackEndProxy.GetRef
     End Get
  End Property

Here is a fuller discussion on the problem of circular references. Although it is focused on fixing it in Visual Basic 6.0.

以下是关于循环引用问题的更全面的讨论。虽然它专注于在Visual Basic 6.0中修复它。

Dynamic Memory Allocation

动态内存分配

Also another solution is aggregating Panel and BackEnd into another object. This is common if both elements are UI Controls and need to behave in a coordinated manner.

另一种解决方案是将Panel和BackEnd聚合到另一个对象中。如果两个元素都是UI控件并且需要以协调的方式运行,则这很常见。

Finally as far as MVC goes I recommend using a a Model View Presenter approach instead.

最后,就MVC而言,我建议使用Model View Presenter方法。

Basically you have your Form Implement a IPanelForm interface. It registers itself with a class called Panel which does all the UI logic. BackEnd should have events that Panel can hook into for when the model changes. Panel handles the event and updates the form through the IPanelForm interface.

基本上,您的表单实现了一个IPanelForm接口。它使用一个名为Panel的类来注册它,该类执行所有UI逻辑。 BackEnd应该具有Panel可以在模型更改时挂钩的事件。 Panel处理事件并通过IPanelForm接口更新表单。

  1. User clicks a button

    用户单击一个按钮

  2. The form passes to Panel that the user clicked a button

    表单传递给Panel,用户单击了一个按钮

  3. Panel handles the button and retrieves the data from the backend

    Panel处理按钮并从后端检索数据

  4. Panel formats the data.

    面板格式化数据。

  5. Panel uses IPanelForm Interface to show the data on the Form.

    Panel使用IPanelForm接口显示Form上的数据。

#6


0  

I've been delaying implementing the lessons learned here, giving me plenty of time to think about the exact right way to do it. As other people said, having a clear separation where the backend objects have listeners for when their properties change is definitely the way to go. Not only will it resolve the specific issue I was asking about in this question, it is going to make a lot of other bad design smells in this code look better. There are actually a lot of different Backend classes (going by the generic class names I used in my example), each with their own corresponding Panel class. And there's even a couple of places where some things can be moved around to separate other pairs of classes into Backend/Panel pairs following the same pattern and reducing a lot of passing junk around as parameters.

我一直拖延实施这里吸取的教训,给我足够的时间来考虑确切的正确方法。正如其他人所说的那样,在后端对象拥有监听器的时候,他们的属性发生了明显的分离,这绝对是可行的方法。它不仅会解决我在这个问题中提出的具体问题,而且会使这个代码中的许多其他糟糕的设计气味看起来更好。实际上有很多不同的Backend类(按照我在我的例子中使用的泛型类名称),每个类都有自己对应的Panel类。而且甚至有几个地方可以移动一些东西,将其他类对分成相同模式的后端/面板对,并减少大量的传递垃圾作为参数。

The rest of this answer is going to get language specific, as I am using Java.

这个答案的其余部分将使用特定语言,因为我正在使用Java。

I've not worried a whole lot about "JavaBeans," but I have found that following basic JavaBean conventions has been very helpful for me in the past: basically, using standard getters and setters for properties. Turns out there's a JavaBean convention I was unaware of which is really going to help here: bound properties. Bound properties are properties available through standard getters and setters which fire PropertyChangeEvents when they change. [I don't know for sure, but the JavaBeans standard may specify that all properties are supposed to be "bound properties." Not relevant to me, at this point. Be aware also that "standard" getters and setters can be very non-standard through the use of BeanInfo classes to define a JavaBean's exact interface, but I never use that, either.] (The main other JavaBean convention that I choose to follow or not as appropriate in each situation is a no-argument constructor; I'm already following it in this project because each of these Backend objects has to be serializable.)

我并不担心“JavaBeans”,但我发现以下基本的JavaBean约定对我来说非常有帮助:基本上,使用标准的getter和setter作为属性。原来有一个JavaBean约定我不知道哪个在这里真的会有所帮助:绑定属性。绑定属性是通过标准getter和setter提供的属性,它们在更改时触发PropertyChangeEvents。 [我不确定,但JavaBeans标准可能指定所有属性都应该是“绑定属性”。在这一点上与我无关。还要注意,通过使用BeanInfo类来定义JavaBean的确切接口,“标准”getter和setter可能非常非标准,但我也从不使用它。](我选择遵循的主要的其他JavaBean约定或在每种情况下都不合适的是无参数构造函数;我已经在这个项目中跟踪它了,因为每个后端对象都必须是可序列化的。)

I've found this blog entry, which was very helpful in cluing me into the bound properties/PropertyChangeEvents issue and helping me construct a plan for how I'm going to rework this code.

我找到了这个博客条目,这非常有助于我找到绑定属性/ PropertyChangeEvents问题,并帮助我构建一个计划,我将如何重做这段代码。

Right now all of my backend objects inherit from a common class called Model, which provides a couple of things every backend in this system needs including serialization support. I'm going to create an additional class JavaBean as a superclass of Model which will provide the PropertyChangeEvent support that I need, inherited by every Model. I'll update the setters in each Model to fire a PropertyChangeEvent when called. I may also have JavaBean inherited by a couple of classes which aren't technically Models in the same sense as these but which could also benefit from having other classes registered as listeners for them. The JavaBean class may not fully implement the JavaBean spec; as I've said, there are several details I don't care about. But it's good enough for this project. It sounds like I could get all this by inheriting from java.awt.Component, but these aren't components in any sense that I can justify, so I don't want to do that. (I also don't know what overhead it might entail.)

现在我的所有后端对象都继承自一个名为Model的公共类,它提供了系统中每个后端需要的一些东西,包括序列化支持。我将创建一个额外的类JavaBean作为Model的超类,它将提供我需要的PropertyChangeEvent支持,由每个Model继承。我将更新每个Model中的setter以在调用时触发PropertyChangeEvent。我也可能有几个类继承的JavaBean,这些类在技术上不是与这些类似的模型,但也可以从其他类注册为它们的监听器中受益。 JavaBean类可能无法完全实现JavaBean规范;正如我所说,有几个细节我不在乎。但这对这个项目来说已经足够了。听起来我可以通过继承java.awt.Component来获得所有这些,但这些不是任何意义上的组件,我可以证明这一点,所以我不想这样做。 (我也不知道它可能带来什么开销。)

Once every Model is a JavaBean, complete with PropertyChangeEvent support, I'll do a lot of code cleanup: Models that are currently keeping references to Panels will be updated and the Panels will register themselves as listeners. So much cleaner! The Model won't have to know (and shouldn't have known in the first place) what methods the Panel should call on itself when the property updates.

一旦每个Model都是一个JavaBean,完成了PropertyChangeEvent支持,我将做很多代码清理:当前保持对Panels的引用的模型将被更新,Panel将自己注册为监听器。太干净了!模型将不必知道(并且首先不应该知道)当属性更新时Panel应该调用哪些方法。