松 紧 耦合, 依赖注入(DI)
在介绍依赖注入(Dependency Injection)之前先介绍下松,紧耦合的概念。 提到松紧耦合就不得不提接口(Interface),我们直接上demo.
先来看下紧耦合的例子:
class Program { static void Main(string[] args) { var engine = new Engine(); var car = new Car(engine); car.Run(3); Console.WriteLine(car.Speed); } } public class Engine { public int RPM { get; private set; } public void work(int gas) { this.RPM = 1000 * gas; } } class Car { private Engine _engine; public Car(Engine engine) { this._engine = engine; } public int Speed { get; private set; } public void Run(int gas) { _engine.work(gas); this.Speed = _engine.RPM / 100; } }
一旦Engine中work发生改变,依赖它的Car也需要更改。项目小还容易修改,要是项目复杂,就很难定位了。
我们再来看下松耦合的例子,提到松耦合,一般都会借助接口来达到所要的效果,接口定义了一组契约,继承调用它的类都会受到这组锲约的约束,调用者不用关心这组功能会由谁提供(实现):
interface IPhone { void Dail(); void PickUp(); void Send(); void Receive(); } class ApplePhone:IPhone { public void Dail() { Console.WriteLine("Buy?"); } public void PickUp() { Console.WriteLine("Buy??"); } public void Send() { Console.WriteLine("Buy???"); } public void Receive() { Console.WriteLine("Buy????"); } } class XiaoMiPhone:IPhone { public void Dail() { Console.WriteLine("Are U Ok?"); } public void PickUp() { Console.WriteLine("Hello?"); } public void Send() { Console.WriteLine("Think You!"); } public void Receive() { Console.WriteLine("Think You Very Much!"); } } class User { private IPhone _phone; public User(IPhone phone) { this._phone = phone; } public void UsePhone() { _phone.Dail(); _phone.Dail(); _phone.Dail(); } } static void Main(string[] args) { var user = new User(new ApplePhone()); var user2 = new User(new XiaoMiPhone()); user.UsePhone(); user2.UsePhone(); }
通过继承手机的统一接口规范我们可以编写多个手机型号,这样用户就可以轻松实现换手机了,这个就是利用接口来实现松耦合。
接着我们一起来看下依赖注入,依赖注入分为以下三种类型:
结构注入(Structure Injection):
属性注入(Property Injection):
方法注入(Method Injection):
(1)先看结构注入的例子,我们创建3个class.
public class Employee { public int ID { get; set; } public string Name { get; set; } public string Department { get; set; } } public class EmployeeDAL { public List<Employee> SelectAllEmployees() { List<Employee> ListEmployees = new List<Employee>(); //Get the Employees from the Database //for now we are hard coded the employees ListEmployees.Add(new Employee() { ID = 1, Name = "Pranaya", Department = "IT" }); ListEmployees.Add(new Employee() { ID = 2, Name = "Kumar", Department = "HR" }); ListEmployees.Add(new Employee() { ID = 3, Name = "Rout", Department = "Payroll" }); return ListEmployees; } } public class EmployeeBL { public EmployeeDAL employeeDAL; public List<Employee> GetAllEmployees() { employeeDAL = new EmployeeDAL(); return employeeDAL.SelectAllEmployees(); } }
EmployeeBL依赖于EmployeeDAL, 一旦EmployeeDAL中GetALLEmployees发生变化,EmployeeBL就需要跟着改变。
现在我们来更改下EmployeeDAL的结构:
public interface IEmployeeDAL { List<Employee> SelectAllEmployees(); } public class EmployeeDAL : IEmployeeDAL { public List<Employee> SelectAllEmployees() { List<Employee> ListEmployees = new List<Employee>(); //Get the Employees from the Database //for now we are hard coded the employees ListEmployees.Add(new Employee() { ID = 1, Name = "Pranaya", Department = "IT" }); ListEmployees.Add(new Employee() { ID = 2, Name = "Kumar", Department = "HR" }); ListEmployees.Add(new Employee() { ID = 3, Name = "Rout", Department = "Payroll" }); return ListEmployees; } }
声明IEmployeeDAL接口,然后申明我们要用的方法。通过EmployeeDAL去实现。
然后我们来修改下EmployeeBL:
public class EmployeeBL { public IEmployeeDAL employeeDAL; public EmployeeBL(IEmployeeDAL employeeDAL) { this.employeeDAL = employeeDAL; } public List<Employee> GetAllEmployees() { Return employeeDAL.SelectAllEmployees(); } }
在EmployeeBL中申明一个构造函数,函数中传入接口作为参数,这样我们就注入了我们依赖的对象到构造函数中,也称结构依赖注入。
static void Main(string[] args) { EmployeeBL employeeBL = new EmployeeBL(new EmployeeDAL()); List<Employee> ListEmployee = employeeBL.GetAllEmployees(); foreach(Employee emp in ListEmployee) { Console.WriteLine("ID = {0}, Name = {1}, Department = {2}", emp.ID, emp.Name, emp.Department); } Console.ReadKey(); }
这里我们不需要创建EmployeeDAL,而是将它作为参数传递给EmployeeBL. 现在这个参数可以接受任何实现了这个接口的实体类。
(2)看下属性依赖注入:
我们更改EmployeeBL如下:
public class EmployeeBL { private IEmployeeDAL employeeDAL; public IEmployeeDAL employeeDataObject { set { this.employeeDAL = value; } get { if (employeeDataObject == null) { throw new Exception("Employee is not initialized"); } else { return employeeDAL; } } } public List<Employee> GetAllEmployees() { return employeeDAL.SelectAllEmployees(); } }
可以看到我们注册依赖对象EmployeeDAL是通过EmployeeBL的公共属性employeeDataObject注入. 看Main中如何调用:
static void Main(string[] args) { EmployeeBL employeeBL = new EmployeeBL(); employeeBL.employeeDataObject = new EmployeeDAL(); List<Employee> ListEmployee = employeeBL.GetAllEmployees(); foreach(Employee emp in ListEmployee) { Console.WriteLine("ID = {0}, Name = {1}, Department = {2}", emp.ID, emp.Name, emp.Department); } Console.ReadKey(); }
结构依赖注入是我们用得最多也是标准的依赖注入,有些特殊情况我们会选择用属性依赖注入. 比如:我们有一个类里面含有很多方法,但是这些方法不依赖其他的object. 这是需要加入一个新的方法,这个方法会依赖另外的对象,这个时候如果用Structure Injection就需要改变之前无依赖的method的调用写法,如果class很大就会很耗费时间,这个时候需要用属性依赖注入就会比较适用。
(3)看下方法依赖注入:
修改EmployeeBL的Code如下:
public class EmployeeBL { public IEmployeeDAL employeeDAL; public List<Employee> GetAllEmployees(IEmployeeDAL _employeeDAL) { employeeDAL = _employeeDAL; return employeeDAL.SelectAllEmployees(); } }
然后修改我们Main函数中引用的Code:
static void Main(string[] args) { //Create object of EmployeeBL class EmployeeBL employeeBL = new EmployeeBL(); //Call to GetAllEmployees method with proper object. List<Employee> ListEmployee = employeeBL.GetAllEmployees(new EmployeeDAL()); foreach (Employee emp in ListEmployee) { Console.WriteLine("ID = {0}, Name = {1}, Department = {2}", emp.ID, emp.Name, emp.Department); } Console.ReadKey(); }
可以看到直接将我们要依赖注入的对象当作参数放在了我们要调用的方法里面。一般也只有单个method依赖于依赖对象的时候我们才会用方法依赖注入这种形式。