Làm thế nào để tạo một kiến ​​trúc trình cắm linh hoạt?


154

Một chủ đề lặp đi lặp lại trong công việc phát triển của tôi là việc sử dụng hoặc tạo ra một kiến ​​trúc trình cắm thêm trong nhà. Tôi đã thấy nó tiếp cận nhiều cách - các tệp cấu hình (XML, .conf, v.v.), khung kế thừa, thông tin cơ sở dữ liệu, thư viện và các thứ khác. Theo kinh nghiệm của tôi:

  • Cơ sở dữ liệu không phải là nơi tuyệt vời để lưu trữ thông tin cấu hình của bạn, đặc biệt là kết hợp với dữ liệu
  • Cố gắng điều này với hệ thống phân cấp thừa kế đòi hỏi kiến ​​thức về các trình cắm được mã hóa, có nghĩa là kiến ​​trúc trình cắm không hoàn toàn năng động
  • Các tệp cấu hình hoạt động tốt để cung cấp thông tin đơn giản, nhưng không thể xử lý các hành vi phức tạp hơn
  • Các thư viện dường như hoạt động tốt, nhưng các phụ thuộc một chiều phải được tạo cẩn thận.

Khi tôi tìm cách học hỏi từ các kiến ​​trúc khác nhau mà tôi đã làm việc cùng, tôi cũng đang tìm kiếm cộng đồng để được gợi ý. Bạn đã triển khai kiến ​​trúc trình cắm RẮN như thế nào? Thất bại tồi tệ nhất của bạn (hoặc thất bại tồi tệ nhất bạn từng thấy) là gì? Bạn sẽ làm gì nếu bạn định triển khai kiến ​​trúc trình cắm thêm mới? SDK hoặc dự án nguồn mở nào mà bạn đã làm việc có ví dụ tốt nhất về kiến ​​trúc tốt?

Một vài ví dụ tôi đã tự mình tìm kiếm:

Những ví dụ này dường như chơi với các thế mạnh ngôn ngữ khác nhau. Là một kiến ​​trúc plugin tốt nhất thiết phải gắn liền với ngôn ngữ? Là tốt nhất để sử dụng các công cụ để tạo một kiến ​​trúc plugin, hoặc thực hiện nó trên các mô hình sau đây của chính mình?


Bạn có thể nói tại sao một kiến ​​trúc plugin là một chủ đề phổ biến? Những vấn đề nào nó giải quyết, hoặc mục tiêu nó giải quyết? Nhiều hệ thống / ứng dụng có thể mở rộng sử dụng các plugin của một số dạng, nhưng hình thức thay đổi đáng kể tùy thuộc vào vấn đề được giải quyết.
mdma

@mdma - đó là một câu hỏi hay Tôi muốn nói có một số mục tiêu chung trong một hệ thống mở rộng. Có lẽ tất cả các mục tiêu đều phổ biến và giải pháp tốt nhất khác nhau tùy thuộc vào mức độ mở rộng của hệ thống, ngôn ngữ mà hệ thống được viết, v.v. Tuy nhiên, tôi thấy các mẫu như IOC được áp dụng trên nhiều ngôn ngữ và tôi đã được yêu cầu thực hiện các plugin tương tự (để thả các mảnh đáp ứng các yêu cầu chức năng tương tự) nhiều lần. Tôi nghĩ thật tốt khi có được một ý tưởng chung về các thực tiễn tốt nhất cho các loại plugin khác nhau.
justkt

Câu trả lời:


89

Đây không phải là một câu trả lời nhiều như một loạt các nhận xét / ví dụ có thể hữu ích.

  • Một cách hiệu quả để làm cho ứng dụng của bạn có thể mở rộng là hiển thị nội bộ của nó dưới dạng ngôn ngữ kịch bản và viết tất cả nội dung cấp cao nhất bằng ngôn ngữ đó. Điều này làm cho nó khá dễ sửa đổi và bằng chứng thực tế trong tương lai (nếu nguyên thủy của bạn được lựa chọn và thực hiện tốt). Một câu chuyện thành công của loại điều này là Emacs. Tôi thích hệ thống plugin kiểu nhật thực này vì nếu tôi muốn mở rộng chức năng, tôi không phải học API và viết / biên dịch một plugin riêng. Tôi có thể viết một đoạn 3 dòng trong chính bộ đệm hiện tại, đánh giá nó và sử dụng nó. Đường cong học tập rất trơn tru và kết quả rất hài lòng.

  • Một ứng dụng mà tôi đã mở rộng một chút là Trac . Nó có một kiến ​​trúc thành phần mà trong tình huống này có nghĩa là các tác vụ được ủy quyền cho các mô-đun quảng cáo các điểm mở rộng. Sau đó, bạn có thể thực hiện các thành phần khác phù hợp với các điểm này và thay đổi luồng. Nó giống như gợi ý của Kalkie ở trên.

  • Một cái khác tốt là py.test . Nó tuân theo triết lý "API tốt nhất là không có API" và hoàn toàn dựa vào các hook được gọi ở mọi cấp độ. Bạn có thể ghi đè các hook này trong các tệp / hàm được đặt tên theo quy ước và thay đổi hành vi. Bạn có thể xem danh sách các plugin trên trang web để xem chúng có thể được thực hiện nhanh chóng / dễ dàng như thế nào.

Một vài điểm chung.

  • Cố gắng giữ cho lõi không thể mở rộng / không thể sửa đổi của bạn càng nhỏ càng tốt. Ủy thác mọi thứ bạn có thể lên một lớp cao hơn để khả năng mở rộng tăng lên. Ít thứ để sửa trong lõi sau đó trong trường hợp lựa chọn xấu.
  • Liên quan đến điểm trên là bạn không nên đưa ra quá nhiều quyết định về hướng đi của dự án ngay từ đầu. Thực hiện tập hợp con cần thiết nhỏ nhất và sau đó bắt đầu viết plugin.
  • Nếu bạn đang nhúng ngôn ngữ kịch bản, hãy đảm bảo đó là ngôn ngữ đầy đủ để bạn có thể viết chương trình chung chứ không phải ngôn ngữ đồ chơi chỉ dành cho ứng dụng của bạn.
  • Giảm nồi hơi càng nhiều càng tốt. Đừng bận tâm với phân lớp, API phức tạp, đăng ký plugin và những thứ tương tự. Cố gắng giữ cho nó đơn giản để dễ dàng và không chỉ có thể mở rộng. Điều này sẽ cho phép API plugin của bạn được sử dụng nhiều hơn và sẽ khuyến khích người dùng cuối viết plugin. Không chỉ các nhà phát triển plugin. py.test làm điều này tốt. Eclipse như tôi biết, không .

1
Tôi sẽ mở rộng điểm ngôn ngữ kịch bản đến mức đáng để xem xét để nhúng một ngôn ngữ hiện có như python hoặc perl
Rudi

Đúng là Rudi. Nhiều ngôn ngữ đang được thực hiện những thiết kế này được nhúng. Guile chẳng hạn, được làm chỉ để nhúng. Điều này giúp tiết kiệm thời gian nếu bạn làm cho ứng dụng của mình có thể được script. Bạn chỉ có thể thả vào một trong những ngôn ngữ này.
Noufal Ibrahim

24

Theo kinh nghiệm của tôi, tôi đã tìm thấy thực sự có hai loại Kiến trúc trình cắm.

  1. Một theo sau Eclipse modelđó là cho phép tự do và được kết thúc mở.
  2. Cái khác thường yêu cầu các plugin tuân theo narrow APIvì plugin sẽ điền vào một chức năng cụ thể.

Để nói điều này theo một cách khác, một cho phép các plugin truy cập ứng dụng của bạn trong khi cách khác cho phép ứng dụng của bạn truy cập các plugin .

Sự khác biệt là tinh tế, và đôi khi không có sự phân biệt ... bạn muốn cả hai cho ứng dụng của mình.

Tôi không có nhiều kinh nghiệm với Eclipse / Mở mô hình ứng dụng cho các plugin của bạn (bài viết trong bài đăng của Kalkie rất hay). Tôi đã đọc một chút về cách nhật thực làm mọi việc, nhưng không có gì hơn thế.

Blog thuộc tính của Yegge nói một chút về cách sử dụng mẫu thuộc tính cho phép bổ trợ và mở rộng.

Hầu hết các công việc tôi đã thực hiện đã sử dụng kiến ​​trúc plugin để cho phép ứng dụng của tôi truy cập các plugin, những thứ như thời gian / hiển thị / dữ liệu bản đồ, v.v.

Nhiều năm trước tôi sẽ tạo các nhà máy, trình quản lý plugin và tập tin cấu hình để quản lý tất cả và để tôi xác định plugin nào sẽ sử dụng khi chạy.

  • Bây giờ tôi thường chỉ có một DI frameworkcông việc đó.

Tôi vẫn phải viết bộ điều hợp để sử dụng thư viện của bên thứ ba, nhưng chúng thường không tệ.


@Justin có, DI = tiêm phụ thuộc.
phái sinh

14

Một trong những kiến ​​trúc trình cắm thêm tốt nhất mà tôi đã thấy được triển khai trong Eclipse. Thay vì có một ứng dụng với mô hình trình cắm, mọi thứ đều là trình cắm. Bản thân ứng dụng cơ sở là khung trình cắm thêm.

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


Chắc chắn là một ví dụ tốt, nhưng nó lớn hơn và phức tạp hơn nhiều ứng dụng yêu cầu?
justkt

Vâng, có thể, sự gia tăng tính linh hoạt đi kèm với một chi phí. Nhưng tôi nghĩ rằng nó cho thấy một quan điểm khác nhau. Tại sao menu của ứng dụng của bạn không phải là trình cắm hoặc chế độ xem chính?
Patrick

6
Cách tiếp cận tương tự (mọi thứ đều là plugin) được sử dụng trong phiên bản mới của khung Symfony. Chúng được gọi là bó nhưng nguyên tắc là như nhau. Kiến trúc của nó khá đơn giản, có lẽ bạn nên xem qua: symfony-reloaded.org/quick-tour-part-4
Marijn Huizendveld

@Marjin Huizendveld - bạn nên thêm câu này dưới dạng câu trả lời riêng để tôi có thể nâng cao câu trả lời. Trông giống như một khuôn khổ thú vị!
justkt

11

Tôi sẽ mô tả một kỹ thuật khá đơn giản mà tôi đã sử dụng trong quá khứ. Cách tiếp cận này sử dụng phản chiếu C # để trợ giúp trong quá trình tải plugin. Kỹ thuật này có thể được sửa đổi để nó có thể áp dụng cho C ++ nhưng bạn mất đi sự tiện lợi khi có thể sử dụng sự phản chiếu.

Một IPlugingiao diện được sử dụng để xác định các lớp thực hiện bổ trợ. Các phương thức được thêm vào giao diện để cho phép ứng dụng giao tiếp với plugin. Ví dụInit phương thức mà ứng dụng sẽ sử dụng để hướng dẫn plugin khởi tạo.

Để tìm plugin, ứng dụng quét một thư mục plugin cho các cụm .Net. Mỗi lắp ráp được tải. Reflection được sử dụng để quét các lớp thực hiệnIPlugin . Một thể hiện của mỗi lớp plugin được tạo ra.

(Ngoài ra, một tệp Xml có thể liệt kê các tập hợp và các lớp để tải. Điều này có thể giúp hiệu suất nhưng tôi không bao giờ thấy có vấn đề với hiệu suất).

Các Initphương pháp được gọi là cho từng đối tượng plugin. Nó được chuyển đến một tham chiếu đến một đối tượng thực hiện giao diện ứng dụng: IApplication(hoặc một cái gì đó khác có tên cụ thể cho ứng dụng của bạn, ví dụ: ITextEditorApplication).

IApplicationchứa các phương thức cho phép plugin giao tiếp với ứng dụng. Chẳng hạn, nếu bạn đang viết một trình soạn thảo văn bản, giao diện này sẽ có mộtOpenDocuments tính cho phép các trình cắm liệt kê bộ sưu tập các tài liệu hiện đang mở.

Hệ thống plugin này có thể được mở rộng sang các ngôn ngữ script, ví dụ Lua, bằng cách tạo một lớp plugin dẫn xuất, ví dụ: LuaPluginchuyển tiếp các IPluginchức năng và giao diện ứng dụng thành tập lệnh Lua.

Kỹ thuật này cho phép bạn thực hiện lặp đi lặp lại của bạn IPlugin, IApplicationvà giao diện ứng dụng cụ thể khác trong quá trình phát triển. Khi ứng dụng hoàn tất và được tái cấu trúc độc đáo, bạn có thể ghi lại các giao diện được hiển thị của mình và bạn sẽ có một hệ thống đẹp để người dùng có thể viết các plugin của riêng họ.


7

Thông thường tôi sử dụng MEF. Khung mở rộng được quản lý (viết tắt là MEF) đơn giản hóa việc tạo các ứng dụng mở rộng. MEF cung cấp các khả năng khám phá và sáng tác mà bạn có thể tận dụng để tải các tiện ích mở rộng ứng dụng.

Nếu bạn quan tâm đọc thêm ...


Cảm ơn, có vẻ thú vị. Làm thế nào dễ dàng để áp dụng hiệu trưởng tương tự trên các ngôn ngữ khác?
justkt

Trên thực tế MEF chỉ dành cho .NET framework Tôi không biết gì về các framework khác
BALKANGraph

7

Tôi đã từng làm việc với một dự án phải linh hoạt theo cách mỗi khách hàng có thể thiết lập hệ thống, mà thiết kế tốt duy nhất chúng tôi tìm thấy là gửi cho khách hàng một trình biên dịch C #!

Nếu thông số kỹ thuật chứa đầy các từ như:

  • Linh hoạt
  • Cắm vào
  • Tùy chỉnh

Đặt nhiều câu hỏi về cách bạn sẽ hỗ trợ hệ thống (và cách hỗ trợ sẽ được tính phí, vì mỗi khách hàng sẽ nghĩ trường hợp của họ là trường hợp bình thường và không cần bất kỳ trình cắm nào.), Như kinh nghiệm của tôi

Sự hỗ trợ của khách hàng (hoặc những người hỗ trợ fount-line) viết Plug-Ins khó hơn nhiều so với Kiến trúc


5

Theo kinh nghiệm của tôi, hai cách tốt nhất để tạo kiến ​​trúc plugin linh hoạt là ngôn ngữ kịch bản và thư viện. Hai khái niệm này là trong tâm trí của tôi trực giao; cả hai có thể được trộn lẫn trong bất kỳ tỷ lệ nào, giống như lập trình hướng đối tượng và chức năng, nhưng tìm ra điểm mạnh lớn nhất của chúng khi cân bằng. Một thư viện thường chịu trách nhiệm hoàn thành một giao diện cụ thể với chức năng động, trong khi các tập lệnh có xu hướng nhấn mạnh chức năng với giao diện động.

Tôi đã thấy rằng một kiến ​​trúc dựa trên các kịch bản quản lý thư viện dường như hoạt động tốt nhất. Ngôn ngữ kịch bản cho phép thao tác cấp cao của các thư viện cấp thấp hơn và do đó các thư viện được giải phóng khỏi mọi giao diện cụ thể, để lại tất cả các tương tác cấp ứng dụng trong tay linh hoạt hơn của hệ thống tập lệnh.

Để làm việc này, hệ thống tập lệnh phải có API khá mạnh mẽ, với các móc nối với dữ liệu ứng dụng, logic và GUI, cũng như chức năng cơ bản của việc nhập và thực thi mã từ các thư viện. Hơn nữa, các tập lệnh thường được yêu cầu phải an toàn theo nghĩa là ứng dụng có thể phục hồi một cách duyên dáng từ một tập lệnh được viết kém. Sử dụng một hệ thống kịch bản như một lớp gián tiếp có nghĩa là ứng dụng có thể dễ dàng tách ra hơn trong trường hợp Something Bad ™.

Các phương tiện đóng gói phụ thuộc phần lớn vào sở thích cá nhân, nhưng bạn không bao giờ có thể sai với một kho lưu trữ nén với giao diện đơn giản, nói PluginName.exttrong thư mục gốc.


3

Tôi nghĩ trước tiên bạn cần trả lời câu hỏi: "Thành phần nào được dự kiến ​​là plugin?" Bạn muốn giữ số này ở mức tối thiểu hoặc số lượng kết hợp mà bạn phải kiểm tra phát nổ. Cố gắng tách sản phẩm cốt lõi của bạn (vốn không nên có quá nhiều tính linh hoạt) khỏi chức năng plugin.

Tôi đã thấy rằng hiệu trưởng IOC (Inversion of Control) (đọc springframework) hoạt động tốt để cung cấp cơ sở linh hoạt, bạn có thể thêm chuyên môn hóa để làm cho việc phát triển plugin đơn giản hơn.

  • Bạn có thể quét vùng chứa cho cơ chế "giao diện dưới dạng quảng cáo loại plugin".
  • Bạn có thể sử dụng bộ chứa để thêm các phụ thuộc phổ biến mà các plugin có thể yêu cầu (ví dụ ResourceLoaderAware hoặc MessageSourceAware).

1

Mẫu trình cắm là một mẫu phần mềm để mở rộng hành vi của một lớp với giao diện sạch. Thông thường hành vi của các lớp được mở rộng bởi sự kế thừa lớp, trong đó lớp dẫn xuất ghi đè lên một số phương thức ảo của lớp. Một vấn đề với giải pháp này là nó mâu thuẫn với việc thực hiện ẩn. Nó cũng dẫn đến các tình huống trong đó lớp dẫn xuất trở thành nơi tập hợp các phần mở rộng hành vi không liên quan. Ngoài ra, kịch bản được sử dụng để triển khai mẫu này như đã đề cập ở trên "Biến nội bộ thành ngôn ngữ kịch bản và viết tất cả nội dung cấp cao nhất trong ngôn ngữ đó. Điều này làm cho nó có thể sửa đổi và thực tế trong tương lai". Thư viện sử dụng thư viện quản lý tập lệnh. Ngôn ngữ kịch bản cho phép thao tác cấp cao của các thư viện cấp thấp hơn. (Cũng như đã đề cập ở trên)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.