2013-12-04 3 views
19

Я давно разработчик python. Я пробовал Go, конвертируя существующее приложение python в Go. Он модульный и отлично работает для меня.Циклические зависимости и интерфейсы в Golang

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

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

Текущая структура приложения питон выглядит следующим образом:

/main.py 

/settings/routes.py  contains main routes depends on app1/routes.py, app2/routes.py etc 
/settings/database.py function like connect() which opens db session 
/settings/constants.py general constants 

/apps/app1/views.py  url handler functions 
/apps/app1/models.py  app specific database functions depends on settings/database.py 
/apps/app1/routes.py  app specific routes 

/apps/app2/views.py  url handler functions 
/apps/app2/models.py  app specific database functions depends on settings/database.py 
/apps/app2/routes.py  app specific routes 

settings/database.py имеет общие функции, такие как connect(), который открывает сеанс дб. Таким образом, приложение в пакете приложений вызывает database.connect() и открывается сеанс db.

То же самое имеет место с settings/routes.py, он имеет функции, позволяющие приложениям добавлять свои подпункты к основному объекту маршрута.

Пакет настроек больше о функциях, чем данных/константах. Это код, который используется приложениями в пакете приложений, которые в противном случае должны были бы дублироваться во всех приложениях. Так что, если мне нужно изменить класс маршрутизатора, например, мне просто нужно изменить settings/router.py, и приложения будут продолжать работать без каких-либо изменений.

+3

Должен показать код, лучше всего подрезать версию, которая показывает проблему. Может быть, вы просто разбиваете свой код на слишком много небольших пакетов? – Volker

+1

Если пакет X принимает/сохраняет/вызывает методы в/возвращает типы, определенные в пакете Y, но фактически не имеет прямого доступа к функциям или переменным Y (не-метод), X может использовать интерфейс, который соответствует типу в Y, а не фактическому импорту Y. Именно так интерфейсы могут помочь сократить зависимости (циклические и другие) в двух словах. – twotwotwo

+0

В Python, если X и Y импортируют друг друга, все, что происходит, является одним из модулей, загружаемым, в то время как другой модуль по-прежнему пуст или имеет половину разбора - и поскольку поиск имени Python в функциях и т. Д. Возникает только тогда, когда функция вызванный, пустой модуль во время разбора из-за циклического импорта часто не является проблемой. Согласитесь с тем, что Volker нам нужен код, чтобы помочь больше - вам, возможно, потребуется объединить вещи, разделить вещи, мы никак не можем сказать. – twotwotwo

ответ

47

Для этого есть две высокоуровневые части: выяснение того, какой код входит в этот пакет, и настройка ваших API-интерфейсов, чтобы уменьшить потребность в пакетах, чтобы задействовать столько зависимостей.

В разработке интерфейсов API, которые позволяют избежать необходимости для некоторых импорта: функции конфигурации

  • Писать для подключения пакетов друг к другу во время выполнения, а не во время компиляции. Вместо routes, импортирующих все пакеты, которые определяют маршруты, можно указать экспортroutes.Register, который может вызывать main (или код в каждом приложении). В общем случае информация о конфигурации, вероятно, протекает от main или выделенного пакета; вы не хотите, чтобы это было разбросано по всему вашему приложению.

  • Пропускать основные типы и значения interface. Если вы зависите от пакета только для имени типа, возможно, вы можете этого избежать. Возможно, некоторая обработка кода []Page может вместо этого использовать []string имен файлов или []int идентификаторов или еще один общий интерфейс (sql.Rows).

  • Рассмотрим наличие «схемы» пакеты с только чистых типов данных и интерфейсов, так User отдельно от кода, который может загружать пользователей из базы данных. Он не должен сильно зависеть (возможно, от чего-либо), поэтому вы можете включить его из любого места. Ben Johnson gave a lightning talk at GopherCon 2016, предлагая и организуя пакеты по зависимостям.

Об организации кода в пакеты:

  • Как правило, разделить пакет, когда каждая часть может быть полезна сама по себе. Если две части функциональности действительно тесно связаны друг с другом, вам не нужно разделить их на пакеты; вы можете организовать несколько файлов или типов. Большие пакеты могут быть в порядке; Go net/http - один, например.

  • Разбивать пакеты для захвата (utils, tools) по теме или зависимости. В противном случае вы можете в конечном итоге импортировать огромный пакет utils (и взять все его зависимости) для одной или двух функций (у которых не было бы так много зависимостей, если бы они были отделены).

  • Рассмотрите возможность использования многоразового кода «вниз» в пакеты нижнего уровня, распущенные из вашего конкретного прецедента. Если у вас есть package page, содержащий как логику для вашей системы управления контентом, так и универсальный код HTML-манипуляции, подумайте о переносе содержимого HTML «вниз» на package html, чтобы вы могли использовать его без импорта несвязанных материалов управления контентом.


Здесь я бы переставить вещи так, что маршрутизатор не нужно включать в маршруты: вместо того, чтобы каждый пакет приложение называет router.Register() метод. Это то, что делает the Gorilla web toolkit's mux package. Ваши пакеты routes, database и constants звучат как низкоуровневые фрагменты, которые необходимо импортировать кодом приложения, а не импортировать.

Как правило, попробуйте создать приложение в слоях. В вашем коде верхнего уровня, используемом для конкретных случаев, необходимо импортировать более низкоуровневые, более фундаментальные инструменты и никогда наоборот. Вот еще несколько мыслей:

  • Пакетов для разделения независимо друг от друга используемых бит функциональности; вам не нужно разделять его, когда исходный файл становится большим. В отличие от, скажем, Python или Java, в Go можно разделить и объединить и переупорядочить файлы, полностью независимые от структуры пакета, чтобы вы могли разбить огромные файлы, не разбивая пакеты.

    Стандартная библиотека net/http составляет около 7 тыс. Строк (считая комментарии/пробелы, но не тесты). Внутри он разбит на несколько меньших файлов и типов. Но это один пакет, я думаю, потому что не было никакой причины, по которой пользователи хотели бы, скажем, просто обработать cookie самостоятельно. С другой стороны, net и net/urlявляются отдельными, поскольку они используют внешние HTTP.

    Это замечательно, если вы может нажать «вниз» утилиты в библиотеки, которые являются независимыми и чувствовать, как их собственных полированные изделия, или чисто слоя самого приложения (например, UI сидит на вершине API сидит на вершине некоторых основных библиотек и данных модели). Аналогично, «горизонтальное» разделение может помочь вам удержать приложение в голове (например, уровень пользовательского интерфейса разбивается на управление учетными записями пользователей, ядром приложения и административными инструментами или что-то более тонкое, чем это). Но, главное, вы можете разделить или нет, как работает для вас.

  • Настройка API для настройки поведения во время выполнения, поэтому вам не нужно импортировать его во время компиляции. Так, например, ваш URL-маршрутизатор может выставить метод Register вместо импорта appA, appB и т. Д. И читать var Routes от каждого. Вы можете сделать пакет myapp/routes, который импортирует router и все ваши мнения и звонки router.Register. Основная идея заключается в том, что маршрутизатор является универсальным кодом, который не требует импорта представлений вашего приложения.

    Некоторые способы положить вместе API конфигурации:

    • Pass поведение приложения с помощью interface с или func с:http можно передать пользовательские реализаций Handler (конечно), но и CookieJar или File. text/template и html/template могут принимать функции, доступные из шаблонов (в FuncMap).

    • Экспорт функции быстрого доступа из пакета при необходимости: В http абоненты могут либо сделать и отдельно настроить некоторые http.Server объекты, или вызвать http.ListenAndServe(...), который использует глобальный Server. Это дает вам приятный дизайн - все в объекте, а вызывающие могут создавать несколько Server s в процессе и т. Д., Но также предлагает ленивый способ настройки в простом односерверном случае.

    • Если у вас есть, только клейкая лента-то: Вы не должны ограничивать себя супер-элегантные системы конфигурации, если вы не можете поместить один в ваше приложение: может быть, какие-то запихнуть package "myapp/conf" с полезен глобальный var Conf map[string]interface{}. Но помните о нижних границах для глобального conf. Если вы хотите писать библиотеки многократного использования, они не могут импортировать myapp/conf; они должны принять всю информацию, в которой они нуждаются в конструкторах и т. д. Глобалы также рискуют провести жесткую проводку в предположении, что в любом случае все будет иметь одно значение для приложения, когда оно в конечном итоге не будет; возможно, сегодня у вас есть одна конфигурация базы данных или конфигурация сервера HTTP или такая, но когда-нибудь вы этого не сделаете.

Некоторые более конкретные способы перемещения кода или изменения определения для уменьшения проблем с зависимостями:

  • Отдельные основные задачи из приложения зависящих от них. В одном приложении, на котором я работаю на другом языке, есть модуль «utils», который смешивает общие задачи (например, время форматирования или работу с HTML) с конкретными приложениями (это зависит от пользовательской схемы и т. Д.). Но пакет пользователей импортирует utils, создавая цикл. Если бы я портировал Go, я бы переместил зависящие от пользователя utils «вверх» из модуля utils, возможно, чтобы жить с кодом пользователя или даже над ним.

  • Рассмотрите возможность разборки пакетов с сумкой. Немного увеличьте в последнем пункте: если две части функциональности независимы (то есть все еще работает, если вы переместите какой-то код в другой пакет) и, не связанные с точки зрения пользователя, они могут быть разделены на два пакета ,Иногда связывание безвредно, но в других случаях это приводит к дополнительным зависимостям, или менее общее название пакета просто проясняет код. Таким образом, мой utils может быть разбит по теме или зависимости (например, strutil, dbutil и т. Д.). Если вы закончите с большим количеством пакетов таким образом, у нас есть goimports, чтобы помочь им справиться.

  • Заменить типы объектов, требующих импорта, в API с базовыми типами и interface s. Скажите, что два объекта в вашем приложении имеют отношение «многие ко многим», такие как User s и Group s. Если они живут в разных пакетах (большой «если»), вы не можете иметь u.Groups(), возвращающий []group.Group и g.Users(), возвращающий []user.User, потому что для этого требуется, чтобы пакеты импортировали друг друга.

    Однако, вы можете изменить один или оба это возвращение, скажем, []uint идентификаторов или sql.Rows или какой-либо другой interface вы можете получить без import ИНГОВ конкретного типа объекта. В зависимости от вашего варианта использования такие типы, как User и Group, могут быть настолько тесно связаны, что лучше просто разместить их в одном пакете, но если вы решите, что они должны быть разными, это способ.

Спасибо за подробный вопрос и последующее наблюдение.

+0

Благодарим вас за подробный ответ. Я не понял первого абзаца. У вас означает, что настройки/router.py должны быть импортированы приложениями точно так же, как settings/database.py? Фактически, я использовал gorilla/mux для преобразования. Проблема в том, что даже для мультиплексоров вспомогательные маршруты должны быть добавлены в объект мультиплексирования (как и в python), возвращенный mux.newRouter(). Я не знаю, как перенести это на модель регистра. Какие-нибудь примеры? – Nithin

+0

Что вы подразумеваете под «Как правило, маршруты, база данных и константы звучат как низкоуровневые фрагменты, которые нужно импортировать с помощью кода приложения, а не импортировать». – Nithin

+0

В вашем пакете 'router' может быть глобальный' var Router = mux.NewRouter() ', и отдельные приложения могут« импортировать »маршрутизатор« »и вызывать' router.Router.HandleFunc (...) '. – twotwotwo

-4

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

Golang намного превосходит по сравнению с python относительно управления пакетами. В python вы даже можете динамически импортировать пакеты.

Для больших проектов golang гарантирует, что ваши пакеты более удобны в обслуживании.

Смежные вопросы