上一篇博客中《兩天完成一個小型工程報價系統》,許多朋友向我討源碼。其實我之后沒發了,確實那種三層架構沒什么意思,只是我個人的孤芳自賞,很多的處理都不是很成熟。所以我重新架構了一番,以一個小例子來闡述我的架構模式,當然,這和企業級的架構還是差距很大,不過,還是值得一讀。不積硅步,無以至江海,只有從小細節慢慢領悟,步步為營,才能設計出優秀的企業級架構。
重構后的架構圖:
解決方案里項目設計:
數據庫訪問層接口設計:IBaseDao
- Repository模式
這兒使用了Repository模式,抽象出一個公共的接口IBaseDao,IBaseDao接口中定義了一系列契約(CRUD ),不管我的我數據訪問用了那種技術,只要我實現了IBaseDao接口,則必須要實現接口定義的契約,所以接口的穩定至關重要,當然這樣做的好處是顯而易見的,減少了冗余代碼。
public interface IBaseDao<T> where T:class { IList<T> GetAllEntities(); bool SaveEntity(T entity); bool UpdateEntity(T entity); bool DeleteEntity(T entity); bool DeleteEntityByID(object id); T GetEntityByID(object id); IList<T> GetPageEntities(int pageIndex, int PageSize); }
- 首先ICustomerDao繼承了IBaseDao接口:
public interface ICustomerDao<Customer>:IBaseDao
public interface ICustomerDao:IBaseDao<Customer>
public interface ICustomerDao:IBaseDao<Customer> { }
- OracleDao實現ICustomerDao接口:
public class CustomerOracleDao:IDao.ICustomerDao { public IList<Model.Customer> GetAllEntities() { return GetAllEntitiesBySqlWhere(""); } public IList<Model.Customer> GetAllEntitiesBySqlWhere(string sqlWhere) { string sql = string.Format("select * from Customer Where 1=1 {0}",sqlWhere); List<Customer> listCustomers = new List<Customer>(); using (OracleDataReader odr = OracleHelper.ExecuteReader(OracleHelper.ConnectionString, System.Data.CommandType.Text, sql, null)) { while (odr.Read()) { Model.Customer customer = new Customer(); customer.ID = odr.GetInt32(0); customer.Name = odr.IsDBNull(1) ? "" : odr.GetString(1); customer.Phone = odr.IsDBNull(2) ? "" : odr.GetString(2); customer.Remark = odr.IsDBNull(3) ? "" : odr.GetString(3); listCustomers.Add(customer); } } return listCustomers; } private int GetNewID() { string sql = "select s_customer.nextval from dual"; return int.Parse(OracleHelper.ExecuteScalar(OracleHelper.ConnectionString, CommandType.Text, sql, null).ToString()); } public bool SaveEntity(Model.Customer entity) { entity.ID = GetNewID(); bool resultValue = false; string sql = string.Format(@"insert into Customer(ID,Name,Phone,Remark) values({0},'{1}','{2}','{3}')",entity.ID,entity.Name,entity.Phone,entity.Remark ); if (OracleHelper.ExecuteNonQuery(OracleHelper.ConnectionString, CommandType.Text, sql, null) > 0) { resultValue = true; } return resultValue; } public bool UpdateEntity(Model.Customer entity) { string sql = string.Format("update Customer set Name='{0}',Phone='{1}',Remark='{2}' where ID={3}",entity.Name,entity.Phone,entity.Remark,entity.ID); return OracleHelper.ExecuteNonQuery(OracleHelper.ConnectionString, CommandType.Text, sql, null) > 0; } public bool DeleteEntity(Model.Customer entity) { return DeleteEntityByID(entity.ID); } public bool DeleteEntityByID(object id) { string sql = string.Format("delete from Customer where ID={0} ",id); return OracleHelper.ExecuteNonQuery(OracleHelper.ConnectionString, CommandType.Text, sql, null) > 0; } public Model.Customer GetEntityByID(object id) { string sqlWhere = string.Format(" and id={0}", id); int CID = (int)id; List<Model.Customer> list = GetAllEntitiesBySqlWhere(sqlWhere) as List<Customer>; return list.SingleOrDefault(c => c.ID == CID); } public IList<Model.Customer> GetPageEntities(int pageIndex, int PageSize) { throw new NotImplementedException(); } }
- EFDao實現ICustomerDao接口:
//用EF來實現數據訪問層接口 public class CustomerEFDao:ICustomerDao { //上下文網關 private Hotel.Model.HotelContainer hotelDB = new Model.HotelContainer(); /// <summary> /// 獲取全部用戶信息 /// </summary> /// <returns></returns> public IList<Insigma.Hotel.Model.Customer> GetAllEntities() { return hotelDB.Customer.ToList<Customer>(); } public bool SaveEntity(Insigma.Hotel.Model.Customer entity) { hotelDB.Customer.AddObject(entity); bool returnValue = false; //返回受影響的行數 if (hotelDB.SaveChanges()>0) { returnValue = true; } return returnValue; } /// <summary> /// 更新客戶信息 /// </summary> /// <param name="entity"></param> /// <returns></returns> public bool UpdateEntity(Insigma.Hotel.Model.Customer entity) { //新的方法來保存 //hotelDB.Customer.Attach(entity);//附加到表對應集合緩沖中 //hotelDB.ObjectStateManager.ChangeObjectState(entity, System.Data.EntityState.Modified); bool resultValue = false; //if (hotelDB.SaveChanges()>0) //{ // resultValue = true; //} //return resultValue; HotelContainer hoteEntities = new HotelContainer(); var oldCustomer = (from c in hoteEntities.Customer where c.ID == entity.ID select c).SingleOrDefault<Customer>(); oldCustomer.Name = entity.Name; oldCustomer.Phone = entity.Phone; oldCustomer.Remark = entity.Remark; if (hoteEntities.SaveChanges()>0) { resultValue = true; } return resultValue; } /// <summary> /// 刪除客戶信息(一般是軟刪除) /// </summary> /// <param name="entity"></param> /// <returns></returns> public bool DeleteEntity(Insigma.Hotel.Model.Customer entity) { return DeleteEntityByID(entity.ID); } /// <summary> /// 根據ID刪除數據 /// </summary> /// <param name="id"></param> /// <returns></returns> public bool DeleteEntityByID(object id) { int Id = (int)id; bool resultValue = false; var deleteCustomer = hotelDB.Customer.Where<Customer>(c => c.ID == Id).SingleOrDefault<Customer>(); if (deleteCustomer != null) { hotelDB.Customer.DeleteObject(deleteCustomer); if (hotelDB.SaveChanges() > 0) { resultValue = true; } } return resultValue; } /// <summary> ////// </summary> /// <param name="id"></param> /// <returns></returns> public Insigma.Hotel.Model.Customer GetEntityByID(object id) { int Id = (int)id; return hotelDB.Customer.Where<Customer>(c => c.ID == Id).SingleOrDefault<Customer>(); } public IList<Insigma.Hotel.Model.Customer> GetPageEntities(int pageIndex, int PageSize) { var result = hotelDB.Customer.OrderBy(c =>c.ID).Skip<Customer>((pageIndex - 1) * PageSize).Take<Customer>(PageSize).ToList(); return result; } }
- SQLDao實現ICustomerDao接口:
public class CustomerSQLDao:ICustomerDao { public IList<Insigma.Hotel.Model.Customer> GetAllEntities() { using (CustomerTableAdapter adapter=new CustomerTableAdapter ()) { var result = (from c in adapter.GetData() select new Customer { ID=c.ID,Name=c.Name,Phone=c.Phone}).ToList<Customer>(); return result; } } public bool SaveEntity(Insigma.Hotel.Model.Customer entity) { bool resultValue=false; using (CustomerTableAdapter adapter=new CustomerTableAdapter ()) { if (adapter.Insert(entity.Name, entity.Phone, entity.Remark) > 0) { resultValue = true; } return resultValue; } } public bool UpdateEntity(Insigma.Hotel.Model.Customer entity) { bool resultValue = false; using (CustomerTableAdapter adapter=new CustomerTableAdapter ()) { if (adapter.UpdateCustomer(entity.Name,entity.Phone,entity.Remark,entity.ID)>0) { resultValue = true; } return resultValue; } } public bool DeleteEntity(Insigma.Hotel.Model.Customer entity) { return DeleteEntityByID(entity.ID); } public bool DeleteEntityByID(object id) { bool resultValue = false; int CID=(int)id; using (CustomerTableAdapter adapter=new CustomerTableAdapter ()) { //若取名Delete會變成Delete1 if (adapter.DeleteCustomerByID(CID)>0) { resultValue = true; } return resultValue; } } public Insigma.Hotel.Model.Customer GetEntityByID(object id) { using (CustomerTableAdapter adapter=new CustomerTableAdapter ()) { var table=adapter.GetCustomerByID(Convert.ToInt32(id)); Customer customer = new Customer(); customer.ID = table[0].ID; customer.Name = table[0].Name; customer.Phone = table[0].Phone; return customer; } } public IList<Insigma.Hotel.Model.Customer> GetPageEntities(int pageIndex, int PageSize) { using (CustomerTableAdapter adapter=new CustomerTableAdapter ()) { var result = (from c in adapter.GetData() select new Customer { ID = c.ID, Name = c.Name, Phone = c.Phone }).OrderBy(c => c.ID).Skip<Customer>((pageIndex - 1) * PageSize).Take<Customer>(PageSize).ToList<Customer>(); return result; } } }
這樣我們就設計好了數據訪問層實現方式,一共有三層方法來實現對數據庫的訪問,但是不管是OracleDao,還是EFDao,SQLDao實現了IBaseDao接口。這就是Repository模式。
業務邏輯層接口設計:IBLL
public interface ICustomerService { //Controller來調用業務邏輯 IList<Customer> GetAllCustomers(); bool SaveCustomer(Customer customer); bool UpdateCustomer(Customer customer); bool DeleteCustomer(int id); IList<Customer> GetPageCustomers(int pageIndex, int pageSize); Customer GetCustomerByID(int id); }
- 這兒我們更希望通過配置文件的方式手動的去選擇需要的數據訪問層技術。所以這兒可以運用抽象工廠+反射的技術,當然還有IOC依賴注入
public class CustomerService:ICustomerService { public CustomerService() { //最好通過抽象工廠和反射 IOC依賴注入 //this.customerDao = new EFDao.CustomerEFDao(); this.customerDao = DaoFactory.GetCustomerDao(); } /// <summary> /// 數據表數據訪問層接口(好處:接口是穩定) /// </summary> private ICustomerDao customerDao; public ICustomerDao CustomerDao { get { return customerDao; } set { customerDao = value; } } public IList<Model.Customer> GetAllCustomers() { return CustomerDao.GetAllEntities(); } public bool SaveCustomer(Model.Customer customer) { return CustomerDao.SaveEntity(customer); } public bool UpdateCustomer(Model.Customer customer) { return CustomerDao.UpdateEntity(customer); } public bool DeleteCustomer(int id) { return CustomerDao.DeleteEntityByID(id); } public IList<Model.Customer> GetPageCustomers(int pageIndex, int pageSize) { return CustomerDao.GetPageEntities(pageIndex, pageSize); } public Model.Customer GetCustomerByID(int id) { return CustomerDao.GetEntityByID(id); } }
- 抽象共廠的實現,因為反射出實例很耗費性能,所以運用了緩存來減輕負擔:
public class DaoCache { public DaoCache() { } public static object GetDao(string key) { System.Web.Caching.Cache daoCache = HttpRuntime.Cache; return daoCache.Get(key); } public static void InsertDaoCache(string key, object value) { if (GetDao(key) == null) { System.Web.Caching.Cache daoCache = HttpRuntime.Cache; daoCache.Insert(key, value); } } }
- 抽象工廠反射實例:
public class DaoFactory { public static readonly string DaoPath=System.Configuration.ConfigurationManager.AppSettings["DaoPath"]; public static readonly string DaoHZ=System.Configuration.ConfigurationManager.AppSettings["HZ"]; public static object CreateDao(string assemblyPath, string objType) { var cacheDao = DaoCache.GetDao(objType); if (cacheDao==null) { cacheDao = Assembly.Load(assemblyPath).CreateInstance(objType); DaoCache.InsertDaoCache(objType, cacheDao); } return cacheDao; } public static ICustomerDao GetCustomerDao() { return CreateDao(DaoPath, string.Format("{0}.Customer{1}", DaoPath, DaoHZ)) as ICustomerDao; } }
這樣,最簡單的業務邏輯層完成了,當然這兒很復雜,還需要用到Facade模式。
表示層(MVC)
- 表示層當然是指揮家Controller去訪問業務邏輯層,把數據裝配到Model中,交給View來顯示。
public class CustomerController : Controller { // private HotelContainer db = new HotelContainer(); private Insigma.Hotel.IBLL.ICustomerService customerService =new BLL.CustomerService(); // // GET: /Customer/ public ViewResult Index() { return View(customerService.GetAllCustomers()); } // // GET: /Customer/Details/5 public ViewResult Details(int id) { Customer customer = customerService.GetCustomerByID(id); return View(customer); } // // GET: /Customer/Create public ActionResult Create() { return View(); } // // POST: /Customer/Create [HttpPost] public ActionResult Create(Customer customer) { if (ModelState.IsValid) { customerService.SaveCustomer(customer); return RedirectToAction("Index"); } return View(customer); } // // GET: /Customer/Edit/5 public ActionResult Edit(int id) { Customer customer = customerService.GetCustomerByID(id); return View(customer); } // // POST: /Customer/Edit/5 [HttpPost] public ActionResult Edit(Customer customer) { if (ModelState.IsValid) { customerService.UpdateCustomer(customer); return RedirectToAction("Index"); } return View(customer); } // // GET: /Customer/Delete/5 public ActionResult Delete(int id) { Customer customer = customerService.GetCustomerByID(id); return View(customer); } // // POST: /Customer/Delete/5 [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { customerService.DeleteCustomer(id); return RedirectToAction("Index"); } protected override void Dispose(bool disposing) { } }
-
現在只需要修改配置文件就可以靈活的選擇OracleDao、EFDao、SQLDao,而我們的項目絲毫不會有任何改動:
<add key="DaoPath" value="Insigma.Hotel.EFDao" /> <add key="HZ" value="EFDao" />
總結:
這是對稍早那篇文章的總結,接口引入穩定了開發。反射的引用讓程序員更加關注業務層,提高了開發效率。
牛人的架構設計圖:來自劉冬.NET