diff Implab/Components/ServiceLocator.cs @ 152:240aa6994018 v2

component model refactoring
author cin
date Thu, 11 Feb 2016 01:56:27 +0300
parents
children bdfdba6b645b
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab/Components/ServiceLocator.cs	Thu Feb 11 01:56:27 2016 +0300
@@ -0,0 +1,248 @@
+using System;
+using System.Collections.Generic;
+
+namespace Implab.Components {
+    /// <summary>
+    /// Коллекция сервисов, позволяет регистрировать и получать сервисы.
+    /// </summary>
+    public class ServiceLocator: Disposable, IServiceLocator, IServiceProvider {
+        // запись о сервисе
+        struct ServiceEntry : IDisposable {
+            public object service; // сервис
+            public bool shared; // признак того, что сервис НЕ нужно освобождать
+            public Func<object> activator; // активатор сервиса при первом обращении
+            public Action<object> cleanup; // функция для очистки сервиса
+            public List<Type> associated; // ссылки на текущую запись
+            public Type origin; // ссылка на оригинальную запись о сервисе
+
+            #region IDisposable implementation
+
+            public void Dispose() {
+                if (shared)
+                    return;
+                if (cleanup != null) {
+                    if (service != null)
+                        cleanup(service);
+                } else
+                    Safe.Dispose(service);
+            }
+
+            #endregion
+        }
+
+        // словарь существующих сервисов
+        readonly Dictionary<Type, ServiceEntry> m_services = new Dictionary<Type,ServiceEntry>();
+
+        /// <summary>
+        /// Получает объект предоставляющий сервис <typeparamref name="T"/>.
+        /// </summary>
+        /// <typeparam name="T">Тип запрашиваемого сервиса</typeparam>
+        /// <returns>Объект, реализующий сервис</returns>
+        /// <exception cref="KeyNotFoundException">Сервис не зарегистрирован</exception>
+        public T GetService<T>() {
+            object result;
+            if (TryGetService(typeof(T), out result))
+                return (T)result;
+            throw new ApplicationException (String.Format ("{0} doesn't provide {1} service", this, typeof(T)));
+        }
+
+
+        /// <summary>
+        /// Пытается получить указанный сервис, в случае, если компонента не предоставляет требуемый сервис
+        /// не возникает исключений.
+        /// </summary>
+        /// <typeparam name="T">Тип требуемого сервиса.</typeparam>
+        /// <param name="service">Объект реализующий сервис, или <c>default(T)</c> если такового нет.</param>
+        /// <returns><c>true</c> - сервис найден, <c>false</c> - сервис не зарегистрирован.</returns>
+        public bool TryGetService<T>(out T service) {
+            object result;
+            if (TryGetService(typeof(T), out result)) {
+                service = (T)result;
+                return true;
+            }
+            service = default(T);
+            return false;
+        }
+
+        /// <summary>
+        /// Получает объект предоставляющий сервис <paramref name="serviceType"/>
+        /// </summary>
+        /// <param name="serviceType">Тип запрашиваемого сервиса</param>
+        /// <returns>Объект, реализующий сервис</returns>
+        /// <exception cref="KeyNotFoundException">Сервис не зарегистрирован</exception>
+		public object GetService(Type serviceType) {
+            object result;
+            if (TryGetService(serviceType, out result))
+                return result;
+			throw new ApplicationException (String.Format ("{0} doesn't provide {1} service", this, serviceType));
+		}
+
+		/// <summary>
+		/// Пытается получить требуемый сервис или совместимый с ним.
+		/// </summary>
+		/// <returns><c>true</c>, если сервис был найден, <c>false</c> в противном случае..</returns>
+		/// <param name="serviceType">Тип запрашиваемого сервиса.</param>
+		/// <param name="service">Искомый сервис.</param>
+		public virtual bool TryGetService(Type serviceType, out object service) {
+            Safe.ArgumentNotNull(serviceType, "serviceType");
+            AssertNotDisposed();
+
+            ServiceEntry se;
+            if (!m_services.TryGetValue(serviceType, out se)) {
+                // ищем ближайщий объект, реализующий нужный сервис
+                Type pt = null;
+                foreach (var t in m_services.Keys)
+                    if (serviceType.IsAssignableFrom(t) && (pt == null || t.IsAssignableFrom(pt)))
+                        pt = t;
+
+				if (pt == null) {
+					// нет нужного сервиса
+					service = null;
+					return false;
+				}
+
+                var pe = m_services[pt];
+
+                // найденная запись может ссылаться на оригинальную запись с сервисом
+                if(pe.origin != null) {
+                    pt = pe.origin;
+                    pe = m_services[pt];
+                }
+                    
+                // добавляем список с обратными ссылками
+                if (pe.associated == null)
+                    pe.associated = new List<Type>();
+
+                pe.associated.Add(serviceType);
+
+                // обновляем родительскую запись
+                m_services[pt] = pe;
+
+                // создаем запись со ссылкой
+                se = new ServiceEntry {
+                    service = pe.service,
+                    origin = pt,
+                    shared = true // предотвращаем множественные попытки освобождения
+                };
+
+                m_services[serviceType] = se;
+            }
+
+            // запись содержит в себе информацию о сервисе
+			if (se.service != null) {
+				service = se.service;
+				return true;
+			}
+
+            // текущая запись является ссылкой
+            if (se.origin != null) {
+                se.service = GetService(se.origin);
+                m_services[serviceType] = se;
+				service = se.service;
+				return true;
+            }
+
+            // текущая запись не является ссылкой и не имеет информации о сервисе
+            // она должна сожержать информацию об активации
+            if (se.activator != null) {
+                se.service = se.activator();
+
+                m_services[serviceType] = se;
+
+				service = se.service;
+				return true;
+            }
+
+			service = null;
+			return false;
+        }
+
+        /// <summary>
+        /// Регистрирует фабрику для активации сервиса по первому требованию.
+        /// </summary>
+        /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam>
+        /// <param name="activator">Фабрика для создания/получения объекта, предоставляющего сервис.</param>
+        /// <param name = "cleanup">Метод для освобождения экземпляра сервиса, будет вызыван при освобождении сервис-локатора.</param>
+        /// <remarks>При освобождении сервис-локатора, сервисы полученные в результате активации также будут освобождены.</remarks>
+        public void Register<T>(Func<T> activator, Action<T> cleanup) {
+            Safe.ArgumentNotNull(activator, "activator");
+
+            AssertNotDisposed();
+
+            Unregister(typeof(T));
+            
+            var serviceEntry = new ServiceEntry();
+            serviceEntry.activator = () => activator();
+            if (cleanup != null)
+                serviceEntry.cleanup = instance => cleanup((T)instance);
+            m_services[typeof(T)] = serviceEntry;
+        }
+
+        public void Register<T>(Func<T> activator) {
+            Register(activator, null);
+        }
+
+        /// <summary>
+        /// Регистрирует объект, предоставляющий сервис.
+        /// </summary>
+        /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam>
+        /// <param name="service">Объект, предоставляющий сервис.</param>
+        /// <exception cref="InvalidOperationException">Указанный сервис уже зарегистрирован.</exception>
+        /// <remarks>Сервис-локатором не управляет временем жизни объекта для зарегистрированного сервиса.</remarks>
+        public void Register<T>(T service) {
+            Register(service, true);
+        }
+
+        /// <summary>
+        /// Регистрирует объект, предоставляющий сервис. Повторная регистрация отменяет уже существующую.
+        /// </summary>
+        /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam>
+        /// <param name="service">Объект, предоставляющий сервис.</param>
+        /// <param name="shared">Признак того, что объект является разделяемым и сервис-локатор не должен его освобождать.</param>
+        public void Register<T>(T service, bool shared) {
+            Safe.ArgumentNotNull(service, "service");
+
+            AssertNotDisposed();
+
+            Unregister(typeof(T));
+            
+            m_services[typeof(T)] = new ServiceEntry { service = service, shared = shared };
+        }
+
+        public void Unregister(Type serviceType) {
+            Safe.ArgumentNotNull(serviceType, "serviceType");
+
+            AssertNotDisposed();
+            
+            ServiceEntry se;
+            if (m_services.TryGetValue(serviceType, out se)) {
+                if (se.origin != null) {
+                    var pe = m_services[se.origin];
+                    pe.associated.Remove(serviceType);
+                }
+                // освобождаем ресурсы
+                se.Dispose();
+                m_services.Remove(serviceType);
+
+                // убираем связанные записи
+                if (se.associated != null)
+                    foreach (var item in se.associated)
+                        m_services.Remove(item);
+            }
+        }
+
+        /// <summary>
+        /// Освобождает зарегистрированные сервисы (которые требуется освобоить).
+        /// </summary>
+        /// <param name="disposing">Призанак того, что нужно освободить ресурсы.</param>
+        protected override void Dispose(bool disposing) {
+            if (disposing) {
+
+                foreach (var entry in m_services.Values)
+                    entry.Dispose();
+
+            }
+            base.Dispose(disposing);
+        }
+    }
+}
\ No newline at end of file