Для двух методов, которым нужно вызывать друг друга: как предотвратить бесконечный цикл? - PullRequest
3 голосов
/ 06 июня 2009

У меня есть два класса (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.

Ну, это работает, но не кажется оптимальным решением.

Как бы вы сделали это?
Существует ли шаблон , который имеет дело с такой проблемой?

EDIT:
Я хочу знать это вообще.
Но скажем, у меня есть Контейнер , который содержит несколько Элементов с. Контейнер предоставляет метод remove (someElement) и Элемент также предоставляет метод removeMe () .
Оба метода зависят друг от друга, но не может быть подключен к одному метод, потому что оба метода дополнительно выполняют некоторые внутренние вещи , что только доступно внутри каждого класса.

Ответы [ 8 ]

8 голосов
/ 06 июня 2009

Я бы справился с этим, переосмыслив логику. Круговые зависимости обычно являются признаком того, что что-то немного ... выключено. Без большего понимания точной проблемы я не могу быть более конкретным.

2 голосов
/ 06 июня 2009

Можете ли вы сделать один из методов приватным / внутренним. Это должно гарантировать, что клиентский код может вызывать только другой, и вы всегда знаете, как работают вызовы.

В качестве альтернативы, используя ваш пример контейнера / элемента, что-то вроде этого:

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.
    }
}

Я не уверен, есть ли общее название для этого шаблона.

1 голос
/ 06 июня 2009

Я думаю, что это можно решить с помощью логического значения, глобального для класса (конечно, сделайте его переменной экземпляра). он проверяет, является ли ([логическая переменная]) и истинным ли это; он запускает вызов другого метода, если он ложный, то нет. только внутри оператора if установите проверку равной false. затем в конце каждого метода установите значение true.

Вот как бы я это сделал.

1 голос
/ 06 июня 2009

Я бы порекомендовал меньше думать о коде и больше о дизайне. Могут ли общие операции быть абстрагированы в новый класс, с которым могут взаимодействовать оба класса? Была ли функциональность неправильно распределена между двумя классами?

В Uni в этом году они вводят концепцию «запахов кода», которая представляет собой группу «интуитивных реакций», которые код нуждается в перефакторинге. Возможно, может помочь?

Для обзора попробуйте: Запах Wikipedia Code или эта книга .

Возможно, вы могли бы рассказать нам больше о том, что вы пытаетесь представить в коде?

Надеюсь, это поможет.

1 голос
/ 06 июня 2009

Представленное решение выглядит вполне приемлемым для данного сценария. Но это действительно зависит от того, что действия означают, является ли это поведение правильным или нет. Если это именно то поведение, которое вам нужно, тогда я не уверен, что у вас есть проблема.

Добавление:
В таком случае, я бы сказал, разделите «дополнительные вещи» на отдельный метод, чтобы remove в контейнере мог вызывать «дополнительные вещи» для элемента без рекурсивного обратного вызова. Аналогично, дополнительные элементы в контейнере могут быть разделены так, что removeMe может вызывать метод для контейнера, который выполняет только нерекурсивные элементы.

0 голосов
/ 06 июня 2009

функция fooA (), вызывающая fooB () и fooB (), снова вызывающая fooA (), является допустимой рекурсией. Рекурсия не всегда должна быть той же самой функцией, вызывающей себя.

Итак, как мы можем предотвратить бесконечное зацикливание рекурсивной функции? Имея правильное условие завершения?

Я думаю, это путь. Однако я согласен с другими замечаниями по поводу переосмысления дизайна и избегаю подобных рекурсий.

Приведенное ниже решение Саймона также основывается на таких условиях завершения, как

if not elements.contains(e):
            return

и

if container is none:
            return
0 голосов
/ 06 июня 2009

Как уже говорили другие, я думаю, что в большинстве ситуаций вам следует избегать этого и реорганизовывать свой код, чтобы проблема не возникала. Но тогда, безусловно, существуют ситуации, когда это не может быть вариантом или совершенно неизбежным. Наиболее общий подход к этому состоит в том, чтобы гарантировать, что перед тем, как метод A вызовет метод B (и наоборот), он должен убедиться, что объект A находится в состоянии, когда дополнительный вызов A немедленно возвращается и, таким образом, приводит к невозможности операции. Это действительно очень абстрактно и может быть довольно сложно применить к конкретному классу и реализации.

Взяв ваш пример классов контейнеров и элементов (скажем, C и E), где C имеет метод C.remove (E 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)
0 голосов
/ 06 июня 2009

Если возникнет исключение, ваша реализация не будет разблокирована. Замки могут быть дорогими, более легкий подход - использовать AtomicBoolean

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.
   }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...