企业代码需要我们用以下方式重新看待代码
一、模块性
代码单元通常表现为类或类型。它们是基于特定目标而设计的。各个代码单元间的交互方式既要能达到较大的期望目标, 又不能违背对它们进行划分的准则。模块化不仅是针对可重用性的代码分离,同时也要求很强的松散耦合度。
二、松散耦合的类
如果代码单元需要使用来自系统其他部分的服务,那么这些服务应该抽象地由传递到该单元中。创建所需的依懒不应该是该单元的职责。 如果针对代码单元编写单元测试很方便,就证明其松散耦合度很低。
看下面的示例:
using System; using System.Collections.Generic; using System.Data; using System.Data.OleDb; using System.Web; namespace ProfessionalEnterprise.Net.Chapter02 { public class Program { const double basePrice = 35.00; static void Main(string[] args) { DataSet ds = new DataSet(); OleDbCommand cmd = new OleDbCommand(); string sql = "Select c.LastName,c.FristName,c.MiddleName,c.CustmerRegion,e.EmailAddress from Customers c inner join Email e on c.ID = e.CustomerID Order by c.LastName ASC"; string name = ""; double price = 0.0; OleDbConnection conn = new OleDbConnection(); OleDbDataAdapter adapter = new OleDbDataAdapter(); cmd.Connection = conn; cmd.CommandType = CommandType.Text; cmd.CommandText = sql; adapter.SelectCommand = cmd; try { adapter.Fill(ds); } catch (Exception e) { Console.WriteLine(e.Message); } foreach (DataRow dr in ds.Tables[0].Rows) { name = String.Format("{0},{1}{2}", dr["LastName"], dr["FirstName"], dr["MiddleName"]); switch (dr["CustomerRegion"].ToString()) { case "1": price = (0.9 * basePrice); break; case "2": price = (0.85 * basePrice); break; case "3": price = (0.8 * basePrice); break; } Console.WriteLine(String.Format("Customer name:{0} Customer Email Address:{1} Customer's Price:{3}", name, dr["EmailAddress"].ToString(), price)); } } } }
Program中的代码可以正常工作,但不是良好设计的代码。它不仅严重违背了模块化规则,还违背了松散耦合的基本准则。 在本例中,我们无法编写单元测试。
对于同一示例,一个好的多的版本可能要分解为不同的类型,每个类型能够符合模块化和松散耦合的规则。
首先,我们创建一个简单的数据实用工具类型:
using System; using System.Collections.Generic; using System.Data; using System.Data.OleDb; using System.Web; namespace ProfessionalEnterprise.Net.Chapter02 { /// <summary> /// 一个简单的数据实用工具类型 /// 执行简单的数据提取,并以ADO.NET数据行集的形式返回提取的数据 /// </summary> public class DataUtility { private string _connectionString; private DataSet _ds; private OleDbCommand _cmd; private OleDbConnection _conn; private OleDbDataAdapter _adapter; public DataUtility(string connectionString) { _connectionString = connectionString; } public DataRowCollection GetData(string sql) { _ds = new DataSet(); _conn = new OleDbConnection(_connectionString); _cmd = new OleDbCommand(); _cmd.Connection = _conn; _cmd.CommandType = CommandType.Text; _cmd.CommandText = sql; _adapter = new OleDbDataAdapter(_cmd); try { _adapter.Fill(_ds); } catch { //handle exception and log the event } return _ds.Tables[0].Rows; } } }
下一步,我们创建一个完成基于基本价格进行打折率计算。由于这些计算代表一组业务规则,因此,实际上不同的上下文下可能需要不同的计算类。因此真正要做的是定义一组规则,抽象地代表这些计算类。可以使用下面这样一个接口:
using System; using System.Collections.Generic; using System.Text; namespace ProfessionalEnterprise.Net.Chapter02 { /// <summary> /// 定义一组规则,抽象地代表所有计算类 /// </summary> public interface iCalcList { double this[int region] { get; } double GetPrice(int region); double BasePrice { get; set; } } }
在建立企业系统时,该抽象接口是一个重要的设计要素。抽象接口是类型解除关联的流行方法。此时,我们需要创建一个打折列表类:
using System; using System.Collections.Generic; using System.Web; namespace ProfessionalEnterprise.Net.Chapter02 { public class DiscountList:iCalcList { private Dictionary<int, double> _rates; private double _basePrice; public double BasePrice { get { return this._basePrice; } set { this._basePrice = value; } } public DiscountList() { _rates = new Dictionary<int, double>(); _rates.Add(1, 0.9); _rates.Add(2, 0.85); _rates.Add(3, 0.8); } public double this[int region] { get { return _rates[region]; } } public double GetPrice(int region) { return _rates[region] * BasePrice; } } }
我们需要建立的下一个类应该实现来自数据行的文本格式化。我们需要循环处理每个数据行,提取相关列值,根据客户所在的区域计算相应的价格,然后打印成一个文本行。这个类同样代表一个业务逻辑单元,因此,我们要先定义一个代表这个抽象类型的接口,然后实现文本格式化器。
using System; using System.Collections.Generic; using System.Text; namespace ProfessionalEnterprise.Net.Chapter02 { /// <summary> /// 数据行的文本格式化抽象逻辑 /// </summary> public interface iTextFormatter { string GetFormattedText(); } }
下面我们建立文本格式化器本身的代码:
using System; using System.Collections.Generic; using System.Data; using System.Text; using System.Web; namespace ProfessionalEnterprise.Net.Chapter02 { /// <summary> /// 文本格式化器 /// </summary> public class DataToTextFormatter:iTextFormatter { private DataRowCollection _rows; private iCalcList _rateCalculator; public DataToTextFormatter(DataRowCollection rows, iCalcList rateCalculator) { _rows = rows; _rateCalculator = rateCalculator; } private string FormatLineEntry(DataRow dr) { string name = String.Format("{0},{1}{2}", dr["LastName"], dr["FirstName"], dr["MiddleName"]); double price = _rateCalculator.GetPrice((int)(dr["CustomerRegion"])); string email = dr["EmailAddress"].ToString(); return String.Format("Customer name:{0} Customer Email Address:{1} Customer's Price:{3}", name, email, price); } public string GetFormattedText() { StringBuilder sb = new StringBuilder(); foreach (DataRow dr in _rows) { sb.Append(FormatLineEntry(dr)); } return sb.ToString(); } } }
仔细观察示例会发现,构造函数需要一个iCalcList的一个实例,那么构造函数可以很容易的接受iCalcList的子类的一个实例。
下面我们封装一个完成任务的所有单元,我们也可以在控制台应用程序的Main方法中调用它们:
using System; using System.Collections.Generic; using System.Web; namespace ProfessionalEnterprise.Net.Chapter02 { /// <summary> /// 用于执行相关动作 /// </summary> public class ListPresenter { private DataUtility _dUtil; private iCalcList _dList; private iTextFormatter _formatter; public ListPresenter(DataUtility dUtil,iCalcList dList,iTextFormatter formatter) { this._dList = dList; this._dUtil = dUtil; this._formatter = formatter; } public string GetList() { string sql = "Select c.LastName,c.FristName,c.MiddleName,c.CustmerRegion,e.EmailAddress from Customers c inner join Email e on c.ID = e.CustomerID Order by c.LastName ASC"; _dUtil = new DataUtility("读取配置文件获取数据库连接字符串"); _dList = new DiscountList(); _dList.BasePrice = 35.0; _formatter = new DataToTextFormatter(_dUtil.GetData(sql),_dList); return _formatter.GetFormattedText(); } } }
三、单元测试
单元测试,引入测试框架来构建自动测试框架。
四、控制反转(Inversion of Control,Ioc)容器,也称为依赖注入(Dependency Injection,DI)容器
用于将对象的依赖实例传递给松散耦合类。有助于保持代码的模块性,同时自动省去需要我们手动编写的逻辑。
让我们看一下两种不同的方法。
using System; using System.Collections.Generic; using System.Web; namespace ProfessionalEnterprise.Net.Chapter02.IoC { class ComplexObject { private ObjA _calssA; internal ObjA ClassA { get { return _calssA; } set { _calssA = value; } } private ObjB _calssB; internal ObjB ClassB { get { return _calssB; } set { _calssB = value; } } private ObjC _calssC; internal ObjC ClassC { get { return _calssC; } set { _calssC = value; } } public ComplexObject() { _calssA = new ObjA(); _calssB = new ObjB(); _calssC = new ObjC(); } } class ObjA { } class ObjB { } class ObjC { private ObjD _calssD; internal ObjD ClassD { get { return _calssD; } set { _calssD = value; } } public ObjC() { _calssD = new ObjD(); } } class ObjD { private int _count; public int Count { get { return _count; } set { _count = value; } } } }
最后的代码可能如下所示:
ComplexObject obj = new ComplexObject();
obj.ClassC.ClassD.Count=5;
这段代码简单优美,但是违背了模块化和松散耦合的准则,使得我们不能对每个类进行测试。
对于同样的代码,一个更清晰的版本可能如下:
using System; using System.Collections.Generic; using System.Web; namespace ProfessionalEnterprise.Net.Chapter02.IoC { class ComplexObject { private ObjA _calssA; internal ObjA ClassA { get { return _calssA; } set { _calssA = value; } } private ObjB _calssB; internal ObjB ClassB { get { return _calssB; } set { _calssB = value; } } private ObjC _calssC; internal ObjC ClassC { get { return _calssC; } set { _calssC = value; } } public ComplexObject(ObjA a,ObjB b,ObjC c) { _calssA = a; _calssB = b; _calssC = c; } } class ObjA { } class ObjB { } class ObjC { private ObjD _calssD; internal ObjD ClassD { get { return _calssD; } set { _calssD = value; } } public ObjC(ObjD d) { _calssD = d; } } class ObjD { private int _count; public int Count { get { return _count; } set { _count = value; } } } }
使用时代码如下:
ObjA A = new ObjA(); ObjB B = new ObjB(); ObjD D = new ObjD(); ObjC C = new ObjC(D); ComplexObject obj = new ComplexObject(A,B,C);
在使用其他对象的类中,需要多的多的代码,而这些代码只是为了创建ComplexObject的一个实例。这正说明了松散耦合设计的好处和它的不足。松散耦合和快速开发之间的折中可以通过使用控制倒置容器来实现。Ioc容器为开发人员提供了一组在编译时对对象编译描述的全局定义。这种容器可以自动感知定义文件中的所有对象和对象依赖关系。当一个使用其他对象的方法或类需要创建复杂对象的时,这些方法和类只需要向IoC容器请求这些对象的实例,请求过程就是调用容器的get()方法,而不是常见的new操作符。下面是针对ComplexObject的IoC容器对象定义的一个示例,该示例来自于流行的Spring.NET IoC容器:
<configuration> <configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.ContextHandler,Spring.Core"/> <section name="objects" type="Spring.Context.Support.DefaultSectionHandler,Spring.Core"/> </sectionGroup> </configSections> <spring> <context> <resource uri="config://spring/objects"/> </context> <objects xmlns="http://www.springframework.net"> </objects> </spring> </configuration>
一旦在容器中定义了对象,就可以通过容器引用来对该对象进行初始化:
IApplicationContext Context = ContextRegistry.GetContext(); ComplexObject obj = (ComplexObject)Context.GetObject("etlPipelineEngine");
IApplicationContext是ComplexObject对象使用上下文的一种Spring.NET定义。一旦拥有了一个IApplicationContext的句柄,就可以简单的使用IApplicationContext的各种静态Get()方法通过名称来调用对象。IoC容器的好处不仅仅是对象实例化简单,还有助于开发使用对象的代码,为对象模型创建清晰的路线图,甚至提供各种层面的面向对象编程。