Abstract Factory(抽象工廠)——物件建立型模式

CodingPioneer發表於2018-10-16

意圖

提供一個建立一系列相關或相互依賴物件的介面,而無需指定它們具體的類。工廠是建立產品的,抽象工廠就是建立抽象產品的。

典型應用場景

當你想對介面的呼叫者遮蔽介面的實現細節,可以通過抽象工廠模式進行封裝實現。典型的應用案例為對於一個支援多種RDMS自由切換的系統,可以通過分層架構配合抽象工廠進行實現。

傳統三層架構

傳統三層架構包括資料訪問層、業務邏輯層、表示層,如下圖:
傳統三層架構示意圖

資料訪問層封裝對資料庫的增、刪、改、查操作。
業務邏輯層封裝各種業務操作。
表示層負責介面邏輯展示與接受使用者輸入。
業務實體負責在各層之間進行資料傳遞。

資料傳遞過程如下圖所示:
資料傳遞示意圖
假如業務需要根據不同業務資料規模的應用場景可以支援部署在SQLServer上也可以支援部署在Oracle上,那麼以上簡單的架構則難以支撐;再比如可以根據使用者的喜好把系統部署為Web模式也可以部署為WinForm桌面應用模式,則需要更為複雜的架構設計,經過最小化改進,我們把架構做如下改進。

改進的三層架構

改進的三層架構如下圖:
三層架構改進示意圖
通過擴充套件,資料訪問層由4部分構成

資料物件工廠——Abstract Factory的具體應用,負責根據配置檔案或引數設定建立資料訪問介面例項
資料訪問介面,定義對各資料表的基本資料訪問操作規範:Insert 、Update、Delete、Get
資料訪問實現類(SQLServerDAL),定義實現資料訪問介面的資料訪問類,基於SQLServer實現基本資料操作。
資料訪問實現類(OracleDAL),定義實現資料訪問介面的資料訪問類,基於Oracle實現基本資料操作

業務邏輯層不會直接呼叫資料訪問實現類(SQLServerDAL或OracleDAL),而是通過資料物件工廠(抽象工廠)建立資料訪問介面例項進行資料訪問的實現。

抽象工廠程式碼實現

工程結構
工程結構
Web.config、App.config中增加一個表示訪問SQLServer還是Oracle的配置項

<configuration>
	<appSettings>
    <!--
		<add key="DBType" value="Accp.OracleDAL"/>
    -->
    <add key="DBType" value="Accp.SQLServerDAL"/>
	</appSettings>
</configuration

資料訪問介面定義

using System;
using System.Collections.Generic;
using System.Text;

namespace Accp.IDAL
{
    using Accp.Model;
    public interface ITicketService
    {
        /// <summary>
        /// 查詢符合條件的機票資訊
        /// </summary>
        /// <param name="fromCity">出發城市ID</param>
        /// <param name="toCity">到達城市ID</param>
        /// <param name="leaveDate">起飛時間</param>
        /// <returns>返回符合條件的機票實體集合</returns>
        List<Ticket> GetTicketByCondition(int fromCity, int toCity, string leaveDate);
    }
}

資料訪問實現類(SQLServer)

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace Accp.SQLServerDAL
{
    using Accp.Model;
    using Accp.IDAL;
    public class ProvinceInfoService : IProvinceInfoService
    {
        /// <summary>
        /// 執行SQL查詢,封裝查詢結果到實體類和集合
        /// </summary>
        /// <param name="sql">要執行的SQL語句</param>
        /// <param name="values">SQL語句中的引數列表</param>
        /// <returns>返回封裝好的實體集合</returns>
        private List<ProvinceInfo> GetBySql(string sql, SqlParameter[] values)
        {
            using (SqlDataReader reader = DBHelper.GetReader(DBHelper.CONSTR, sql, values))
            {
                List<ProvinceInfo> lst = new List<ProvinceInfo>();
                ProvinceInfo province = null;
                while (reader.Read())
                {
                    province = new ProvinceInfo();
                    province.ProvinceId = Convert.ToInt32(reader["provinceId"]);
                    province.ProvinceName = reader["provinceName"] as string;
                    lst.Add(province);
                }
                reader.Close();
                return lst;
            }
        }

        #region IProvinceInfoService 成員
        /// <summary>
        /// 根據省份ID,查詢省份資訊
        /// </summary>
        /// <param name="provinceId">省份ID</param>
        /// <returns>返回省份實體資訊</returns>
        public ProvinceInfo GetProvinceInfoById(int provinceId)
        {
            string sql = "select * from ProvinceInfo where provinceId=@provinceId";
            SqlParameter[] values ={
                new SqlParameter("@provinceId",provinceId)
            };
            List<ProvinceInfo> lst = this.GetBySql(sql, values);
            if (lst != null && lst.Count > 0) return lst[0];
            return null;
        }
        /// <summary>
        /// 查詢所有省份
        /// </summary>
        /// <returns>返回所有省份實體集合</returns>
        public List<ProvinceInfo> GetAllProvinceInfo()
        {
            string sql = "select * from ProvinceInfo";
            return this.GetBySql(sql, null);
        }

        #endregion
    }
}

資料訪問實現類(Oracle)

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.OracleClient;

namespace Accp.OracleDAL
{
    using Accp.Model;
    using Accp.IDAL;
    public class TicketService : ITicketService
    {
        private CityInfoService cityInfoService = new CityInfoService();
        /// <summary>
        /// 執行SQL查詢,封裝查詢結果到實體類和集合
        /// </summary>
        /// <param name="sql">要執行的SQL語句</param>
        /// <param name="values">SQL語句中的引數列表</param>
        /// <returns>返回封裝好的實體集合</returns>
        private List<Ticket> GetBySql(string sql, OracleParameter[] values)
        {
            using (OracleDataReader reader = DBHelper.GetReader(DBHelper.CONSTR, sql, values))
            {
                List<Ticket> lst = new List<Ticket>();
                Ticket ticket = null;
                while (reader.Read())
                {
                    ticket = new Ticket();
                    ticket.TicketId = Convert.ToInt32(reader["ticketId"]);
                    ticket.FlightOrder = reader["flightOrder"] as string;
                    ticket.FromCity = this.cityInfoService.GetCityInfoById(Convert.ToInt32(reader["fromCity"]));
                    ticket.ToCity = this.cityInfoService.GetCityInfoById(Convert.ToInt32(reader["toCity"]));
                    ticket.Price = Convert.ToDecimal(reader["price"]);
                    ticket.LeaveDate = Convert.ToDateTime(reader["leaveDate"]);
                    ticket.RoomType = Convert.ToInt32(reader["roomType"]);
                    lst.Add(ticket);
                }
                reader.Close();
                return lst;
            }
        }

        #region ITicketService 成員
        /// <summary>
        /// 查詢符合條件的機票資訊
        /// </summary>
        /// <param name="fromCity">出發城市ID</param>
        /// <param name="toCity">到達城市ID</param>
        /// <param name="leaveDate">起飛時間</param>
        /// <returns>返回符合條件的機票實體集合</returns>
        public List<Ticket> GetTicketByCondition(int fromCity, int toCity, string leaveDate)
        {
            string sql = "select * from ticket where fromCity=:fromCity and toCity=:toCity and to_char(leaveDate,'yyyy-MM-dd')=:leaveDate";
            OracleParameter[] values ={
                new OracleParameter("fromCity",fromCity),
                new OracleParameter("toCity",toCity),
                new OracleParameter("leaveDate",leaveDate)
            };
            return GetBySql(sql, values);
        }

        #endregion
    }
}

抽象工廠類實現

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Configuration;

namespace Accp.Factory
{
    using Accp.IDAL;
    public class DBFactory<T>
    {
        private static readonly string DBType = ConfigurationManager.AppSettings["DBType"];
        /// <summary>
        /// 建立介面例項的方法
        /// </summary>
        /// <returns></returns>
        public static T CreateService()
        {
            Type type = typeof(T);
            string fullType = DBType + "." + type.Name.Substring(1);
            Assembly assembly = Assembly.Load(DBType);
            T instance = (T)assembly.CreateInstance(fullType);
            return instance;
        }
    }
}

業務邏輯層通過抽象工廠獲取資料訪問介面例項,進行資料訪問,示例如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;

namespace Accp.BLL
{
    using Accp.IDAL;
    using Accp.Factory;
    using Accp.Model;
    [DataObject]
    public class TicketManager
    {
        private static ITicketService ticketService = DBFactory<ITicketService>.CreateService();
        /// <summary>
        /// 查詢符合條件的機票資訊
        /// </summary>
        /// <param name="fromCity">出發城市ID</param>
        /// <param name="toCity">到達城市ID</param>
        /// <param name="leaveDate">起飛時間</param>
        /// <returns>返回符合條件的機票實體集合</returns>
        [DataObjectMethod(DataObjectMethodType.Select)]
        public static List<Ticket> GetTicketByCondition(int fromCity, int toCity, string leaveDate)
        {
            return ticketService.GetTicketByCondition(fromCity, toCity, leaveDate);
        }
    }
}

表示層程式碼略

總結

本案例中

資料訪問實現類(SQLServerDAL、OracleDAL)為實體產品;
資料訪問介面(IDAL)為抽象產品;
資料物件工廠(DBFactory)為抽象工廠,遮蔽了在業務邏輯層對SQLServerDAL和OracleDAL的具體依賴,通過修改配置檔案中DBType的值就可以實現對不同RDBMS的切換支援;
業務邏輯類(BLL)為客戶;

完整程式碼案例下載地址

相關文章