你所描述的听起来很像动画。动画是几个框架中的正式概念(想到CSS 和WPF)。
动画的目标是将某物从一个值过渡到下一个值。
有多种方法可以实现这种转变。有时您希望 2D 点跟随 Bézier curve,因为曲线的 t 变量会在一段时间内从 0 线性变为 1。其他时候,您希望通过在color wheel 周围走很长一段路,使颜色从红色平滑过渡到蓝色。
这是一个可以抽象该概念的接口:
public interface IAnimation<T>
{
T Current { get; }
void Update(double progress); // progress is a number between 0 and 1
}
例如,如果您希望 2D 点在 800 单位宽和 2 单位高的正弦波上进行动画处理:
public sealed class Point2DAnimation : IAnimation<Point2D>
{
public Point2D Current { get; private set; }
public void Update(double progress)
{
Current = new Point2D(progress * 800, Math.Sin(progress * Math.PI * 2));
}
}
还有多种方法可以推动过渡。驱动它的一种常见方式是说“我希望这个动画在 X 秒内尽可能顺利地发生”。但有时您可能希望动画从头开始重复几次。或者,也许您希望动画先向前然后向后再向前,以此类推。
我们可以定义一个接口来抽象出IAnimation<T> 可以被驱动的不同方式。一种实现可能在内部有一个计时器,它的滴答频率足以产生平滑的错觉,因为progress 从 0 变为 1 与每个滴答时刻经过的时间量成正比。
但我想在这里暂停片刻,问一个问题。
抽象的消费者需要什么?
如果您的所有代码都需要对T 和一个名为Update() 的方法的读取访问权限,那么Property<T> 听起来就像您的票:
public sealed class Property<T>
{
readonly Func<T, T> _update;
public Property(T initial, Func<T, T> update)
{
Value = initial;
_update = update;
}
public T Value { get; private set; }
public void Update()
{
Value = _update(Value);
}
}
该类封装了一个可读值,并为您提供了一个Update() 方法来调用。不需要IAnimation<T> 接口或任何其他花哨的装饰。当然,您必须使用 update 委托来实例化 Property<T> 的实例,该委托可以满足您的需求。但是您可以在某处添加一些静态方法,以便以最常用的方式进行设置。例如:
public static class PropertyHelpers
{
public Property<double> CreateZoomLevelProperty(double initial, double increment, double target)
{
return new Property<double>(initial, old =>
{
var @new = old + increment;
if (increment > 0 && @new > target || increment < 0 && @new < target)
return target;
else
return @new;
});
}
}
另一方面,如果您想将值视为由代码中其他内容控制的值流,并且您只需要在它们到达时获得对值的读取访问权限,那么可能是IObservable<T>(和Reactive NuGet 包)是您所追求的。例如:
public class Camera
{
double _zoomLevel;
public IDisposable UpdateZoomLevel(IObservable<double> zoomLevels)
{
return zoomLevels.Subscribe(zoomLevel => _zoomLevel = zoomLevel);
}
}
当然,其他代码有责任在某处提出IObservable<double>,以您想要的方式发布缩放级别。
所以我想我的答案可以总结为:
这取决于。
免责声明:也许上面的代码都不会编译