[C#][Videgree] 為了封裝"附加(Attachment)"功能,使用了 "Interface + Method Extension + 繼承" 硬是把它給生出來
前言 : 這是我在公司寫"Videgree愛顧客"這產品時,所遇到的一個很有趣的情境,為了降低重複的程式碼,很用力的寫出這個方式出來
故事的開始 : 有一天老大跟大家討論,如果我們產品"記事(Note)"這功能,能夠有類似FB塗鴉牆的功能的話,那麼User在使用上就能更方便的張貼
自己想要的內容,而不是單純的只有文字描述,所以希望Note能夠有掛上"附件(Attachment)"的能力(就像FB,G+的訊息功能一樣),
可以上傳一個檔案,連結一張圖或是連一個blog網站之類的,於是乎就有這樣的Domain Model
簡單的解釋這張圖 : 首先主角Note,附件Attachment跟它的兒子們(SubType),每個note都可以掛上上多種Attachment
物件的關係看起來非常之簡單但因應它的變化就要很小心
先看Attachment的interface 跟Impl,UploadedFile這顆物件存放是實體檔案的相關資訊,上方我沒有畫出來,也就只有File 跟MailEmbeddedImg會用到
1: public interface IAttachment : IEntity
2: {3: string Description { get; set; }
4: INote Note { get; set; } 5: } 6: 7: public interface IFileAttachment : IAttachment
8: {9: string FileName { get; set; }
10: IUploadedFile UploadedFile { get; set; } 11: } 12: 13: public interface ILinkAttachment : IAttachment
14: { 15: Uri AttachmentUrl { get; set; } 16: } 17: 18: public interface IMailEmbeddedImg : IAttachment
19: {20: string FileName { get; set; }
21: IUploadedFile UploadedFile { get; set; } 22: } 23: 24: // 實作部份
25: public class Attachment : Entity, IAttachment
26: {27: public virtual string Description { get; set; }
28: public virtual INote Note { get; set; }
29: } 30: 31: public class FileAttachment : Attachment, IFileAttachment
32: {33: public virtual string FileName { get; set; }
34: public virtual IUploadedFile UploadedFile { get; set; }
35: } 36: 37: public class LinkAttachment : Attachment, ILinkAttachment
38: {39: public virtual Uri AttachmentUrl { get; set; }
40: } 41: 42: public class MailEmbeddedImg : Attachment, IMailEmbeddedImg
43: {44: public virtual string FileName { get; set; }
45: public virtual IUploadedFile UploadedFile { get; set; }
46: }
接下來看Note的Interface跟實作
1: public interface INote : IEntity, IAttachable
2: {3: //咦?Note只有這個屬性,要注意繼承後面又跟了一個IAttachable,我們把Attachment相關的動作定到這個Interface上了
4: string Description { get; set; }
5: } 6: 7: public class Note : Entity, INote
8: {9: public virtual string Description { get; set; }
10: 11: /* 以下的部份全部都在實作IAttachable所訂出來的Method跟屬性 */
12: //附加檔案的集合
13: private IList attachments = new ArrayList();
14: public virtual IList Attachments
15: {16: get { return attachments; }
17: set { attachments = value; }
18: } 19: 20: //處理增加一個Link的Attachment
21: public virtual ILinkAttachment AddAttachments(ILinkAttachment linkAttachment)
22: {23: linkAttachment.Note = this;
24: this.Attachments.Add(linkAttachment);
25: return linkAttachment;
26: }27: //處理增加一個File的Attachment
28: public virtual IFileAttachment AddAttachments(IFileAttachment fileAttachment)
29: {30: fileAttachment.Note = this;
31: this.Attachments.Add(fileAttachment);
32: return fileAttachment;
33: }34: //處理增加一個Mail Embedded的圖檔
35: public virtual IMailEmbeddedImg AddAttachments(IMailEmbeddedImg mailEmbeddedImg)
36: {37: mailEmbeddedImg.Note = this;
38: this.Attachments.Add(mailEmbeddedImg);
39: return mailEmbeddedImg;
40: } 41: } 42: 43: /* 最後再來看看IAttachable這個Interface,它定了物件存放Attachment集合的屬性,
44: 並且把各種Attachment類型的新增方式也定下來*/
45: public interface IAttachable
46: { 47: IList Attachments { get; set; } 48: 49: ILinkAttachment AddAttachments(ILinkAttachment linkAttachment); 50: 51: IFileAttachment AddAttachments(IFileAttachment fileAttachment); 52: 53: IMailEmbeddedImg AddAttachments(IMailEmbeddedImg mailEmbeddedImg); 54: }
看到這邊應該就會有幾個問題了,
1.為什麼要另外定一個IAttachable?
2.那三個Add Attachment的Method只做物件關聯,其他啥事都沒做,而且名稱都一樣?
其實會定IAttachable是為了,把Attachment給獨立出來,之後只要有其他物件(如: 公告Announcement,工作Task)有需要掛上附件的話,
只要實作這個 Interface –> IAttachable,那麼這物件自然就有能力可以掛上Attachment
而在Note的實作上那三個Add Attachment的Method只做物件關聯,是因為只有物件關聯的部份無法獨立出來,
其他的邏輯部份不管你現在是使用那個物件(Note,Announcement,Task),其實邏輯都一樣,所以我們把它寫到Attachable Extension如下:
1: public static class AttachableExtension
2: { 3: public static ILinkAttachment AddLinkAttachment(this IAttachable attachable, string description, Uri url)
4: { 5: ILinkAttachment linkAttachment = RheaFactory.DomainFactory.MakePreProcessDomain<ILinkAttachment>(); 6: linkAttachment.Description = description; 7: linkAttachment.AttachmentUrl = url; 8: attachable.AddAttachments(linkAttachment);9: return linkAttachment;
10: } 11: 12: public static IFileAttachment AddFileAttachment(this IAttachable attachable, string description, string fileName, string eigenvalue, long fileSize)
13: { 14: IFileAttachment fileAttachment = RheaFactory.DomainFactory.MakePreProcessDomain<IFileAttachment>(); 15: fileAttachment.Description = description; 16: fileAttachment.FileName = fileName; 17: attachable.AddAttachments(fileAttachment);18: return fileAttachment;
19: } 20: 21: public static IMailEmbeddedImg AddMailEmbeddedImg(this IAttachable attachable, string description, string fileName, string eigenvalue, long fileSize)
22: { 23: IMailEmbeddedImg mailEmbeddedImg = RheaFactory.DomainFactory.MakePreProcessDomain<IMailEmbeddedImg>(); 24: mailEmbeddedImg.Description = description; 25: mailEmbeddedImg.FileName = fileName; 26: attachable.AddAttachments(mailEmbeddedImg);27: return mailEmbeddedImg;
28: } 29: }
在Extension部份可以看到粗體的attachable,這是定IAttachable 最主要的原因,
在外不用認識你是誰(Note,Announcement,Task),我只要知道你有實做IAttachable ,
所以我就都用IAttachable 跟你溝通,在來我把Method名稱都定成一樣,就不用去知道
傳入的是種類型,會知道Attachment類型的當然是最外層的人,而這個Extension就是
給外層使用,所以只在這裡去命名外面人看的懂有意義類型的Method名稱
整個底都用好後使用起來就像這樣
1: [HttpPost]2: [ValidateInput(false)]
3: public ActionResult Save(FormCollection collection)
4: { 5: INote note = RheaFactory.DomainFactory.MakeDomain<INote>();6: // 加一個Add FileAttachment
7: note.AddFileAttachment(collection["Description"], collection["FileName"], collection["Eigenvalue"], 1024);
8: // 加一個Add LinkAttachment
9: note.AddLinkAttachment(collection["LinkDescription"], new Uri(collection["Url"]));
10: // 加一個Add MailEmbeddedImg
11: note.AddMailEmbeddedImg(collection["ImgDescription"], collection["ImgFileName"], collection["ImgEigenvalue"], 1024);
12: 13: NoteService.Create(note); 14: }
然後過沒兩天老大真的就說Announcement,Task等也都掛上可加Attachment的行為吧
所以我只需要做稍為的調整就行了~如下:
Attachment加上Announcement,Task
1: [Serializable]2: public class Attachment : Entity, IAttachment
3: {4: public virtual string Description { get; set; }
5: public virtual INote Note { get; set; }
6: public virtual IAnnouncement Announcement { get; set; }
7: public virtual ICase Case { get; set; }
8: }
Announcement的Interface掛上IAttachable
1: public interface IAnnouncement : IEntity, IAttachable
2: {3: /// <summary>
4: /// 公告有效日期-起日(以日為單位)
5: /// </summary>
6: DateTime FromDate { get; set; } 7: 8: /// <summary>
9: /// 公告有效日期-迄日(以日為單位)
10: /// </summary>
11: DateTime ThruDate { get; set; } 12: 13: /// <summary>
14: /// 公告標題
15: /// </summary>
16: string Title { get; set; }
17: 18: /// <summary>
19: /// 公告內容
20: /// </summary>
21: string Content { get; set; }
22: 23: /// <summary>
24: /// 重要性
25: /// </summary>
26: EnumImportance Importance { get; set; } 27: }
Announcement補上IAttachable的實作
1: public class Announcement : Entity, IAnnouncement
2: {3: public virtual DateTime FromDate { get; set; }
4: public virtual DateTime ThruDate { get; set; }
5: public virtual string Title { get; set; }
6: public virtual string Content { get; set; }
7: public virtual EnumImportance Importance { get; set; }
8: 9: private IList attachments = new ArrayList();
10: public virtual IList Attachments
11: {12: get { return attachments; }
13: set { attachments = value; }
14: } 15: 16: public virtual ILinkAttachment AddAttachments(ILinkAttachment linkAttachment)
17: {18: linkAttachment.Announcement = this;
19: this.Attachments.Add(linkAttachment);
20: return linkAttachment;
21: } 22: 23: public virtual IFileAttachment AddAttachments(IFileAttachment fileAttachment)
24: {25: fileAttachment.Announcement = this;
26: this.Attachments.Add(fileAttachment);
27: return fileAttachment;
28: } 29: 30: public virtual IMailEmbeddedImg AddAttachments(IMailEmbeddedImg mailEmbeddedImg)
31: {32: mailEmbeddedImg.Announcement = this;
33: this.Attachments.Add(mailEmbeddedImg);
34: return mailEmbeddedImg;
35: } 36: }完成這幾個步驟後Announcement也就有掛上Attachment的能力了,Task也只要補上這幾個動作也就可以掛附件了
而上方提到的只有物件關聯的部份無法獨立出來,原因就是紅色字段的部份,在最後的部份還是要給個明確的物件當關聯
由於這是範例所以我拿掉很多code,像檔案的處理部份之類的,所以看的時候需要點想像力,辛苦了。
以上...