Jak stworzyć elastyczną architekturę wtyczek?

154

Powtarzającym się tematem w moich pracach programistycznych było wykorzystanie lub stworzenie własnej architektury wtyczek. Widziałem, że podchodzono do tego na wiele sposobów - pliki konfiguracyjne (XML, .conf itd.), Struktury dziedziczenia, informacje o bazach danych, biblioteki i inne. Z mojego doświadczenia:

  • Baza danych nie jest dobrym miejscem do przechowywania informacji o konfiguracji, zwłaszcza w połączeniu z danymi
  • Próba tego z hierarchią dziedziczenia wymaga wiedzy na temat wtyczek do zakodowania, co oznacza, że ​​architektura wtyczek nie jest aż tak dynamiczna
  • Pliki konfiguracyjne dobrze sprawdzają się w dostarczaniu prostych informacji, ale nie obsługują bardziej złożonych zachowań
  • Wydaje się, że biblioteki działają dobrze, ale zależności jednokierunkowe muszą być starannie tworzone.

Kiedy staram się uczyć z różnych architektur, z którymi pracowałem, szukam także sugestii społeczności. W jaki sposób zaimplementowałeś architekturę wtyczek SOLID? Jaka była Twoja najgorsza porażka (lub najgorsza porażka, jaką widziałeś)? Co byś zrobił, gdybyś miał zaimplementować nową architekturę wtyczek? Który projekt SDK lub open source, z którym pracowałeś, ma najlepszy przykład dobrej architektury?

Kilka przykładów, które sam znalazłem:

Te przykłady wydają się grać dla różnych mocnych stron języka. Czy dobra architektura wtyczki jest koniecznie powiązana z językiem? Czy najlepiej jest użyć narzędzi do tworzenia architektury wtyczek, czy zrobić to na własnych, następujących modelach?

justkt
źródło
Czy możesz powiedzieć, dlaczego architektura wtyczek jest wspólnym tematem? Jakie problemy rozwiązuje lub jakie cele rozwiązuje? Wiele rozszerzalnych systemów / aplikacji używa wtyczek w jakiejś formie, ale forma różni się znacznie w zależności od rozwiązywanego problemu.
mdma
@mdma - to świetne pytanie. Powiedziałbym, że istnieją pewne wspólne cele w rozszerzalnym systemie. Być może cele są wspólne, a najlepsze rozwiązanie różni się w zależności od tego, jak rozszerzalny musi być system, w jakim języku jest napisany i tak dalej. Widzę jednak wzorce, takie jak IOC, stosowane w wielu językach i wielokrotnie proszono mnie o wykonanie podobnych wtyczek (w celu upuszczania elementów odpowiadających na te same żądania funkcjonalności). Myślę, że dobrze jest uzyskać ogólne pojęcie o najlepszych praktykach dla różnych typów wtyczek.
justkt

Odpowiedzi:

89

To nie jest odpowiedź, a garść potencjalnie przydatnych uwag / przykładów.

  • Jednym ze skutecznych sposobów na rozszerzenie aplikacji jest ujawnienie jej elementów wewnętrznych jako języka skryptowego i napisanie wszystkich najważniejszych rzeczy w tym języku. To sprawia, że ​​jest dość modyfikowalny i praktycznie przyszłościowy (jeśli twoje prymitywy są dobrze wybrane i zaimplementowane). Historią sukcesu tego rodzaju rzeczy jest Emacs. Wolę to od systemu wtyczek w stylu eclipse, ponieważ jeśli chcę rozszerzyć funkcjonalność, nie muszę uczyć się API i pisać / kompilować osobnej wtyczki. Potrafię napisać 3-liniowy fragment w samym bieżącym buforze, ocenić go i użyć. Bardzo płynna krzywa uczenia się i bardzo przyjemne wyniki.

  • Jedną z aplikacji, którą trochę rozszerzyłem, jest Trac . Ma architekturę komponentów, co w tej sytuacji oznacza, że ​​zadania są delegowane do modułów ogłaszających punkty rozszerzeń. Następnie możesz zaimplementować inne komponenty, które pasowałyby do tych punktów i zmienić przepływ. To trochę jak sugestia Kalkie powyżej.

  • Kolejnym dobrym jest py.test . Jest zgodne z filozofią „best API is no API” i opiera się wyłącznie na wywoływaniu hooków na każdym poziomie. Możesz przesłonić te punkty zaczepienia w plikach / funkcjach nazwanych zgodnie z konwencją i zmienić zachowanie. Możesz zobaczyć listę wtyczek na stronie, aby zobaczyć, jak szybko / łatwo można je zaimplementować.

Kilka ogólnych uwag.

  • Postaraj się, aby Twój nierozszerzalny / niemodyfikowalny przez użytkownika rdzeń był jak najmniejszy. Przekaż wszystko, co możesz, na wyższą warstwę, aby zwiększyć rozszerzalność. Mniej rzeczy do poprawienia w rdzeniu niż w przypadku złych wyborów.
  • Z powyższym wiąże się to, że na początku nie powinieneś podejmować zbyt wielu decyzji dotyczących kierunku swojego projektu. Zaimplementuj najmniejszy potrzebny podzbiór, a następnie zacznij pisać wtyczki.
  • Jeśli osadzasz język skryptowy, upewnij się, że jest to pełny język, w którym możesz pisać ogólne programy, a nie język zabawek tylko dla swojej aplikacji.
  • Zmniejsz moc obliczeniową tak bardzo, jak tylko możesz. Nie przejmuj się podklasami, złożonymi interfejsami API, rejestracją wtyczek i tym podobnymi. Postaraj się, aby było to proste, aby było łatwe, a nie tylko możliwe do rozszerzenia. Pozwoli to na większe wykorzystanie API wtyczek i zachęci użytkowników końcowych do pisania wtyczek. Nie tylko twórcy wtyczek. py.test robi to dobrze. Zaćmienie, o ile wiem, nie .
Noufal Ibrahim
źródło
1
Rozszerzyłbym język skryptowy na to, że warto rozważyć osadzenie istniejącego języka, takiego jak python czy perl
Rudi
Prawdziwy Rudi. Wiele języków jest przygotowywanych do osadzania. Na przykład podstęp jest stworzony tylko do osadzania. Oszczędza to czas, jeśli chcesz, aby aplikacja była skryptowalna. Możesz po prostu wpaść w jednym z tych języków.
Noufal Ibrahim
24

Z mojego doświadczenia wynika, że ​​istnieją naprawdę dwa typy architektur wtyczek.

  1. Jeden podąża za tym, Eclipse modelco ma pozwolić na wolność i jest otwarty.
  2. Drugi zwykle wymaga wtyczek, które następują narrow APIpo znaku, ponieważ wtyczka będzie wypełniać określoną funkcję.

Aby wyrazić to w inny sposób, jedna umożliwia wtyczkom dostęp do aplikacji, a druga umożliwia aplikacji dostęp do wtyczek .

Rozróżnienie jest subtelne i czasami nie ma rozróżnienia ... potrzebujesz obu do swojego zastosowania.

Nie mam dużego doświadczenia z Eclipse / Otwieraniem twojej aplikacji na model wtyczek (artykuł w poście Kalkie jest świetny). Czytałem trochę o tym, jak zaćmienie działa, ale nic poza tym.

Blog właściwości Yegge mówi trochę o tym, jak użycie wzorca właściwości pozwala na wtyczki i rozszerzalność.

Większość pracy, którą wykonałem, wykorzystywała architekturę wtyczek, aby umożliwić mojej aplikacji dostęp do wtyczek, rzeczy takich jak dane dotyczące czasu / wyświetlania / mapy itp.

Wiele lat temu tworzyłem fabryki, menedżery wtyczek i pliki konfiguracyjne, aby zarządzać tym wszystkim i pozwolić mi określić, której wtyczki użyć w czasie wykonywania.

  • Teraz zwykle DI frameworkwykonuję większość tej pracy.

Nadal muszę pisać adaptery, aby korzystać z bibliotek innych firm, ale zwykle nie są takie złe.

menjaraz
źródło
@ Justin tak, DI = iniekcja zależności.
wyprowadzenie
14

Jedna z najlepszych architektur wtyczek, jakie widziałem, została zaimplementowana w Eclipse. Zamiast mieć aplikację z modelem wtyczki, wszystko jest wtyczką. Podstawową aplikacją jest sama struktura wtyczek.

http://www.eclipse.org/articles/Article-Plug-in-architecture/plugin_architecture.html

Patrick
źródło
Z pewnością dobry przykład, ale czy jest większy i bardziej złożony niż wymaga tego wiele aplikacji?
justkt
Tak, mogłoby być, wzrost elastyczności wiąże się z kosztami. Ale myślę, że to pokazuje inny punkt widzenia. Dlaczego menu twojej aplikacji nie mogło być wtyczką lub głównym widokiem?
Patrick
6
To samo podejście (wszystko jest wtyczką) jest stosowane w nowej wersji frameworka Symfony. Nazywa się je wiązkami, ale zasada jest taka sama. Jego architektura jest dość prosta, być może powinieneś na nią spojrzeć: symfony-reloaded.org/quick-tour-part-4
Marijn Huizendveld
@Marjin Huizendveld - należy to dodać jako osobną odpowiedź, abym mógł ją zagłosować. Wygląda na ciekawe ramy!
justkt
11

Opiszę dość prostą technikę, której używałem w przeszłości. To podejście wykorzystuje odbicie C #, aby pomóc w procesie ładowania wtyczki. Tę technikę można zmodyfikować, aby można ją było zastosować w C ++, ale tracisz wygodę korzystania z odbicia.

IPluginInterfejs służy do identyfikacji klasy implementujące wtyczek. Do interfejsu dodano metody umożliwiające komunikację aplikacji z wtyczką. Na przykład Initmetoda, której aplikacja będzie używać do instruowania wtyczki, aby zainicjowała.

Aby znaleźć wtyczki, aplikacja skanuje folder wtyczek w poszukiwaniu zestawów .Net. Każdy zespół jest ładowany. Odbicie służy do skanowania klas, które implementują IPlugin. Tworzona jest instancja każdej klasy wtyczki.

(Alternatywnie, plik XML może zawierać listę zestawów i klas do załadowania. Może to zwiększyć wydajność, ale nigdy nie znalazłem problemu z wydajnością).

InitMetoda jest wywoływana dla każdego obiektu wtyczek. Jest przekazywana referencja do obiektu, który implementuje interfejs aplikacji: IApplication(lub coś innego o nazwie specyficznej dla Twojej aplikacji, np. ITextEditorApplication).

IApplicationzawiera metody umożliwiające wtyczce komunikację z aplikacją. Na przykład, jeśli piszesz edytor tekstu, ten interfejs miałby OpenDocumentswłaściwość, która umożliwia wtyczkom wyliczanie kolekcji aktualnie otwartych dokumentów.

Ten system wtyczek można rozszerzyć o języki skryptowe, np. Lua, tworząc pochodną klasę wtyczki, np. LuaPluginPrzekazującą IPluginfunkcje i interfejs aplikacji do skryptu Lua.

Technika ta pozwala na iteracyjnie realizować swoje IPlugin, IApplicationi inne interfejsy specyficzne dla aplikacji w trakcie rozwoju. Kiedy aplikacja jest kompletna i ładnie zreformowana, możesz udokumentować swoje ujawnione interfejsy i powinieneś mieć fajny system, dla którego użytkownicy mogą pisać własne wtyczki.

Ashley Davis
źródło
7

Zwykle używam MEF. Managed Extensibility Framework (lub w skrócie MEF) upraszcza tworzenie rozszerzalnych aplikacji. MEF oferuje funkcje wykrywania i kompozycji, które można wykorzystać do ładowania rozszerzeń aplikacji.

Jeśli jesteś zainteresowany, przeczytaj więcej ...

BALKANGraph
źródło
Dzięki, wygląda ciekawie. Jak łatwo jest zastosować podobne zasady w innych językach?
justkt
Właściwie MEF jest tylko dla frameworka .NET Nie wiem nic o innych frameworkach
BALKANGraph
7

Kiedyś pracowałem nad projektem, który musiał być tak elastyczny pod względem sposobu, w jaki każdy klient mógł skonfigurować system, a jedynym dobrym projektem, jaki znaleźliśmy, było dostarczenie klientowi kompilatora C #!

Jeśli specyfikacja jest wypełniona słowami takimi jak:

  • Elastyczne
  • Podłącz
  • Możliwość dostosowania

Zadawaj wiele pytań o to, w jaki sposób będziesz obsługiwać system (i jak będzie naliczana opłata za wsparcie, ponieważ każdy klient będzie uważał, że jego sprawa jest normalna i nie potrzebuje żadnych wtyczek), tak jak z mojego doświadczenia.

Wsparcie klientów (lub osób wspierających z linii fontann) piszących Wtyczki jest o wiele trudniejsze niż Architektura

Ian Ringrose
źródło
5

Z mojego doświadczenia wynika, że ​​dwa najlepsze sposoby na stworzenie elastycznej architektury wtyczek to języki skryptowe i biblioteki. Te dwie koncepcje są w moim umyśle ortogonalne; można je mieszać w dowolnych proporcjach, podobnie jak programowanie funkcjonalne i obiektowe, ale ich największe zalety znajdują się w równowadze. Biblioteka jest zwykle odpowiedzialna za wypełnienie określonego interfejsu dynamiczną funkcjonalnością, podczas gdy skrypty mają tendencję do kładzenia nacisku na funkcjonalność za pomocą dynamicznego interfejsu.

Odkryłem, że najlepiej sprawdza się architektura oparta na skryptach zarządzających bibliotekami . Język skryptowy umożliwia wysokopoziomową manipulację bibliotekami niższego poziomu, dzięki czemu biblioteki są zwolnione z jakiegokolwiek określonego interfejsu, pozostawiając całą interakcję na poziomie aplikacji w bardziej elastycznych rękach systemu skryptowego.

Aby to działało, system skryptów musi mieć dość solidne API, z zaczepami do danych aplikacji, logiki i GUI, a także podstawową funkcjonalność importu i wykonywania kodu z bibliotek. Co więcej, skrypty są zwykle wymagane, aby były bezpieczne w tym sensie, że aplikacja może z wdziękiem odzyskać dane po źle napisanym skrypcie. Używanie systemu skryptów jako warstwy pośredniej oznacza, że ​​aplikacja może łatwiej odłączyć się w przypadku wystąpienia czegoś złego ™.

Sposób pakowania wtyczek zależy w dużej mierze od osobistych preferencji, ale nigdy nie można się pomylić ze skompresowanym archiwum z prostym interfejsem, powiedzmy PluginName.extw katalogu głównym.

Jon Purdy
źródło
3

Myślę, że najpierw musisz odpowiedzieć na pytanie: „Jakie komponenty mają być wtyczkami?” Chcesz ograniczyć tę liczbę do absolutnego minimum lub liczbę kombinacji, które musisz przetestować, wybucha. Spróbuj oddzielić swój podstawowy produkt (który nie powinien mieć zbyt dużej elastyczności) od funkcjonalności wtyczki.

Odkryłem, że zasada IOC (Inversion of Control) (przeczytaj springframework) działa dobrze, zapewniając elastyczną bazę, do której możesz dodać specjalizację, aby uprościć tworzenie wtyczek.

  • Możesz przeskanować kontener w poszukiwaniu mechanizmu "interfejs jako reklama typu wtyczki".
  • Możesz użyć kontenera do wstrzyknięcia typowych zależności, których mogą wymagać wtyczki (np. ResourceLoaderAware lub MessageSourceAware).
Justin
źródło
1

Wzorzec wtyczki to programowy wzorzec służący do rozszerzania zachowania klasy o przejrzysty interfejs. Często zachowanie klas jest rozszerzone przez dziedziczenie klas, gdzie klasa pochodna nadpisuje niektóre metody wirtualne klasy. Problem z tym rozwiązaniem polega na tym, że koliduje z ukrywaniem implementacji. Prowadzi to również do sytuacji, w których klasa pochodna staje się miejscem gromadzenia niepowiązanych rozszerzeń zachowania. Również skrypty są używane do implementacji tego wzorca, jak wspomniano powyżej "Twórz elementy wewnętrzne jako język skryptowy i pisz wszystkie rzeczy najwyższego poziomu w tym języku. To sprawia, że ​​jest on dość modyfikowalny i praktycznie zabezpieczony w przyszłości". Biblioteki używają bibliotek zarządzających skryptami. Język skryptowy umożliwia wysokopoziomową manipulację bibliotekami niższego poziomu. (Również jak wspomniano powyżej)

PresB4Me
źródło