Я использую 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 класс.
Вопросы
- Что произойдет, если я захочу ввести другой
HandlerвMyServerInitializer- возможно, этот новыйHandlerне принимаетIntegerв качестве аргумента. Придется ли мне создавать новыйFactoryтолько для этого случая? - Имеет ли смысл иметь
Factory, который, вероятно, когда-либо создавал бы только один экземплярMyServer? - Есть ли другой вариант, позволяющий исключить эту циклическую ссылку?
Вопрос, выделенный жирным шрифтом, - это моя основная цель, чтобы задать его в StackOverflow. Я чувствую, что упускаю из виду что-то более простое или более элегантное. Я надеюсь, что некоторые из вас, более опытные пользователи, поделятся своим мнением. Пожалуйста, дайте мне знать, если потребуется дополнительная информация.
Справочные материалы
- Разрешение циклических зависимостей с помощью внедрения зависимостей
- Циклическая зависимость в конструкторах и внедрение зависимостей автор: Misko Hevery
- Чистый код - руководство по гибкому разработке программного обеспечения Роберта К. Мартина
- Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования, автор Эрих Гамма и др. аль
handler, а затем класс, являющийся регистрантом, должен вызывать этот метод при его создании. ПосколькуMyServerHandlerзнает о своем контейнере, я бы предложил попробовать зарегистрировать его в этом контейнере, а не разделять связанные знания на два места. - person Matt Whipple   schedule 30.07.2013MyServerнаMyServerDirector, который также будет содержатьIntegerиIntegerUpdaterэкземпляры, которые будут обрабатывать обновлениеInteger. Помните, чтоInteger- это просто заполнитель для более сложного объекта в этом примере. Кажется, чтоIntegerдаже не должно быть наMyServer. Я считаю, что нахождение наMyServerнарушает SRP. SRPMyServerдолжен действовать как связь между двумя точками, а не поддерживать обновление объекта. Что вы думаете? - person crush   schedule 30.07.2013Bootstrap, который будет вторым существующим классом, который имеет дело со строительством / подключением. Я бы посоветовал более внимательно изучить отношения между уже существующими частями, прежде чем, возможно, перестроить или поймать паттернит. - person Matt Whipple   schedule 30.07.2013