通过前两篇文章的学习,我们已经了解了接口的一些入门级功能。
现在我们就拿前面的知识来举个栗子。
我们假定一个需求:主程序连接一个数据库,用户可以登录后,做一些常规操作。为了能够进行二次开发,我们须要这个主程序能够动态地加载指定目录(Plugin)后期开发出来的插件,以扩展主程序中的功能的不足。
我们按照上一篇文章中的思路,先在主程序中设计一个接口,这个接口提供 插件名称、插件说明、制作人 三个只读属性,再加一个 打开 的方法。
此时,我们考虑到插件不应独立于主程序存在,它可能会使用到数据库 或 一些主程序资源,所以我们再加两上两个属性:数据库连接(注意:由于这个属性是要在主程序中进行赋值,所以不是只读属性)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data; namespace Test { public interface IPlugin { /// <summary> /// 插件名称 /// </summary> string Name { get; } /// <summary> /// 插件描述 /// </summary> string Descript { get; } /// <summary> /// 插件开发者 /// </summary> string Developer { get; } /// <summary> /// 主程序中使用的数据库接连 /// </summary> IDbConnection DBConnection { get; set; } /// <summary> /// 打开插件 /// </summary> void Open(); } }
然后我们开始开发正常的程序代码,目前我们只有一个登录需要开发。
目前,我们使用新手最容易的思路进行这段开发的演示,在以后的教学中,我会对这些代码不断的修改,直到是一个合理的架构。
namespace Test { public partial class Form1 : Form { private IDbConnection conn; public Form1() { InitializeComponent(); conn = new System.Data.SqlClient.SqlConnection(ConfigurationManager.ConnectionStrings["ConnStr"].ConnectionString); conn.Open(); } private void _loginButton_Click(object sender, EventArgs e) { string sql = string.Format("SELECT * FROM LOGIN WHERE L = {0} AND P = {1}",_loginNameTextbox.Text,_passwordTextBox.Text); IDbCommand cmd = conn.CreateCommand(); cmd.CommandText = sql; IDataReader reader = cmd.ExecuteReader(); bool canLogin = false; if (reader.Read()) { canLogin = true; } if (canLogin) { this.Hide(); //打开主功能窗体 } } } }
做好了登录界面,我们开始做主功能界面吧。
目前主功能界面没有什么特别的功能,就是一个加载插件的过程。
主要思路是这样的:(以下是伪代码)
1、从Plugin目录下取得所有*.dll文件
2、循环这些文件
2 - 1、将这些*.dll文件加载成为程序集
2 - 2、获取程序集中的所有类型
2 - 3、循环这些类型
2 - 3 - 1、判断当前类型是否是IPlugin的实现
2 - 3 - 1 - Y、将该类型实例化,并加入到插件列表中
2 - 3 - 1 - N、忽略
3、将拿到的所有插件类型,显示到窗体中去
根据这些伪代码,我们来构建完整的代码
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO; using System.Reflection; namespace Test { public partial class MainWindow : Form { private List<IPlugin> _pluginList = new List<IPlugin>(); /// <summary> /// 数据库连接 /// </summary> public IDbConnection Conn { get; set; } public MainWindow() { InitializeComponent(); //1、从Plugin目录下取得所有*.dll文件 DirectoryInfo dirInfo = new DirectoryInfo("Plugin"); if (!dirInfo.Exists) return; FileInfo[] dllArray = dirInfo.GetFiles("*.dll"); //2、循环这些文件 foreach (FileInfo fileInfo in dllArray) { //2 - 1、将这些*.dll文件加载成为程序集 Assembly dll = Assembly.LoadFile(fileInfo.FullName); //2 - 2、获取程序集中的所有类型 Type[] types = dll.GetTypes(); //2 - 3、循环这些类型 foreach (Type t in types) { //2 - 3 - 1、判断当前类型是否是IPlugin的实现 Type iType = t.GetInterface(typeof(IPlugin).FullName); if (iType != null) { //2 - 3 - 1 - Y、将该类型实例化,并加入到插件列表中 IPlugin plugin = (IPlugin)Activator.CreateInstance(t); _pluginList.Add(plugin); } else { //2 - 3 - 1 - N、忽略 continue; } } } //3、将拿到的所有插件类型,显示到窗体中去 ShowPlugin(); } private void ShowPlugin() { } } }
我们再来看看ShowPlugin方法的逻辑:
1、循环所有的插件对象
1-1、构建一个按钮,并将插件类型赋给按钮的Tag属性,另外再设置一下按钮的文本
1-2、为按钮的单击事件添加一个方法
1-3、将按钮显示到窗体上去
根据上述伪代码,我们可以构建出
private void ShowPlugin() { foreach (IPlugin p in _pluginList) { Button btn = new Button(); btn.Tag = p; btn.Text = p.Name; btn.Click += btn_Click; _pluginPanel.Controls.Add(btn); } }
最后我们要实现的功能就是,当按钮按下去以后的工作了
void btn_Click(object sender, EventArgs e) { //将sender转换为Button类型 Button btn = (Button)sender; //将Tag转换为插件类型 IPlugin p = (IPlugin)btn.Tag; //显示有关插件的信息 MessageBox.Show(string.Format("正在打开由{0}开发的插件"{1}" 该插件的主要功能是 {2}",p.Developer,p.Name,p.Descript)); //给予插件数据库连接器 p.DBConnection = this.Conn; //打开插件 p.Open(); }
到此利用接口开发一套动态加载插件的程序主要功能就全部开发完成了。
在下一篇文章中,将继续优化此程序,使其在开发插件时,可以对插件进行权限的设置。
最后符上程序(作为示例,没有携带数据库) http://pan.baidu.com/s/1i3KvCix