學習設計模式《十》——代理模式

一、基礎概念

????????代理模式的本質【控制對象訪問】;

????????代理模式的定義:為其他對象提供一種代理以控制對這個對象的訪問;

????????代理模式的功能:代理模式是通過創建一個代理對象,用這個代理對象去代表真實的對象;客戶端得到這個代理對象后,對客戶端沒有什么影響,就跟得到了真實對象一樣來使用【當客戶端操作整個代理對象的時候,實際上功能最終還是會由真實的對象來完成,只不過是由通過代理操作的,也就是客戶端操作代理,代理操作真正的對象】正是因為有代理對象夾在客戶端和被代理的真實對象中間,相當于一個中轉,那么在中轉的時候就有很多花招可以使用了(如:判斷權限,若沒有足夠權限就不給你中轉等等)。

代理的分類
序號代理分類說明
1虛代理根據需要來創建開銷很大的對象,該對象只有在需要的時候才會被真正創建
2遠程代理用來在不同的地址空間上代表同一個對象,這個不同的地址空間可以是在本機,也可以在其他機器上
3Copy-on-Write代理在客戶端操作的時候,只有對象改變了,才會真的拷貝(或克隆)一個目標對象,算是虛代理的一個分支
4保護代理控制對原始對象的訪問,如果有需要,可以給不同的用戶提供不同的訪問權限,以控制他們對原始對象的訪問
5Cache代理為那些昂貴操作的結果提供臨時的存儲空間,以便多個客戶端可以共享這些結果
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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/80711.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/80711.shtml
英文地址,請注明出處:http://en.pswp.cn/web/80711.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

阿里云web端直播(前端部分)

阿里云&#xff1a;Web播放器快速接入_視頻點播(VOD)-阿里云幫助中心 import Aliplayer from aliyun-aliplayerimport aliyun-aliplayer/build/skins/default/aliplayer-min.css<div id"J_prismPlayer" style"width: 300px; height: 300px;" />var …

深入解析OrientDB:多模型數據庫的技術優勢與實際應用

OrientDB 是一款開源的多模型 NoSQL 數據庫&#xff0c;融合了文檔數據庫、圖數據庫和對象數據庫的特性。它不僅支持靈活的數據建模&#xff0c;還提供了高性能的查詢能力&#xff0c;適用于社交網絡、物聯網、內容管理等場景。本文詳細探討 OrientDB 的核心特性、應用場景&…

STM32控制電機

初始化時鐘&#xff1a;在 STM32 的程序中&#xff0c;初始化系統時鐘&#xff0c;一般會使用 RCC&#xff08;Reset and Clock Control&#xff09;相關函數來配置時鐘。例如&#xff0c;對于 STM32F103 系列&#xff0c;可能會使用 RCC_APB2PeriphClockCmd 函數來使能 GPIO 和…

(05)數字化轉型之生產制造:從通常的離散制造到柔性化生產的全景指南

當今制造業正經歷著前所未有的數字化變革&#xff0c;從傳統的離散制造到流程制造&#xff0c;再到新興的項目制造和柔性制造&#xff0c;各種生產模式都在加速向智能化方向演進。本文將系統性地介紹制造業生產管理的完整體系&#xff0c;為企業數字化轉型提供全面的方法論和實…

龍虎榜——20250520

上證指數今天縮量向上&#xff0c;個股漲多跌少&#xff0c;大盤股和小盤股總體表現都還可以。 深證同樣縮量上漲&#xff0c;向上補缺口的概率增大。 2025年5月20日龍虎榜行業方向分析 寵物經濟&#xff08;消費升級政策催化&#xff09; ? 代表標的&#xff1a;天元寵物、…

CVE-2022-22978源碼分析與漏洞復現

漏洞概述 CVE-2022-22978 是 Spring Security 框架中的一個高危認證繞過漏洞&#xff0c;影響版本包括 Spring Security 5.5.x < 5.5.7、5.6.x < 5.6.4 及更早的不受支持版本。攻擊者可通過構造包含換行符&#xff08;如 %0a&#xff09;的 URL 路徑&#xff0c;繞過正則…

PostGIS實現柵格數據入庫【raster2pgsql】

raster2pgsql使用與最佳實踐 一、工具概述 raster2pgsql是PostGIS提供的命令行工具,用于將GDAL支持的柵格格式(如GeoTIFF、JPEG、PNG等)導入PostgreSQL數據庫,支持批量加載、分塊切片、創建空間索引及金字塔概覽,是柵格數據入庫的核心工具。 二、核心功能與典型用法 1…

Redis企業級開發實戰:核心應用場景與最佳實踐

引言 Redis&#xff08;Remote Dictionary Server&#xff09;作為一款高性能的內存數據庫&#xff0c;在企業級開發中扮演著至關重要的角色。無論是緩存加速、分布式鎖、實時統計&#xff0c;還是消息隊列&#xff0c;Redis都能以極低的延遲和極高的吞吐量滿足業務需求。本文…

深入解析Spring Boot與Spring Cloud在微服務架構中的實踐

深入解析Spring Boot與Spring Cloud在微服務架構中的實踐 引言 隨著云計算和分布式系統的快速發展&#xff0c;微服務架構已成為現代軟件開發的主流模式。Spring Boot和Spring Cloud作為Java生態中微服務開發的核心框架&#xff0c;為開發者提供了強大的工具和組件&#xff0…

AI量化交易是什么?它是如何重塑金融世界的?

第一章&#xff1a;證券交易的進化之路 1.1 從喊價到代碼&#xff1a;交易方式的革命性轉變 在電子交易普及之前&#xff0c;證券交易依賴于交易所內的公開喊價系統。交易員通過手勢、喊話甚至身體語言傳遞買賣信息&#xff0c;這種模式雖然直觀&#xff0c;但效率低下且容易…

芯馳科技與安波福聯合舉辦技術研討會,深化智能汽車領域合作交流

5月15日&#xff0c;芯馳科技與全球移動出行技術解決方案供應商安波福&#xff08;Aptiv&#xff09;在上海聯合舉辦以“芯智融合&#xff0c;共贏未來”為主題的技術研討會。會上&#xff0c;雙方聚焦智能座艙與智能車控的發展趨勢&#xff0c;展開深入交流與探討&#xff0c;…

大數據Spark(五十九):Standalone集群部署

文章目錄 Standalone集群部署 一、節點劃分 二、搭建Standalone集群 1、將下載好的Spark安裝包上傳解壓 2、配飾spark-env.sh 3、配置workers 4、將配置好的安裝包發送到node2、node3節點上 5、啟動Standalone集群 三、提交任務測試 Standalone集群部署 Standalone 模…

Feign異步模式丟失上下文問題

Feign異步模式丟失上下文問題 問題描述 當我們使用異步對我們代碼進行操作優化時&#xff0c;代碼中使用了RequestContextHolder去獲取上下文的數據&#xff0c;當我們執行原來可以執行的業務時發現報了空指針異常或數據為空&#xff0c;這是為什么呢&#xff1f; 原理解釋 …

JavaScript作用域和作用域鏈

在JavaScript中&#xff0c;作用域和作用域鏈是理解代碼執行和變量訪問的關鍵概念。它們決定了變量和函數在代碼中的可見性和生命周期。 一、作用域&#xff08;Scope&#xff09; &#xff08;一&#xff09;什么是作用域&#xff1f; 作用域是在運行時代碼中的某些特定部分…

人工智能的“歧視”:“她數據”在算法運行中隱形

縱觀人類的發展史&#xff0c;每一次科技進步都將對性別平等產生深刻影響。尤其是當下&#xff0c;人們對于借助人工智能技術快速發展來彌合性別不平等寄予厚望。 但很多人沒想過&#xff0c;人工智能技術本身是客觀中立、不存在“算法歧視”“性別偏見的嗎&#xff1f; 弗吉…

設備全生命周期管理:從采購到報廢的數字化閉環方案

在當今數字化時代&#xff0c;企業對設備的管理已不再局限于簡單的維護與修理&#xff0c;而是追求從采購到報廢的全生命周期數字化閉環管理。易點易動設備管理系統&#xff0c;正是這一趨勢下的佼佼者&#xff0c;它為企業提供了一套高效便捷的設備管理解決方案。 采購階段&a…

React中useState中更新是同步的還是異步的?

文章目錄 前言一、useState 的基本用法二、useState 的更新機制1. 內部狀態管理2. 狀態初始化3. 狀態更新 三、useState 的更新頻率與異步行為1. 異步更新與批量更新2. 為什么需要異步更新&#xff1f; 四、如何正確處理 useState 的更新1. 使用回調函數形式的更新2. 理解異步更…

FEKO許可證與其他電磁仿真軟件的比較

在電磁仿真領域&#xff0c;眾多軟件工具競相爭艷&#xff0c;而FEKO軟件及其許可證制度在其中獨樹一幟。本文將對比FEKO許可證與其他電磁仿真軟件&#xff0c;突出FEKO在許可證方面的卓越性能與獨特優勢&#xff0c;幫助您做出明智的選擇。 一、許可證成本與價值比較 相較于其…

綠色云計算:數字化轉型與可持續發展的完美融合

目錄 引言 綠色云計算的概念與定義 云計算的環境影響與綠色云計算的重要性 綠色云計算的技術實踐與策略 綠色云計算的案例研究與最佳實踐 綠色云計算的挑戰與限制 綠色云計算的未來趨勢與預測 結論與展望 引言 隨著云計算技術的迅猛發展和廣泛應用&#xff0c;其環境影…

在innovus中如何設置讓信號線打上雙孔

知識星球【芯冰樂】入口 為了讓設計的芯片良率能得到顯著提升,一般在繞線資源允許的情況下,我們會在盡可能多的signal線上打上雙孔,然而在進行某個項目的時候,小編驚訝的發現,在數字的layout上一個雙孔都沒出現,這是為什么呢?今天就讓小編分享一下這次新奇的發現; 經…