Одна из самых недооцененных функций, представленных с версии Go 1.8, - это пакет плагинов Go. Плагины - это один из многих архитектурных проектов программного обеспечения, которые позволяют создавать слабосвязанные и модульные программы. В Go плагины пишутся и компилируются отдельно как общие объекты (.so) или библиотеки и могут загружаться динамически во время выполнения.
Включение расширяемости в ваши программы всегда считается хорошей практикой, и есть много подходов к этому. Однако текущий пакет плагинов в Go действительно сопряжен с изрядной долей головной боли. Ниже приведена таблица, в которой перечислены плюсы и минусы плагинов Go в их текущем состоянии.
Плюсы
- Плагины Go дают вам инструмент, который поможет вам принять принцип единой ответственности и разделения задач.
- Это помогает разбить ваш код на небольшие управляемые и повторно используемые компоненты.
- Это дает вам возможность динамически загружать плагины во время выполнения приложения без перекомпиляции программы.
Минусы
- Среда для создания плагина Go, такая как ОС, языковая версия Go и версии зависимостей, должна точно совпадать.
- На данный момент выгрузка плагинов не разрешена без перезапуска программы.
- Вы не можете заменить плагин более новой версией во время выполнения; это потому, что Go в настоящее время не поддерживает выгрузку плагинов.
- Начиная с Go v1.11, вы можете создавать плагины только для Linux, FreeBSD и Mac.
Пример программы: калькулятор доставки
Чтобы продемонстрировать, как разрабатывать плагины на Go, мы создадим минимум и, по общему признанию, непрактичный пример калькулятора доставки. Этот пример, хотя и непрактичный, будет полезен, чтобы понять, как плагины работают в Go.
Базовый калькулятор доставки предоставит вам тарифы в зависимости от того, какой способ доставки и вес посылки вы предоставите. Вы можете поддерживать различные способы доставки, добавляя новые плагины, и калькулятор будет определять ставки и валюту в зависимости от вашего предпочтительного способа доставки. Интерфейс для способа доставки содержит три функции: GetCurrency, CalculateRate и Name.
Приступим к кодированию!
Среда разработки и пакеты
- Go 1.15 с модулями Go
- GoLand IDE (vscode или любой текстовый редактор будет работать нормально)
- Настольный компьютер v0.0.4
Создайте и инициализируйте проект с помощью модулей Go
mkdir go-plugins-shipping-calculator cd go-plugins-shipping-calculator go mod init go-plugins-shipping-calculator go get github.com/olekukonko/[email protected]
Основная точка входа в приложение
package main import ( "fmt" "github.com/olekukonko/tablewriter" "log" "os" "plugin" "strconv" ) type Shipper interface { Name() string Currency() string CalculateRate(weight float32) float32 } func main() { args := os.Args[1:] if len(args) == 2 { pluginName := args[0] weight, _ := strconv.ParseFloat(args[1], 32) // Load the plugin // 1. Search the plugins directory for a file with the same name as the pluginName // that was passed in as an argument and attempt to load the shared object file. plug, err := plugin.Open(fmt.Sprintf("plugins/%s.so", pluginName)) if err != nil { log.Fatal(err) } // 2. Look for an exported symbol such as a function or variable // in our case we expect that every plugin will have exported a single struct // that implements the Shipper interface with the name "Shipper" shipperSymbol, err := plug.Lookup("Shipper") if err != nil { log.Fatal(err) } // 3. Attempt to cast the symbol to the Shipper // this will allow us to call the methods on the plugins if the plugin // implemented the required methods or fail if it does not implement it. var shipper Shipper shipper, ok := shipperSymbol.(Shipper) if !ok { log.Fatal("Invalid shipper type") } // 4. If everything is ok from the previous assertions, then we can proceed // with calling the methods on our shipper interface object rate := shipper.CalculateRate(float32(weight)) rate1Day := fmt.Sprintf("%.2f %s", rate, shipper.Currency()) rate2Days := fmt.Sprintf("%.2f %s", rate - (rate * .20), shipper.Currency()) rate7Days := fmt.Sprintf("%.2f %s", rate - (rate * .70), shipper.Currency()) table := tablewriter.NewWriter(os.Stdout) fmt.Println(shipper.Name()) table.SetHeader([]string{"Number of Days", "Rate"}) table.Append([]string{"1 Day Express", rate1Day}) table.Append([]string{"2 Days Shipping", rate2Days}) table.Append([]string{"7 Days Shipping", rate7Days}) table.Render() } }
Плагины
Реализация FedEx грузоотправителя
// file: fedex/fedex.go package main type shipper struct {} func (s shipper) Name() string { return "Federal Express (Fedex)" } func (s shipper) Currency() string { return "USD" } func (s shipper) CalculateRate(weight float32) float32 { cost := weight * 1.8 tax := cost * .10 return cost + tax } var Shipper shipper
Реализация отправителя королевской почты
# file: royalmail/royalmail.go package main type shipper struct {} func (s shipper) Name() string { return "Royal Mail (RM)" } func (s shipper) Currency() string { return "GBP" } func (s shipper) CalculateRate(weight float32) float32 { cost := weight * .9 tax := cost * .5 return cost + tax } var Shipper shipper
Спасибо за чтение и удачного кодирования!
Исходный код: https://github.com/ManiMuridi/go-plugins-shipping-calculator