在程序设计的过程中,我们经常用到面向对象设计原理:
1. 单一责任原则: 一个【类,方法】只做一件事情。
2. 开放/封闭原则: 添加任何新行为,应该是扩展到新类中,而不应该直接修改原来运行良好的代码。
3. 李式替代原则: 类型T的对象可以用其子类型的对象替换,程序可以正常运行。
4. 接口隔离原则: 非常大的接口应该分成更小和更具体的接口,以便客户端类只需要知道它们使用的方法:不应该强制客户端类依赖于它不使用的方法。
5. 依赖倒置原则: 高级模块不应该依赖于低级模块。两者都应该取决于抽象。抽象不应该依赖于细节。细节应取决于抽象。
对于大型复杂的系统,我们早期进行有效的设计是非常有必要的,可以增加程序的:可维护性,可测性,灵活性和可扩展性,并行开发,低耦合等。这篇文章的主题是低耦合编程。低耦合的主要表现是程序依赖接口编程,但不依赖接口的具体实现。通常,应用程序越大越复杂,维护起来就越困难,因此这些技术越有可能发挥作用。我们先看一下耦合编程的示例:
public class TenantStore : ITenantStore
{
public Tenant GetTenant(string tenant)
{
...
}
}
public class ManagementController : Controller
{
private ITenantStore _tenantStore;
public ManagementController()
{
_tenantStore = new TenantStore();
}
}
这个代码的缺点有:
1. 所有使用TenantStore类的客户端类都需要负责实例化TenantStore对象。TenantStore类中的特定构造函数会和所有的客户端代码紧密绑定在一起,可能会导致更改TenantStore类的实现,这会让TenantStore类的维护更复杂,更容易出错,并且更耗时。
2. 对ManagementController类运行单元测试,您需要实例化TenantStore对象,测试过程变得更加复杂和脆弱。
我们可以用依赖注入来改良这段代码,让它变得低耦合,代码示例:
public class ManagementController : Controller
{
private ITenantStore tenantStore;
public ManagementController(ITenantStore tenantStore)
{
this.tenantStore = tenantStore;
}
}
这个代码的优点:
1. ManagementController类不再负责实例化TenantStore对象。
2. 现在还清楚了控制器对其构造函数参数的依赖性,而不是隐藏在控制器方法实现中。
3. 更加容易测试客户端类(如ManagementController类)的某些行为,您现在可以提供ITenantStore接口的轻量级实现,该实现返回一些示例数据。
4. 我们删除了类之间的直接依赖关系,降低了耦合级别,并有助于提高解决方案的可维护性,可测试性,灵活性和可扩展性。
我们看到了低耦合的好处,但是如果某个类没有直接实例化其所需的其他对象,则其他一些类或组件必须承担此责任。在介绍依赖注入模式之前,我们先看一下低耦合在早期的一个实现方法:工厂模式。代码示例:
public class ManagementController : Controller
{
private ITenantStore tenantStore;
public ManagementController()
{
var tenantStoreFactory = new TenantStoreFactory();
this.tenantStore = tenantStoreFactory.CreateTenantStore();
}
}
简单工厂模式删除了ManagementController类对特定商店实现的直接依赖性,控制器类现在依赖于TenantStoreFactory类来代表它创建实例。但是它有一个特征:客户端通过委托第三方组件来实例化想要的对象,仍然是高级模块在依赖低级模块的表现。只是并不是直接依赖,高级模块直接依赖负责创建对象的工厂。这也意味着高级客户端类的依赖关系隐藏在那些类中,而是在单个位置指定,这使得它们更难以测试。我们称客户端的这种行为是pull模型。下图显示了简单工厂模式中的依赖关系,其中工厂代表ManagementController类实例化TenantStore对象:
依赖注入采用相反的方法,采用push模型代替pull模型。控制反转是一个经常用来描述这种push模型的术语,依赖注入是控制反转的一种特定实现。通过依赖注入,另一个类负责在运行时将依赖项注入(推送)到高级客户端类,例如:
public class ManagementController : Controller
{
private ITenantStore tenantStore;
public ManagementController(ITenantStore tenantStore)
{
this.tenantStore = tenantStore;
}
}
正如您在此示例中所看到的,ManagementController构造函数接收ITenantStore实例作为参数,由其他类注入。ManagementContoller类中唯一的依赖项是接口类型。这样做更好,因为它不了解负责实例化ITenantStore对象的类或组件。在下图中,负责实例化TenantStore对象并将其插入ManagementController类的类称为DependencyInjectionContainer类。
图1和图2之间的关键区别在于ManagementController类的依赖关系的方向。在图2中,ManagementController类唯一的依赖是ITenantStore接口。
ManagementController类定义的构造函数,该构造函数需要注入一个ITenantStore类型的对象,应用程序必须在运行时知道它应该实例化ITenantStore接口的哪个实现,然后才能继续实例化ManagementController对象。这里发生了两件事:
1. 如何实例化实现ITenantStore接口的对象。
2. 应用程序中的某些东西实例化该对象和ManagementController对象。
我们称第一项叫Regist,第二项叫Resolve。现在我们使用Unity来实现我们依赖注入,Unity支持编码和配置两种方式Regist。
配置的Regist,作为复杂的应用并不推荐使用,因为最后会形成巨大而复杂的配置文件,及其容易出错。通过编码Regist:
var container = new UnityContainer();
container.RegisterType<ITenantStore, TenantStore>();
这个Regist一个类型的api,但是每次创建一个新的接口都需要手动Regist映射关系也很不方便,所以我们更好的做法是自动注册。它可以最大限度地减少您需要编写的类型注册的数量。您可以指示Unity容器扫描一组程序集,然后根据一组规则自动注册多个映射,而不是单独指定每个类型映射。如果你有很多类型要注册,它将为你节省大量的工作。
创建IOCAutowire的独立类库:
依赖的包:Unity
public class AutowireInfo : IComparable<AutowireInfo>
{
public AutowireInfo(Type from, Type to)
{
this.From = from;
this.To = to;
this.Lifetime = AutowireAttribute.DEFAULT_LIFETIME_SCOPE;
this.Priority = AutowireAttribute.DEFAULT_PRIORITY;
}
public Type From { get; set; }
public Type To { get; set; }
public string Name { get; set; }
public Lifetime Lifetime { get; set; }
public byte Priority { get; set; }
public int CompareTo(AutowireInfo other)
{
return this.Priority.CompareTo(other.Priority);
}
public override string ToString()
{
return string.Format("{0} > {1}, Prio={2}, Lifetime={3}", From.ToString(), To.ToString(), Priority, Lifetime.ToString());
}
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class AutowireAttribute : Attribute
{
public const Lifetime DEFAULT_LIFETIME_SCOPE = Lifetime.Singleton;
public const byte DEFAULT_PRIORITY = 100;
public AutowireAttribute(Type from)
{
if (from == null)
{
throw new ArgumentNullException("from");
}
this.From = from;
this.Lifetime = DEFAULT_LIFETIME_SCOPE;
this.Priority = DEFAULT_PRIORITY;
}
public Type From { get; private set; }
public string Name { get; set; }
public Lifetime Lifetime { get; set; }
public byte Priority { get; set; }
}
public class AutowireRegister
{
IUnityContainer _container;
public AutowireRegister(IUnityContainer container)
{
_container = container;
}
public void AutoRegist()
{
var assemblies = GetAssemblies();
this.AutowireTypes(assemblies);
}
private void AutowireTypes(params Assembly[] assemblies)
{
if (assemblies == null || assemblies.Length == 0)
{
return;
}
var autowireList = AutowireHelpers.GetAutowireInfo(assemblies);
this.AutowireTypes(autowireList);
}
private Assembly[] GetAssemblies()
{
var results = new List<Assembly>();
// AppDomain.CurrentDomain.GetAssemblies() does not work here because some DLLs might not be loaded into
// the current app domain yet, so enumerate all the DLLs in current app folder
var assemblyFolder = AppDomain.CurrentDomain.RelativeSearchPath;
if (string.IsNullOrWhiteSpace(assemblyFolder))
{
assemblyFolder = AppDomain.CurrentDomain.BaseDirectory;
}
var dllFiles = Directory.GetFiles(assemblyFolder, "*.dll").ToList();
var exeFiles= Directory.GetFiles(assemblyFolder, "*.exe").ToList();
dllFiles.AddRange(exeFiles);
foreach (var file in dllFiles)
{
try
{
var assembly = Assembly.Load(Path.GetFileNameWithoutExtension(file));
if (!IsSystemAssembly(assembly))
{
results.Add(assembly);
}
}
catch
{
// Squash any exceptions
}
}
return results.ToArray();
}
private bool IsSystemAssembly(Assembly assembly)
{
if (assembly == null
|| assembly.FullName.StartsWith("mscorlib.")
|| assembly.FullName.StartsWith("System.")
|| assembly.FullName.StartsWith("Microsoft."))
{
return true;
}
return false;
}
private void AutowireTypes(IList<AutowireInfo> autowireList)
{
if (autowireList == null || autowireList.Count == 0)
{
return;
}
// Order the registrations descending to allow a lower priority number (50) to overwrite a higher priority number (100)
var ordered = autowireList.OrderByDescending(a => a.Priority);
foreach (var info in ordered)
{
this._container.RegisterType(
info.From,
info.To,
info.Name,
CreateLifetimeManager(info.Lifetime)
);
}
}
private ITypeLifetimeManager CreateLifetimeManager(Lifetime lifetime)
{
switch (lifetime)
{
case Lifetime.Transient:
return this._container.Resolve<TransientLifetimeManager>();
case Lifetime.PerThread:
return this._container.Resolve<PerThreadLifetimeManager>();
default:
// Singleton and default
return this._container.Resolve<ContainerControlledLifetimeManager>();
}
}
}
public enum Lifetime
{
Transient = 0,
Singleton = 1,
PerThread = 2
}
public static class AutowireHelpers
{
public static IList<AutowireInfo> GetAutowireInfo(params Assembly[] assemblies)
{
var results = new List<AutowireInfo>();
if (assemblies != null)
{
foreach (var a in assemblies)
{
GetAutowireInfo(a, results);
}
}
return results;
}
private static void GetAutowireInfo(Assembly assembly, IList<AutowireInfo> results)
{
try
{
var attrType = typeof(AutowireAttribute);
foreach (var type in assembly.GetTypes())
{
var attrs = type.GetCustomAttributes(attrType, inherit: false);
if (attrs.Length > 0)
{
foreach (AutowireAttribute attr in attrs)
{
results.Add(new AutowireInfo(attr.From, type)
{
Name = attr.Name,
Lifetime = attr.Lifetime,
Priority = attr.Priority
});
}
}
}
}
catch (ReflectionTypeLoadException ex)
{
// Ignore exceptions if assembly types could not be loaded
}
}
}
public static class UnityContainerFactory
{
public static IUnityContainer CreateContainer()
{
UnityContainer container = new UnityContainer();
var autowireRegister = container.Resolve<AutowireRegister>();
autowireRegister.AutoRegist();
return container;
}
}
创建引用IOCAutowire类库的示例代码:
public interface ILogger
{
void Write(string message);
}
public interface ITest
{
}
public interface ITest2
{
}
[Autowire(typeof(ILogger), Name = "Text")]
public class TextLogger : ILogger
{
public void Write(string message)
{
Console.WriteLine("Text Logger:" + message);
}
}
[Autowire(typeof(ILogger), Name = "Data", Lifetime = Lifetime.Transient)]
public class DataLogger : ILogger
{
ITest _test;
string _name;
public DataLogger(ITest test, string name = "123")
{
_test = test;
_name = name;
}
public void Write(string message)
{
Console.WriteLine("Data Logger, name is " + _name + "; message is " + message);
}
}
[Autowire(typeof(ITest))]
public class Test : ITest
{
public Test(ITest2 test2)
{
}
}
[Autowire(typeof(ITest2))]
public class Test2 : ITest2
{
}
在Console应用中的使用情况:
static void Main(string[] args)
{
var container = UnityContainerFactory.CreateContainer();
ITest test = container.Resolve<ITest>();
ILogger logger = container.Resolve<ILogger>("Text");
logger.Write("hello Unity");
logger = container.Resolve<ILogger>("Data");
logger.Write("hello Unity");
logger = container.Resolve<ILogger>("Data", new ParameterOverride("name", "456"));
logger.Write("hello Unity");
//查看容器中所有的类型注册信息
foreach (IContainerRegistration item in container.Registrations)
{
Console.WriteLine(item.RegisteredType.ToString()
+ ":" + item.MappedToType.ToString());
}
Console.ReadKey();
}
在MVC应用中的使用情况:
依赖的包:Unity,Unity.Mvc
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
DependencyResolver.SetResolver(new UnityDependencyResolver(UnityConfig.Container));
}
protected void Application_End()
{
UnityConfig.Container.Dispose();
}
}
public static class UnityConfig
{
private static Lazy<IUnityContainer> container =
new Lazy<IUnityContainer>(() =>
{
var container = UnityContainerFactory.CreateContainer();
return container;
});
public static IUnityContainer Container => container.Value;
}
public class HomeController : Controller
{
private ITest _test;
public HomeController(ITest test)
{
_test = test;
}
}
在WebApi应用中的使用情况:
依赖的包:Unity,Unity.AspNet.WebApi
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
}
public static class UnityConfig
{
private static Lazy<IUnityContainer> container =
new Lazy<IUnityContainer>(() =>
{
var container = UnityContainerFactory.CreateContainer();
return container;
});
public static IUnityContainer Container => container.Value;
}
public static class UnityWebApiActivator
{
public static void Start()
{
var resolver = new UnityDependencyResolver(UnityConfig.Container);
GlobalConfiguration.Configuration.DependencyResolver = resolver;
}
public static void Shutdown()
{
UnityConfig.Container.Dispose();
}
}