[C#][Videgree] 為了封裝"附加(Attachment)"功能,使用了 "Interface + Method Extension + 繼承" 硬是把它給生出來

[C#][Videgree] 為了封裝"附加(Attachment)"功能,使用了 "Interface + Method Extension + 繼承" 硬是把它給生出來

前言 : 這是我在公司寫"Videgree愛顧客"這產品時,所遇到的一個很有趣的情境,為了降低重複的程式碼,很用力的寫出這個方式出來

故事的開始 :  有一天老大跟大家討論,如果我們產品"記事(Note)"這功能,能夠有類似FB塗鴉牆的功能的話,那麼User在使用上就能更方便的張貼

                       自己想要的內容,而不是單純的只有文字描述,所以希望Note能夠有掛上"附件(Attachment)"的能力(就像FB,G+的訊息功能一樣),

                       可以上傳一個檔案,連結一張圖或是連一個blog網站之類的,於是乎就有這樣的Domain Model

                        domin

                        簡單的解釋這張圖 : 首先主角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,像檔案的處理部份之類的,所以看的時候需要點想像力,辛苦了。
以上...