设计模式是什么?

4

我有一个类A,维护着一个类B对象的列表。 但是类B的每个对象都可以在类A的任何对象中被引用。 类B还维护了一个类A对象的列表,其中它被引用。 程序可以随意创建(多个)类A和B的对象,并且也可以删除它们。

如果我使用C#,可以使用以下代码向两个类添加和删除对象:

public class A
{
    private List<B>ListOfObjects_B;
    public bool Add(B Object)
    {
       bool bAdd = false;
       if ((Object != null) && (ListOfObjects_B.IndexOf(B) <0))
       {
          ListOfObjects_B.Add(Object);
          Object.Add(this);
          bAdded = true;
       }
       return bAdded;
    }

    public bool Delete(B Object)
    {
       bool bDeleted = ListOfObjects_B.Remove(Object);
       if (bDeleted == true) Object.Delete(this);
       return bDeleted;
    }
}

public class B
{
    private List<A>ListOfObjects_A;
    public bool Add(A Object)
    {
        bool bAdd = false;
        if ((Object != null) && (ListOfObjects_A.IndexOf(A) <0))
        {
            ListOfObjects_A.Add(Object);
            Object.Add(this);
            bAdded = true;
        }
        return bAdded;
   }

   public bool Delete(A Object)
   {
       bool bDeleted = ListOfObjects_A.Remove(Object);
       if (bDeleted == true) Object.Delete(this);
       return bDeleted;
   }
}

由于在第二次(通过递归)将对象从ListOfObjects中删除/添加,函数将无法删除/添加,从而避免了无限循环。
但是,尽管A和B不知道对方类的“太多”信息,只是调用了一个Delete/Add函数,但我不喜欢这段代码。
我认为这种问题很普遍,存在一种设计模式可以处理它,以便可以避免递归并且更新两个列表将会更好。我应该使用哪种设计模式?如果还能添加一些代码,我将不胜感激。

关于您在我的回答下面的评论:我认为您可以通过参数化(通过类型参数)AssocationTable类来处理您描述的情况(几个不同的类对之间的关联)。因此,您将拥有AssociationTable<A,B>用于A-B配对和AssociationTable<C,D>用于C-D配对等。 - Itay Maman
3个回答

4
您可以通过将“对象关联问题”移入专门的类中来简化事情。我有以下想法。
定义一个名为AssociationTable的类。该类将维护一对对的列表,其中每个对都包含对A对象和B对象的引用。
每个A对象(和每个B对象)都将持有对AssociationTable对象的引用。 A.Add(B)将被实现为table.add(this, b); B.Add(A)将被实现为table.add(a, this);
删除将被实现为table.delete(this, b)或table.delete(a, this)
class Pair { 
  A a; B b; 
  Pair(A a, B b) { this.a = a; this.b = b; } 
  // Also override Equals(), HashCode()
}

class AssociationTalbe {
  Set<Pair> pairs = ...;

  void add(A a, B b) { pairs.add(new Pair(a, b)); }
  void remove(A a, B b) { pairs.remove(new Pair(a, b)); }
}

class A {
  AssociationTable table;

  public A(AssociationTable t) { table = t; }

  void add(B b) { table.add(this, b); }
  void remove(B b) { table.remove(this, b); }
}

编辑: 这种设计的问题在于垃圾回收。表格将保留对对象的引用,从而抑制它们的回收。在Java中,您可以使用WeakReference对象来解决此问题。我相信在.NET世界中也有类似的东西。
另外,该表格可以是单例模式。我不太喜欢单例模式。在这里,单例模式将使A-B关联在整个程序中是唯一的。这可能是不可取的,但这取决于您的具体需求。
最后(仅为了让事情更清楚),这种设计与关系数据库中的多对多关系的工作方式相同。

这也是我所想的... +1 - bruno conde
因此,这个想法对我来说听起来不错(谢谢)。但是在我的程序中,我有几个具有这种关系的“组合”。因此,我希望使用模板来解决。因此,在Pair类中,“A”和“B”不是固定的,而可以变成“C”和“D”,或者可能是“E”和“A”。在我的程序中,单例可能很有用,因为每个A只能与一个B链接一次。 - SoftwareTester

2

我最近编写了一个处理类似问题的。实际上,这是一个更简单的情况(父子关系,子项引用它们的父项),但您可能可以根据自己的需求进行调整。主要区别在于,我的实现中的Parent属性应该替换为父项集合。


非常有趣的文章,我会尝试一下。您能否提供您省略的代码(也许在文章末尾或者一个可下载的文件中)以便完成实现? - SoftwareTester
您所需的所有代码都在文章中... 您只需要复制/粘贴IChildItem接口和ChildItemCollection类即可。 - Thomas Levesque

1

关于唯一能想到的是使用中介者模式,这样A就不会将自己添加到B中。以下是一个例子:

public class Mediator {

    public void Add(A List, B Object) {
        if(list.Add(Object)) {
            object.Add(List);
        }
    }

    public void Delete(A List, B Object) {
        if(List.Delete(Object)) {
            Object.Delete(List);
        }
    }
}

接下来,您需要删除代码中的“Object.Add(this);”和“if (bDeleted == true) Object.Delete(this);”。这样做还有一个好处,就是减少每个方法被调用的次数,因为在此之前,对象A的方法被调用了两次,因为对象B也在调用对象A的方法。

编辑:经过进一步审查,我意识到您已经在某种程度上使用了观察者设计模式。对象A是观察者,对象B是可观察对象。对象A维护其正在观察的对象列表,而对象B维护其正在观察它的对象列表。唯一的问题是我没有看到任何额外的功能,尽管可能存在一些。基本上,对象B将通知所有观察它的对象A它已经发生了变化,所有这些对象A都会请求进行更改。如果这正是您要寻找的内容,那么您只需要从B代码中删除“Object.Add(this);”和“if(bDeleted == true) Object.Delete(this);”,因为这是不必要的。


显然类A和B中有更多的功能。用户可能会调用删除对象A,但用户也可以单独调用删除对象B。因此,无论哪个对象被删除,它都需要通知另一个对象以便另一个对象更新其列表。你说得对,A维护了一个正在观察的对象列表,而B维护了一个观察它的对象列表。但我不明白为什么我可以删除“Object.Add(this)”这一行,因为这一行实际上创建了引用。 - SoftwareTester
我假设A被告知从外部源添加对象B。如果是通过对象B被告知添加对象B,那么您可能需要从对象A的源代码中删除该行。如果可以从任一对象中完成,则应该创建两个不同的函数,一个从另一个对象调用,另一个从不同的对象调用,以避免进行额外的调用。 - mnuzzo
此外,当我说“删除Object.Add(this)”时,我特别指的是在B的Add函数内部,因为如果它只从A中调用,那么它只会导致再次调用A的Add函数。A已经有了对B的引用,并且B在A对象列表中也有对A的引用。 - mnuzzo
如果可以从两个不同的对象调用函数,那么你可能需要编写两个不同的函数:一个从另一个对象中调用,另一个从不同的对象中调用来避免额外的调用。我该怎样让这个函数只能被一个类所调用呢?(也许我的C#知识不够好,我不知道) - SoftwareTester
你不会将其从一个类中调用,而是创建另一个方法,并仅从一个类中调用它。我认为没有任何语言允许您指定哪个类可以调用方法。最好的做法是从另一个类中使用一个方法和任何未在此处提到的类中的一个方法。 - mnuzzo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接