西西軟件園多重安全檢測下載網(wǎng)站、值得信賴的軟件下載站!
軟件
軟件
文章
搜索

首頁編程開發(fā)C#.NET → 關(guān)于用C#實現(xiàn)B/S與C/S平臺之間功能通用性的設(shè)計思路

關(guān)于用C#實現(xiàn)B/S與C/S平臺之間功能通用性的設(shè)計思路

相關(guān)文章發(fā)表評論 來源:liping13599168時間:2010/3/1 17:28:40字體大�。�A-A+

作者:admin點擊:1172次評論:0次標簽: C/S

  • 類型:備份還原大�。�46.7M語言:英文 評分:6.6
  • 標簽:
立即下載

設(shè)計場景

1. 有A,B兩組開發(fā)人員進行某個系統(tǒng)的開發(fā),其中A組開發(fā)人員負責B/S平臺的功能設(shè)計與開發(fā),B組開發(fā)人員負責C/S平臺的功能設(shè)計與開發(fā)。

2. 在當時的項目背景下,B/S端的項目是先啟動的,而A組的開發(fā)人員還沒有意識到將來需要配合C/S端來做功能協(xié)作,因此產(chǎn)生的問題就是,前期的系統(tǒng)架構(gòu)設(shè)計沒有過多地考慮以適應多個平臺下的功能適應性。當然,從B/S端的設(shè)計角度上看,系統(tǒng)架構(gòu)還算比較清晰。接著A組的開發(fā)人員就在這樣的情況下,完成了系統(tǒng)功能的實現(xiàn)。

3. 接著高層領(lǐng)導告訴項目經(jīng)理需要做一套C/S架構(gòu)的軟件來配合B/S端平臺的使用,而這時候B/S端的功能實現(xiàn)已經(jīng)基本完成,B組開發(fā)人員成立。

4. 在B組架構(gòu)人員開始設(shè)計架構(gòu)的時候,并沒有衍用B/S端的開發(fā)架構(gòu),很多基礎(chǔ)架構(gòu)(如分層模式、數(shù)據(jù)庫結(jié)構(gòu)、數(shù)據(jù)實體類等等)都存在很大的差異(C/S端項目在初期的要求沒有那么高,有的功能能削減掉就削減掉),后來B組架構(gòu)人員發(fā)現(xiàn)需求文檔上的有個功能和B/S平臺上的某個功能是一樣的,于是他和A組架構(gòu)人員進行交流,希望負責B/S平臺上這個功能的開發(fā)人員能夠幫助C/S平臺幫助完成這一功能。于是A組的Leepy就匆匆忙忙地上陣了。

5. 最初Leepy同學因為在B/S平臺上也有大量的任務需要完成,任務趕得狠,又收到這樣一個“功能復制”的任務,心想:“那么就先把功能復制一份上去,然后如果B/S平臺上的功能有更新,就同步修改C/S平臺就好”。于是打開C/S平臺的項目,發(fā)現(xiàn)和B/S平臺項目的差異性比較大,包括數(shù)據(jù)庫結(jié)構(gòu)和數(shù)據(jù)實體類等等,更頭疼的是這里采用的是.net framework 2.0進行開發(fā),而B/S端采用的是.net framework 3.5進行開發(fā),而且從功能上,Leepy使用大量的3.5的屬性。要直接復用是不可能的,還需要調(diào)整相應的代碼。

6. 于是C/S平臺該功能出來了,運行得還行�,F(xiàn)在才是郁悶的開始,因為該功能屬于平臺的核心模塊,于是B/S平臺上要時刻調(diào)整得比較大,所以同步的C/S端的功能也要相應的調(diào)整,然后又運行完好。于是問題出來了,這樣反復地修改導致系統(tǒng)(C/S和B/S)維護成本很高,架構(gòu)間的設(shè)計耦合度太大。剛開始Leepy抱怨為什么C/S端沒有和B/S端統(tǒng)一架構(gòu),至少底層基礎(chǔ)平臺能夠設(shè)計得具有可擴展性,光光抱怨無法解決問題,因為這是項目的人員配置的問題。于是,Leepy想到了必須對該功能進重構(gòu),使用一個通用的組件進行抽象,而實際實現(xiàn)的,如C/S、B/S端具體應用,只要維護相應的業(yè)務代碼。

 

設(shè)計思路

1. 說完場景,現(xiàn)在說說動手的部分。以一個中學生教育平臺591up的網(wǎng)站為例,以及教育平臺客戶端的輔助軟件。

這一功能實現(xiàn)一份Word文檔試卷的導入保存并分解文檔中的試題,將試題逐個保存入庫(解析出來的試題部分還包括很多屬性,如答案、知識點、解題關(guān)鍵點等很多屬性)�,F(xiàn)在B/S平臺和C/S平臺都需要這個功能,但是B/S平臺和C/S平臺下的相關(guān)數(shù)據(jù)庫實體類,設(shè)計不很統(tǒng)一,導致維護系統(tǒng)的成本很高。于是,考慮是否能將解析器的設(shè)計與業(yè)務功能分開,將試卷解析器設(shè)計成通用的組件,而與B/S端和C/S端的業(yè)務代碼徹底分開,對于解析的邏輯代碼(基礎(chǔ)代碼)在兩端都可以引用到,而B/S端和C/S端所需要做得就是調(diào)整業(yè)務代碼,并不需要關(guān)解析的基礎(chǔ)代碼是什么,組件與業(yè)務代碼解耦。如下圖所示:

 

2. 現(xiàn)在講講具體設(shè)計思路,先從試卷解析器基礎(chǔ)組件開始(為了簡化,該范例是削弱版的),創(chuàng)建一個.net 2.0的類庫(為了適應客戶端.net 2.0的配置)聲明一個試卷解析器范型接口:

代碼
/// <summary>
/// 試卷轉(zhuǎn)換器泛型接口
/// </summary>
public interface IPaperConvertor<TIn, TOut>
{
/// <summary>
/// 轉(zhuǎn)換方法
/// </summary>
/// <param name="tIn">轉(zhuǎn)換輸入類型</param>
/// <param name="helper">Word處理接口</param>
/// <returns>轉(zhuǎn)換輸出類型</returns>
TOut Convert(TIn tIn, IWordHelper helper);
}
其中TIn類型作為輸入類型,TOut類型作為輸出類型(TIn將來作為業(yè)務代碼中實際的輸入類型,如WordInfo類;TOut作為實際輸出類型,如PaperInfo類;IWordHelper為一個Word處理接口,這里的實現(xiàn)是Microsoft.Office.Interop.Word)

考慮到轉(zhuǎn)換器在轉(zhuǎn)換過程Convert中,會產(chǎn)生一系列的步驟,首先對于轉(zhuǎn)換這個過程進行細化,分解成各個步驟:

代碼
public abstract class BasePaperConvertor<TIn, TOut> : IPaperConvertor<TIn, TOut>
where TIn : class, new()
where TOut : class, new()
{
//成員

/// <summary>
/// 輸出試卷實體
/// </summary>
protected TOut Paper { get; set; }

/// <summary>
/// 輸入Word條件
/// </summary>
protected TIn WordInfo { get; set; }

#region Word操作實體屬性
/// <summary>
/// Word操作實體屬性
/// </summary>
protected IWordHelper WordHelper { get; set; }
#endregion

//公共方法

/// <summary>
/// 轉(zhuǎn)換方法
/// </summary>
/// <param name="tIn"></param>
/// <returns></returns>
public virtual TOut Convert(TIn tIn, IWordHelper helper)
{
WordHelper = helper;
WordInfo = tIn;
Paper = Initialize(tIn);

if (Prepare())
Execute();

Finished();

return Paper;
}

//抽象方法

/// <summary>
/// 初始化
/// </summary>
/// <param name="tIn"></param>
/// <returns></returns>
protected abstract TOut Initialize(TIn tIn);

/// <summary>
/// 預裝載
/// </summary>
/// <param name="tOut"></param>
/// <returns></returns>
protected abstract bool Prepare();

/// <summary>
/// 執(zhí)行
/// </summary>
/// <param name="tOut"></param>
protected abstract void Execute();

/// <summary>
/// 完成
/// </summary>
protected abstract void Finished();
}
從代碼中,我們可以看到Convert方法中調(diào)用了一系列的抽象方法,首先對于輸入類型進行初始化(Initialize),接著通過輸入類型預裝載(Prepare),如果預裝載成功,并開始執(zhí)行。最后完成(Finished)所有的工作。

接著,需要定義一個包含Word解析邏輯代碼的抽象類,這里使用Microsoft.Office.Interop.Word進行Office編程,于是創(chuàng)建名為

OfficeWordPaperConvertor.cs的類:

OfficeWordPaperConvertor
/// <summary>
/// 試卷解析器泛型抽象類
/// </summary>
public abstract class OfficeWordPaperConvertor<TIn, TQuestion, TOut> : BasePaperConvertor<TIn, TOut>
where TIn : class, new()
where TQuestion : class, new()
where TOut : class, new()
{

#region 試卷Word結(jié)構(gòu)信息
/// <summary>
/// 試卷Word結(jié)構(gòu)信息
/// </summary>
protected PaperWordInfo PaperWordInfo { get; private set; }
#endregion

#region Word操作輔助類屬性
private OfficeWordHelper _OfficeWordHelper;
/// <summary>
/// Word操作輔助類屬性
/// </summary>
protected OfficeWordHelper OfficeWordHelper
{
get
{
if (_OfficeWordHelper == null)
_OfficeWordHelper = GetWordHelper();
return _OfficeWordHelper;
}
}
#endregion

#region 預處理試卷
/// <summary>
/// 預處理試卷
/// </summary>
/// <param name="tOut"></param>
/// <returns></returns>
protected override bool Prepare()
{
//過濾試卷無效信息
FilterPaper();

//解析試卷
ParsePaper();

return true;
}
#endregion

#region 執(zhí)行試卷
/// <summary>
/// 執(zhí)行試卷
/// </summary>
/// <param name="tOut"></param>
protected override void Execute()
{
for (int i = 0; i < PaperWordInfo.Count; i++)
{
QuestionWordInfo questionWordInfo = PaperWordInfo[i];

//執(zhí)行試題
ExcuteQuestion(questionWordInfo);
}
}
#endregion

#region 完成時調(diào)用
/// <summary>
/// 完成時調(diào)用
/// </summary>
protected override void Finished()
{
//這里進行完成時調(diào)用的實現(xiàn)

//..
}
#endregion

//虛方法

/// <summary>
/// 過濾試卷無效信息
/// </summary>
protected virtual void FilterPaper()
{

}

/// <summary>
/// 解析試卷
/// </summary>
protected virtual void ParsePaper()
{
PaperWordInfo = new PaperWordInfo();

//通過計算 OfficeWordHelper.Document.Text 得到文本中的題目數(shù),這里省去這段邏輯
PaperWordInfo.AddQuestion(new QuestionWordInfo { StartIndex = 0, EndIndex = 0 });
PaperWordInfo.AddQuestion(new QuestionWordInfo { StartIndex = 1, EndIndex = 1 });
PaperWordInfo.AddQuestion(new QuestionWordInfo { StartIndex = 2, EndIndex = 2 });
}

/// <summary>
/// 執(zhí)行試題
/// </summary>
/// <param name="questionWordInfo"></param>
protected virtual void ExcuteQuestion(QuestionWordInfo questionWordInfo)
{
string[] array = OfficeWordHelper.Document.Text.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

//創(chuàng)建試題解析器實體
TQuestion question = CreateQuestionConvertor(WordInfo, array[questionWordInfo.StartIndex]);

//將試題添加到試卷中
if (question != null) AddQuestion(question);
}

#region 獲取Word工具類
/// <summary>
/// 獲取Word工具類
/// </summary>
/// <returns></returns>
protected OfficeWordHelper GetWordHelper()
{
return WordHelper as OfficeWordHelper;
}
#endregion

//抽象方法

/// <summary>
/// 創(chuàng)建試題解析器實體
/// </summary>
/// <param name="subject"></param>
protected abstract TQuestion CreateQuestionConvertor(TIn tIn, string wordContent);

/// <summary>
/// 將試題添加到試卷中
/// </summary>
/// <param name="tPart"></param>
/// <param name="tQuestion"></param>
protected abstract void AddQuestion(TQuestion tQuestion);
}
為何這里沒有重寫Initialize方法呢?由于這里需要將Initialize暴露于業(yè)務代碼中,可以通過業(yè)務代碼來重寫該方法,如果業(yè)務組件沒有調(diào)用Initialize,將報錯。

這里Prepare方法主要完成一份Word文檔的信息過濾,并且將文檔中按照試題題號進行拆分試題,形成試題列表。

Execute方法完成一份試卷的執(zhí)行,通過試題列表將題目逐題入庫。

Finshed方法在Execute之后,可通過事件委托告訴用戶解析已經(jīng)完成。

在后面附加的例子中,我會引用OfficeWordHelper.Document.Text 等于“1.試題1\r\n2.試題2\r\n3.試題3”的文本字符串來模擬Word文檔中的文字(實際情況更

加復雜,Word文檔中包括圖片,符號,OLE對象等等,一切為了簡化說明,這里省略該步驟),說明它拆分出來的試題有3道。QuestionWordInfo 類的

StartIndex,EndIndex對應試題所在行數(shù)索引。

接著注意ExcuteQuestion這個方法,調(diào)用了CreateQuestionConvertor和AddQuestion兩個抽象方法。該兩個抽象方法將在業(yè)務組件中實現(xiàn)。

 

試卷解析器基本設(shè)計實現(xiàn)了,現(xiàn)在看下試題解析器該如何實現(xiàn):

聲明一個試題解析器范型接口:

/// <summary>
/// 試題轉(zhuǎn)換器泛型接口
/// </summary>
public interface IQuestionConvertor<TIn, TOut>
{
TOut Convert(TIn tIn, string wordContent);
}
其中TIn類型作為輸入類型,TOut類型作為輸出類型(TIn將來作為業(yè)務代碼中實際的輸入類型,如WordInfo類;TOut作為實際輸出類型,如QuestionInfo類)

考慮到轉(zhuǎn)換器在轉(zhuǎn)換過程Convert中,會產(chǎn)生一系列的步驟,首先對于轉(zhuǎn)換這個過程進行細化,分解成各個步驟:

代碼
public abstract class BaseQuestionConvertor<TIn, TOut> : IQuestionConvertor<TIn, TOut> where TIn : class, new()
{
//成員

#region 輸出試卷屬性
/// <summary>
/// 輸出試卷實體
/// </summary>
protected TOut Question { get; set; }
#endregion
#region 輸入Word實體屬性
/// <summary>
/// 輸入Word實體屬性
/// </summary>
protected TIn WordInfo { get; set; }
#endregion

//公共方法

#region 轉(zhuǎn)換方法
/// <summary>
/// 轉(zhuǎn)換方法
/// </summary>
/// <param name="tIn"></param>
/// <param name="helper"></param>
/// <returns></returns>
public virtual TOut Convert(TIn tIn, string wordContent)
{
WordInfo = tIn;
Question = Initialize(tIn);

//解析試題
TOut tOut = Execute(wordContent);

//完成
Finished();

return tOut;
}
#endregion

//抽象方法

#region 初始化
/// <summary>
/// 初始化
/// </summary>
/// <param name="tIn"></param>
/// <returns></returns>
protected abstract TOut Initialize(TIn tIn);
#endregion

#region 執(zhí)行
/// <summary>
/// 執(zhí)行
/// </summary>
/// <param name="tOut"></param>
protected abstract TOut Execute(string wordContent);
#endregion

#region 完成
/// <summary>
/// 完成
/// </summary>
protected abstract void Finished();
#endregion
}


接著,需要定義一個包含Word解析邏輯代碼的抽象類,這里使用Microsoft.Office.Interop.Word進行Office編程,于是創(chuàng)建名為

OfficeWordQuestionConvertor.cs的類:

OfficeWordQuestionConvertor /// <summary>
/// 試題解析器泛型抽象類
/// </summary>
public abstract class OfficeWordQuestionConvertor<TIn, TOut> : BaseQuestionConvertor<TIn, TOut>
where TIn : class, new()
where TOut : class, new()
{
protected override TOut Execute(string wordContent)
{
ParseQuestionContent(wordContent);
ParseDifficultyCode(wordContent);

//...其他解析屬性,這里省略

return Question;
}

#region 解析試題題干
/// <summary>
/// 解析試題題干
/// </summary>
/// <returns></returns>
protected virtual void ParseQuestionContent(string questionText)
{
//通過questionText解析出試題提干,這里省略
string content = questionText;
SetQuestionContent(content);
}
#endregion

#region 解析試題難度
/// <summary>
/// 解析試題難度
/// </summary>
/// <param name="questionText"></param>
/// <returns></returns>
protected virtual void ParseDifficultyCode(string questionText)
{
//通過questionText解析出難度文本,這里省略
string difficulty = "A";
SetDifficultyCode(difficulty);
}
#endregion

//抽象方法

/// <summary>
/// 設(shè)置試題標題
/// </summary>
/// <param name="text"></param>
protected abstract void SetQuestionContent(string text);

/// <summary>
/// 設(shè)置試題難度
/// </summary>
/// <param name="difficulty"></param>
protected abstract void SetDifficultyCode(string difficulty);
}


Execute方法通過Word文本內(nèi)容解析相應試題的屬性(如題干、難度、是否系統(tǒng)試題等)。

于是這里抽象出了兩個方法(按照需求來進行方法擴展),SetQuestionContent和SetDifficultyCode將在業(yè)務組件中實現(xiàn)。

 

3. 現(xiàn)在開始創(chuàng)建其他項目,如下圖所示:

 

其中WebApp為B/S平臺項目,WebApp.Lib為B/S平臺業(yè)務類庫,兩個項目均采用.net framework 3.5;WinApp為C/S平臺項目,WinApp.Lib為C/S業(yè)務類庫;

注意到,WebApp.Lib和WinApp.Lib在數(shù)據(jù)實體類上存在差異(實際情況差異更大,不僅僅數(shù)據(jù)實體類上,這里為了簡化),兩個項目均采用.net framework 2.0;

WordConvertor即為上面說的解析器組件。

以WebApp.Lib為例,實現(xiàn)業(yè)務試卷和試題解析器:

WebPaperConvertor .cs:

代碼
/// <summary>
/// Web端試卷解析器
/// </summary>
public class WebPaperConvertor : OfficeWordPaperConvertor<WordInfo, QuestionInfo, PaperInfo>
{
/// <summary>
/// 初始化試卷
/// </summary>
protected override PaperInfo Initialize(WordInfo wordInfo)
{
Paper = new PaperInfo();
Paper.Title = wordInfo.PaperTitle;
return Paper;
}

/// <summary>
/// 創(chuàng)建試題解析器
/// </summary>
protected override QuestionInfo CreateQuestionConvertor(WordInfo wordInfo, string wordContent)
{
WebQuestionConvertor convertor = new WebQuestionConvertor();
return convertor.Convert(wordInfo, wordContent);
}

/// <summary>
/// 增加試題
/// </summary>
protected override void AddQuestion(QuestionInfo tQuestion)
{
if(Paper.QuestionInfoList == null)
Paper.QuestionInfoList = new List<QuestionInfo>();
Paper.QuestionInfoList.Add(tQuestion);
}

//其他業(yè)務擴展...
}

WebQuestionConvertor .cs:

代碼
/// <summary>
/// Web端試題解析器
/// </summary>
public class WebQuestionConvertor : OfficeWordQuestionConvertor<WordInfo, QuestionInfo>
{
/// <summary>
/// 根據(jù)條件初始化試題
/// </summary>
protected override QuestionInfo Initialize(WordInfo wordInfo)
{
QuestionInfo questionInfo = new QuestionInfo();
questionInfo.IsSystem = wordInfo.IsSystem;
return questionInfo;
}

/// <summary>
/// 完成解析后觸發(fā)
/// </summary>
protected override void Finished()
{
}

/// <summary>
/// 設(shè)置試題題干
/// </summary>
protected override void SetQuestionContent(string text)
{
Question.QuestionContent = text;
}

/// <summary>
/// 設(shè)置試題難度
/// </summary>
protected override void SetDifficultyCode(string difficulty)
{
switch (difficulty)
{
case "A":
Question.DifficultyCode = 1;
break;
case "B":
Question.DifficultyCode = 2;
break;
case "C":
Question.DifficultyCode = 3;
break;
}
}

//其他業(yè)務擴展...
}

從類中可以看出,它們分別繼承于OfficeWordPaperConvertor和OfficeWordQuestionConvertor類,這里實現(xiàn)的只是和平臺相關(guān)的業(yè)務邏輯,至于如何對一份Word文檔解析,交給解析器組件去做,平臺上無需知道。
同理,C/S平臺也用了類似的方法,不同的只是個別類型通過泛型抽象類得到實現(xiàn)。并且能夠使B/S平臺和C/S平臺擁有各自的業(yè)務邏輯。

這樣,維護兩個平臺的這個功能成本降低了,如果解析器組件需要改動,只要更動基礎(chǔ)組件的設(shè)計,而不會影響業(yè)務上的邏輯。

 

這是Leepy同學在開發(fā)項目的時候遇到的問題,可以說是提供了一種思路吧,也可以算是經(jīng)驗之談吧:)

在591up以及客戶端的功能效果如下圖所示:

 

591up 客戶端軟件

最后附上該范例的Demo
 

    相關(guān)評論

    閱讀本文后您有什么感想? 已有人給出評價!

    • 8 喜歡喜歡
    • 3 頂
    • 1 難過難過
    • 5 囧
    • 3 圍觀圍觀
    • 2 無聊無聊

    熱門評論

    最新評論

    發(fā)表評論 查看所有評論(0)

    昵稱:
    表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
    字數(shù): 0/500 (您的評論需要經(jīng)過審核才能顯示)