近期的一个项目中,需要用到C#书写插件,控制不同功能在编辑器中的实现,也是刚接手,慢慢领悟这种方法的美感。
用C#写个简单的插件的例子,类似于状态模式,提供一个计算器的接口,然后在派生类中实现不同的计算。最后通过加载这些实现的dll文件,来实现不同的运算
public interface ICalculator
{
int Calculate(int a, int b);
char GetSymbol();
}
我们有不同的方法实现这个接口
class Divider:ICalculator
{
#region ICalculator Members
public int Calculate(int a, int b)
{
return a / b;
}
public char GetSymbol()
{
return '/';
}
#endregion
}
我们可以提供一个默认的除法给主机,同时允许其他的实现插入到主机,这种模式也容易进行单元测试。
public class CalculatorHost
{
public CalculatorHost(ICalculator calculator)
{
m_calculator = calculator;
}
public CalculatorHost() : this(new Divider()) { }
private int m_x, m_y;
private ICalculator m_calculator;
public int X
{
get { return m_x; }
set { m_x = value; }
}
public int Y
{
get { return m_y; }
set { m_y = value; }
}
public int Calculate()
{
return m_calculator.Calculate(m_x, m_y);
}
public override string ToString()
{
return string.Format("{0} {1} {2} = {3}",
m_x.ToString(),
m_calculator.GetSymbol(),
m_y.ToString(),
m_calculator.Calculate(m_x, m_y));
}
}
Late Binding
写一个新的计算器实现方法,并将 其dll 放到文件中。然后可以让应用层能够使用这个新的函数使用了绑定机制。我们把所有的插件放入 “Plugins” 文件中,我们使用 static 类去管理绑定的中间结果。 使用lazy-load 并创建一个load 方法,所以可以得记录用层第一次使用插件的情况
public static class CalculatorHostProvider
{
private static List<CalculatorHost> m_calculators;
public static List<CalculatorHost> Calculators
{
get
{
if (null == m_calculators)
Reload();
return m_calculators;
}
}
}
执行的界面如下:
当reload() 在首次调用的时候,我们创建一个新的list,在应用程序运行的时候可能还会下载新的插件。此时需要清空现有的 calculator list。 然后我们将要laod 插件中所有的 assemblies
public static void Reload()
{
if (null == m_calculators)
m_calculators = new List<CalculatorHost>();
else
m_calculators.Clear();
m_calculators.Add(new CalculatorHost()); // load the default
List<Assembly> plugInAssemblies = LoadPlugInAssemblies();
List<ICalculator> plugIns = GetPlugIns(plugInAssemblies);
foreach (ICalculator calc in plugIns)
{
m_calculators.Add(new CalculatorHost(calc));
}
}
Attributes
使用 custom attribute decorator 来 标记实现icalculator, 使用一些identifier 在 attibute中,这样可以用来对插件排序或者过滤,后面GetPlugins() 函数中,有体现
[AttributeUsage(AttributeTargets.Class)]
public class CalculationPlugInAttribute : Attribute
{
public CalculationPlugInAttribute(string description)
{
m_description = description;
}
private string m_description;
public string Description
{
get { return m_description; }
set { m_description = value; }
}
}
Guts
loading 插件的 assemblies , 使用Assembly.LoadFile() 来把它们添加到一个list中
private static List<Assembly> LoadPlugInAssemblies()
{
DirectoryInfo dInfo = new DirectoryInfo(Path.Combine(Environment.CurrentDirectory, "Plugins"));
FileInfo[] files = dInfo.GetFiles("*.dll");
List<Assembly> plugInAssemblyList = new List<Assembly>();
if (null != files)
{
foreach (FileInfo file in files)
{
plugInAssemblyList.Add(Assembly.LoadFile(file.FullName));
}
}
return plugInAssemblyList;
}
然后,使用下载后的list并 get all types implement out ICalculator,
使用 Plug-in 结构
使用控制台app,可以方便的进行测试 如下
static void Main(string[] args)
{
int
x = 34,
y = 56;
Console.WriteLine(String.Format("x={0} y={1}", x.ToString(), y.ToString()));
foreach (CalculatorHost calculator in CalculatorHostProvider.Calculators)
{
calculator.X = x;
calculator.Y = y;
Console.WriteLine(calculator.ToString());
}
Console.ReadLine();
}
windows 应用层,有友好的界面
private void Form1_Load(object sender, EventArgs e)
{
m_cbCalculation.DisplayMember = "Operator";
m_cbCalculation.DataSource = CalculatorHostProvider.Calculators;
}
以上的工程,需要另外添加一个文件夹"Plugins",里面放入各种主程序需要用的插件。
工程文件 download:
github.com/codeAPmind/pluggable/tree/master/PluggableHostApplication
Reference:
http://www.c-sharpcorner.com/UploadFile/rmcochran/plug_in_architecture09092007111353AM/plug_in_architecture.aspx