前言
项目中系统通常会使用单例模式,但是普通单例模式会将系统API全部暴露给外界,而与该系统交互的其他系统或上层模块往往仅需要与系统的单个模块功能进行交互,为了对系统间互相调用时模块的访问权限进行限制,我设计了以下的一种解决方案
一、代码示例
系统方:
/*
* 总结:一个系统需要的组件有,定义模块功能的接口,权限控制的接口,访问模块的访问器接口
*/
internal class Services : IModule1, IModule2, IServiceVistor
{
#region 分割模块功能的接口
public interface IModule1
{
void API1();
}
public interface IModule2
{
void API2();
}
#endregion
#region 模块权限控制接口
public interface IModule1Auth
{
}
public interface IModule2Auth
{
}
#endregion
//访问器接口(供外界获取模块API)
public interface IServiceVistor
{
Services GetModule(object o);
IModule1 GetModule(IModule1Auth o);
IModule2 GetModule(IModule2Auth o);
}
public static readonly IServiceVistor instance = new Services();
public void API2()
{
Console.WriteLine(nameof(API2));
}
public void API1()
{
Console.WriteLine("ServiceAPI1");
}
//public static Services GetModule(object o)
//{
// return instance;
//}
//public static IModule1 GetModule(IModule1Auth o)
//{
// return instance;
//}
//public static IModule2 GetModule(IModule2Auth o)
//{
// return instance;
//}
public Services GetModule(object o)
{
return (Services)instance;
}
public IModule1 GetModule(IModule1Auth o)
{
return (IModule1)instance;
}
public IModule2 GetModule(IModule2Auth o)
{
return (IModule2)instance;
}
}
static class AuthConstraintExtent
{
public static IModule1 GetModule(this IModule1Auth auth)
{
return instance.GetModule(auth);
}
public static IModule2 GetModule(this IModule2Auth auth)
{
return instance.GetModule(auth);
}
}
调用方:
internal class Client1: IModule2Auth
{
//定义一个用于缓存模块的字段
public IModule2 API { get;private set; }
//初始化方法中进行初始化
public void Init()
{
//方式一通过系统实例获取模块
//API = Services.instance.GetModule(this);
//方式二通过扩展方法获取模块
API = this.GetModule();
API.API2();
}
}
当调用方尝试获取一个未经授权的模块时,会发生编译错误,示例如下:
这样便从编辑器层面解决了调用方试图访问授权范围之外模块的问题。
如果不需要限制调用方对系统模块访问权限,还可以以如下调用方式获取整个系统实例:
var instance= Services.instance.GetModule(this);
二、实现思路
实现思路:
通过接口隔离技术,将系统API功能拆分成各个模块,并在系统中定义系统对应的权限约束接口,该接口会作为标记以告知系统调用哪个重载方法以获取API,调用方仅仅需要实现该约束接口就可以获取到受权限约束的模块部分
Tips: 这实际为访问者模式的一种实现,通过引入访问者模式,使得客户端系统即使通过系统实例也无法访问系统API,仅能获取自身对应权限的接口
三、改进方案
上述解决方案在调用方尝试获取未授权模块时虽然编辑器会报错起到一定警示作用,但调用方依然可以进行强制转换绕过权限限制,为了解决此问题,给出以下改进方案:
/*
* 总结:一个系统需要的组件有,定义模块功能的接口,权限控制的接口,访问模块的访问器接口
*/
internal class Services :IModule1,IModule2,IServiceVistor
{
int data1=1;
int data2 = 2;
#region 分割模块功能的接口
public interface IModule1
{
void API1();
}
public interface IModule2
{
void API2();
}
#endregion
#region 模块权限控制接口
public interface IModule1Auth
{
}
public interface IModule2Auth
{
}
#endregion
//访问器接口(供外界获取模块API)
public interface IServiceVistor
{
Services GetModule(object o);
IModule1 GetModule(IModule1Auth o);
IModule2 GetModule(IModule2Auth o);
}
public static readonly IServiceVistor vistor = new Services();
//public static Services GetModule(object o)
//{
// return instance;
//}
//public static IModule1 GetModule(IModule1Auth o)
//{
// return instance;
//}
//public static IModule2 GetModule(IModule2Auth o)
//{
// return instance;
//}
public void API1()
{
Console.WriteLine($"ServiceAPI1,data:{data1}");
}
public void API2()
{
Console.WriteLine($"ServiceAPI2,data:{data2}");
}
public Services GetModule(object o)
{
return this;
}
public IModule1 GetModule(IModule1Auth o)
{
return new Module1(this);
}
public IModule2 GetModule(IModule2Auth o)
{
return new Module2(this);
}
struct Module1 : IModule1
{
private Services instance;
public Module1(Services instance)
{
this.instance = instance;
}
public void API1()
{
instance.API1();
}
}
struct Module2 : IModule2
{
private Services instance;
public Module2(Services instance)
{
this.instance = instance;
}
public void API2()
{
instance.API2();
}
}
}
与上述解决方案不同之处在于,改进方案将不同的api调用拆分到嵌套的子类模块中,调用方能够获取的仅仅是一个代理对象,所有api调用都是通过该代理转接到系统实例,因此也杜绝了调用方通过强制转换获取非法模块的风险。
总结
通过结合使用接口隔离技术、代理模式与访问者模式,实现了对系统模块访问权限的控制,这是我想到的一种解决方案原型,当然我也发现其中的一些问题:
- 由于每个系统模块功能拆分都是由自己决定,接口必须由系统自身实现,难以抽象出上层,使得系统实现部分代码量增多。
- 如果调用方需要获取系统的数个模块调用权限,这种设计很难实现,只能扩展新的模块接口,当然实际应用中这种情形应该很少会有,系统功能模块划分理应都是针对性的提供服务