摘要:[Architecture] Service Locator
動機
Service Locator是一個在開發系統時,很常用的一個模式。在Martin Fowler寫的Inversion of Control Containers and the Dependency Injection pattern裡,可以發現這個Pattern的身影。Service Locator最主要是定義BLL層內物件生成、物件存放、物件取得的職責,讓系統在取得物件時不需要知道物件是如何生成及存放,有效降低系統的耦合性。
同時學習Service Locator,也為架構設計帶入了空間的概念。在設計架構的時候,可以套用Service Locator來做為架構空間的封裝。將經物件生成建立的物件,「存放」在Service Locator內,讓目標架構「存在」一組物件可以提供使用。
本篇文章介紹一個Service Locator的實做,這個實做定義物件之間的職責跟互動,用來完成Service Locator應該提供的功能及職責。為自己做個紀錄,也希望能幫助到有需要的開發人員。
結構
接下來沿用[Architecture Pattern] Repository建立的UserCountService,來當作範例的內容。另外在BLL層使用Service Locator來封裝UserCountService的物件生成、物件存放、物件取得。範例的結構如下:
主要的參與者有:
ServiceLocator
-提供存放物件的功能給系統使用。
-提供存放的物件給系統使用。
Client
-使用ServiceLocator內存放的UserCountService。
UserCountService
-使用系統內User資料計算各種Count。
-由Client生成或是ServiceLocator生成。
透過下面的圖片說明,可以了解相關物件之間的互動流程。
實做
Service Locator由兩種運作邏輯所組成:定位邏輯、生成邏輯。定位邏輯是整個Service Locator的核心,它定義物件存放、物件取得的職責。而物件必須要被生成才能夠使用,生成邏輯就是定義物件生成的職責。接著透過實做一組Service Locator,解析Service Locator內的運作邏輯,幫助開發人員理解Service Locator模式。
範列下載
實做說明請參照範例程式內容:ServiceLocatorSample點此下載
定位邏輯
首先建立ServiceLocatorSample.BLL專案,並且建立ServiceLocator物件用來封裝Service Locator的定位邏輯:ServiceLocator提供SetInstance方法,讓系統可以存放各種型別的物件。並且ServiceLocator提供GetInstance方法,讓系統可以取得先前存放的物件。
public partial class ServiceLocator
{
// Fields
private readonly Dictionary<Type, object> _serviceDictionary = new Dictionary<Type, object>();
// Methods
public TService GetInstance<TService>() where TService : class
{
// Result
TService service = default(TService);
// Exist
if (_serviceDictionary.ContainsKey(typeof(TService)) == true)
{
service = _serviceDictionary[typeof(TService)] as TService;
}
// Return
return service;
}
public void SetInstance<TService>(TService service) where TService : class
{
#region Require
if (service == null) throw new ArgumentNullException();
#endregion
// Set
if (_serviceDictionary.ContainsKey(typeof(TService)) == false)
{
_serviceDictionary.Add(typeof(TService), service);
}
else
{
_serviceDictionary[typeof(TService)] = service;
}
}
}
另外ServiceLocator也套用了Singleton pattern,讓系統能方便的使用ServiceLocator。
public partial class ServiceLocator
{
// Singleton
private static ServiceLocator _current;
public static ServiceLocator Current
{
get
{
if (_current == null)
{
_current = new ServiceLocator();
}
return _current;
}
set
{
_current = value;
}
}
}
外部生成邏輯
再來建立一個Console專案,透過ServiceLocator取得UserCountService用來列印的人員數量。而UserCountService是由Console專案來生成、注入ServiceLocator。
class Program
{
static void Main(string[] args)
{
// Initialize
InitializeServiceLocator();
// UserCountService
UserCountService userCountService = ServiceLocator.Current.GetInstance<UserCountService>();
// Print
Console.WriteLine("All Count : " + userCountService.GetAllCount());
Console.WriteLine("Men Count : " + userCountService.GetMenCount());
// End
Console.ReadLine();
}
static void InitializeServiceLocator()
{
// UserRepository
IUserRepositoryProvider userRepositoryProvider = new SqlUserRepositoryProvider();
UserRepository userRepository = new UserRepository(userRepositoryProvider);
UserCountService userCountService = new UserCountService(userRepository);
// SetInstance
ServiceLocator.Current.SetInstance<UserCountService>(userCountService);
}
}
內部生成邏輯
到目前為止範例程式,已經可以透過ServiceLocator取得UserCountService用來列印的人員數量。但UserCountService是由ServiceLocator之外的函式來生成、存放至ServiceLocator。這造成每次重用的時候,必須要重新建立物件生成、存放的功能。
為了增加ServiceLocator的重用性,所以修改ServiceLocator物件,封裝Service Locator的生成邏輯:ServiceLocator提供CreateInstance方法,讓系統可以建立各種型別的物件。並且變更GetInstance的運作流程,讓系統取不到先前存放的物件時,會去使用CreateInstance來生成物件。
(因為是模擬範例,簡化了很多UserCountService的設計,並且採用直接建立的方式來示意。實際專案可以採用各種IoC Framework來做生成注入,或是套用各種Factory pattern,這些都能提高ServiceLocator的重用性。)
public partial class ServiceLocator
{
// Fields
private readonly Dictionary<Type, object> _serviceDictionary = new Dictionary<Type, object>();
// Methods
protected virtual TService CreateInstance<TService>() where TService : class
{
// Result
TService service = default(TService);
// UserCountService
if (typeof(TService) == typeof(UserCountService))
{
IUserRepositoryProvider userRepositoryProvider = new CsvUserRepositoryProvider();
UserRepository userRepository = new UserRepository(userRepositoryProvider);
UserCountService userCountService = new UserCountService(userRepository);
service = userCountService as TService;
}
// Return
return service;
}
public TService GetInstance<TService>() where TService : class
{
// Result
TService service = default(TService);
// Exist
if (_serviceDictionary.ContainsKey(typeof(TService)) == true)
{
service = _serviceDictionary[typeof(TService)] as TService;
}
if (service != null) return service;
// Create
service = this.CreateInstance<TService>();
if (service != null) this.SetInstance<TService>(service);
// Return
return service;
}
public void SetInstance<TService>(TService service) where TService : class
{
#region Require
if (service == null) throw new ArgumentNullException();
#endregion
// Set
if (_serviceDictionary.ContainsKey(typeof(TService)) == false)
{
_serviceDictionary.Add(typeof(TService), service);
}
else
{
_serviceDictionary[typeof(TService)] = service;
}
}
}
最後修改Console專案,移除專案內生成UserCountService的邏輯。並且同樣透過ServiceLocator取得UserCountService用來列印的人員數量。
class Program
{
static void Main(string[] args)
{
// UserCountService
UserCountService userCountService = ServiceLocator.Current.GetInstance<UserCountService>();
// Print
Console.WriteLine("All Count : " + userCountService.GetAllCount());
Console.WriteLine("Men Count : " + userCountService.GetMenCount());
// End
Console.ReadLine();
}
}
後記
整個Service Locator實做看來下,眼尖的開發人員會發現,它跟IoC Framework有異曲同工的意味。Service Locato跟IoC Framework的差異,主要是在:IoC Framework的主要職責是物件生成,Service Locator的主要職責是物件存放、物件取得。只是發展到了後來,兩者幾乎都實做了物件生成、物件存放、物件取得三個職責。換個方式說,大多的IoC Framework都封裝了Service Locator的職責。部分Service Locator也封裝了IoC Framework的職責。雖然說結果看起來是一樣,但兩者設計的出發點是有差異的。
而Service Locator有很多實做版本,這些實做版本依照需求分割、設計,BLL層內物件生成、物件存放、物件取得的職責,用以提高整體架構的重用性、可維護性。一個系統的成敗,除了最基本的滿足客戶需求之外,這些額外的非功能需求也是很重要的一環。這讓後續接手維護的開發人員,能夠早點回家吃晚餐。:D
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。