一、基礎概念
????????代理模式的本質【控制對象訪問】;
????????代理模式的定義:為其他對象提供一種代理以控制對這個對象的訪問;
????????代理模式的功能:代理模式是通過創建一個代理對象,用這個代理對象去代表真實的對象;客戶端得到這個代理對象后,對客戶端沒有什么影響,就跟得到了真實對象一樣來使用【當客戶端操作整個代理對象的時候,實際上功能最終還是會由真實的對象來完成,只不過是由通過代理操作的,也就是客戶端操作代理,代理操作真正的對象】正是因為有代理對象夾在客戶端和被代理的真實對象中間,相當于一個中轉,那么在中轉的時候就有很多花招可以使用了(如:判斷權限,若沒有足夠權限就不給你中轉等等)。
序號 | 代理分類 | 說明 |
1 | 虛代理 | 根據需要來創建開銷很大的對象,該對象只有在需要的時候才會被真正創建 |
2 | 遠程代理 | 用來在不同的地址空間上代表同一個對象,這個不同的地址空間可以是在本機,也可以在其他機器上 |
3 | Copy-on-Write代理 | 在客戶端操作的時候,只有對象改變了,才會真的拷貝(或克隆)一個目標對象,算是虛代理的一個分支 |
4 | 保護代理 | 控制對原始對象的訪問,如果有需要,可以給不同的用戶提供不同的訪問權限,以控制他們對原始對象的訪問 |
5 | Cache代理 | 為那些昂貴操作的結果提供臨時的存儲空間,以便多個客戶端可以共享這些結果 |
6 | 防火墻代理 | 保護對象不被惡意用戶訪問和操作 |
7 | 同步代理 | 使多個用戶能夠同時訪問目標對象而沒有沖突 |
8 | 智能指引 | 在訪問對象時執行一些附加操作(如:對指向對象的引用計數、第一次引用一個持久對象時,將它裝入內存等) |
????????何時選用代理模式???????
????????????????1、需要為一個對象在不同的地址空間提供局部代表的時候,可以使用遠程代理;
? ? ? ? ? ? ? ? 2、需要按照需要創建開銷很大的對象的時候,可以使用虛代理;
? ? ? ? ? ? ? ? 3、需要控制對原始對象的訪問的時候,可以使用保護代理;
? ? ? ? ? ? ? ? 4、需要在訪問對象執行一些附加操作的時候,可以使用智能指引代理。
二、代理模式示例
????????業務需求:在HR項目中,客戶提出當選擇一個部門或分公司的時候,要把該部門或分公司下的所有員工都顯示出來(且只需要顯示用戶的名稱即可),而且不要翻頁,方便他們進行業務處理;但是當點擊某個員工時,可查看該員工的詳細信息。
? 2.1、不使用任何模式的示例
????????直接使用sql語句將指定部門下關聯的所有員工信息都獲取出來即可:
????????2.1.1、準備工作:
????????(為了方便獲取某個部門或者某個分公司下的所有員工信息,設計部門編號的時候,就是按照層級來進行編碼,如:母公司編碼為01,下面的分公司就是0101、0102、0103以此類推,在下一級的公司部門編號就是:010101、010102、010103,...;010201、010202、010203,...這樣的部門編碼設計雖然不優雅,但是實用,像這種獲取某個部門或某個分公司下的所有員工信息功能,就不用遞歸查找,直接使用like匹配部門編號開頭即可)。
在sqlserver中創建部門表與用戶信息表:
--創建部門表
CREATE TABLE [dbo].[Depment]([ID] [nvarchar](10) PRIMARY KEY, NOT NULL,[DepmentName] [nvarchar](10) NOT NULL,[DepmentDesc] [nvarchar](100) NULL
);INSERT INTO [dbo].[Depment]([ID], [DepmentName], [DepmentDesc]) VALUES (N'01', N'總公司', N'這是公司總部,管理旗下所有公司');
INSERT INTO [dbo].[Depment]([ID], [DepmentName], [DepmentDesc]) VALUES (N'0101', N'一分公司', N'這是公司在上海的第一個分公司 ');
INSERT INTO [dbo].[Depment]([ID], [DepmentName], [DepmentDesc]) VALUES (N'0102', N'二分公司', N'這是公司在蘇州的第二個分公司');
INSERT INTO [dbo].[Depment]([ID], [DepmentName], [DepmentDesc]) VALUES (N'0103', N'三分公司', N'這是公司在深圳的第三個分公司');
INSERT INTO [dbo].[Depment]([ID], [DepmentName], [DepmentDesc]) VALUES (N'010101', N'信息部', N'這是公司在上海的第一個分公司的下屬信息部,主要復雜公司技術相關的所有事物(如軟件產品研發、實施部署運維等相關工作)');
INSERT INTO [dbo].[Depment]([ID], [DepmentName], [DepmentDesc]) VALUES (N'010102', N'人力資源部', N'這是公司在上海的第一分公司的下屬人力資源部門,主要負責公司所需人才的招聘、管理、薪酬、崗位職責、培訓、離職等相關工作');
INSERT INTO [dbo].[Depment]([ID], [DepmentName], [DepmentDesc]) VALUES (N'010201', N'行政部', N'這是公司在蘇州的第二個分公司的下屬行政部門,主要負責公司的行政事務(如:商務接待、會議安排、食堂管理、安保管理等相關工作)');
INSERT INTO [dbo].[Depment]([ID], [DepmentName], [DepmentDesc]) VALUES (N'010202', N'銷售部', N'這是公司在蘇州的第二個分公司的下屬銷售部門,主要負責公司產品的銷售推廣(如:對外推廣公司產品、擴大公司產品的受眾、讓公司產品打開銷路等相關工作)');
--創建用戶信息表
CREATE TABLE [dbo].[UserInfo]([ID] [nvarchar](10) PRIMARY KEY,NOT NULL,[UserID] [nchar](18) NOT NULL,[UserName] [varchar](50) NOT NULL,[UserSex] [char](2) NOT NULL,[UserAge] [int] NOT NULL,[UserTelnumber] [nchar](11) NOT NULL,[UserHeight] [int] NULL,[UserWeight] [int] NULL,[UserAddress] [nchar](30) NULL,[DepmentId] [varchar](50) NOT NULL
);INSERT INTO [dbo].[UserInfo]([ID], [UserID], [UserName], [UserSex], [UserAge], [UserTelnumber], [UserHeight], [UserWeight], [UserAddress], [DepmentId]) VALUES (N'001', N'522020199001124561', '張三', '男', 35, N'14236598541', 168, 70, NULL, '010101');
INSERT INTO [dbo].[UserInfo]([ID], [UserID], [UserName], [UserSex], [UserAge], [UserTelnumber], [UserHeight], [UserWeight], [UserAddress], [DepmentId]) VALUES (N'002', N'522020199101124452', '李四', '男', 34, N'14238765541', 170, 72, NULL, '010101');
INSERT INTO [dbo].[UserInfo]([ID], [UserID], [UserName], [UserSex], [UserAge], [UserTelnumber], [UserHeight], [UserWeight], [UserAddress], [DepmentId]) VALUES (N'003', N'00201020000606345X', '王茜', '女', 25, N'13522687451', 166, 50, NULL, '010102');
INSERT INTO [dbo].[UserInfo]([ID], [UserID], [UserName], [UserSex], [UserAge], [UserTelnumber], [UserHeight], [UserWeight], [UserAddress], [DepmentId]) VALUES (N'004', N'00201020010507234X', '思雨', '女', 24, N'14587653241', 165, 50, NULL, '010201');
INSERT INTO [dbo].[UserInfo]([ID], [UserID], [UserName], [UserSex], [UserAge], [UserTelnumber], [UserHeight], [UserWeight], [UserAddress], [DepmentId]) VALUES (N'005', N'222021199506013756', '王五', '男', 30, N'18623597461', 169, 71, NULL, '010202');
??????????????2.1.2、正式不用任何模式實現業務示例
1、創建用戶信息對象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;namespace ProxyPattern
{/// <summary>/// 用戶對象/// </summary>internal class UserModel{//用戶編號public string? Id { get; set; }//用戶身份證編號public string? UserId { get; set; }//用戶名稱public string? UserName { get; set; }//用戶性別public string? UserSex { get; set; }//用戶年齡public string? UserAge { get; set; }//聯系電話public string? UserTelnumber { get; set; }//用戶身高public string? UserHeight { get; set; }//用戶體重public string? UserWeight { get; set; }//用戶地址public string? UserAddress { get; set; }//用戶所屬部門編號public string? DepmentId { get; set; }public override string ToString(){string str = $"用戶的身份證編號【{UserId}】姓名【{UserName}】性別【{UserSex}】年齡【{UserAge}】" +$"聯系電話【{UserTelnumber}】身高【{UserHeight}】體重【{UserWeight}】地址【{UserAddress}】所屬部門編號【{DepmentId}】";return str;}}//Class_end
}
2、創建用戶管理對象操作數據庫中的用戶信息
/***
* Title:"WinFormClient" 項目
* 主題:通用的SQLServer數據庫操作類
* Description:
* 功能:實現數據庫的基礎增、刪、查、改操作
* Date:2025
* Version:0.1版本
* Author:Coffee
* Modify Recoder:
*/using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;namespace ProxyPattern
{public class SqlServerHelper{private string connectionString;/// <summary>/// 數據庫連接定義/// </summary>//private SqlConnection dbConnection;private SqlConnection dbConnection;/// <summary>/// SQL命令定義/// </summary>private SqlCommand dbCommand;/// <summary>/// 數據讀取定義/// </summary>private SqlDataReader dataReader;/// <summary>/// 設置數據庫連接字符串/// </summary>public string ConnectionString{set { connectionString = value; }}/// <summary>/// 構造函數/// </summary>/// <param name="connectionString">數據庫連接字符串</param>public SqlServerHelper(string connectionString){this.connectionString = connectionString;}/// <summary>/// 執行一個查詢,并返回結果集/// </summary>/// <param name="sql">要執行的查詢SQL文本命令</param>/// <returns>返回查詢結果集</returns>public DataTable ExecuteDataTable(string sql){return ExecuteDataTable(sql, CommandType.Text, null);}/// <summary>/// 執行一個查詢,并返回查詢結果/// </summary>/// <param name="sql">要執行的SQL語句</param>/// <param name="commandType">要執行的查詢語句的類型,如存儲過程或者SQL文本命令</param>/// <returns>返回查詢結果集</returns>public DataTable ExecuteDataTable(string sql, CommandType commandType){return ExecuteDataTable(sql, commandType, null);}/// <summary>/// 執行一個查詢,并返回查詢結果/// </summary>/// <param name="sql">要執行的SQL語句</param>/// <param name="commandType">要執行的查詢語句的類型,如存儲過程或者SQL文本命令</param>/// <param name="parameters">Transact-SQL 語句或存儲過程的參數數組</param>/// <returns></returns>public DataTable ExecuteDataTable(string sql, CommandType commandType, SqlParameter[] parameters){DataTable data = new DataTable();//實例化DataTable,用于裝載查詢結果集using (SqlConnection connection = new SqlConnection(connectionString)){using (SqlCommand command = new SqlCommand(sql, connection)){command.CommandType = commandType;//設置command的CommandType為指定的CommandType//如果同時傳入了參數,則添加這些參數if (parameters != null){foreach (SqlParameter parameter in parameters){command.Parameters.Add(parameter);}}//通過包含查詢SQL的SqlCommand實例來實例化SqlDataAdapterSqlDataAdapter adapter = new SqlDataAdapter(command);adapter.Fill(data);//填充DataTablecommand.Dispose();}connection.Dispose();}return data;}/// <summary>/// /// </summary>/// <param name="sql">要執行的查詢SQL文本命令</param>/// <returns></returns>public SqlDataReader ExecuteReader(string sql){return ExecuteReader(sql, CommandType.Text, null);}/// <summary>/// /// </summary>/// <param name="sql">要執行的SQL語句</param>/// <param name="commandType">要執行的查詢語句的類型,如存儲過程或者SQL文本命令</param>/// <returns></returns>public SqlDataReader ExecuteReader(string sql, CommandType commandType){return ExecuteReader(sql, commandType, null);}/// <summary>/// /// </summary>/// <param name="sql">要執行的SQL語句</param>/// <param name="commandType">要執行的查詢語句的類型,如存儲過程或者SQL文本命令</param>/// <param name="parameters">Transact-SQL 語句或存儲過程的參數數組</param>/// <returns></returns>public SqlDataReader ExecuteReader(string sql, CommandType commandType, SqlParameter[] parameters){SqlConnection connection = new SqlConnection(connectionString);SqlCommand command = new SqlCommand(sql, connection);//如果同時傳入了參數,則添加這些參數if (parameters != null){foreach (SqlParameter parameter in parameters){command.Parameters.Add(parameter);}}//0表示永久,默認是30command.CommandTimeout = 240;connection.Open();//CommandBehavior.CloseConnection參數指示關閉Reader對象時關閉與其關聯的Connection對象return command.ExecuteReader(CommandBehavior.CloseConnection);}/// <summary>/// /// </summary>/// <param name="sql">要執行的查詢SQL文本命令</param>/// <returns></returns>public Object ExecuteScalar(string sql){return ExecuteScalar(sql, CommandType.Text, null);}/// <summary>/// /// </summary>/// <param name="sql">要執行的SQL語句</param>/// <param name="commandType">要執行的查詢語句的類型,如存儲過程或者SQL文本命令</param>/// <returns></returns>public Object ExecuteScalar(string sql, CommandType commandType){return ExecuteScalar(sql, commandType, null);}/// <summary>/// /// </summary>/// <param name="sql">要執行的SQL語句</param>/// <param name="commandType">要執行的查詢語句的類型,如存儲過程或者SQL文本命令</param>/// <param name="parameters">Transact-SQL 語句或存儲過程的參數數組</param>/// <returns></returns>public Object ExecuteScalar(string sql, CommandType commandType, SqlParameter[] parameters){object result = null;using (SqlConnection connection = new SqlConnection(connectionString)){using (SqlCommand command = new SqlCommand(sql, connection)){command.CommandType = commandType;//設置command的CommandType為指定的CommandType//如果同時傳入了參數,則添加這些參數if (parameters != null){foreach (SqlParameter parameter in parameters){command.Parameters.Add(parameter);}}//0表示永久,默認是30command.CommandTimeout = 240;connection.Open();//打開數據庫連接result = command.ExecuteScalar();command.Dispose();}connection.Dispose();}return result;//返回查詢結果的第一行第一列,忽略其它行和列}/// <summary>/// 對數據庫執行增刪改操作/// </summary>/// <param name="sql">要執行的查詢SQL文本命令</param>/// <returns></returns>public int ExecuteNonQuery(string sql){return ExecuteNonQuery(sql, CommandType.Text, null);}/// <summary>/// 對數據庫執行增刪改操作/// </summary>/// <param name="sql">要執行的SQL語句</param>/// <param name="commandType">要執行的查詢語句的類型,如存儲過程或者SQL文本命令</param>/// <returns></returns>public int ExecuteNonQuery(string sql, CommandType commandType){return ExecuteNonQuery(sql, commandType, null);}/// <summary>/// 對數據庫執行增刪改操作/// </summary>/// <param name="sql">要執行的SQL語句</param>/// <param name="commandType">要執行的查詢語句的類型,如存儲過程或者SQL文本命令</param>/// <param name="parameters">Transact-SQL 語句或存儲過程的參數數組</param>/// <returns></returns>public int ExecuteNonQuery(string sql, CommandType commandType, SqlParameter[] parameters){int count = 0;using (SqlConnection connection = new SqlConnection(connectionString)){using (SqlCommand command = new SqlCommand(sql, connection)){command.CommandType = commandType;//設置command的CommandType為指定的CommandType//如果同時傳入了參數,則添加這些參數if (parameters != null){foreach (SqlParameter parameter in parameters){command.Parameters.Add(parameter);}}//0表示永久,默認是30command.CommandTimeout = 240;connection.Open();//打開數據庫連接count = command.ExecuteNonQuery();command.Dispose();}connection.Dispose();}return count;//返回執行增刪改操作之后,數據庫中受影響的行數}/// <summary>/// 返回當前連接的數據庫中所有由用戶創建的數據庫/// </summary>/// <returns></returns>public DataTable GetTables(){DataTable data = null;using (SqlConnection connection = new SqlConnection(connectionString)){connection.Open();//打開數據庫連接data = connection.GetSchema("Tables");connection.Dispose();}return data;}/// <summary>/// 執行多條SQL語句,實現數據庫事務。/// </summary>/// <param name="SQLStringList">多條SQL語句</param> public int ExecuteSqlTran(List<String> SQLStringList){using (SqlConnection connection = new SqlConnection(connectionString)){connection.Open();SqlCommand cmd = new SqlCommand();cmd.Connection = connection;SqlTransaction tx = connection.BeginTransaction();cmd.Transaction = tx;try{int count = 0;for (int n = 0; n < SQLStringList.Count; n++){string strsql = SQLStringList[n];if (strsql.Trim().Length > 1){cmd.CommandText = strsql;count += cmd.ExecuteNonQuery();}}tx.Commit();return count;}catch{tx.Rollback();return 0;}finally{cmd.Dispose();connection.Dispose();}}}/// <summary>/// 執行帶一個存儲過程參數的的SQL語句。/// </summary>/// <param name="SQLString">SQL語句</param>/// <param name="content">參數內容,比如一個字段是格式復雜的文章,有特殊符號,可以通過這個方式添加</param>/// <returns>影響的記錄數</returns>public int ExecuteSql(string SQLString, string content){using (SqlConnection connection = new SqlConnection(connectionString)){SqlCommand cmd = new SqlCommand(SQLString, connection);SqlParameter myParameter = new SqlParameter("@content", SqlDbType.NText);myParameter.Value = content;cmd.Parameters.Add(myParameter);try{connection.Open();int rows = cmd.ExecuteNonQuery();return rows;}catch (Exception e){throw e;}finally{cmd.Dispose();connection.Dispose();}}}/// <summary>/// 執行帶一個存儲過程參數的的SQL語句。/// </summary>/// <param name="SQLString">SQL語句</param>/// <param name="content">參數內容,比如一個字段是格式復雜的文章,有特殊符號,可以通過這個方式添加</param>/// <returns>影響的記錄數</returns>public object ExecuteSqlGet(string SQLString, string content){using (SqlConnection connection = new SqlConnection(connectionString)){SqlCommand cmd = new SqlCommand(SQLString, connection);SqlParameter myParameter = new SqlParameter("@content", SqlDbType.NText);myParameter.Value = content;cmd.Parameters.Add(myParameter);try{connection.Open();object obj = cmd.ExecuteScalar();if ((Object.Equals(obj, null)) || (Object.Equals(obj, System.DBNull.Value))){return null;}else{return obj;}}catch (Exception e){throw e;}finally{cmd.Dispose();connection.Dispose();}}}/// <summary>/// 向數據庫里插入圖像格式的字段(和上面情況類似的另一種實例)/// </summary>/// <param name="strSQL">SQL語句</param>/// <param name="fs">圖像字節,數據庫的字段類型為image的情況</param>/// <returns>影響的記錄數</returns>public int ExecuteSqlInsertImg(string strSQL, byte[] fs){using (SqlConnection connection = new SqlConnection(connectionString)){SqlCommand cmd = new SqlCommand(strSQL, connection);SqlParameter myParameter = new SqlParameter("@fs", SqlDbType.Image);myParameter.Value = fs;cmd.Parameters.Add(myParameter);try{connection.Open();int rows = cmd.ExecuteNonQuery();return rows;}catch (Exception e){throw e;}finally{cmd.Dispose();connection.Dispose();}}}/// <summary>/// 執行查詢語句,返回DataSet/// </summary>/// <param name="SQLString">查詢語句</param>/// <returns>DataSet</returns>public DataSet Query(string SQLString){using (SqlConnection connection = new SqlConnection(connectionString)){DataSet ds = new DataSet();try{connection.Open();SqlDataAdapter command = new SqlDataAdapter(SQLString, connection);command.Fill(ds, "ds");command.Dispose();}catch (Exception ex){throw new Exception(ex.Message);}finally{connection.Dispose();}return ds;}}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ProxyPattern
{/// <summary>/// 用戶管理對象/// </summary>internal class UserManager{public static List<UserModel> GetUserByDepmentId(string depmentId){string conStr = $"server=.;database=Test;uid=test;pwd=123456";SqlServerHelper sqlServerHelper = new SqlServerHelper(conStr);string sql = $"select uo.* from UserInfo uo left join Depment dt on uo.depmentId=dt.ID where dt.ID like '{depmentId}%'";DataTable dt = sqlServerHelper.ExecuteDataTable(sql);使用參數內容時周圍不能有特殊修符號如單引號等//SqlParameter[] sqlParameters ={// new SqlParameter("@dd",SqlDbType.VarChar){Value=depmentId}//}//;//DataTable dt = sqlServerHelper.ExecuteDataTable(sql, System.Data.CommandType.Text, sqlParameters);if (dt.Rows.Count>0){List<UserModel> userModels = new List<UserModel>(); foreach (DataRow dr in dt.Rows){UserModel userModel = new UserModel();userModel.Id = dr["ID"].ToString();userModel.UserId = dr["UserID"].ToString();userModel.UserName = dr["UserName"].ToString();userModel.UserSex = dr["UserSex"].ToString();userModel.UserAge = dr["UserAge"].ToString();userModel.UserTelnumber = dr["UserTelnumber"].ToString();userModel.UserHeight = dr["UserHeight"].ToString();userModel.UserWeight = dr["UserWeight"].ToString();userModel.UserAddress = dr["UserAddress"].ToString();userModel.DepmentId = dr["DepmentId"].ToString();userModels.Add(userModel);}return userModels;}return null;}}//Class_end
}
3、編寫客戶端示例
namespace ProxyPattern
{internal class Program{static void Main(string[] args){GetDepmentUsersTest();Console.ReadLine();}/// <summary>/// 測試獲取部門人員/// </summary>private static void GetDepmentUsersTest(){Console.WriteLine("---測試獲取部門人員---");string depmentId = "0101";Console.WriteLine($"{depmentId} 部門的所有人員如下");List<UserModel> userModels = UserManager.GetUserByDepmentId(depmentId);foreach (UserModel userModel in userModels){Console.WriteLine(userModel.ToString());}depmentId = "0102";Console.WriteLine($"\n{depmentId} 部門的所有人員如下");List<UserModel> userModels2 = UserManager.GetUserByDepmentId(depmentId);foreach (UserModel userModel in userModels2){Console.WriteLine(userModel.ToString());}}}//Class_end
}
4、運行結果
如上所示我們已經不使用任何模式實現了獲取指定部門的所有用戶信息;但是當我們一次性訪問的數據條數很多,且每條數據量很大的情況下,那么會十分消耗我們寶貴的內存空間;并且從客戶使用的角度來說,查看用戶信息具有很大的隨機性,客戶有可能訪問每一條數據,也有可能一條都不訪問;也就是說,一次性訪問很多條數據,消耗了大量內存,但是很可能是浪費的,因為客戶根本不會查看這么多數據;對于每條數據用戶只查看姓名而已【那么,我們該怎么實現,才能既把多條用戶數據的姓名展示出來,而又能節省內存空間?只有當用戶想要查看指定用戶更多數據的時候才顯示對應用戶的詳細數據?】
? 2.2、使用代理模式的示例
????????使用代理模式就能解決如上的問題:代理模式解決的思路是:①由于客戶開始訪問的時候只查看用戶姓名,,因此開始的時候就只從數據庫中查詢返回所有的用戶編號與姓名數據;②當客戶要查看某個用戶相信信息的時候,再根據該用戶編號從數據庫中查詢到該用戶的詳細數據信息;這樣一來再滿足用戶需求的前提下,還減少了對內存的消耗,只是每次需要重新查詢一下數據庫【可以看做是以時間換空間的做法】。
1、定義用戶數據對象接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ProxyPattern.Proxy
{/// <summary>/// 用戶數據對象接口/// </summary>internal interface IUserModel{//用戶編號string? Id { get; set; }//用戶身份證編號public string? UserId { get; set; }//用戶名稱public string? UserName { get; set; }//用戶性別public string? UserSex { get; set; }//用戶年齡public string? UserAge { get; set; }//聯系電話public string? UserTelnumber { get; set; }//用戶身高public string? UserHeight { get; set; }//用戶體重public string? UserWeight { get; set; }//用戶地址public string? UserAddress { get; set; }//用戶所屬部門編號public string? DepmentId { get; set; }}//Interface_end
}
2、用戶對象模型
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;namespace ProxyPattern.Proxy
{/// <summary>/// 用戶對象/// </summary>internal class UserModel{//用戶編號public string? Id { get; set; }//用戶身份證編號public string? UserId { get; set; }//用戶名稱public string? UserName { get; set; }//用戶性別public string? UserSex { get; set; }//用戶年齡public string? UserAge { get; set; }//聯系電話public string? UserTelnumber { get; set; }//用戶身高public string? UserHeight { get; set; }//用戶體重public string? UserWeight { get; set; }//用戶地址public string? UserAddress { get; set; }//用戶所屬部門編號public string? DepmentId { get; set; }}//Class_end
}
3、創建代理對象繼承接口并實現
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Threading.Tasks;namespace ProxyPattern.Proxy
{internal class Proxy : IUserModel{//持有被代理的具體目標對象private UserModel userModel;//是否已經重新裝載過數據標識private bool loaded = false;/// <summary>/// 構造函數/// </summary>/// <param name="userModel">被代理的具體目標對象</param>public Proxy(UserModel userModel){this.userModel = userModel;}public string? Id { get=>this.userModel.Id ; set =>this.userModel.Id=value; }public string? UserId { get => this.userModel.UserId; set => this.userModel.UserId=value; }public string? UserName { get => this.userModel.UserName; set => this.userModel.UserName=value; }public string? UserSex { get => this.userModel.UserSex; set => this.userModel.UserSex=value; }public string? UserAge { get => this.userModel.UserAge; set => this.userModel.UserAge=value; }public string? UserTelnumber { get => this.userModel.UserTelnumber; set => this.userModel.UserTelnumber=value; }public string? UserHeight { get => this.userModel.UserHeight; set => this.userModel.UserHeight=value; }public string? UserWeight { get => this.userModel.UserWeight; set => this.userModel.UserWeight=value; }public string? UserAddress { get => this.userModel.UserAddress; set => this.userModel.UserAddress=value; }public string? DepmentId {get {if (!this.loaded){//從數據庫重寫加載數據Reload();//重新設置加載標識為truethis.loaded = true;}return this.userModel.DepmentId;}set {this.userModel.DepmentId = value;} }private void Reload(){Console.WriteLine($"重新查詢數據庫獲取完整的用戶數據,UserId={userModel.UserId}");string conStr = $"server=.;database=Test;uid=test;pwd=123456";SqlServerHelper sqlServerHelper = new SqlServerHelper(conStr);string sql = "select ID,UserAge,UserSex,UserTelnumber,UserHeight,UserWeight,UserAddress,DepmentId from UserInfo where UserID=@userId";//使用參數內容時周圍不能有特殊修符號如單引號等SqlParameter[] sqlParameters ={new SqlParameter("@userId",SqlDbType.VarChar){Value=userModel.UserId}};DataTable dt = sqlServerHelper.ExecuteDataTable(sql, System.Data.CommandType.Text, sqlParameters);if (dt.Rows.Count > 0){foreach (DataRow dr in dt.Rows){userModel.Id = dr["ID"].ToString();userModel.UserSex = dr["UserSex"].ToString();userModel.UserAge = dr["UserAge"].ToString();userModel.UserTelnumber = dr["UserTelnumber"].ToString();userModel.UserHeight = dr["UserHeight"].ToString();userModel.UserWeight = dr["UserWeight"].ToString();userModel.UserAddress = dr["UserAddress"].ToString();userModel.DepmentId = dr["DepmentId"].ToString();}}}public override string ToString(){string str = $"完整用戶信息——用戶的身份證編號【{UserId}】姓名【{UserName}】性別【{UserSex}】年齡【{UserAge}】" +$"聯系電話【{UserTelnumber}】身高【{UserHeight}】體重【{UserWeight}】地址【{UserAddress}】所屬部門編號【{DepmentId}】";return str;}}//Class_end
}
4、創建用戶管理對象
《1》此時的用戶管理對象查詢的時候不需要全部獲取數據,只需要查詢用戶編號與姓名就可以了;
《2》從數據庫獲取到用戶編號與姓名數據后也只用將這兩個內容轉換為用戶對象的屬性賦值,其他內容不用設置。
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ProxyPattern.Proxy
{/// <summary>/// 用戶管理對象/// </summary>internal class UserManager{/// <summary>/// 根據部門編號來獲取該部門下的所有人員/// </summary>/// <param name="depmentId">部門編號</param>/// <returns>返回該部門下的所有人員</returns>public List<IUserModel> GetUserByDepmentId(string depmentId){string conStr = $"server=.;database=Test;uid=test;pwd=123456";SqlServerHelper sqlServerHelper = new SqlServerHelper(conStr);//只需要查詢UserId與UserName兩個值就可以了string sql = $"select uo.UserId,uo.UserName from UserInfo uo left join Depment dt on uo.depmentId=dt.ID where dt.ID like '{depmentId}%'";DataTable dt = sqlServerHelper.ExecuteDataTable(sql);if (dt.Rows.Count>0){List<IUserModel> userModels = new List<IUserModel>();foreach (DataRow dr in dt.Rows){//這里是只創建代理對象,而不是直接創建UserModel對象Proxy proxy = new Proxy(new UserModel());proxy.UserId = dr["UserID"].ToString();proxy.UserName = dr["UserName"].ToString();userModels.Add(proxy);}return userModels;}return null;}}//Class_end
}
5、創建客戶端測試
using ProxyPattern.Proxy;namespace ProxyPattern
{internal class Program{static void Main(string[] args){GetUserInfoProxyTest();Console.ReadLine();}/// <summary>/// 測試用戶代理客戶端/// </summary>private static void GetUserInfoProxyTest(){Proxy.UserManager userManager = new Proxy.UserManager();List<IUserModel> userModels = userManager.GetUserByDepmentId("0101");//若只是顯示用戶名稱,則不需要重新查詢數據庫foreach (var item in userModels){string str = $"用戶的身份證編號是【{item.UserId}】姓名是【{item.UserName}】";Console.WriteLine(str);}Console.WriteLine();//若要訪問非用戶身份證編號和姓名外的屬性內容,那就需要重新查詢數據庫foreach (var item in userModels){string str = $"用戶的身份證編號是【{item.UserId}】姓名是【{item.UserName}】部門是【{item.DepmentId}】";Console.WriteLine(str);Console.WriteLine(item.ToString());}}}//Class_end
}
6、運行結果
????????如上的代理模式實現了開始獲取所有的用戶編號與用戶姓名數據;只有當訪問到這兩個數據外的數據時才需要重新查詢數據獲得完整用戶數據信息。但如上示例也存在一個問題,就是如果客戶對每條數據都要求查看詳細數據的話,那么總的查詢數據庫的次數會達到【1+N】次;這種代理模式最合適的場景是:大多數情況下只查看用戶編號與姓名數據,只有少量情況查看個人詳情數據。
?2.3、保護代理
保護代理是一種控制對原始對象訪問的代理,多用于對象應該有不同的訪問權限的情況。
????????業務需求:現有一個訂單系統業務,要求一旦訂單被創建,只有訂單的創建人才可以修改訂單數據,其他人則不能修改。
1、創建訂單接口規范行為
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ProxyPattern.ProtectProxy
{/// <summary>/// 訂單對象接口/// </summary>internal interface IOrder{//獲取訂單訂購的產品名稱string GetProductName();//設置訂單訂購的產品名稱與人員void SetProductName(string productName,string user);//獲取訂購訂單的數量int GetOrderNumber();//設置訂購訂單的數量與人員void SetOrderNumber(int orderNumber,string user);//獲取創建訂單的人員string GetOrderUser();//設置創建訂單的人員與人員void SetOrderUser(string orderUser, string user);}//Interface_end
}
2、創建訂單對象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ProxyPattern.ProtectProxy
{/// <summary>/// 訂單對象/// </summary>internal class Order : IOrder{//訂單訂購的產品名稱private string productName=string.Empty;//訂單的訂購數量private int orderNumber=0;//創建訂單的人員private string orderUser=string.Empty;/// <summary>/// 構造函數/// </summary>/// <param name="productName">訂單訂購的產品名稱</param>/// <param name="orderNumber">訂單數量</param>/// <param name="orderUserName">創建訂單的人員</param>public Order(string productName,int orderNumber,string orderUser){this.productName = productName;this.orderNumber = orderNumber;this.orderUser=orderUser;}public int GetOrderNumber(){return this.orderNumber;}public string GetOrderUser(){return this.orderUser;}public string GetProductName(){return this.productName;}public void SetOrderNumber(int orderNumber, string user){this.orderNumber=orderNumber;}public void SetOrderUser(string orderUser, string user){this.orderUser=orderUser;}public void SetProductName(string productName, string user){this.productName=productName;}}//Class_end
}
3、創建訂單對象的代理并繼承接口實現具體功能
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ProxyPattern.ProtectProxy
{/// <summary>/// 訂單代理對象/// </summary>internal class OrderProxy : IOrder{//持有被代理的具體目標對象private Order order = null;/// <summary>/// 構造函數/// </summary>/// <param name="realSubject">被代理的具體目標對象</param>public OrderProxy(Order realSubject){this.order = realSubject; }public int GetOrderNumber(){return this.order.GetOrderNumber();}public string GetOrderUser(){return this.order.GetOrderUser();}public string GetProductName(){return this.order.GetProductName();}public void SetOrderNumber(int orderNumber, string user){//控制訪問權限,只有創建訂單的人員才能夠修改if (!string.IsNullOrEmpty(user) && user.Equals(this.GetOrderUser())){order.SetOrderNumber(orderNumber, user);}else{Console.WriteLine($"抱歉【{user}】,您無權修改訂單中的產品數量");}}public void SetOrderUser(string orderUser, string user){//控制訪問權限,只有創建訂單的人員才能夠修改if (!string.IsNullOrEmpty(user) && user.Equals(this.GetOrderUser())){order.SetOrderUser(orderUser, user);}else{Console.WriteLine($"抱歉【{user}】,您無權修改訂單中的創建人員");}}public void SetProductName(string productName, string user){//控制訪問權限,只有創建訂單的人員才能夠修改if (!string.IsNullOrEmpty(user) && user.Equals(this.GetOrderUser())){order.SetProductName(productName, user);}else{Console.WriteLine($"抱歉【{user}】,您無權修改訂單中的產品名稱");}}public override string ToString(){string str = $"訂單訂購的產品名稱是【{GetProductName()}】數量是【{GetOrderNumber()}】創建訂單的人員是【{GetOrderUser()}】";return str;}}//Class_end
}
4、客戶端測試
using ProxyPattern.ProtectProxy;
using ProxyPattern.Proxy;namespace ProxyPattern
{internal class Program{static void Main(string[] args){OrderProxyTest();Console.ReadLine();}/// <summary>/// 訂單代理測試/// </summary>private static void OrderProxyTest(){//張三先登錄系統創建一個訂單IOrder order = new OrderProxy(new Order("設計模式",666,"張三"));Console.WriteLine($"初始創建訂單的信息是\n{order.ToString()}\n\n");//李四想要修改,此時應該有報錯提示order.SetOrderNumber(999,"李四");Console.WriteLine($"李四嘗試修改訂單數量后,訂單信息是\n{order.ToString()}");//創建者張三修改訂單,可正常修改且不會報錯order.SetOrderNumber(888,"張三");Console.WriteLine($"\n\n張三修改訂單數量后,訂單信息是\n{order.ToString()}");}}//Class_end
}
5、運行結果
對于代理模式某些情況下還可以使用繼承的方式取代掉接口:
1、創建訂單對象并將需要設置權限控制的方法設置為虛方法
using ProxyPattern.ProtectProxy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ProxyPattern.ModifyProxy
{/// <summary>/// 訂單對象/// </summary>internal class Order : IOrder{//訂單訂購的產品名稱private string productName=string.Empty;//訂單的訂購數量private int orderNumber=0;//創建訂單的人員private string orderUser=string.Empty;/// <summary>/// 構造函數/// </summary>/// <param name="productName">訂單訂購的產品名稱</param>/// <param name="orderNumber">訂單數量</param>/// <param name="orderUserName">創建訂單的人員</param>public Order(string productName,int orderNumber,string orderUser){this.productName = productName;this.orderNumber = orderNumber;this.orderUser=orderUser;}public int GetOrderNumber(){return orderNumber;}public string GetOrderUser(){return orderUser;}public string GetProductName(){return productName;}public virtual void SetOrderNumber(int orderNumber, string user){this.orderNumber=orderNumber;}public virtual void SetOrderUser(string orderUser, string user){this.orderUser=orderUser;}public virtual void SetProductName(string productName, string user){this.productName=productName;}}//Class_end
}
2、創建一個訂單代理對象繼承該訂單對象重新虛方法
using ProxyPattern.ProtectProxy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ProxyPattern.ModifyProxy
{/// <summary>/// 訂單代理對象/// </summary>internal class OrderProxy : Order{public static OrderProxy instance;//單例public OrderProxy(string productName, int orderNumber, string orderUser) : base(productName, orderNumber, orderUser){if (instance == null){instance = this;}else{Console.WriteLine($"{GetType()}/OrderProxy()函數不允許重復實例化!!!");}}public override void SetOrderNumber(int orderNumber, string user){//控制訪問權限,只有創建訂單的人員才能夠修改if (!string.IsNullOrEmpty(user) && user.Equals(this.GetOrderUser())){base.SetOrderNumber(orderNumber, user);}else{Console.WriteLine($"抱歉【{user}】,您無權修改訂單中的產品數量");}}public override void SetOrderUser(string orderUser, string user){//控制訪問權限,只有創建訂單的人員才能夠修改if (!string.IsNullOrEmpty(user) && user.Equals(this.GetOrderUser())){base.SetOrderUser(orderUser, user);}else{Console.WriteLine($"抱歉【{user}】,您無權修改訂單中的創建人員");}}public override void SetProductName(string productName, string user){//控制訪問權限,只有創建訂單的人員才能夠修改if (!string.IsNullOrEmpty(user) && user.Equals(this.GetOrderUser())){base.SetProductName(productName, user);}else{Console.WriteLine($"抱歉【{user}】,您無權修改訂單中的產品名稱");}}public override string ToString(){string str = $"訂單訂購的產品名稱是【{GetProductName()}】數量是【{GetOrderNumber()}】創建訂單的人員是【{GetOrderUser()}】";return str;}}//Class_end
}
?3、客戶端測試
using ProxyPattern.ProtectProxy;
using ProxyPattern.Proxy;namespace ProxyPattern
{internal class Program{static void Main(string[] args){OrderProxyTest2();Console.ReadLine();}/// <summary>/// 訂單代理測試【繼承方式】/// </summary>private static void OrderProxyTest2(){//張三先登錄系統創建一個訂單ModifyProxy.Order order = new ModifyProxy.OrderProxy("設計模式", 666, "張三");Console.WriteLine($"初始創建訂單的信息是\n{order.ToString()}\n\n");//李四想要修改,此時應該有報錯提示order.SetOrderNumber(999, "李四");Console.WriteLine($"李四嘗試修改訂單數量后,訂單信息是\n{order.ToString()}");//創建者張三修改訂單,可正常修改且不會報錯order.SetOrderNumber(888, "張三");Console.WriteLine($"\n\n張三修改訂單數量后,訂單信息是\n{order.ToString()}");}}//Class_end
}
?4、運行結果
三、項目源碼工程
kafeiweimei/Learning_DesignPattern: 這是一個關于C#語言編寫的基礎設計模式項目工程,方便學習理解常見的26種設計模式https://github.com/kafeiweimei/Learning_DesignPattern