[模式]C# 外觀模式(Facade)
前言
呼,筆者第一篇的設計模式的紀錄,其實筆者也讀過不少設計模式的文章或書藉,不過也從來沒有特別去背過任何一個模式應該怎麼寫,因為筆者更重視的是重構和clean code這些議題,因為程式碼在一開始寫的時候基本上都是很單純的,通常都是因為善變的需求還有無情歲月的累積,才會導致程式碼越來越糟糕,不過因為常常在重構不管是javascript或c#的程式碼,所以後來又看設計模式的時候,就會發現怎麼我曾經做過的程式碼,原來就是xxx設計模式啊,或者有時候看到某設計模式的時候,就會想到原來我那時候的難題,這個設計模式正好可以套用得很好,但其實要寫設計模式的文章也不簡單,寫一些三國志的例子或狗啊貓的,書本已經一大堆了,好像跟我們實際真實需求都摸不著邊,非常難以體會啊,所以總希望用一些業界真實會碰到的需求來表達設計模式,所以如果有靈感或想法或發現的話,希望能漸漸的補滿所有設計模式,也可以給自己未來一點印象或參考。
目的
通常一個正常的C#架構,都至少會有Repository和Service跟Controller層,我們通常會把邏輯放在service層處理,但是常常會發現a類別call了很多不同的類別和方法,b類別也call了很多不同的類別和方法,但是跟a類別幾乎一模一樣,只有一點點的不一樣,這時候就能套用外觀模式來簡單包裝一下了。(事實上最難的部份不是一樣的抽出來,而是八成一樣,但是就兩成不一樣的時候,這些程式碼怎麼抽出來,而不是一直的copy paste)
動手實做
假設我有一個公佈欄需求,當我的controller呼叫的時候,需要回傳公佈欄資訊,而且同時也要回傳公佈欄的下載檔案,這時候設計可能如下
void Main()
{
var dto = new BulletinDao();
var bulletinData = dto.Get();
var filename = ServerConfig.GetPath + GenerateHelper.GetImgSerilal + dto.GetFileName();
LogHelper.Info(filename);
new Poco {BulletinData=bulletinData,FileName=filename}.Dump();//假設這個是要回傳的物件資料
}
public class Poco
{
public string BulletinData { get; set; }
public string FileName { get; set; }
}
//公佈欄
public class BulletinDao
{
public string Get()
{
return "取得公佈欄資料";
}
public string GetFileName()
{
return "File名稱";
}
}
//Server配置
public class ServerConfig
{
public static string GetPath { get; } = "/Upload/";
}
//Log相關
public class LogHelper
{
public static void Info(string message)
{
$"發生時間:{DateTime.Now},情境:{message}".Dump();
}
}
//一些產生的配置
public class GenerateHelper
{
public static string GetImgSerilal { get; } = DateTime.Now.ToString("yyMMddhhmmss");
}
現在假設有一個新需求來了,就是我們取公佈欄,必須要在某些下載的檔案,加印浮水印,這時候我們程式碼可能會再加一個方法應付這個新需求
//這邊注意一下,為了好分辨新增的程式碼,我會加上新的註解,並移除原本的註解
void Main()
{
//原本程式碼
// var dao = new BulletinDao();
// var bulletinData = dao.Get();
// var filename = ServerConfig.GetPath + GenerateHelper.GetImgSerilal + dao.GetFileName();
// LogHelper.Info(filename);
// new Poco { BulletinData = bulletinData, FileName = filename }.Dump();
//新增需求程式碼
var dao = new BulletinDao();
var bulletinData = dao.Get();
var filename = ServerConfig.GetPath + GenerateHelper.GetImgSerilal + dao.GetStampFileName(); //改成取浮水印的file name
StampHelper.PersonStamp(filename);//印上浮水印
LogHelper.Info(filename);
new Poco {BulletinData=bulletinData,FileName=filename}.Dump();
}
public class Poco
{
public string BulletinData { get; set; }
public string FileName { get; set; }
}
public class BulletinDao
{
public string Get()
{
return "取得公佈欄資料";
}
public string GetStampFileName()
{
return "取得需要印浮水印的File名稱";
}
public string GetFileName()
{
return "File名稱";
}
}
//印logo相關類別
public class StampHelper
{
public static void PersonStamp(string fileName)
{
$"{fileName}已加上浮水印".Dump();
}
}
public class ServerConfig
{
public static string GetPath { get; } = "/Upload/";
}
public class LogHelper
{
public static void Info(string message)
{
$"發生時間:{DateTime.Now},情境:{message}".Dump();
}
}
public class GenerateHelper
{
public static string GetImgSerilal { get; } = DateTime.Now.ToString("yyMMddhhmmss");
}
可以看到原本的程式碼,跟新需求的程式碼非常的像,只有一點點的不同,那麼我們就抽出來一個方法,把共用的包在一起。
static Poco Facade(BulletinDao dao, string fileName)
{
var bulletinData = dao.Get();
var path = ServerConfig.GetPath + GenerateHelper.GetImgSerilal + fileName; //改成取浮水印的file name
StampHelper.PersonStamp(path);//印上浮水印
LogHelper.Info(path);
return new Poco { BulletinData = bulletinData, FileName = path }.Dump();
}
原本呼叫的地方則改成如下
void Main()
{
//原本需求
var dao=new BulletinDao();
var fileName=dao.GetFileName();
Facade(dao,fileName).Dump();
//要印上浮水印的需求
var stampFileName=dao.GetStampFileName();
var pocos=Facade(dao,stampFileName);
StampHelper.PersonStamp(pocos.FileName);
pocos.Dump();
}
完整程式碼
//這邊注意一下,為了好分辨新增的程式碼,我會加上新的註解,並移除原本的註解
void Main()
{
//原本需求
var dao=new BulletinDao();
var fileName=dao.GetFileName();
Facade(dao,fileName).Dump();
//要印上浮水印的需求
var stampFileName=dao.GetStampFileName();
var pocos=Facade(dao,stampFileName);
StampHelper.PersonStamp(pocos.FileName);
pocos.Dump();
}
static Poco Facade(BulletinDao dao, string fileName)
{
var bulletinData = dao.Get();
var path = ServerConfig.GetPath + GenerateHelper.GetImgSerilal + fileName; //改成取浮水印的file name
StampHelper.PersonStamp(path);//印上浮水印
LogHelper.Info(path);
return new Poco { BulletinData = bulletinData, FileName = path }.Dump();
}
public class Poco
{
public string BulletinData { get; set; }
public string FileName { get; set; }
}
public class BulletinDao
{
public string Get()
{
return "取得公佈欄資料";
}
public string GetStampFileName()
{
return "取得需要印浮水印的File名稱";
}
public string GetFileName()
{
return "File名稱";
}
}
//印logo相關類別
public class StampHelper
{
public static void PersonStamp(string fileName)
{
$"{fileName}已加上浮水印".Dump();
}
}
public class ServerConfig
{
public static string GetPath { get; } = "/Upload/";
}
public class LogHelper
{
public static void Info(string message)
{
$"發生時間:{DateTime.Now},情境:{message}".Dump();
}
}
public class GenerateHelper
{
public static string GetImgSerilal { get; } = DateTime.Now.ToString("yyMMddhhmmss");
}
結論
雖然只是描述一個簡單的案例,程式碼也是產生了不少,期望大家能體會我想表達的,或許看完程式碼你也會發現,這段還真的是重構的時候常常在用的部份,如果對此有任何想法的話,再請不吝提供給筆者哦。