Scrierea pluginurilor în Go

Nu puteți încărca dinamic codul înainte de Go 1.8. Sunt un susținător mare al sistemelor bazate pe pluginuri, care, în multe cazuri, necesită dinamic încărcarea pluginurilor. Am considerat chiar la un moment dat scrierea unui pachet bazat pe integrarea C.

Sunt foarte entuziasmat de faptul că designerii Go au adăugat această capacitate la limbă. În acest tutorial, veți afla de ce pluginurile sunt atât de importante, ce platforme sunt în prezent acceptate și cum să creați, să construiți, să încărcați și să utilizați pluginuri în programele dvs..   

Rationale pentru pluginurile Go

Go plugins pot fi folosite în mai multe scopuri. Acestea vă permit să vă descompuneți sistemul într-un motor generic, ușor de raționat și testat, și o mulțime de plugin-uri să adere la o interfață strictă cu responsabilități bine definite. Plugin-urile pot fi dezvoltate independent de programul principal care le folosește. 

Programul poate folosi simultan diferite combinații de pluginuri și chiar mai multe versiuni ale aceluiași plugin. Limitele clare dintre programul principal și pluginurile promovează cea mai bună practică a cuplării libere și a separării preocupărilor.

Pachetul "plugin"

Noul pachet "plugin" introdus în Go 1.8 are un domeniu foarte mic și o interfață. Acesta oferă Deschis() pentru a încărca o bibliotecă partajată, care returnează un obiect Plugin. Obiectul Plugin are a Privește în sus() funcția care returnează un simbol (interfața goală ) poate fi tipărită într-o funcție sau variabilă expusă de plugin. Asta e.

Suport pentru platforme

Pachetul plugin-ului este acceptat numai pe Linux în acest moment. Dar există modalități, după cum veți vedea, de a juca cu plugin-uri pe orice sistem de operare.

Pregătirea unui mediu bazat pe Docker

Dacă sunteți în curs de dezvoltare pe o cutie Linux, atunci trebuie doar să instalați Go 1.8 și sunteți bine de făcut. Dar, dacă sunteți pe Windows sau MacOS, aveți nevoie de un container Linux VM sau Docker. Pentru ao folosi, trebuie să instalați mai întâi Docker.

Odată ce ați instalat Docker, deschideți o fereastră a consolei și tastați: docker rula -i -v ~ / go: / go golang: 1.8-wheezy bash

Această comandă îmi arată harta locală $ GOPATH la ~ / Du-te la /merge în interiorul containerului. Asta îmi permite să editez codul utilizând instrumentele mele preferate pe gazdă și să îl pun la dispoziție în interiorul containerului pentru construirea și funcționarea în mediul Linux.

Pentru mai multe informații despre Docker, consultați seria mea "Docker From the Ground Up" aici pe Envato Tuts +:

  • Docker de la sol: înțelegerea imaginilor
  • Docker de la Ground Up: Construirea de imagini
  • Docker de la sol: lucrul cu containerele, partea 1
  • Docker de la sol: lucrul cu containerele, partea 2

Crearea unui Plug Go

Plug-in-ul A Go pare un pachet obișnuit și îl puteți folosi și ca pachet obișnuit. Acesta devine un plugin numai atunci când îl construiți ca un plugin. Iată câteva cupluri care implementează o Fel()funcție care sortează o felie de numere întregi. 

QuickSort Plugin

Primul plugin implementează un algoritm naiv QuickSort. Implementarea funcționează pe felii cu elemente unice sau cu duplicate. Valoarea returnată este un indicator pentru o felie de numere întregi. Acest lucru este util pentru funcțiile de sortare care își sortează elementele în loc, deoarece permite returnarea fără copiere. 

În acest caz, de fapt, creez mai multe felii intermediare, deci efectul este cel mai mult risipit. Eu sacrific performanța pentru citire aici, deoarece obiectivul este să demonstreze plugin-uri și să nu pună în aplicare un algoritm super-eficient. Logica merge după cum urmează:

  • Dacă există elemente zero sau un element, returnați fragmentul original (deja sortat).
  • Alegeți un element aleatoriu ca un cuier.
  • Adăugați toate elementele care sunt mai mici decât cuierul de mai jos felie.
  • Adăugați toate elementele care sunt mai mari decât cuierul de mai sus felie. 
  • Adăugați toate elementele care sunt egale cu cârligul mijloc felie.

În acest moment, mijloc slice este sortat deoarece toate elementele sale sunt egale (dacă au existat duplicate ale cuiului, vor exista mai multe elemente aici). Acum vine partea recursivă. Se sortează de mai jos și de mai sus felii prin apelare Fel() din nou. Când aceste apeluri se întorc, toate felii vor fi sortate. Apoi, pur și simplu adăugați-le rezultă într-un fel complet de felie originală de elemente.

pachetul principal de import "math / rand" func Sort (elemente [] int) * [] int if len (items) < 2  return &items  peg := items[rand.Intn(len(items))] below := make([]int, 0, len(items)) above := make([]int, 0, len(items)) middle := make([]int, 0, len(items)) for _, item := range items  switch  case item < peg: below = append(below, item) case item == peg: middle = append(middle, item) case item > peg: deasupra = append (de mai sus, element) mai jos = * Sortare (de mai jos) de mai sus =

Plugin-ul BubbleSort

Al doilea plugin implementează algoritmul BubbleSort într-un mod naiv. BubbleSort este adesea considerat lent, dar pentru un număr mic de elemente și cu unele optimizări minore adesea bate algoritmi mai sofisticați precum QuickSort. 

Este de fapt obișnuită utilizarea unui algoritm de sortare hibrid care începe cu QuickSort, iar atunci când recursiunea ajunge la matrice suficient de mici, algoritmul trece la BubbleSort. Pluginul de sortare a bulei implementează a Fel() funcția cu aceeași semnătură ca algoritmul de sortare rapidă. Logica merge după cum urmează:

  • Dacă există elemente zero sau un element, returnați fragmentul original (deja sortat).
  • Iterați peste toate elementele.
  • În fiecare iterație, repetați restul elementelor.
  • Schimbați articolul curent cu orice element care este mai mare.
  • La sfârșitul fiecărei iterații, elementul curent va fi în locul său corespunzător.
pachet principal func Sort (elemente [] int) * [] int if len (elemente) < 2  return &items  tmp := 0 for i := 0; i < len(items); i++  for j := 0; j < len(items)-1; j++  if items[j] > articolele [j + 1] tmp = elementele [j] elementele [j] = elementele [j + 1] 

Construirea pluginului

Acum, avem două pluginuri pe care trebuie să le construim pentru a crea o bibliotecă care să poată fi încărcată dinamic de programul nostru principal. Comanda pentru a construi este: go build -buildmode = plugin

Deoarece avem mai multe pluginuri, am plasat fiecare într-un director separat sub un director "plugins" partajat. Aici este aspectul directorului din directorul de pluginuri. În fiecare subdirector de plugin-uri, există fișierul sursă "_plugin.go "și un script shell shell" build.sh "pentru a construi plugin-ul. Fișierele finale .so intră în directorul parent" plugins ":

$ Copac pluginuri plugin-uri ├── bubble_sort │ ├── bubble_sort_plugin.go │ └── build.sh ├── bubble_sort_plugin.so ├── sortare_rapidă │ ├── build.sh │ └── quick_sort_plugin.go └── quick_sort_plugin .asa de

Motivul pentru care fișierele * .so intră în directorul de pluginuri este că pot fi descoperite cu ușurință de programul principal, după cum veți vedea mai târziu. Comanda de construire reală în fiecare script "build.sh" specifică faptul că fișierul de ieșire ar trebui să intre în directorul părinte. De exemplu, pentru pluginul de sortare a bulelor este:

go build -buildmode = plugin -o ... /bubble_sort_plugin.so

Încărcarea pluginului

Încărcarea pluginului necesită cunoașterea locului unde să localizeze pluginurile vizate (* .de asemenea biblioteci partajate). Acest lucru se poate face în mai multe moduri:

  • parcurgând argumentele liniei de comandă
  • stabilirea unei variabile de mediu
  • folosind un director bine-cunoscut
  • utilizând un fișier de configurare

O altă preocupare este dacă programul principal cunoaște numele pluginurilor sau dacă descoperă dinamic toate pluginurile dintr-un anumit director. În exemplul următor, programul se așteaptă ca în directorul de lucru curent să existe un sub-director denumit "plugins" și să încarce toate pluginurile pe care le găsește.

Apel la filepath.Glob ( "plugins / *. asa") funcția returnează toate fișierele cu extensia ".so" din sub-directorul de pluginuri și plugin.Open (filename) încărcați pluginul. Dacă ceva nu merge bine, programul panică.

pachet de import principal ( "FMT", "plug-in", "calea / filepath") plinit principale () all_plugins, ERR: ( "plugin-uri / * astfel încât") = filepath.Glob dacă ERR = zero panică (ERR) pentru! _, filename: = interval (all_plugins) fmt.Println (filename) p, err:! = plugin.Open (filename) dacă err = zero panică (err) 

Utilizarea pluginului într-un program

Localizarea și încărcarea plugin-ului este doar jumătate din bătălie. Obiectul plugin oferă Privește în sus() metoda care dă un nume de simbol returnează o interfață. Trebuie să tastați afirmați că interfața într-un obiect concret (de exemplu, o funcție precum Fel()). Nu există nicio modalitate de a descoperi ce simboluri sunt disponibile. Trebuie doar să cunoașteți numele și tipul acestora, astfel încât să puteți tasta afirmația corectă. 

Atunci când simbolul este o funcție, îl puteți invoca ca orice altă funcție după o afirmație de succes de tip. Următorul program exemplu demonstrează toate aceste concepte. Încărcă dinamic toate pluginurile disponibile, fără a ști care sunt pluginurile, cu excepția faptului că se află în subdirectorul "pluginuri". Rezultă căutând în simbol "Sortare" în fiecare plugin și tastând-o într-o funcție cu semnătura func ([] int) * [] int. Apoi, pentru fiecare plugin, acesta invocă funcția sortare cu o felie de numere întregi și imprimă rezultatul.

pachetul principal de import ("fmt" "plugin" "path / filepath") func main () numere: = [] int 5,2,7,6,1,3,4,8 * .so fișierele) trebuie să se afle într-un subdirector "plugins" all_plugins, err: = filepath.Glob ("plugins / *. (all_plugins) p, err: = plugin.Open (filename) dacă err = zero panică (err) simbol, err:!! = p.Lookup ( "Sort") dacă err = zero panică (err) sortFunc, OK = simbolul (FUNC ([] int) * [] int) în cazul în care ok panica ( "Plugin-ul nu are nici o 'Sort ([] int) [] int' funcția") sortate:.! = sortFunc (numere) fmt.Println (nume de fișier, sortate) Output: plugins / bubble_sort_plugin.so & [1 2 3 4 5 6 7 8] plugins / quick_sort_plugin.so & [1 2 3 4 5 6 7 8] 

Concluzie

Pachetul "plugin" oferă o fundație excelentă pentru scrierea programelor Go sofisticate care pot încărca dinamic plugin-urile, după cum este necesar. Interfața de programare este foarte simplă și necesită o cunoaștere detaliată a programului de utilizare pe interfața plugin. 

Este posibil să construiți un cadru de pluginuri mai avansat și mai ușor de utilizat pe lângă pachetul "plugin". Sperăm că va fi transferată în curând la toate platformele. Dacă implementați sistemele pe Linux, luați în considerare utilizarea pluginurilor pentru a vă face programele mai flexibile și mai extensibile.

Cod