[模式]C# 外觀模式(Facade)

[模式]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");
}

結論

雖然只是描述一個簡單的案例,程式碼也是產生了不少,期望大家能體會我想表達的,或許看完程式碼你也會發現,這段還真的是重構的時候常常在用的部份,如果對此有任何想法的話,再請不吝提供給筆者哦。