浅谈基于Prism的软件系统的架构设计
很早就想寫這么一篇文章來對(duì)近幾年使用Prism框架來設(shè)計(jì)軟件來做一次深入的分析了,但直到最近才開始整理,說到軟件系統(tǒng)的設(shè)計(jì)這里面有太多的學(xué)問,只有經(jīng)過大量的探索才能夠設(shè)計(jì)出好的軟件產(chǎn)品,就本人的理解,一個(gè)好的軟件必須有良好的設(shè)計(jì),這其中包括:易閱讀、易擴(kuò)展、低耦合、模塊化等等,如果你想設(shè)計(jì)一個(gè)好的系統(tǒng)當(dāng)然還要考慮更多的方面,本篇文章主要是基于微軟的Prism框架來談一談構(gòu)建模塊化、熱插拔軟件系統(tǒng)的思路,這些都是經(jīng)過大量項(xiàng)目實(shí)戰(zhàn)的結(jié)果,希望能夠通過這篇文章的梳理能夠?qū)?gòu)建軟件系統(tǒng)有更加深刻的理解。
? ? ?首先要簡單介紹一下Prism這個(gè)框架:Prism框架通過功能模塊化的思想,將復(fù)雜的業(yè)務(wù)功能和UI耦合性進(jìn)行分離,通過模塊化,來最大限度的降低耦合性,很適合我們進(jìn)行類似插件化的思想來組織系統(tǒng)功能,并且模塊之間,通過發(fā)布和訂閱事件來完成信息的通信,而且其開放性支持多種框架集成。通過這些簡單的介紹就能夠?qū)Υ擞幸粋€(gè)簡單的理解,這里面加入了兩種依賴注入容器,即:Unity和MEF兩種容器,在使用的時(shí)候我們首先需要確定使用何種容器,這個(gè)是第一步。第二步就是如何構(gòu)建一個(gè)成熟的模塊化軟件,這個(gè)部分需要我們能夠?qū)φ麄€(gè)軟件系統(tǒng)功能上有一個(gè)合理的拆分,只有真正地完全理解整個(gè)系統(tǒng)才能夠合理抽象Module,然后降低Module之間的耦合性。第三步就是關(guān)于模塊之間是如何進(jìn)行通訊的,這一部分也是非常重要的部分,今天這篇文章就以Prism的Unity依賴注入容器為例來說明如何構(gòu)建模塊化軟件系統(tǒng),同時(shí)也簡要說明一下軟件系統(tǒng)的構(gòu)建思路。
這里以百度地圖為例來說一下如果使用WPF+Prism的框架來設(shè)計(jì)的話,該怎樣來設(shè)計(jì),當(dāng)然這里只是舉一個(gè)例子,當(dāng)然這篇文章不會(huì)就里面具體的代碼的邏輯來進(jìn)行分析,事實(shí)上我們也不清楚這個(gè)里面具體的內(nèi)部實(shí)現(xiàn),這里僅僅是個(gè)人的觀點(diǎn)。
圖一 百度地圖主界面
注意下面所有的代碼并非和上面的截圖一致,截圖僅供參考
如圖一所示,整個(gè)界面從功能主體上區(qū)分的話,就能夠很好的分成這幾個(gè)部分,左側(cè)是一個(gè)搜索區(qū)域,右邊是兩個(gè)功能區(qū)和一個(gè)個(gè)人信息區(qū)域,中間是地圖區(qū)域,這個(gè)是我們?cè)诳赐赀@個(gè)地圖之后第一眼就能想到的使用Prism能夠構(gòu)建的幾個(gè)模塊(Modules)。在定完整個(gè)系統(tǒng)可以分為哪幾個(gè)模塊之后我們緊接著就要分析每一個(gè)模塊包含哪些功能,并根據(jù)這些功能能夠定義哪些接口,我們可以新建一個(gè)類庫,專門用于定義整個(gè)應(yīng)用程序的接口,并放在單獨(dú)的類庫中,比如左側(cè)的地圖搜索區(qū)域我們可以定義一個(gè)IMapSearch的接口,用于定于這個(gè)部分有哪些具體的功能,如下面代碼所示。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows;namespace IGIS.SDK {public delegate List<Models.SearchResult> OnMapSearchHandle(string keyword);public interface IMapSearch{void AddSearchListener(string type, OnMapSearchHandle handle);void RemoveSearchListener(string type);void ShowResults(List<Models.SearchResult> results);void ClearResults();System.Collections.ObjectModel.ObservableCollection<Models.SearchResult> GetAllResults();event EventHandler<string> OnSearchCompleted;event EventHandler<System.Collections.ObjectModel.ObservableCollection<Models.SearchResult>> OnClearSearchResult;event EventHandler<System.Collections.ObjectModel.ObservableCollection<Models.SearchResult>> OnExecuteMultiSelected;void ShowFloatPanel(Models.SearchResult targetResult, FrameworkElement ui);} }
這是第一步,為左側(cè)的搜索區(qū)域定義好接口,當(dāng)然模塊化的設(shè)計(jì)必然包括界面和界面抽象,即WPF中的View層和ViewModel層以及Model層,我們可以單獨(dú)新建一個(gè)項(xiàng)目(自定義控件庫為佳)來單獨(dú)實(shí)現(xiàn)這一部分的MVVM,然后生成單獨(dú)的DLL供主程序去調(diào)用,比如新建一個(gè)自定義空間庫命名為Map.SearchModule,然后分別設(shè)計(jì)這幾個(gè)部分,這里列出部分代碼僅供參考。
<UserControl x:Class="IGIS.MapSearch"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" Title="IGIS"xmlns:cvt="clr-namespace:IGIS.Utils" xmlns:gisui="clr-namespace:IGIS.UI;assembly=IGIS.UI"xmlns:region="http://www.codeplex.com/CompositeWPF"xmlns:ui="clr-namespace:X.UI;assembly=X.UI"xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"d:DesignHeight="600" d:DesignWidth="1100"><Grid> ......</Grid> </UserControl>當(dāng)然最重要的部分代碼都是在ViewModel層中去實(shí)現(xiàn)的,這個(gè)層必須要繼承自IMapSearch這個(gè)接口,然后和View層通過DataContext綁定到一起,這樣一個(gè)完整的模塊化的雛形就出來了,后面還有幾個(gè)重要的部分再一一講述。
using IGIS.SDK.Models; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Collections.ObjectModel; using X; using X.Infrastructure;namespace IGIS.ViewModels {class SearchManager : X.Infrastructure.VMBase, IGIS.SDK.IMapSearch{public SearchManager(){Search = new Microsoft.Practices.Prism.Commands.DelegateCommand(DoSearch);ClearResult = new Microsoft.Practices.Prism.Commands.DelegateCommand(DoClearResult);ShowSelected = new Microsoft.Practices.Prism.Commands.DelegateCommand(DoShowSelected);Listeners.Add(new Listener { Name = "全部", Handle = null });}private void DoShowSelected(){if (null != OnExecuteMultiSelected){System.Collections.ObjectModel.ObservableCollection<SearchResult> selected = new ObservableCollection<SearchResult>();foreach (var itm in SelectedItems){if (itm is SearchResult)selected.Add(itm as SearchResult);}OnExecuteMultiSelected(this, selected);}}private static SearchManager _instance;public static SearchManager Instance{get{if (null == _instance)_instance = new SearchManager();return _instance;}set { _instance = value; }}private void DoSearch(){ClearResults();foreach (var ls in Listeners){if (string.IsNullOrEmpty(SelectedType) || SelectedType == "全部" || SelectedType == ls.Name)if (ls.Handle != null){List<SearchResult> res = null;Application.Current.Dispatcher.Invoke(new Action(() =>{res = ls.Handle.Invoke(Keyword);}), System.Windows.Threading.DispatcherPriority.Normal);if (null != res && res.Count > 0){foreach (var itm in res){Application.Current.Dispatcher.Invoke(new Action(() =>{Results.Add(itm);}));}}}}if (null != OnSearchCompleted)OnSearchCompleted(Results, Keyword);DoRemoteSearch(SelectedType, Keyword);}private string _keyword;public string Keyword{get { return _keyword; }set{if (_keyword != value){_keyword = value;OnPropertyChanged("Keyword");}}}private string _selectedType = "全部";public string SelectedType{get { return _selectedType; }set{if (_selectedType != value){_selectedType = value;OnPropertyChanged("SelectedType");}}}private ICommand _showSelected;public ICommand ShowSelected{get { return _showSelected; }set { _showSelected = value; }}private ICommand _search;public ICommand Search{get { return _search; }set{if (_search != value){_search = value;OnPropertyChanged("Search");}}}private ICommand _ClearResult;public ICommand ClearResult{get { return _ClearResult; }set { _ClearResult = value; }}private void DoClearResult(){ClearResults();}private System.Collections.ObjectModel.ObservableCollection<SearchResult> _results= new System.Collections.ObjectModel.ObservableCollection<SearchResult>();public System.Collections.ObjectModel.ObservableCollection<SearchResult> Results{get { return _results; }set{if (_results != value){_results = value;OnPropertyChanged("Results");}}}private System.Collections.IList _selectedItems;public System.Collections.IList SelectedItems{get { return _selectedItems; }set { _selectedItems = value; }}#region SDKpublic class Listener : X.Infrastructure.NotifyObject{private string _name;public string Name{get { return _name; }set{if (_name != value){_name = value;OnPropertyChanged("Name");}}}private SDK.OnMapSearchHandle _handle;public SDK.OnMapSearchHandle Handle{get { return _handle; }set { _handle = value; }}}public event EventHandler<string> OnSearchCompleted;public event EventHandler<System.Collections.ObjectModel.ObservableCollection<SDK.Models.SearchResult>> OnClearSearchResult;public event EventHandler<ObservableCollection<SearchResult>> OnExecuteMultiSelected;private System.Collections.ObjectModel.ObservableCollection<Listener> _listeners= new System.Collections.ObjectModel.ObservableCollection<Listener>();public System.Collections.ObjectModel.ObservableCollection<Listener> Listeners{get { return _listeners; }set{if (_listeners != value){_listeners = value;OnPropertyChanged("Listeners");}}}public System.Collections.ObjectModel.ObservableCollection<SearchResult> GetAllResults(){return Results;}public void AddSearchListener(string type, SDK.OnMapSearchHandle handle){Application.Current.Dispatcher.Invoke(new Action(() =>{var itm = Listeners.Where(x => x.Name == type).SingleOrDefault() ?? null;if (null == itm){itm = new Listener() { Name = type };Listeners.Add(itm);}itm.Handle = handle;}));}public void RemoveSearchListener(string type){Application.Current.Dispatcher.Invoke(new Action(() =>{try{var itm = Listeners.Where(x => x.Name == type).SingleOrDefault() ?? null;if (null != itm){Listeners.Remove(itm);}}catch (Exception){}}));}public void ShowResults(List<SearchResult> results){ClearResults();foreach (var itm in results){Application.Current.Dispatcher.Invoke(new Action(() =>{Results.Add(itm);}));}}public void ClearResults(){Application.Current.Dispatcher.Invoke(new Action(() =>{if (null != OnClearSearchResult && Results.Count > 0)OnClearSearchResult(this, Results);Results.Clear();ClearRemoteResults();}));}public void ShowFloatPanel(SearchResult targetResult, FrameworkElement ui){if (null != OnShowFloatPanel)OnShowFloatPanel(targetResult, ui);}internal event EventHandler<FrameworkElement> OnShowFloatPanel;#endregion#region 大屏端同步命令void DoRemoteSearch(string type, string keyword){X.Factory.GetSDKInstance<X.IDataExchange>().Send(new IGIS.SDK.Messages.RemoteMapSearchMessage() { SelectedType = this.SelectedType, Keyword = this.Keyword }, "IGISMapSearch");}void ClearRemoteResults(){X.Factory.GetSDKInstance<X.IDataExchange>().Send(new X.Messages.MessageBase(), "IGISClearMapSearch");}#endregion} }如果熟悉Prism的開發(fā)者肯定知道這部分可以完整的定義為一個(gè)Region,在完成這部分之后,最重要的部分就是將當(dāng)前的實(shí)現(xiàn)接口IGIS.SDK.IMapSearch的對(duì)象注入到UnityContainer中從而在其他的Module中去調(diào)用,這樣就能夠?qū)崿F(xiàn)不同的模塊之間進(jìn)行通信,具體注入的方法請(qǐng)參考下面的代碼。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Practices.Unity; using X;namespace IGIS {public class IGISProductInfo : IModule{Microsoft.Practices.Prism.Regions.IRegionViewRegistry m_RegionViewRegistry;public IGISProductInfo(Microsoft.Practices.Unity.IUnityContainer container){m_RegionViewRegistry = _RegionViewRegistry; container.RegisterInstance<IGIS.SDK.IMapSearch>(ViewModels.SearchManager.Instance); } public void Initialize(){m_RegionViewRegistry.RegisterViewWithRegion(“MapSearchRegion”, typeof(Views.IGIS.MapSearch)); } }}
首先我們通過m_RegionViewRegistry.RegisterViewWithRegion(“MapSearchRegion”, typeof(Views.IGIS.MapSearch))來將當(dāng)前的View注冊(cè)到主程序的Shell中,在主程序中我們只需要通過<ContentControl region:RegionManager.RegionName="MapSearchRegion"></ContentControl>就能夠?qū)?dāng)前的View放到主程序的中,從而作為主程序的界面的一部分,然后通過代碼:container.RegisterInstance<IGIS.SDK.IMapSearch>(ViewModels.SearchManager.Instance),就能夠?qū)?dāng)前實(shí)現(xiàn)IMapSearch的接口的實(shí)例注入到Prism框架的全局的UnityContainer中,最后一步也是最關(guān)鍵的就是在其它的模塊中,如果我們需要調(diào)用當(dāng)前實(shí)現(xiàn)IMapSearch的接口的方法,那該怎么來獲取到實(shí)現(xiàn)這個(gè)接口的實(shí)例呢?
下面的代碼提供了兩個(gè)方法,一個(gè)同步方法和一個(gè)異步的方法來獲取當(dāng)前的實(shí)例,比如使用同步的方法,我們調(diào)用GetSDKInstance這個(gè)方法傳入類型:IGIS.SDK.IMapSearch時(shí)就能夠獲取到注入到容器中的唯一實(shí)例:ViewModels.SearchManager.Instance,這樣我們就能夠獲取到這個(gè)實(shí)例了。
public static T GetSDKInstance<T>() where T : class{if (currentInstances.ContainsKey(typeof(T)))return currentInstances[typeof(T)] as T;try{var instance = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<T>();currentInstances[typeof(T)] = instance;return instance;}catch (Exception ex){System.Diagnostics.Trace.TraceError(ex.ToString());return null;}}private static object Locker = new object();public static void GetSDKInstanceAysnc<T>(Action<T> successAction) where T : class{if (currentInstances.ContainsKey(typeof(T))){successAction.Invoke(currentInstances[typeof(T)] as T);return;}Task.Factory.StartNew(new Action(() =>{lock (Locker){T instance = null;int tryCount = 0;while (instance == null && tryCount <= 100){tryCount++;try{instance = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<T>();}catch{}if (null != instance){currentInstances[typeof(T)] = instance;successAction.Invoke(instance);return;}else{System.Threading.Thread.Sleep(50);}}}}));}在看完上面的介紹之后我們似乎對(duì)基于Prism的模塊化開發(fā)思路有了一定的理解了,但是這些模塊是在何時(shí)進(jìn)行加載的呢?Prism框架是一種預(yù)加載模式,即生成的每一個(gè)Module在主程序Shell初始化的時(shí)候就會(huì)去加載每一個(gè)繼承自IModule的接口的模塊,當(dāng)然這些模塊是分散在程序的不同目錄中的,在加載的時(shí)候需要為其指定具體的目錄,這樣在主程序啟動(dòng)時(shí)就會(huì)加載不同的模塊,然后每個(gè)模塊加載時(shí)又會(huì)將繼承自特定接口的實(shí)例注冊(cè)到一個(gè)全局的容器中從而供不同的模塊之間相互調(diào)用,從而實(shí)現(xiàn)模塊之間的相互調(diào)用,同理圖一中的功能區(qū)、個(gè)人信息區(qū)、地圖區(qū)都能夠通過繼承自IModule接口來實(shí)現(xiàn)Prism框架的統(tǒng)一管理,這樣整個(gè)軟件就可以分成不同的模塊,從而彼此獨(dú)立最終構(gòu)成一個(gè)復(fù)雜的系統(tǒng),當(dāng)然這篇文章只是做一個(gè)大概的分析,為對(duì)Prism框架有一定理解的開發(fā)者可以有一個(gè)指導(dǎo)思想,如果想深入了解Prism的思想還是得通過官方的參考代碼去一點(diǎn)點(diǎn)理解其指導(dǎo)思想,同時(shí)如果需要對(duì)Prism有更多的理解,也可以參考我之前的博客,本人也將一步步完善這個(gè)系列。
? ? ? 最后我們要看看主程序如何在初始化的時(shí)候來加載這些不同的模塊的dll的,請(qǐng)參考下面的代碼:
using System; using System.Windows; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Practices.Prism.Modularity; using Microsoft.Practices.Unity; using Microsoft.Practices.Prism.UnityExtensions; using Microsoft.Practices.Prism.Logging;namespace Dvap.Shell.CodeBase.Prism {public class DvapBootstrapper : Microsoft.Practices.Prism.UnityExtensions.UnityBootstrapper{private readonly string[] m_PluginsFolder=new string[3] { "FunctionModules", "DirectoryModules", "Apps"};private readonly CallbackLogger m_callbackLogger = new CallbackLogger();#region Override/// <summary>/// 創(chuàng)建唯一的Shell對(duì)象/// </summary>/// <returns></returns>protected override DependencyObject CreateShell(){return this.Container.TryResolve<Dvap.Shell.Shell>(); }protected override void InitializeShell(){base.InitializeShell();Application.Current.MainWindow = (Window)this.Shell;Application.Current.MainWindow.Show();}/// <summary>/// 創(chuàng)建唯一的Module的清單/// </summary>/// <returns></returns>protected override IModuleCatalog CreateModuleCatalog(){return new CodeBase.Prism.ModuleCatalogCollection(); }/// <summary>/// 配置唯一的ModuleCatalog,這里我們通過從特定的路徑下加載/// dll/// </summary>protected override void ConfigureModuleCatalog(){try{var catalog = ((CodeBase.Prism.ModuleCatalogCollection)ModuleCatalog);foreach (var pluginFolder in m_PluginsFolder){if (pluginFolder.Contains("~")){DirectoryModuleCatalog catApp = new DirectoryModuleCatalog() { ModulePath = pluginFolder.Replace("~", AppDomain.CurrentDomain.BaseDirectory) };catalog.AddCatalog(catApp);}else{if (!System.IO.Directory.Exists(@".\" + pluginFolder)){System.IO.Directory.CreateDirectory(@".\" + pluginFolder);}foreach (string dic in System.IO.Directory.GetDirectories(@".\" + pluginFolder)){DirectoryModuleCatalog catApp = new DirectoryModuleCatalog() { ModulePath = dic };catalog.AddCatalog(catApp);}}}}catch (Exception){throw;} }protected override ILoggerFacade CreateLogger(){return this.m_callbackLogger; }#endregion}}看到?jīng)]有每一個(gè)宿主應(yīng)用程序都有一個(gè)繼承自Microsoft.Practices.Prism.UnityExtensions.UnityBootstrapper的類,我們需要重寫其中的一些方法來實(shí)現(xiàn)Prism程序的模塊加載,例如重寫?override void ConfigureModuleCatalog() 我們的宿主程序就知道去哪里加載這些繼承自IModule的dll,還有必須重載CreateShell和InitializeShell()
這些基類的方法來制定主程序的Window,有了這些我們就能夠構(gòu)造一個(gè)完整的Prism程序了,對(duì)了還差最后一步,就是啟動(dòng)Prism的Bootstrapper,我們一般是在WPF程序的App.xaml.cs中啟動(dòng)這個(gè),例如:?
using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading;namespace Dvap.Shell {/// <summary>/// App.xaml 的交互邏輯/// </summary>public partial class App : Application{protected override void OnStartup(StartupEventArgs e){base.OnStartup(e);new CodeBase.Prism.DvapBootstrapper().Run();this.DispatcherUnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(App_DispatcherUnhandledException);AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);}private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e){try{if (e.ExceptionObject is System.Exception){WriteLogMessage((System.Exception)e.ExceptionObject);}}catch (Exception ex){WriteLogMessage(ex);}}private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e){try{WriteLogMessage(e.Exception);e.Handled = true;}catch (Exception ex){WriteLogMessage(ex);}}public static void WriteLogMessage(Exception ex){//如果不存在則創(chuàng)建日志文件夾if (!System.IO.Directory.Exists("Log")){System.IO.Directory.CreateDirectory("Log");}DateTime now = DateTime.Now;string logpath = string.Format(@"Log\Error_{0}{1}{2}.log", now.Year, now.Month, now.Day);System.IO.File.AppendAllText(logpath, string.Format("\r\n************************************{0}*********************************\r\n", now.ToString("yyyy-MM-dd HH:mm:ss")));System.IO.File.AppendAllText(logpath, ex.Message);System.IO.File.AppendAllText(logpath, "\r\n");System.IO.File.AppendAllText(logpath, ex.StackTrace);System.IO.File.AppendAllText(logpath, "\r\n");System.IO.File.AppendAllText(logpath, "\r\n*************************************************r\n");}} }在應(yīng)用程序啟動(dòng)時(shí)調(diào)用?new CodeBase.Prism.DvapBootstrapper().Run()啟動(dòng)Prism應(yīng)用程序,從而完成整個(gè)過程,當(dāng)然上面的講解只能夠說明Prism的冰山一角,了解好這個(gè)框架將為我們開發(fā)復(fù)雜的應(yīng)用程序提供一種新的思路。
?
轉(zhuǎn)載于:https://www.cnblogs.com/seekdream/p/7637512.html
總結(jié)
以上是生活随笔為你收集整理的浅谈基于Prism的软件系统的架构设计的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 开发免签支付
 - 下一篇: 常见的解析方式和解析器