前言
23種設(shè)計(jì)模式
都會(huì)了嗎秦驯?今天講一下靜態(tài)代理模式
的實(shí)戰(zhàn)場(chǎng)景。
代理模式給某一個(gè)對(duì)象提供一個(gè)代理對(duì)象亲桥,并由代理對(duì)象控制對(duì)原對(duì)象的引用题篷。通俗的來講代理模式就是我們生活中常見的中介。
舉個(gè)例子來說明:假如說我現(xiàn)在想買一輛二手車法严,雖然我可以自己去找車源葫笼,做質(zhì)量檢測(cè)等一系列的車輛過戶流程路星,但是這確實(shí)太浪費(fèi)我得時(shí)間和精力了。我只是想買一輛車而已為什么我還要額外做這么多事呢呈昔?于是我就通過中介公司來買車韩肝,他們來給我找車源九榔,幫我辦理車輛過戶流程哲泊,我只是負(fù)責(zé)選擇自己喜歡的車,然后付錢就可以了育特。
為什么要用代理模式缰冤?
中介隔離作用:在某些情況下喳魏,一個(gè)客戶類不想或者不能直接引用一個(gè)委托對(duì)象刺彩,而代理類對(duì)象可以在客戶類和委托對(duì)象之間起到中介的作用枝恋,其特征是代理類和委托類實(shí)現(xiàn)相同的接口焚碌。
解決問題
這篇文章借用 FreeSql.Cloud 開源項(xiàng)目的代碼霸妹,講解代理模式的實(shí)際應(yīng)用和收益抑堡,以及需要注意的地方。
FreeSql 是 c#.NET 功能強(qiáng)大的 ORM 框架,定義了 IFreeSql 接口有缆,主要針對(duì)單個(gè) ConnectionString 生產(chǎn) ORM 操作對(duì)象棚壁。
跨多數(shù)據(jù)庫的時(shí)候栈虚,不同的 ConnectionString 需要生成多個(gè) IFreeSql 原始對(duì)象魂务,如果是多租戶場(chǎng)景每個(gè)租戶 ConnectionString 都不相同的情況下,就需要?jiǎng)?chuàng)建 N個(gè) IFreeSql 原始對(duì)象鬓照。
FreeSql.Cloud 正是為了跨多數(shù)據(jù)庫的問題而產(chǎn)生豺裆,它可以解決:
1号显、FreeSqlCloud 實(shí)現(xiàn)多庫版 IFreeSql 接口押蚤,從使用習(xí)慣上保持與單庫版 IFreeSql 一致;
2丐膝、運(yùn)行時(shí)帅矗,F(xiàn)reeSqlCloud 可動(dòng)態(tài)添加或刪除多個(gè) ConnectionString 對(duì)應(yīng)的 IFreeSql;
3累颂、FreeSqlCloud 存儲(chǔ)多租戶 IFreeSql紊馏,最后活躍時(shí)間 > 10分鐘的租戶蒲犬,釋放對(duì)應(yīng) IFreeSql 減少內(nèi)存開銷原叮;
4、FreeSqlCloud 支持隨時(shí) Change 切換到對(duì)應(yīng)的 IFreeSql 進(jìn)行操作擂送;
代理模式實(shí)戰(zhàn)(一)Scoped FreeSqlCloud 多庫版本
IFreeSql 是一個(gè)極為嚴(yán)格嘹吨、簡(jiǎn)單境氢,且功能強(qiáng)大的接口产还,我們一直在嚴(yán)格控制 API 泛濫增長(zhǎng)脐区,泛濫的 API 在后續(xù)改造時(shí)非常痛苦。
正因?yàn)樗暮?jiǎn)單定義炕柔,讓我們有機(jī)會(huì)使用到代理模式實(shí)現(xiàn)新的 IFreeSql 實(shí)現(xiàn)類 FreeSqlCloud匕累。
public class FreeSqlCloud : IFreeSql
{
IFreeSql CurrentOrm => ...; //請(qǐng)看后面
public IAdo Ado => CurrentOrm.Ado;
public IAop Aop => CurrentOrm.Aop;
public ICodeFirst CodeFirst => CurrentOrm.CodeFirst;
public IDbFirst DbFirst => CurrentOrm.DbFirst;
public GlobalFilter GlobalFilter => CurrentOrm.GlobalFilter;
public void Transaction(Action handler) => CurrentOrm.Transaction(handler);
public void Transaction(IsolationLevel isolationLevel, Action handler) => CurrentOrm.Transaction(isolationLevel, handler);
public ISelect<T1> Select<T1>() where T1 : class => CurrentOrm.Select<T1>();
public ISelect<T1> Select<T1>(object dywhere) where T1 : class => Select<T1>().WhereDynamic(dywhere);
public IDelete<T1> Delete<T1>() where T1 : class => CurrentOrm.Delete<T1>();
public IDelete<T1> Delete<T1>(object dywhere) where T1 : class => Delete<T1>().WhereDynamic(dywhere);
public IUpdate<T1> Update<T1>() where T1 : class => CurrentOrm.Update<T1>();
public IUpdate<T1> Update<T1>(object dywhere) where T1 : class => Update<T1>().WhereDynamic(dywhere);
public IInsert<T1> Insert<T1>() where T1 : class => CurrentOrm.Insert<T1>();
public IInsert<T1> Insert<T1>(T1 source) where T1 : class => Insert<T1>().AppendData(source);
public IInsert<T1> Insert<T1>(T1[] source) where T1 : class => Insert<T1>().AppendData(source);
public IInsert<T1> Insert<T1>(List<T1> source) where T1 : class => Insert<T1>().AppendData(source);
public IInsert<T1> Insert<T1>(IEnumerable<T1> source) where T1 : class => Insert<T1>().AppendData(source);
public IInsertOrUpdate<T1> InsertOrUpdate<T1>() where T1 : class => CurrentOrm.InsertOrUpdate<T1>();
}
如上代碼衰琐,若 CurrentOrm
返回值是原始 IFreeSql 對(duì)象炼蹦,即會(huì)代理調(diào)用原始數(shù)據(jù)庫 ORM 操作方法掐隐。方法不多,功能卻強(qiáng)大匿刮,代理模式應(yīng)用起來就會(huì)很輕松熟丸。
由于多個(gè) ConnectionString 的原因膝擂,我們需要定義一個(gè)字典保存多個(gè)原始 IFreeSql 對(duì)象:
readonly Dictionary<string, IFreeSql> _orms = new Dictionary<string, IFreeSql>();
我們已經(jīng)有了 _orms
架馋,還缺什么叉寂?总珠?還缺一個(gè)當(dāng)前 string _dbkey
局服,有了它我們的 CurrentOrm
方法才知道怎么獲取對(duì)應(yīng)的 IFreeSql:
string _dbkey;
IFreeSql CurrentOrm => _orms[_dbkey]; //測(cè)試不糾結(jié)代碼安全
//切換數(shù)據(jù)庫
IFreeSql Change(string db)
{
_dbkey = db;
return _orms[_dbkey];
}
//添加 IFreeSql
FreeSqlCloud Add(string db, IFreeSql orm)
{
if (_dbkey == null) _dbkey = db;
_orms.Add(db, orm);
return this;
}
至此我們基于 Scoped 生命周期的 FreeSqlCloud 就完成了淫奔,DI 代碼大概如下:
public void ConfigureServices(IServiceCollection services)
{
var db1 = new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, "data source=db1.db").Build();
var db2 = new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, "data source=db2.db").Build();
services.AddScoped(provider =>
{
var cloud = new FreeSqlCloud();
cloud.Add("db1", db1);
cloud.Add("db2", db2);
return cloud;
});
}
代理模式實(shí)戰(zhàn)(二)Singleton FreeSqlCloud 多庫版本
實(shí)戰(zhàn)(一)我們實(shí)現(xiàn)了 Scoped 版本唆迁,可是其實(shí)項(xiàng)目中 Singleton 單例才是高性能的保證,特別是多租戶場(chǎng)景鳞溉,每次 new FreeSqlCloud 不止還要循環(huán) Add 那么多次熟菲,實(shí)屬浪費(fèi)!T黍肌厉萝!
其實(shí)單例并非難事榨崩,只需要將 _dbkey
類型修改成 AsyncLocal
母蛛,這個(gè)類型多線程是安全的彩郊,有關(guān)它的原理請(qǐng)看資料:https://www.cnblogs.com/eventhorizon/p/12240767.html
AsyncLocal<string> _dbkey;
IFreeSql CurrentOrm => _orms[_dbkey.Value];
IFreeSql Change(string db)
{
_dbkey.Value = db;
return _orms[_dbkey];
}
FreeSqlCloud Add(string db, IFreeSql orm)
{
if (_dbkey.Value == null) _dbkey.Value = db;
_orms.Add(db, orm);
return this;
}
至此我們就完成了一個(gè)多線程安全的代理模式實(shí)現(xiàn)秫逝,因此我們只需要在 Ioc 注入之前 Register 好原始 IFreeSql 對(duì)象即可:
public void ConfigureServices(IServiceCollection services)
{
var db1 = new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, "data source=db1.db").Build();
var db2 = new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, "data source=db2.db").Build();
var cloud = new FreeSqlCloud();
cloud.Add("db1", db1);
cloud.Add("db2", db2);
services.AddSingleton(cloud);
}
代理模式實(shí)現(xiàn)(三)Singleton FreeSqlCloud 多庫多租戶版本
如上违帆,我們使用字典存儲(chǔ)多個(gè) IFreeSql 原始對(duì)象,在數(shù)量不多的情況下是可行的的畴。
但是如果我們做的是多租戶系統(tǒng)丧裁,那么數(shù)量很可能達(dá)到幾百含衔,甚至上千個(gè) IFreeSql 對(duì)象抱慌,并且這些租戶不全都是活躍狀態(tài)抑进。
因此我們需要一種釋放機(jī)制,當(dāng)租戶最后活躍時(shí)間 > 10分鐘匿情,釋放 IFreeSql 資源炬称,減少內(nèi)存開銷;
我們可以引用 IdleBus
組件解決該問題据德,IdleBus 空閑對(duì)象管理容器棘利,有效組織對(duì)象重復(fù)利用善玫,自動(dòng)創(chuàng)建密强、銷毀或渤,解決【實(shí)例】過多且長(zhǎng)時(shí)間占用的問題薪鹦。有時(shí)候想做一個(gè)單例對(duì)象重復(fù)使用提升性能,但是定義多了,有的又可能一直空閑著占用資源框仔。
IdleBus
專門解決:又想重復(fù)利用离斩,又想少占資源的場(chǎng)景瘪匿。
此時(shí)我們只需要修改內(nèi)部實(shí)現(xiàn)部分代碼如下:
public class FreeSqlCloud : IFreeSql
{
IdleBus<IFreeSql> _orms = new IdleBus<string, IFreeSql>();
AsyncLocal<string> _dbkey;
IFreeSql CurrentOrm => _orms.Get(_dbkey.Value);
IFreeSql Change(string db)
{
_dbkey.Value = db;
return this;
}
FreeSqlCloud Register(string db, Func<IFreeSql> create) //注意 create 類型是 Func
{
if (_dbkey.Value == null) _dbkey.Value = db;
_orms.Register(db, create);
return this;
}
//...
}
public void ConfigureServices(IServiceCollection services)
{
var cloud = new FreeSqlCloud();
cloud.Add("db1", () => new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, "data source=db1.db").Build());
cloud.Add("db2", () => new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, "data source=db1.db").Build());
services.AddSingleton(cloud);
}
代理模式實(shí)現(xiàn)(四)跟隨切換數(shù)據(jù)庫的倉儲(chǔ)對(duì)象
1幻梯、靜態(tài)倉儲(chǔ)對(duì)象
FreeSql.Repository 對(duì)象創(chuàng)建時(shí)固定了原始 IFreeSql吧碾,因此無法跟隨 FreeSqlCloud Change 切換數(shù)據(jù)庫莹桅。
注意:是同一個(gè)對(duì)象實(shí)例創(chuàng)建之后左腔,無法跟隨切換捅儒,創(chuàng)建新對(duì)象實(shí)例不受影響巧还。
因?yàn)橐?Repository 創(chuàng)建之前狞悲,先調(diào)用 fsql.Change 切換好數(shù)據(jù)庫。
2丹拯、動(dòng)態(tài)倉儲(chǔ)對(duì)象
但是乖酬。咬像。生宛。仍然有一種特殊需求陷舅,Repository 在創(chuàng)建之后莱睁,仍然能跟隨 fsql.Change 切換數(shù)據(jù)庫仰剿。
實(shí)戰(zhàn)中 Scoped 生命同期可能有多個(gè) Repository 對(duì)象,因此切換 cloud 即改變所有 Repository 對(duì)象狀態(tài)琳彩,才算方便汁针。
var repo1 = cloud.GetCloudRepository<User>();
var repo2 = cloud.GetCloudRepository<UserGroup>();
cloud.Change("db2");
Console.WriteLine(repo1.Orm.Ado.ConnectionString); //repo -> db2
Console.WriteLine(repo2.Orm.Ado.ConnectionString); //repo -> db2
cloud.Change("db1");
Console.WriteLine(repo1.Orm.Ado.ConnectionString); //repo -> db1
Console.WriteLine(repo2.Orm.Ado.ConnectionString); //repo -> db1
我們?nèi)匀皇褂昧舜砟J绞┪蓿琁BaseRepository 接口定義也足夠簡(jiǎn)單:
提示:關(guān)鍵看 CurrentRepo 的獲取
class RepositoryCloud<TEntity> : IBaseRepository<TEntity> where TEntity : class
{
readonly FreeSqlCloud _cloud;
public RepositoryCloud(FreeSqlCloud cloud)
{
_cloud = cloud;
}
public IBaseRepository<TEntity> CurrentRepo => ...; //請(qǐng)看后面
public IUnitOfWork UnitOfWork
{
get => CurrentRepo.UnitOfWork;
set => CurrentRepo.UnitOfWork = value;
}
public IFreeSql Orm => CurrentRepo.Orm;
public Type EntityType => CurrentRepo.EntityType;
public IDataFilter<TEntity> DataFilter => CurrentRepo.DataFilter;
public ISelect<TEntity> Select => CurrentRepo.Select;
public IUpdate<TEntity> UpdateDiy => CurrentRepo.UpdateDiy;
public ISelect<TEntity> Where(Expression<Func<TEntity, bool>> exp) => CurrentRepo.Where(exp);
public ISelect<TEntity> WhereIf(bool condition, Expression<Func<TEntity, bool>> exp) => CurrentRepo.WhereIf(condition, exp);
public void Attach(TEntity entity) => CurrentRepo.Attach(entity);
public void Attach(IEnumerable<TEntity> entity) => CurrentRepo.Attach(entity);
public IBaseRepository<TEntity> AttachOnlyPrimary(TEntity data) => CurrentRepo.AttachOnlyPrimary(data);
public Dictionary<string, object[]> CompareState(TEntity newdata) => CurrentRepo.CompareState(newdata);
public void FlushState() => CurrentRepo.FlushState();
public void BeginEdit(List<TEntity> data) => CurrentRepo.BeginEdit(data);
public int EndEdit(List<TEntity> data = null) => CurrentRepo.EndEdit(data);
public TEntity Insert(TEntity entity) => CurrentRepo.Insert(entity);
public List<TEntity> Insert(IEnumerable<TEntity> entitys) => CurrentRepo.Insert(entitys);
public TEntity InsertOrUpdate(TEntity entity) => CurrentRepo.InsertOrUpdate(entity);
public void SaveMany(TEntity entity, string propertyName) => CurrentRepo.SaveMany(entity, propertyName);
public int Update(TEntity entity) => CurrentRepo.Update(entity);
public int Update(IEnumerable<TEntity> entitys) => CurrentRepo.Update(entitys);
public int Delete(TEntity entity) => CurrentRepo.Delete(entity);
public int Delete(IEnumerable<TEntity> entitys) => CurrentRepo.Delete(entitys);
public int Delete(Expression<Func<TEntity, bool>> predicate) => CurrentRepo.Delete(predicate);
public List<object> DeleteCascadeByDatabase(Expression<Func<TEntity, bool>> predicate) => CurrentRepo.DeleteCascadeByDatabase(predicate);
}
如上代碼關(guān)鍵實(shí)現(xiàn)部分 CurrentRepo
,我們定義了字典存儲(chǔ)多個(gè) IBaseRepository<TEntity> 原始倉儲(chǔ)對(duì)象:
因?yàn)橐粋€(gè) CloudRepository 對(duì)象會(huì)創(chuàng)建 1-N 個(gè) IBaseRepository 原始對(duì)象幢哨,在不使用 cloud.Change(..) 方法的時(shí)候只會(huì)創(chuàng)建 1 個(gè)捞镰,最多創(chuàng)建 cloud.Registers 數(shù)量毙替,真實(shí)場(chǎng)景中不會(huì)有人在同一個(gè)業(yè)務(wù)把所有 db 都切換個(gè)遍厂画。
readonly Dictionary<string, IBaseRepository<TEntity>> _repos = new Dictionary<string, IBaseRepository<TEntity>>();
protected void ForEachRepos(Action<IBaseRepository<TEntity>> action)
{
foreach (var repo in _repos.Values) action(repo);
}
public void Dispose()
{
ForEachRepos(repo => repo.Dispose());
_repos.Clear();
}
protected IBaseRepository<TEntity> CurrentRepo
{
get
{
var dbkey = _cloud._dbkey.Value;
if (_repos.TryGetValue(dbkey, out var repo) == false)
{
_repos.Add(dbkey, repo = _cloud.Use(dbkey).GetRepository<TEntity>());
if (_dbContextOptions == null) _dbContextOptions = repo.DbContextOptions;
else
{
repo.DbContextOptions = _dbContextOptions;
if (_asTypeEntityType != null) repo.AsType(_asTypeEntityType);
if (_asTableRule != null) repo.AsTable(_asTableRule);
}
}
return repo;
}
}
Type _dbContextOptions;
public DbContextOptions DbContextOptions
{
get => CurrentRepo.DbContextOptions;
set => ForEachRepos(repo => repo.DbContextOptions = value);
}
Type _asTypeEntityType;
public void AsType(Type entityType)
{
_asTypeEntityType = entityType;
ForEachRepos(repo => repo.AsType(entityType));
}
Func<string, string> _asTableRule;
public void AsTable(Func<string, string> rule)
{
_asTableRule = rule;
ForEachRepos(repo => repo.AsTable(rule));
}
由于 DbContextOptions屎慢、AsType忽洛、AsTable 比較特殊欲虚,需要將多個(gè)原始倉儲(chǔ)對(duì)象傳播設(shè)置苍在,代碼如上荠商。
最終還要為 CloudRepository 創(chuàng)建擴(kuò)展方法:
public static IBaseRepository<TEntity> GetCloudRepository<TEntity>(this FreeSqlCloud cloud)
where TEntity : class
{
return new RepositoryCloud<TEntity>(cloud);
}
結(jié)語
Repository 是一種非常方便做設(shè)計(jì)的模式,F(xiàn)reeSql 還有很多一些其他設(shè)計(jì)模式的應(yīng)用莱没,如果有興趣以后找機(jī)會(huì)再寫文章初肉。
作者是什么人?
作者是一個(gè)入行 18年的老批饰躲,他目前寫的.net 開源項(xiàng)目有:
開源項(xiàng)目 | 描述 | 開源地址 | 開源協(xié)議 |
---|---|---|---|
FreeIM | 聊天系統(tǒng)架構(gòu) | https://github.com/2881099/FreeIM | MIT |
FreeRedis | Redis SDK | https://github.com/2881099/FreeRedis | MIT |
csredis | https://github.com/2881099/csredis | MIT | |
FightLandlord | 斗DI主網(wǎng)絡(luò)版 | https://github.com/2881099/FightLandlord | 學(xué)習(xí)用途 |
FreeScheduler | 定時(shí)任務(wù) | https://github.com/2881099/FreeScheduler | MIT |
IdleBus | 空閑容器 | https://github.com/2881099/IdleBus | MIT |
FreeSql | ORM | https://github.com/dotnetcore/FreeSql | MIT |
FreeSql.Cloud | 分布式tcc/saga | https://github.com/2881099/FreeSql.Cloud | MIT |
FreeSql.AdminLTE | 低代碼后臺(tái)生成 | https://github.com/2881099/FreeSql.AdminLTE | MIT |
FreeSql.DynamicProxy | 動(dòng)態(tài)代理 | https://github.com/2881099/FreeSql.DynamicProxy | 學(xué)習(xí)用途 |
需要的請(qǐng)拿走牙咏,這些都是最近幾年的開源作品臼隔,以前更早寫的就不發(fā)了。