面向对象程序中的功能原则
本文关键字:功能 原则 程序 面向对象 | 更新日期: 2023-09-27 18:05:59
今天我看到了一段代码,我在想它的意义。所以我做了一个小例子,你可以看到它是如何工作的。据我所知,它应该是嵌套的功能,大量使用lambda -微积分,就像在函数式语言中使用的那样。因为我发现这不是很好理解(从一个文件跳到另一个文件),我只是想知道你们中是否有人有类似的经历。
功能示例一
public void DoSomethingA(int inputInt, Action<int?, Exception> result)
{
try
{
int? retVal = inputInt;
result(retVal, null);
}
catch (Exception e)
{
result(null, e);
}
}
函数二示例
static void DoSomethingB(int? baseInt, Action<Exception> result)
{
try
{
result(null);
}
catch (Exception e)
{
result(e);
}
}
示例两者同时调用。
public int? CalculateSomething(int input)
{
int? result;
DoSomethingA(input, (resultInt, aException) =>
{
if (aException != null)
{
return;
}
DoSomethingB(resultInt, bException =>
{
if (bException != null)
{
return;
}
result = resultInt;
});
});
return null;
}
这可能是有趣的正常化"Main"函数将有某种承诺。所以它注册了函数,等待结束,然后得到结果。
你所描述的是一个单子。特别是Try
单子。我将在下面展示如何实现一个Try单子,您的CalculateSomething
代码将如下所示:
public Try<int> CalculateSomething(int input) =>
from x in DoSomethingA(input)
from y in DoSomethingB(x)
select y;
无论如何都很容易理解。
首先声明一个代表Try操作的委托:
public delegate TryResult<T> Try<T>();
下一步定义TryResult<T>
。它将捕获成功时的返回值,以及失败时的异常:
public struct TryResult<T>
{
internal readonly T Value;
internal readonly Exception Exception;
public TryResult(T value)
{
Value = value;
Exception = null;
}
public TryResult(Exception e)
{
Exception = e;
Value = default(T);
}
public static implicit operator TryResult<T>(T value) =>
new TryResult<T>(value);
internal bool IsFaulted => Exception != null;
public override string ToString() =>
IsFaulted
? Exception.ToString()
: Value.ToString();
public static readonly TryResult<T> Bottom = new InvalidOperationException();
}
接下来为Try
委托定义一些扩展方法(这是c#委托支持扩展方法的一个鲜为人知的特性):
public static class TryExtensions
{
public static TryResult<T> Try<T>(this Try<T> self)
{
try
{
if (self == null) return TryResult<T>.Bottom;
return self();
}
catch (Exception e)
{
return new TryResult<T>(e);
}
}
public static R Match<T, R>(this Try<T> self, Func<T, R> Succ, Func<Exception, R> Fail)
{
var res = self.Try();
return res.IsFaulted
? Fail(res.Exception)
: Succ(res.Value);
}
}
它们都允许以安全的方式调用Try
委托。关键是Match
扩展方法,它在结果上'模式匹配':
int res = CalculateSomething(1).Match(
Succ: value => value,
Fail: excep => 0
);
因此,您被迫承认函数可能会抛出异常以获取该值。
这里缺少的一件事是它如何与LINQ:
一起工作。public static class TryExtensions
{
public static Try<U> Select<T, U>(this Try<T> self, Func<T, U> select) =>
new Try<U>(() =>
{
var resT = self.Try();
if (resT.IsFaulted) return new TryResult<U>(resT.Exception);
return select(resT.Value);
});
public static Try<V> SelectMany<T, U, V>(
this Try<T> self,
Func<T, Try<U>> bind,
Func<T, U, V> project ) =>
new Try<V>(() =>
{
var resT = self.Try();
if (resT.IsFaulted) return new TryResult<V>(resT.Exception);
var resU = bind(resT.Value).Try();
if (resU.IsFaulted) return new TryResult<V>(resT.Exception);
return new TryResult<V>(project(resT.Value, resU.Value));
});
}
Select
允许此操作:
var res = from x in y
select x;
SelectMany
允许此操作:
var res = from x in y
from z in x
select z;
也就是说它允许多个from语句按顺序运行。这就是所谓的monadic绑定(但是你不需要知道它的工作原理——我不想在这里写monad教程)。从本质上讲,它捕获了CalculateSomething
示例中的嵌套模式,因此您不必再次手动编写它。
就是这样。上面的代码,你只写一次。现在让我们实现DoSomething
函数:
public Try<int> DoSomethingA(int inputInt) => () =>
inputInt;
public Try<int> DoSomethingB(int inputInt) => () =>
{
throw new Exception();
};
注意它们是如何被定义为lambda的。如果你使用c# 5或更低版本,它们看起来像这样:
public Try<int> DoSomethingA(int inputInt)
{
return () => inputInt;
}
如果你想看到更完整的实现,请查看我的c#语言-ext函数库,那里有一个Try实现。它有许多有用的扩展方法,允许您编写功能代码,而无需使用try/catch的模板。
有时候遵循良好设计的代码可能比单一函数更难理解,但重用某些部分代码并避免代码重复要容易得多……
显然,通过正确地命名对象和函数,代码应该更容易理解。
在上面的代码中,最好忽略函数1和函数2中的异常,并在调用者处处理它们。这样,操作就简化了。例如,下面的代码要简单得多,但等效:public int? CalculateSomething(int input)
{
try
{
var result1 = SomeMath(input);
var result2 = SomeOtherMath(result1);
return result2;
}
catch
{
return null;
}
}
这段代码可以更容易地推广到处理一个操作列表,其中一个函数的结果是下一个函数的输入。