可抽換元件設計模式
TRANSCRIPT
![Page 1: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/1.jpg)
可抽換元件設計模式
![Page 2: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/2.jpg)
相依
名詞:dependency
動詞:depend(s) on
形容詞:dependent on
類別UserAppService相依於
UserRepository及Logger兩個類別
public class UserAppService
{
public void AddUser(UserInfo user)
{
UserRepository repository = new UserRepository();
repository.Add(user);
Logger log = new Logger();
Dictionary<string, string> messages = new Dictionary<string, string>();
messages.Add("Action", "建立使用者");
messages.Add("Detail", string.Format("UserId: {0}, UserName: {1}", user.UserId,
user.UserName));
messages.Add("LogTime", DateTime.Now.ToString());
log.Write(messages);
}
}
![Page 3: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/3.jpg)
耦合
Coupling,以程度區分為loose-coupling / loosely-coupled / 鬆散耦合:相依於介面(interface)
tight-coupling / tightly-coupled / 緊密耦合:相依於實作(implementation)
![Page 4: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/4.jpg)
需求案例
驚!某甲要求導入log機制,且不得使用third-
party元件(如log4net, NLog, Enterprise library
的Logging Application Block, etc.)
![Page 5: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/5.jpg)
一塊蛋糕
public class TextLogger
{
public void Write(Dictionary<string, string> messages)
{
StringBuilder sb = new StringBuilder();
messages.ToList().ForEach(c => sb.AppendLine(string.Format("{0}:{1}", c.Key, c.Value)));
sb.AppendLine();
File.AppendAllText(string.Format("{0}.txt", DateTime.Now.ToString("yyyyMMdd")), sb.ToString());
}
}
public class UserAppService
{
public void AddUser(UserInfo user)
{
UserRepository repository = new UserRepository();
repository.Add(user);
TextLogger log = new TextLogger();
Dictionary<string, string> messages = new Dictionary<string, string>();
messages.Add("Action", "建立使用者");
messages.Add("Detail", string.Format("UserId: {0}, UserName: {1}", user.UserId, user.UserName));
messages.Add("LogTime", DateTime.Now.ToString());
log.Write(messages);
}
}
![Page 6: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/6.jpg)
WHAT IF…
某甲:文字檔太粗糙,請log成XML格式的檔案
某乙:沒問題!
![Page 7: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/7.jpg)
仍然是一塊蛋糕
public class XmlLogger
{
public void Write(Dictionary<string, string> messages)
{
XElement element = new XElement(
new XElement("Record",
new XElement("Action", messages["Action"]),
new XElement("Detail", messages["Detail"]),
new XElement("LogTime", messages["LogTime"])
)
);
File.AppendAllText(string.Format("{0}_{1}.xml", DateTime.Now.ToString("yyyyMMdd"), Guid.NewGuid()),
element.ToString());
}
}
public class UserAppService
{
public void AddUser(UserInfo user)
{
…
XmlLogger log = new XmlLogger();
…
}
}
![Page 8: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/8.jpg)
WHAT IF AGAIN…
某甲:長官說要改成寫進資料庫唷, ^.<
某乙:…(OS: what the…(╯-_-)╯╧╧ )
![Page 9: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/9.jpg)
翻桌前思考一下
耦合度太高。系統提供新的 log機制時,UserAppService類別就必須修改實作內容以符合需求(TextLogger->XmlLogger)
彈性不足,無法任意抽換實作機制
![Page 10: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/10.jpg)
解決方案
Plugin Pattern
Provider Pattern (.NET Framework內建)
IoC Pattern
![Page 11: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/11.jpg)
PLUGIN PATTERN
Links classes during configuration rather
than compilation. – Patterns of Enterprise
Application Architecture [P of EAA], p.499
透過反射(reflection)機制於執行期(run time)
由設定檔(configuration)取得實際要執行的物件 TextLogger / XmlLogger
/ …
![Page 12: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/12.jpg)
PLUGIN PATTERN特色
會有一個實作Factory Method Pattern的類別,用於生成實際要執行之物件(plugin object)
設定檔內會有實際生成物件的組件名稱及型別,如放置在App.config或Web.config的appSettings中
實際生成之物件必定實作一通用介面
![Page 13: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/13.jpg)
PLUGIN PATTERN實作(1)
建立一通用介面
public interface ILogger
{
void Write(Dictionary<string, string> messages);
}
![Page 14: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/14.jpg)
PLUGIN PATTERN實作(2)
建立TextLogger類別(plugin object),並實作ILogger介面
public class TextLogger : ILogger
{
public void Write(Dictionary<string, string> messages)
{
StringBuilder sb = new StringBuilder();
messages.ToList().ForEach(c => sb.AppendLine(string.Format("{0}:{1}", c.Key, c.Value)));
sb.AppendLine();
File.AppendAllText(string.Format("{0}.txt", DateTime.Now.ToString("yyyyMMdd")),
sb.ToString());
}
}
![Page 15: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/15.jpg)
PLUGIN PATTERN實作(3)
建立XmlLogger類別(plugin object) ,並實作ILogger介面
public class XmlLogger : ILogger
{
public void Write(Dictionary<string, string> messages)
{
XElement element = new XElement(
new XElement("Record",
new XElement("Action", messages["Action"]),
new XElement("Detail", messages["Detail"]),
new XElement("LogTime", messages["LogTime"])
)
);
File.AppendAllText(string.Format("{0}_{1}.xml", DateTime.Now.ToString("yyyyMMdd"),
Guid.NewGuid()), element.ToString());
}
}
![Page 16: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/16.jpg)
PLUGIN PATTERN實作(4)
建立一實作Factory Method Pattern的類別public class LoggerFactory
{
private static ILogger _logger;
public static ILogger CreateLogger()
{
if (_logger == null)
{
string assemblyName = ConfigurationManager.AppSettings["AssemblyName"];
string classType = ConfigurationManager.AppSettings["ClassType"];
Assembly assembly = Assembly.Load(assemblyName);
_logger = assembly.CreateInstance(classType) as ILogger;
}
return _logger;
}
}
![Page 17: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/17.jpg)
建立設定檔
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="AssemblyName" value="LoggerSample"/>
<add key="ClassType" value="LoggerSample.PluginPattern.TextLogger"/>
</appSettings>
</configuration>
PLUGIN PATTERN實作(5)
Fully qualified name
![Page 18: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/18.jpg)
修改相依TextLogger/XmlLogger/…類別的程式碼public class UserAppService
{
public void AddUser(UserInfo user)
{
UserRepository repository = new UserRepository();
repository.Add(user);
//TextLogger log = new TextLogger();
ILogger log = LoggerFactory.CreateLogger();
Dictionary<string, string> messages = new Dictionary<string, string>();
messages.Add("Action", "建立使用者");
messages.Add("Detail", string.Format("UserId: {0}, UserName: {1}", user.UserId,
user.UserName));
messages.Add("LogTime", DateTime.Now.ToString());
log.Write(messages);
}
}
PLUGIN PATTERN實作(6)
相依實作轉為相依介面,耦合度降低
![Page 19: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/19.jpg)
若要切換log機制,僅須修改設定檔
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="AssemblyName" value="LoggerSample"/>
<add key="ClassType" value="LoggerSample.PluginPattern.XmlLogger"/>
</appSettings>
</configuration>
PLUGIN PATTERN實作(7)
![Page 20: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/20.jpg)
PLUGIN PATTERN兩三事
一組介面,提供多種實作
通用介面(ILogger)與實際要生成的物件(plugin
object)通常會存在於不同的組件中
提供新的plugin object時,不需修改原始程式碼(UserAppService),僅需提供新的plugin組件並修改設定檔
plugin object實作內容若有異動,僅需patch其組件
![Page 21: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/21.jpg)
PROVIDER PATTERN
.NET Framework內建的設計模式
提供一組介面,多種實作切換的功能
![Page 22: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/22.jpg)
PROVIDER PATTERN特色
會有一個實作Factory Method Pattern的類別,用於生成實際要執行之物件
會有一個繼承ConfigurationSection類別的類別且設定檔內會存放可於執行期生成之物件的組件名稱及型別
實際生成之物件必定繼承ProviderBase類別並實作一通用介面
![Page 23: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/23.jpg)
PROVIDER PATTERN實作(1)
建立一通用介面
public interface ILoggerProvider
{
void Write(Dictionary<string, string> messages);
}
![Page 24: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/24.jpg)
PROVIDER PATTERN實作(2)
建 立 TextLoggerProvider 類 別 , 繼 承ProviderBase類別並實作 ILoggerProvider介面public class TextLoggerProvider : ProviderBase, ILoggerProvider
{
public void Write(Dictionary<string, string> messages)
{
StringBuilder sb = new StringBuilder();
messages.ToList().ForEach(c => sb.AppendLine(string.Format("{0}:{1}", c.Key, c.Value)));
sb.AppendLine();
File.AppendAllText(string.Format("{0}.txt", DateTime.Now.ToString("yyyyMMdd")),
sb.ToString());
}
}
![Page 25: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/25.jpg)
PROVIDER PATTERN實作(3)
建 立 XmlLoggerProvider 類 別 , 繼 承ProviderBase類別並實作 ILoggerProvider介面public class XmlLoggerProvider : ProviderBase, ILoggerProvider
{
public void Write(Dictionary<string, string> messages)
{
XElement element = new XElement(
new XElement("Record",
new XElement("Action", messages["Action"]),
new XElement("Detail", messages["Detail"]),
new XElement("LogTime", messages["LogTime"])
)
);
File.AppendAllText(string.Format("{0}_{1}.xml", DateTime.Now.ToString("yyyyMMdd"),
Guid.NewGuid()), element.ToString());
}
}
![Page 26: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/26.jpg)
PROVIDER PATTERN實作(4)
建 立 LoggerProviderSection 類 別 並 繼 承ConfigurationSection類別
public class LoggerProviderSection : ConfigurationSection
{
public const string SectionName = "loggerProvider";
[ConfigurationProperty("providers", IsDefaultCollection = true)]
public ProviderSettingsCollection Providers
{
get
{
return (ProviderSettingsCollection)base["providers"];
}
}
[ConfigurationProperty("defaultProvider")]
public string DefaultProvider
{
get
{
return (string)base["defaultProvider"];
}
set
{
base["defaultProvider"] = value;
}
}
}
![Page 27: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/27.jpg)
PROVIDER PATTERN實作(5)
建立一實作Factory Method Pattern的類別public class LoggerProviderFactory
{
private static ProviderBase _provider;
public static ILoggerProvider CreateLoggerProvider()
{
if (_provider == null)
{
LoggerProviderSection section =
(LoggerProviderSection)ConfigurationManager.GetSection(LoggerProviderSection.SectionName);
ProviderSettings settings = section.Providers[section.DefaultProvider];
_provider = Activator.CreateInstance(Type.GetType(settings.Type)) as ProviderBase;
_provider.Initialize(settings.Name, settings.Parameters);
}
return _provider as ILoggerProvider;
}
}
![Page 28: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/28.jpg)
建立設定檔,設定預設provider(defaultProvider)<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="loggerProvider" type="LoggerSample.ProviderPattern.LoggerProviderSection,
LoggerSample" />
</configSections>
<loggerProvider defaultProvider="xml">
<providers>
<add name="text" type="LoggerSample.ProviderPattern.TextLoggerProvider, LoggerSample" />
<add name="xml" type="LoggerSample.ProviderPattern.XmlLoggerProvider, LoggerSample" />
</providers>
</loggerProvider>
</configuration>
PROVIDER PATTERN實作(6)
Fully qualified name Assembly name
![Page 29: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/29.jpg)
IOC PATTERN
To be continued…
![Page 30: 可抽換元件設計模式](https://reader033.vdocuments.net/reader033/viewer/2022050907/55aabde61a28ab54138b47f2/html5/thumbnails/30.jpg)
參考
Patterns of Enterprise Application Architecture
Microsoft .NET: Architecting Applications for the Enterprise
Design Patterns: Elements of Reusable Object-Oriented Software
Provider Pattern
撰寫自己的 Configuration 區段 Part 1:不要再賴在 appSettings 的屋簷下了,寫個自己的 Configuration 區段吧~
Provider Model Design Pattern and Specification, Part 1
ProviderBase Class
Designing loosely coupled components in .NET - Provider Pattern