65.9K
CodeProject 正在变化。 阅读更多。
Home

使用托管可扩展性框架构建插件架构

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.44/5 (2投票s)

2024 年 3 月 22 日

CPOL

8分钟阅读

viewsIcon

6592

downloadIcon

112

如何在 C# 中构建插件架构?

引言

软件最有利的品质之一无疑是可扩展性。这本质上意味着我们的程序设计应该能够无缝集成新的功能或特性,而无需进行大量的重构或引入脆弱性。作为工程师,我们必须能够识别代码库中的这些可扩展点,或者在必要时,快速调整程序的某些区域以赋予其这种品质。

本文大纲如下:我们将首先探讨如何通过精心设计的模式(例如策略模式或模板方法模式)来实现这种能力,并揭示这些模式固有的局限性。因此,我们将过渡到一种更复杂但被广泛采用的方法,即围绕插件进行。通过这次旅程,我们将深入探讨插件体系结构的概念及其含义。

以下教科书对于结束本系列很有用:Software Architecture by Example: Using C# and .NET (Michaels)

本文最初发布于:使用托管可扩展性框架构建插件体系结构

何时应在软件中引入可扩展性?

将可扩展性功能集成到软件系统本身可能并不具有内在的挑战性。更复杂的是辨别、评估或预测何时引入此类可扩展点是适时且有益的。通常,这种需求是由客户隐式传达的,即使他们并未意识到这一点。

我们可以采用哪些方法来将可扩展点无缝集成到软件系统中?

在这种情况下,利用 C# 或 Java(后者并非过于严格)等面向对象编程语言,我们通常会创建一个接口,多个类可以从中继承。然后,客户需要在运行时调用适当的类。

信息
在 OOP 领域,目标是封装表现出变异性的元素。

我们考虑前面概述的简单场景,即必须为虚拟电子商务网站定义多种付款方式。

因此,第一步是定义一个合同,通常是一个 interface,以展示可用的方法。

public interface IPaymentMethod
{
    string Name { get; }
    
    bool TryProcessPayment(double amount);
}

信息

此处显示的接口是极简的。在实际场景中,很可能会有更多的方法和更复杂的数据结构(在这里,我们使用简单的 double 来表示金额)。

一旦建立了此合同,就很容易派生出相关的类来适应每种付款方式的特定特征。

public class PayPalPaymentMethod : IPaymentMethod
{
    public string Name => "PAYPAL";
    
    public bool TryProcessPayment(double amount)
    {
        // Process payment with PayPal
        return true;
    }
}

public class VisaPaymentMethod : IPaymentMethod
{
    public string Name => "VISA";
    
    public bool TryProcessPayment(double amount)
    {
        // Process payment with Visa
        return true;
    }
}

每种付款方式以此类推...

然后,客户有责任调用适当的类,这可以通过多种方法来实现。我们在此提出以下方法。

public class PaymentMethodFactory
{
    private static PaymentMethodFactory _instance;

    private PaymentMethodFactory() { }
    
    public static PaymentMethodFactory Instance
    {
        get
        {
            if (_instance == null) _instance = new PaymentMethodFactory();
            return _instance;
        }
    }
    
    public IPaymentMethod Get(string name)
    {
        IPaymentMethod method = name switch
        {
            "PAYPAL" => new PayPalPaymentMethod(),
            "VISA" => new VisaPaymentMethod(),
            _ => throw new NotImplementedException()
        };
        
        return method;
    }    
}

信息

请注意,此类实现为单例。每次需要付款方式时,无需实例化它。

然后,主程序可以将这些组件组装在一起。

internal class Program
{
    static void Main(string[] args)
    {        
        Console.WriteLine("Enter method:");
        var s = Console.ReadLine();
        var method = PaymentMethodFactory.Instance.Get(s);

        // Process payment
        var res = method.TryProcessPayment(200.0);
    }
}

信息

这种处理方法基于**策略**设计模式。

使用此方法集成新付款方式的流程是什么?

假设我们需要引入一种新的付款方式。在这种情况下,我们应该如何进行?

确实,这是一个简单的过程:我们只需创建一个继承自 IPaymentMethod 的新类,并相应地调整 PaymentMethodFactory 类。

public class MasterCardPaymentMethod : IPaymentMethod
{
    public string Name => "MASTERCARD";
    
    public bool TryProcessPayment(double amount)
    {
        // Process payment with MasterCard
        return true;
    }
}

public class PaymentMethodFactory
{
    // ...
    
    public IPaymentMethod Get(string name)
    {
        IPaymentMethod method = name switch
        {
            "PAYPAL" => new PayPalPaymentMethod(),
            "VISA" => new VisaPaymentMethod(),
            "MASTERCARD" => new MasterCardPaymentMethod(),
            _ => throw new NotImplementedException()
        };
        
        return method;
    }    
}

此体系结构有哪些局限性?

  • 前面的方法没有问题。它是扩展软件的一种非常有效且广泛使用的方法。此外,如果我们只需要添加几种付款方式,这确实是首选方法。

  • 此方法也非常适合我们不需要第三方独立开发扩展的情况。相反,确实,我们需要公开我们的合同以使其公开可用。在这种情况下,基于插件的体系结构将更有优势,我们将在下一篇文章中对此进行探讨。

  • 尽管如此,正如 PaymentMethodFactory 所说明的,每次引入新类时,都需要手动注册每个方法。此过程可能变得繁琐且容易出错,尤其是在涉及众多模块的情况下。

如何有效地纳入这些考虑因素?请继续阅读。

什么是插件体系结构?

插件体系结构是一种设计模式或框架,它允许通过插件或模块来扩展或增强应用程序或系统。 (插件体系结构允许应用程序或系统通过插件或模块来扩展或增强其附加功能。)

在插件体系结构中

  • 应用程序或系统提供一组核心功能和特性(核心系统)。

  • 插件是可添加到核心系统的独立代码片段,用于扩展其功能。插件通常设计用于执行特定任务或添加特定功能(DarkThemeManagerLightThemeManager)。

  • 核心系统设计用于在运行时动态加载和集成插件,无需修改核心代码库。这允许轻松安装、删除和更新插件,而不会中断主应用程序的运行。

  • 插件通过定义明确的接口APIIPaymentMethodIThemeManager)与核心系统进行交互。这确保插件能够与核心系统无缝集成并有效通信。

  • 插件体系结构使用户能够根据其特定需求或偏好来自定义应用程序的功能。用户可以根据所需的功能选择安装哪些插件,从而实现高度灵活和可定制的用户体验。

插件体系结构广泛用于各种软件应用程序,包括内容管理系统、Web 浏览器、多媒体播放器和集成开发环境(IDE)等。它们提供了一种强大的机制来扩展软件系统的功能,同时保持灵活性、模块化和易于维护。

我们可以识别系统中存在的众多可扩展点。与我们之前依赖于实现策略设计模式的方法不同,当前系统表现出更大的开放性。这意味着第三方开发人员有机会创建自己的插件。此外,软件被有意设计为促进这一点,这与以前只有制造商或所有者才能编写可扩展点的模式不同。

插件体系结构包含哪些不同的组件?

什么是插件?

插件是可添加到软件应用程序中的模块化组件或扩展,用于增强其功能或引入新特性。插件设计用于与核心系统无缝集成,并且通常与主应用程序独立开发。它们通常用于执行特定任务或提供专门的功能,例如向编辑器添加新工具、与外部服务集成或扩展 Web 浏览器的功能。

定义主机

在此上下文中,“主机”一词是指软件的核心系统。本质上,它代表了负责管理安全和引导等整体事务的主程序。此外,主机充当插件 API 的提供者,该 API 规定了系统中可访问的各种可扩展点。

信息

主机应用程序负责加载和管理插件,确保其正常运行,并为它们提供所需资源或数据。这包括将插件动态加载到系统中,管理它们的生命周期,以及协调它们与主机环境的交互。此外,主机负责确保插件在应用程序生态系统中无缝运行,从而为软件的整体功能和性能做出贡献。

定义接口

插件体系结构需要提供接口,以促进插件在主机系统中的无缝集成。这些接口基本上充当合同,规定了插件必须遵守的方法和属性。因此,它们建立了一套插件必须遵循的指南,以确保正确集成到主机环境中。

本质上,这些接口充当了管理插件与主机系统之间交互的标准框架,从而在整个体系结构中促进了兼容性和一致性。

定义插件

插件,也称为模块或扩展,是独立的组件,它们使主机应用程序无需重新编译即可进行扩展。这些独立的单元通过引入新功能或功能来增强主机系统的功能,同时保持与核心代码库的独立性。这使得开发人员能够将附加功能无缝集成到主机环境中,从而增强其通用性和适应性,而无需对整个应用程序进行大量修改或重建。

插件体系结构包含哪些限制?

正如在现实生活中经常发生的那样,每一个进步都伴随着自己的权衡。虽然插件体系结构促进了功能的无缝集成,但它也带来了一些限制。

信息

本节基于我们的具体经验,不一定普遍或全面适用于其他场景。

虽然插件体系结构可以用于内部目的,但其主要应用在于促进第三方实体的使用。在此框架内,安全性成为一个首要问题,需要持续警惕,以阻止恶意行为者试图破坏主机系统的潜在尝试。

解决这些安全问题绝非易事;实施起来通常需要大量的时间和精力。

插件经常需要访问内部数据,例如付款方式的订单,并且可能需要与各种类型的信息进行交互。确保插件仅接收必要的数据——不多也不少——更像是一门艺术而非科学。

鉴于在发布后修改接口所涉及的复杂性,至关重要的是要通过仔细的远见来预见并解决此问题。

但理论够了;让我们深入探讨实际实现!现在,我们将探讨如何在 .NET Framework 中使用托管可扩展性框架 (MEF) 快速实现插件体系结构。但是,为了避免使本文过载,对此实现感兴趣的读者可以在此处找到后续内容。

历史

  • 2024 年 3 月 22 日:初始版本
© . All rights reserved.