1:項目場景 在設計數據表的時候有時候為了將來統計或查詢的方便,我們會冗餘一些欄位。如有三張數據表,學校信息表、班級動態表、班級信息表。 班級動態由學校老師所發,可以進行評論點贊等操作,為了提升這種非結構化數據的訪問效率,存儲於Mongodb中,冗餘了學校名稱欄位,假設班級表也冗餘了學校名稱欄位。而 ...
1:項目場景
在設計數據表的時候有時候為了將來統計或查詢的方便,我們會冗餘一些欄位。如有三張數據表,學校信息表、班級動態表、班級信息表。
班級動態由學校老師所發,可以進行評論點贊等操作,為了提升這種非結構化數據的訪問效率,存儲於Mongodb中,冗餘了學校名稱欄位,假設班級表也冗餘了學校名稱欄位。而冗餘欄位的存在可能會帶來數據不一致問題。這邊需要保持修改學校名稱時,其他相關的表中學校名稱欄位保持一致,先看根據業務邏輯最直接的寫法。
1.1:先定義類
/// <summary> /// 學校信息表 /// </summary> public class School { public string RowGuid { get; set; } public string SchoolName { get; set; } } /// <summary> /// 班級動態(假設存儲於Mongodb,為按學校名稱模糊查詢時方便,冗餘學校名稱欄位) /// </summary> public class PersonDynamic { public string RowGuid { get; set; } public string Title { get; set; } public string MainContent { get; set; } /// <summary> /// 發佈人標識 /// </summary> public string TeacherGuid { get; set; } /// <summary> /// 冗餘發佈人所在學校Guid /// </summary> public string SchoolGuid { get; set; } /// <summary> /// 冗餘發佈人所在學校名稱 /// </summary> public string SchoolName { get; set; } } /// <summary> /// 班級信息 假設冗餘學校名稱 /// </summary> public class Class { public string RowGuid { get; set; } public string ClassName { get; set; } public string SchoolGuid { get; set; } public string SchoolName { get; set; } }
1.2對應邏輯層設計
public class BlClass { //更新班級表中學校名稱數據 public void UpdateClass(School school) { //string sql = string.Format("update ClassInfo set SchoolName ='{0}' where SchoolGuid ='{1}'", school.SchoolName, school.RowGuid); Console.WriteLine("完成修改班級表的學校名稱。"); } }
public class BlPersonDynamic { //更新班級動態數據表中學校名稱 public void UpdatePersonDynamic(School school) { //MongoDBHelper.Update(……) Console.WriteLine("完成修改班級動態表的學校名稱。"); } }
public class BlSchool { public void UpdateSchool(School school) { //修改學校名稱邏輯 // string sql = string.Format("update Schoolinfo set SchoolName ='{0}' where RowGuid ='{1}'", school.SchoolName, school.RowGuid); Console.WriteLine("完成修改學校信息表的學校名稱。"); new BlPersonDynamic().UpdatePersonDynamic(school); new BlClass().UpdateClass(school); } }
1.3:需求擴展:
可以預見到,之後還會增加教師文章,學校新聞等等內容,裡面仍然可能冗餘學校名稱欄位,那麼更新學校名稱的邏輯則時常需要修改,違反了開閉原則。其他類中的學校名稱欄位依賴於學校表的學校名稱,當學校表中學校名稱改變時,其他表學校名稱也需要同步改變,由此想到了一種開發模式,觀察者模式。
2:使用觀察者模式改造
2.1百度百科中的定義
觀察者設計模式定義了對象間的一種一對多的依賴關係,以便一個對象的狀態發生變化時,所有依賴於它的對象都得到通知並自動刷新。
2.2類圖
2.3 C#使用委托事件方式實現觀察者模式
此處改造先不考慮抽象觀察者 和 抽象主題,學校作為通知者,班級和班級動態作為觀察者角色,這樣的好處是不影響現有的觀察者邏輯,調整後的邏輯層。
//具體觀察者1 public class BlClass { public void UpdateClass(School school) { // string sql = string.Format("update ClassInfo set SchoolName ='{0}' where SchoolGuid ='{1}'", school.SchoolName, school.RowGuid); Console.WriteLine("完成修改班級表的學校名稱。"); } }
//具體觀察者2 public class BlPersonDynamic { //更新班級動態數據表中學校名稱 public void UpdatePersonDynamic(School school) { //MongoDBHelper.Update(……) Console.WriteLine("完成修改班級動態表的學校名稱。"); } }
//聲明一個委托 public delegate void UpdateSchoolEvent(School school); //通知者 public class BlSchool { //聲明一個事件 public event UpdateSchoolEvent updateSchoolEvent; public void UpdateSchool(School school) { //首先更新學校表中名稱 //string sql = string.Format("update Schoolinfo set SchoolName ='{0}' where RowGuid ='{1}'", school.SchoolName, school.RowGuid); Console.WriteLine("完成修改學校表數據!"); //修改學校名稱後發起通知,觸發事件 this.Notice(school); } public void Notice(School school) { //如果事件非空則調用 updateSchoolEvent?.Invoke(school); } }
這樣調整後則完成了學校 與 班級、動態之間的耦合度,新增其他需要更新的地方也不需要修改學校邏輯層。只需要在使用的地方註冊一下事件,如下麵的模擬調用:
//修改後的實體類信息 School school = new School(); school.RowGuid = Guid.NewGuid().ToString(); school.SchoolName = "第一中學"; //註冊修改學校名稱事件 BlSchool blSchool = new BlSchool(); blSchool.updateSchoolEvent += new BlPersonDynamic().UpdatePersonDynamic; blSchool.updateSchoolEvent += new BlClass().UpdateClass; //更新學校名稱 blSchool.UpdateSchool(school); Console.ReadKey();
2.4思考一
當做功能擴展的時候,原先的寫法是在學校邏輯層調整修改學校的邏輯,而使用觀察者模式之後仍然需要註冊事件的地方增加註冊事件,都是需要修改是否有區別?個人理解首先是解除了類之間的耦合度,其次我覺得開閉原則對修改關閉、對擴展開放,並不是意味著功能拓展不需要改變任何以前的代碼,而是拓展後改變需要花費代價的大小,如同一些配置參數的使用,也是為了最小的代價完成功能的修改或拓展。而此處學校業務邏輯層如果已經是封裝好的主程式邏輯,那麼修改需要花費較大的代價。
2.5思考二
對於觀察者模式的抽象主題、和抽象觀察者的使用。比如在原先基礎上增加了學校新聞的功能,此時並不知道需要寫下麵這個 和以前相同參數的方法。
//具體觀察者3 public class BlSchoolNews { //更新班級動態數據表中學校名稱 public void UpdateSchoolNews(School school) { //MongoDBHelper.Update(……) Console.WriteLine("完成修改學校新聞表的學校名稱。"); } }
那麼這裡則可以對具體的觀察者進行抽象,對於對修改學校感興趣的類,實現這個抽象的介面。抽象後如下
/// <summary> /// 抽象一個更新學校介面 /// </summary> public interface IUpdateSchool { void AfterUpdateSchool(School school); } //具體觀察者1 public class BlClass : IUpdateSchool { public void AfterUpdateSchool(School school) { this.UpdateClass(school); } private void UpdateClass(School school) { // string sql = string.Format("update ClassInfo set SchoolName ='{0}' where SchoolGuid ='{1}'", school.SchoolName, school.RowGuid); Console.WriteLine("完成修改班級表的學校名稱。"); } } //具體觀察者2 public class BlPersonDynamic : IUpdateSchool { public void AfterUpdateSchool(School school) { this.UpdatePersonDynamic(school); } //更新班級動態數據表中學校名稱 private void UpdatePersonDynamic(School school) { //MongoDBHelper.Update(……) Console.WriteLine("完成修改班級動態表的學校名稱。"); } } //具體觀察者3 public class BlSchoolNews : IUpdateSchool { public void AfterUpdateSchool(School school) { this.UpdateSchoolNews(school); } //更新班級動態數據表中學校名稱 private void UpdateSchoolNews(School school) { //MongoDBHelper.Update(……) Console.WriteLine("完成修改學校新聞表的學校名稱。"); } }
模擬調用
//修改後的實體類信息 School school = new School(); school.RowGuid = Guid.NewGuid().ToString(); school.SchoolName = "第一中學"; //註冊修改學校名稱事件 BlSchool blSchool = new BlSchool(); blSchool.updateSchoolEvent += new BlPersonDynamic().AfterUpdateSchool; blSchool.updateSchoolEvent += new BlClass().AfterUpdateSchool; blSchool.updateSchoolEvent += new BlSchoolNews().AfterUpdateSchool; //更新學校名稱 blSchool.UpdateSchool(school); Console.ReadKey();