Якоюсь обмеженою формою вашого наміру є, наскільки мені відомо, на Java та C # за допомогою комбінації анотацій та динамічного проксі-шаблону (існують вбудовані реалізації для динамічних проксі-серверів у Java та C #).
Версія Java
Анотація:
@Target(ElementType.PARAMETER)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface IntRange {
int min ();
int max ();
}
Клас Wrapper, що створює екземпляр Proxy:
public class Wrapper {
public static Object wrap(Object obj) {
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new MyInvocationHandler(obj));
}
}
InvocationHandler, що виконує функцію обходу при кожному виклику методу:
public class MyInvocationHandler implements InvocationHandler {
private Object impl;
public MyInvocationHandler(Object obj) {
this.impl = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Annotation[][] parAnnotations = method.getParameterAnnotations();
Annotation[] par = null;
for (int i = 0; i<parAnnotations.length; i++) {
par = parAnnotations[i];
if (par.length > 0) {
for (Annotation anno : par) {
if (anno.annotationType() == IntRange.class) {
IntRange range = ((IntRange) anno);
if ((int)args[i] < range.min() || (int)args[i] > range.max()) {
throw new Throwable("int-Parameter "+(i+1)+" in method \""+method.getName()+"\" must be in Range ("+range.min()+","+range.max()+")");
}
}
}
}
}
return method.invoke(impl, args);
}
}
Приклад-інтерфейс для використання:
public interface Example {
public void print(@IntRange(min=0,max=100) int num);
}
Основний метод:
Example e = new Example() {
@Override
public void print(int num) {
System.out.println(num);
}
};
e = (Example)Wrapper.wrap(e);
e.print(-1);
e.print(10);
Вихід:
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy0.print(Unknown Source)
at application.Main.main(Main.java:13)
Caused by: java.lang.Throwable: int-Parameter 1 in method "print" must be in Range (0,100)
at application.MyInvocationHandler.invoke(MyInvocationHandler.java:27)
... 2 more
C # -Версія
Анотація (в C # називається атрибут):
[AttributeUsage(AttributeTargets.Parameter)]
public class IntRange : Attribute
{
public IntRange(int min, int max)
{
Min = min;
Max = max;
}
public virtual int Min { get; private set; }
public virtual int Max { get; private set; }
}
Підклас DynamicObject:
public class DynamicProxy : DynamicObject
{
readonly object _target;
public DynamicProxy(object target)
{
_target = target;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
TypeInfo clazz = (TypeInfo) _target.GetType();
MethodInfo method = clazz.GetDeclaredMethod(binder.Name);
ParameterInfo[] paramInfo = method.GetParameters();
for (int i = 0; i < paramInfo.Count(); i++)
{
IEnumerable<Attribute> attributes = paramInfo[i].GetCustomAttributes();
foreach (Attribute attr in attributes)
{
if (attr is IntRange)
{
IntRange range = attr as IntRange;
if ((int) args[i] < range.Min || (int) args[i] > range.Max)
throw new AccessViolationException("int-Parameter " + (i+1) + " in method \"" + method.Name + "\" must be in Range (" + range.Min + "," + range.Max + ")");
}
}
}
result = _target.GetType().InvokeMember(binder.Name, BindingFlags.InvokeMethod, null, _target, args);
return true;
}
}
ExampleClass:
public class ExampleClass
{
public void PrintNum([IntRange(0,100)] int num)
{
Console.WriteLine(num.ToString());
}
}
Використання:
static void Main(string[] args)
{
dynamic myObj = new DynamicProxy(new ExampleClass());
myObj.PrintNum(99);
myObj.PrintNum(-5);
}
На закінчення ви бачите, що ви можете змусити щось подібне працювати на Java , але це не зовсім зручно, тому що
- Клас проксі можна просто інстанціювати для інтерфейсів, тобто ваш клас повинен реалізувати інтерфейс
- Дозволений діапазон може бути оголошений лише на рівні інтерфейсу
- Пізніше використання починається з додатковими зусиллями на початку (MyInvocationHandler, завершення кожної інстанції), що також трохи знижує зрозумілість
Можливості класу DynamicObject в C # знімають обмеження інтерфейсу, як ви бачите в реалізації C #. На жаль, у цьому випадку ця динамічна поведінка знімає безпеку статичного типу, тому для перевірки, чи дозволений виклик методу на динамічному проксі, потрібно перевірити час виконання.
Якщо ці обмеження прийнятні для вас, то це може послужити основою для подальшого копання!