針對早期「C# 外掛架構實戰」的回饋,本文重構設計:引入宿主外掛與擴充外掛區分、連線介面、版本資訊、工廠模式建立實例,並以 UML 靜態結構圖說明類別關係,解決原架構擴充性不足的問題。
IConnectable、IExtendible 定義契約。ExtendibleVersionInfo 支援多版本共存與查詢。public interface IConnectable
{
/// 連線至宿主應用程式;null 表示本身即為宿主
ConnectionResult Connect(object application);
/// 版本資訊
ExtendibleVersionInfo VersionInfo { get; }
/// 載入完成初始化
void OnLoad();
/// 執行主要邏輯
void Run();
/// 卸載清理
void OnDestroy();
}
public enum ConnectionResult { Success, Failed }
public class ExtendibleVersionInfo
{
private ExtendibleVersionInfo() { } // 單例 Empty 用
public ExtendibleVersionInfo(string name, string version, string copyright) { ... }
public ExtendibleVersionInfo(string name, int v1, int v2, int v3, string copyright) { ... }
public int Major { get; }
public int Minor { get; }
public int Build { get; }
public string Name { get; }
public string VersionString => $"{Major}.{Minor}.{Build}";
public string Copyright { get; }
public static readonly ExtendibleVersionInfo Empty = new ExtendibleVersionInfo();
}
public interface IExtendible
{
/// 取得最新版本實例
IConnectable GetLatestVersion();
/// 取得指定版本實例
IConnectable GetSpecifiedVersion(ExtendibleVersionInfo version);
/// 列舉所有可用版本
ExtendibleVersionInfo[] EnumerateVersions();
}
+----------------+ +------------------+ +------------------+
| IConnectable |<------| HostPlugin | | ExtensionPlugin |
+----------------+ +------------------+ +------------------+
| Connect(app) | | VersionInfo | | VersionInfo |
| VersionInfo | | OnLoad/Run/Destroy| | OnLoad/Run/Destroy|
| OnLoad/Run/ | | Connect(host) | | Connect(host) |
| OnDestroy | +------------------+ +------------------+
+----------------+ ^ ^
^ | |
| implements implements
| | |
+----------------+ +------------------+ +------------------+
| IExtendible |<------| HostFactory | | ExtensionFactory|
+----------------+ +------------------+ +------------------+
| GetLatest() | | GetLatestVersion()| | GetLatestVersion()|
| GetSpecified() | | EnumerateVersions()| | EnumerateVersions()|
| Enumerate() | +------------------+ +------------------+
+----------------+
// 原始:單一工廠類別,新增外掛需修改工廠原始碼 (違反 OCP)
public class PluginFactory
{
public IConnectable Create(string name)
{
return name switch
{
"PluginA" => new PluginA(),
"PluginB" => new PluginB(),
_ => null
};
}
}
// 抽象工廠
public abstract class PluginFactoryBase : IExtendible
{
public abstract IConnectable CreateInstance(ExtendibleVersionInfo version);
public abstract ExtendibleVersionInfo[] EnumerateVersions();
// IExtendible 實作
public IConnectable GetLatestVersion() =>
CreateInstance(EnumerateVersions().OrderByDescending(v => v.Major).ThenByDescending(v => v.Minor).First());
public IConnectable GetSpecifiedVersion(ExtendibleVersionInfo v) => CreateInstance(v);
}
// 具體工廠 - 每個外掛一個工廠類別,新增外掛只需新增類別
public class PluginAFactory : PluginFactoryBase
{
public override ExtendibleVersionInfo[] EnumerateVersions() => new[]
{
new ExtendibleVersionInfo("PluginA", 1, 0, 0, "Copyright 2024"),
new ExtendibleVersionInfo("PluginA", 2, 0, 0, "Copyright 2024")
};
public override IConnectable CreateInstance(ExtendibleVersionInfo v)
{
return v.Major switch
{
1 => new PluginA_V1(),
2 => new PluginA_V2(),
_ => throw new NotSupportedException($"Unsupported version {v.VersionString}")
};
}
}
public class ApplicationHost
{
private readonly List _factories = new();
public void RegisterFactory(IExtendible factory) => _factories.Add(factory);
public IConnectable LoadPlugin(string pluginName, ExtendibleVersionInfo? version = null)
{
var factory = _factories.FirstOrDefault(f =>
f.EnumerateVersions().Any(v => v.Name == pluginName));
if (factory == null) throw new InvalidOperationException($"Plugin {pluginName} not found");
IConnectable plugin = version != null
? factory.GetSpecifiedVersion(version)
: factory.GetLatestVersion();
plugin.Connect(this); // 傳入宿主參考
plugin.OnLoad();
return plugin;
}
}
| 優點 | 缺點/待改進 |
|---|---|
| 符合開放封閉原則 (OCP) | 工廠類別數量隨外掛增加而線性增長 |
| 版本管理內建,支援並存 | 尚未解決外掛間相依性解析 (需引入 DI 容器) |
| 介面隔離,利於單元測試 | 生命週期管理 (OnLoad/Run/OnDestroy) 需宿主協調 |
| 支援宿主/擴充雙層架構 | 安全性:未簽名驗證、沙箱隔離機制 |