Циклическая зависимость при внедрении зависимостей через конструкторы

Я использую Netty (4.0.4.Final) в проекте и продолжаю сталкиваться с циклической зависимостью, которую мне нужно исключить. Этот вопрос в основном включает концепцию выделения круговой зависимости, но я буду использовать некоторую терминологию Netty для тех, кто знаком. Поскольку моя проблема на самом деле не в Netty, я решил не помечать ее.

Ниже я разместил свой код, опуская части, которые не считаю важными.

Ситуация

У меня есть класс MyServer, который добавляет ChannelInboundHandlerAdapter к Bootstrap:

public class MyServer extends AbstractMyServer {
    private Integer someInteger; //Using Integer just for example's sake.

    public MyServer(MyServerInitializer initializer) {
        //...
        bootstrap.handler(initializer);
        //...
    }

    public void updateInteger(Integer value) {
        someInteger = value;
        //Send an update packet to another server.
    }
}

MyServerInitializer необходимо добавить ChannelInboundHandlerAdapter к ChannelPipeline:

public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    private ChannelInboundHandlerAdapter handler;

    public MyServerInitializer(ChannelInboundHandlerAdapter handler) {
        this.handler = handler;
    }

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(
                new ObjectEncoder(),
                new ObjectDecoder(),
                handler);
    }
}

У меня также есть MyServerHandler, который является аргументом конструктора MyServerInitializer в случае, о котором я говорю:

public class MyServerHandler extends ChannelInboundHandlerAdapter {
    private MyServer server;

    public MyServerHandler(MyServer server) {
        this.server = server;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Integer obj = (Integer) msg; //Remember just using Integer for example. Think of it as an Object rather than an Integer.
        server.updateInteger(obj);
    }
}

Итак, циклическая зависимость становится очевидной во время инициализации:

public static void main(String[] args) {
    //I can't set a reference to MyServer instance here because it hasn't been created yet. I want to avoid the circular dependency here.
    MyServerHandler handler = new MyServerHandler(...);
    MyServerInitializer initializer = new MyServerInitializer(handler);
    MyServer server = new MyServer(initializer);
}

Возможные решения

Рефакторинг в main()

Я мог бы вытащить создание Integer someInteger из MyServer, создать его в рамках функции main(), а затем вставить ссылку в MyServerHandler и MyServer. Это, конечно, даст MyServerHandler возможность изменять его напрямую, вместо того, чтобы проходить MyServer. Обратной стороной является то, что теперь он объявлен в рамках main(). Я не хочу делать это для каждого члена класса, который может существенно измениться классом Handler.

Создать MyServerFactory

Одна из концепций, о которых я читал, заключалась в том, чтобы отделить конструкцию от использования. В этом есть большой смысл, поэтому я попробовал это с помощью приведенной ниже реализации шаблона Factory.

public class MyServerFactory implements AbstractFactory<MyServer> {
    public MyServer create() {
        Integer someInteger = createInteger();
        MyServerHandler handler = createHandler(someInteger);
        MyServerInitializer initializer = createInitializer(handler);
        return new MyServer(initializer);
    }

    /* creator methods for the different components above. */
}

Однако похоже, что я просто переместил код из main() в этот Factory класс.

Вопросы

  1. Что произойдет, если я захочу ввести другой Handler в MyServerInitializer - возможно, этот новый Handler не принимает Integer в качестве аргумента. Придется ли мне создавать новый Factory только для этого случая?
  2. Имеет ли смысл иметь Factory, который, вероятно, когда-либо создавал бы только один экземпляр MyServer?
  3. Есть ли другой вариант, позволяющий исключить эту циклическую ссылку?

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

Справочные материалы


person crush    schedule 30.07.2013    source источник
comment
Я думаю, что почти наверняка должен быть более чистый способ. Трудно сказать, где именно должен произойти разрыв, учитывая мое незнание фреймворка и менее чем полезные имена классов, но в настоящее время все классы слишком осведомлены о других классах. Где-то должно быть само приложение, и части должны регистрироваться в приложении (что позволяет регистрировать дополнительные части, не требуя модификации существующих классов) ...   -  person Matt Whipple    schedule 30.07.2013
comment
Либо MyServer, либо MyServerInitializer должны иметь общедоступный метод, указывающий на соответствующий handler, а затем класс, являющийся регистрантом, должен вызывать этот метод при его создании. Поскольку MyServerHandler знает о своем контейнере, я бы предложил попробовать зарегистрировать его в этом контейнере, а не разделять связанные знания на два места.   -  person Matt Whipple    schedule 30.07.2013
comment
В качестве быстрого продолжения ... у вас уже есть инициализатор, предназначенный для построения / подключения, поэтому я, конечно, сомневаюсь в необходимости добавления фабрики для склеивания тех же частей вместе.   -  person Matt Whipple    schedule 30.07.2013
comment
Я начал рассматривать шаблон «Посредник» как потенциальное средство решения моей проблемы. Я планирую разместить MyServer на MyServerDirector, который также будет содержать Integer и IntegerUpdater экземпляры, которые будут обрабатывать обновление Integer. Помните, что Integer - это просто заполнитель для более сложного объекта в этом примере. Кажется, что Integer даже не должно быть на MyServer. Я считаю, что нахождение на MyServer нарушает SRP. SRP MyServer должен действовать как связь между двумя точками, а не поддерживать обновление объекта. Что вы думаете?   -  person crush    schedule 30.07.2013
comment
Я, конечно, согласен с тем, что MyServer не должен делать ничего, кроме действий в качестве диспетчера / реактора, и вам было бы лучше иметь какую-то форму обслуживания или что-то подобное для обслуживания объектов, но очень трудно сказать больше, не тратя время имея дело со всей картиной. Мне также напоминают, что у вас также есть объект Bootstrap, который будет вторым существующим классом, который имеет дело со строительством / подключением. Я бы посоветовал более внимательно изучить отношения между уже существующими частями, прежде чем, возможно, перестроить или поймать паттернит.   -  person Matt Whipple    schedule 30.07.2013


Ответы (1)


Отказ от ответственности: я мало что знаю о Netty, это всего лишь некоторые мысли из чтения вашего кода:

Я не вижу проблем с MyServerInitializer. MyServerInitializer не имеет никаких зависимостейy от MyServerHandler или MyServer. Это нормально. Было бы хуже, если бы конструктор MyServerInitializer потребовал бы MyServerHandler вместо ChannelInboundHandlerAdapter.

Если возможно, вам следует изменить параметр конструктора MyServer с MyServerInitializer на ChannelInitializer<SocketChannel>.

Проблема в том, что MyServerHandler зависит от MyServer, а MyServer имеет косвенную зависимость времени выполнения от MyServerHandler. Я бы попытался избавиться от MyServer зависимости в MyServerHandler. Для этого вы можете:

  • Переместите метод updateInteger() из MyServer в другой класс, назовем его IntegerUpdater. MyServerHandler следует использовать IntegerUpdater вместо MyServer. IntegerUpdater не должен зависеть от MyServer. Используя этот способ, у вас не будет никакой круговой зависимости.

  • Добавьте абстракцию между MyServerHandler и MyServer. Например:

    public interface IntegerMessageReceiver {
      void handleMessage(Integer i);
    }
    

-

    public class MyServerHandler extends ChannelInboundHandlerAdapter {
      private List<IntegerMessageReceiver> integerMessageReceivers;

      public void addIntegerMessageReceiver(IntegerMessageReceiver imr) {
        integerMessageReceivers.add(imr);
      }

      @Override
      public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Integer obj = (Integer) msg; 
        for (IntegerMessageReceiver imr : integerMessageReceivers) { 
          imr.handleMessage(obj);
        }
      }
    }

-

    public class MyServer extends AbstractMyServer implements IntegerMessageReceiver {
      public void handleMessage(Integer i) {
        ...
      }
      ...
    }

Инициализация:

MyServerHandler handler = new MyServerHandler();
MyServerInitializer initializer = new MyServerInitializer(handler);
MyServer server = new MyServer(initializer);
handler.addIntegerMessageReceiver(server);

Используя этот подход, вы все равно будете иметь циклические зависимости во время выполнения, но, по крайней мере, вы избавитесь от прямой зависимости времени компиляции MyServer в MyServerHandler.

person micha    schedule 03.08.2013