代码之家  ›  专栏  ›  技术社区  ›  ivan_ivanovich_ivanoff

对于两个需要相互调用的方法:如何防止无限循环?

  •  3
  • ivan_ivanovich_ivanoff  · 技术社区  · 16 年前

    我有两个类(A和B),它们在以下意义上相互依赖:
    每个类都有一个执行某些操作的方法。
    每个类的动作依赖于另一个类的动作。

    因此,如果用户调用类A的操作,它应该自动调用 B类作用。
    另一方面也一样。但应该防止出现无限循环。

    我找到了一些处理这个问题的代码,但它似乎是 我有点傻:无限循环被 锁定 .

    import java.util.concurrent.locks.*;
    import static java.lang.System.*;
    import org.junit.*;
    
    public class TEST_DependentActions {
    
      static class A {
        private B b = null;
        private final ReentrantLock actionOnBLock = new ReentrantLock();
    
        public void setB(B b) {
          this.b = b;
        }
    
        public void actionOnB() {
          if (!actionOnBLock.isLocked()) {
            actionOnBLock.lock();
            b.actionOnA();
            actionOnBLock.unlock();
          }
        }
      }
    
      static class B {
        private A a = null;
        private final ReentrantLock actionOnALock = new ReentrantLock();
    
        public void setA(A a) {
          this.a = a;
        }
    
        public void actionOnA() {
          if (!actionOnALock.isLocked()) {
            actionOnALock.lock();
            a.actionOnB();
            actionOnALock.unlock();
          }
        }
      }
    
      @Test
      public void test1()
          throws Exception {
    
        out.println("acting on class A first:");
    
        A a = new A(); B b = new B();
        a.setB(b);     b.setA(a);
    
        a.actionOnB();
      }
    
      @Test
      public void test2()
          throws Exception {
    
        out.println("acting on class B first:");
    
        A a = new A(); B b = new B();
        a.setB(b);     b.setA(a);
    
        b.actionOnA();
      }
    }
    

    输出如下:

    acting on class A first:
    A : calling class B's action.
    B : calling class A's action.
    acting on class B first:
    B : calling class A's action.
    A : calling class B's action.
    

    好吧,这是可行的,但似乎不是最佳解决方案。

    将如何 做到了吗?
    有没有 模式 哪个处理这个问题?

    编辑:
    我想大致了解一下。
    但是假设我有一个 集装箱 包含多个 元素 S. 容器提供了方法 删除(某些元素) 以及 元素还提供了一种方法 移除() .
    两种方法相互依赖,但 不能 连接到一个 方法,因为这两个方法都执行 一些内部的东西 ,这只是 可接近的 里面 每堂课。

    8 回复  |  直到 16 年前
        1
  •  2
  •   Scott Langham    16 年前

    你能把这些方法之一变成私有/内部的吗? 这应该确保只有另一个可以被客户机代码调用,并且您总是知道调用的工作方式。

    或者,使用容器/元素示例,如下所示:

    public class Container
    {
        public void Remove(Element e)
        {
            e.RemoveImplementation();
            RemoveImplementation();
        }
    
        // Not directly callable by client code, but callable
        // from Element class in the same package
        protected void RemoveImplementation()
        {
           // Mess with internals of this class here
        }
    }
    
    
    public class Element
    {
        private Container container;
    
        public void Remove()
        {
           RemoveImplementation();
           container.RemoveImplementation();
        }
    
        // Not directly callable by client code, but callable
        // from Container class in the same package
        protected void RemoveImplementation()
        {
            // Mess with internals of this class here.
        }
    }
    

    我不确定这个模式是否有一个通用名称。

        2
  •  8
  •   Kris    16 年前

    我会通过重新思考逻辑来处理这个问题。循环依赖性通常是一种迹象,表明某些东西有点…关闭。如果没有对具体问题的深入了解,我就不能更具体地说。

        3
  •  1
  •   Edd Barrett    16 年前

    我的建议是少考虑代码,多考虑设计。共享操作是否可以抽象为两个类都可以通信的新类?功能是否在两个类之间被错误地共享?

    在今年的uni大会上,他们引入了“代码气味”的概念,这是一系列代码需要重新分解的“直觉反应”。也许能帮上忙?

    要获得概述,请尝试: Wikipedia Code Smells this book .

    也许您可以告诉我们更多关于您试图在代码中表示什么的信息?

    我希望这有帮助。

        4
  •  1
  •   jerryjvl    16 年前

    对于给定的场景,所提供的解决方案看起来完全可以接受。但这实际上取决于这些行为意味着什么,这些行为是否正确。如果这正是你想要的行为,那么我不确定你是否有问题。

    附录:
    在这种情况下,我会说,把“额外的东西”分成一个单独的方法,这样 remove 在容器上,可以调用元素上的“额外内容”,而无需递归回调。同样,容器上多余的东西也可以分开,以便 removeMe 无法对只执行非递归操作的容器调用方法。

        5
  •  1
  •   crazybmanp    16 年前

    我认为这可以通过类的布尔值全局来解决(当然要使它成为实例变量)。它检查([布尔变量])是否为真;它运行对另一个方法的调用,如果为假,则不为真。仅在if语句内部,将检查设置为假。然后在每个方法的末尾将其设置为true。

    不过,我就是这么做的。

        6
  •  0
  •   Peter Lawrey    16 年前

    如果引发异常,您的实现将不会解锁。锁可能很贵,稍微轻一点的方法是使用原子布尔

    private final AtomicBoolean actionOnBLock = new AtomicBoolean();
    
    public void actionOnB() {
        if (!actionOnBLock.getAndSet(true))
            try {
                b.actionOnA();
            } finally {
                actionOnBLock.set(false);
            }
    }
    

    正如其他人所建议的,更好的方法是编写代码,这样一个写下来就不必调用另一个。相反,您可以有一个方法来调用a和b,这样两个对象就不需要相互了解了。

    public class AB {
       private final A a;
       private final B b;
       public AB(A a, B b) {
         this.a = a;
         this.b = b;
       }
    
       public void action() {
         a.action(); // doesn't call b.
         b.action(); // doesn't call a.
       }
    }
    
        7
  •  0
  •   Simon Lehmann    16 年前

    正如其他人所说,我认为在大多数情况下,您应该尽量避免这种情况,并重构代码,这样问题就不会发生。但在某些情况下,这可能不是一个选择,或者完全不可避免。最一般的方法是确保在方法A调用方法B之前(反之亦然),它必须确保A的对象处于立即返回对A的附加调用从而导致no-op的状态。这是非常抽象的,很难应用于具体的类和实现。

    以容器和元素类(例如,c和e)为例,其中c有一个方法c.remove(e),e有一个方法e.remove(),您可以这样实现它:

    class C:
        elements = [...] // List of elements
    
        remove(e):
            if not elements.contains(e):
                return
    
            elements.remove(e)
            // Do necessary internal stuff here...
            // and finally call remove on e
            e.remove()
    
    class E:
        container = ... // The current container of E
    
        remove():
            if container is none:
                return
    
            c = container
            container = none
            // Do necessary internal stuff here...
            // and finally call remove on c
            c.remove(this)
    
        8
  •  0
  •   Sathya    16 年前

    函数fooa()再次调用foob()和foob()是一个有效的递归。 递归并不总是必须是调用自身的同一个函数。

    那么,我们如何防止递归函数无限循环呢? 有终止条件,对吗?

    我想是这样的。但是,我同意其他人关于重新考虑设计并避免此类递归的意见。

    下面Simon的解决方案还依赖于诸如

    if not elements.contains(e):
                return
    

    if container is none:
                return
    
    推荐文章