oopで関数合成ライクなことが実現したい(のと、少しaopについて

発端
アスペクト指向プログラミング (AOP) の核心は、横断的な関心事です。作成者は、ロギング、認可、認証、トランザクション管理などの懸念事項を認識していました。彼らは、なぜこれらをプログラムを通じて広める必要があるのかと尋ねました。
それらの実装はどこでもほぼ同じです
これらはビジネス ロジックに固有のものではありません。それらは関数の意味上の明確さから目をそらしてしまいます
これらはシステムのほとんどのコンポーネントで必要となります。
このアイデアは、これらの横断的な懸念事項を、他のロジック フローに対する一種のアドオンまたはインターセプターとして実行することです。これにより、横断的な懸念事項を一元的に定義し、コア ロジックとは別に展開し、コア ロジックに焦点を当てたままにすることができます。

c#の簡単な例
loggingの共通部分として想定される部分
try
{
//Do something here
logger.LogSuccess(…
//..
}
catch (Exception ex)
{
logger.LogError(…
throw;
}

LoggingAwareDocumentSourceというデコレーターを作る
public class LoggingAwareDocumentSource : IDocumentSource
{
private readonly IDocumentSource decoratedDocumentSource;
//..
public Document[] GetDocuments(string format)
{
try
{
var documents = decoratedDocumentSource.GetDocuments(format);
logger.LogSuccess(
"Obtained " + documents.Length + " documents of type " + format);
return documents;
}
catch (Exception ex)
{
logger.LogError(
"Error obtaining documents of type " + format, ex);
throw;
}
}
}
var source =
new LoggingAwareDocumentSource (
new DocumentSource (connectionString),
new Logger(…));

これだと汎用性が足りない、デコレーターをたくさん作る羽目になる
あと詳細の情報が抜け落ちる
- インターフェースを少数にする
- 動的プロキシを使う
で解決する

- インターフェースを少数にする
ただこれだと、インターフェースが制限されて不便
それと詳細情報はまだ抜け落ちてる
public interface IQueryHandler<TQuery, TResult>
{
TResult Handle(TQuery query);
}
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
public class DocumentSource : IQueryHandler<GetDocumentsQuery, GetDocumentsResult>
{
//..
public GetDocumentsResult Handle(GetDocumentsQuery query)
{
using (var context = CreateEFContext())
{
return
new GetDocumentsResult(
context
.Documents
.Where(c => c.Name.EndsWith("." + query.Format))
.ToArray());
}
}
//..
}
public class LoggingAwareQueryHandler<TQuery, TResult> : IQueryHandler<TQuery,TResult>
{
private readonly IQueryHandler<TQuery, TResult> decoratedHandler;
//..
public TResult Handle(TQuery query)
{
try
{
var result = decoratedHandler.Handle(query);
logger.LogSuccess(...);
return result;
}
catch (Exception ex)
{
logger.LogError(..., ex);
throw;
}
}
}

- 動的プロキシを使う
設計時に 100 個のデコレータを作成する代わりに、1 つの汎用アスペクトを作成し、それを使用して実行時に 100 個のデコレータを生成
良さそうに見えるが、リフレクションに依存していて性能に悪い
namespace Application.CastleDynamicProxy
{
class Program
{
static void Main(string[] args)
{
var finder =
new FakeDocumentSource("connectionString1")
.AsLoggable<IDocumentSource>(
new LoggingData { Name = "connectionString", Value = "connectionString1"},
new LoggingData { Name = "Department", Value = "A" });
var result = finder.GetDocuments("docx");
Console.ReadLine();
}
}
}
namespace Application.CastleDynamicProxy
{
public static class ExtensionMethods
{
public static T AsLoggable<T>(
this T instance, params LoggingData[] constantLoggingData)
{
if(!typeof(T).IsInterface)
throw new Exception("T should be an interface");
ProxyGenerator proxyGenerator = new ProxyGenerator();
return
(T)proxyGenerator.CreateInterfaceProxyWithTarget(
typeof(T),
instance,
new LoggingAspect(
new ConsoleLogger(),
new CompositeMethodLoggingDataExtractor(
new MethodDescriptionLoggingDataExtractor(),
new ConstantMethodLoggingDataExtractor(constantLoggingData)),
new CompositeLoggingDataExtractor(
new LogCountAttributeBasedLoggingDataExtractor(),
new LogAttributeBasedLoggingDataExtractor()),
new CompositeLoggingDataExtractor(
new LogCountAttributeBasedLoggingDataExtractor(),
new LogAttributeBasedLoggingDataExtractor())));
}
}
}

いずれにせよ従来のoopで関数合成っぽいことは結構大変そう

pythonの@から始まる関数デコレーターとかあれば、function渡して引数とかを受け取れるようにinterceptすれば、任意のfunctionをloggingとかできるかも

def log(func):
"""関数の呼び出しをログ出力するデコレーター"""
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args={args} kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"Result: {result}")
return result
return wrapper
@log
def greet(name):
return f"Hello {name}!"
greet("Alice") # Calling greet with args=('Alice',) kwargs={} / Result: Hello Alice!

def log(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == 'info':
print(f"[INFO] Calling {func.__name__} with args={args} kwargs={kwargs}")
elif level == 'debug':
print(f"[DEBUG] Calling {func.__name__} with args={args} kwargs={kwargs}")
result = func(*args, **kwargs)
if level == 'info':
print(f"[INFO] Result: {result}")
elif level == 'debug':
print(f"[DEBUG] Result: {result}")
return result
return wrapper
return decorator
@log(level='info')
def greet(name):
return f"Hello {name}!"
greet("Alice") # [INFO] Calling greet with args=('Alice',) kwargs={} / [INFO] Result: Hello Alice!

ビンゴで無名関数というかオブジェクトに紐づかない関数/関数を受け取る関数を作れる言語だと大丈夫そう?