[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,像檔案的處理部份之類的,所以看的時候需要點想像力,辛苦了。
以上...