使用 Silverlight 4 构建 Pandora 克隆






4.94/5 (50投票s)
利用 Silverlight 4 构建一个有趣、真实的应用程序
- 源码:CodePlex 上的源码
- 演示:在线演示
对于不熟悉的人来说,Pandora 是一种流行的基于 Flash 的互联网电台服务:用户通过提供一位艺术家或一首歌曲作为种子,并通过对播放的歌曲进行“赞”或“踩”来创建自己的电台。该软件会根据这些输入播放更多用户喜欢的音乐。
在本文中,我将向您展示如何使用 Silverlight 4、WCF 和 Entity Framework 构建一个类似 Pandora 的音乐服务。最终成果是 Chavah(“HA-vah”),一个有趣的 Pandora Silverlight 克隆应用,可以在 judahhimango.com/chavah 在线体验。
引言
您可能会问,为什么要构建一个 Pandora 克隆应用?
我有一个自私的动机:Pandora 不播放我最感兴趣的音乐。作为一名耶稣的犹太追随者,我喜欢一种名为弥赛亚犹太音乐的微小宗教音乐流派,这个流派太小了,以至于 Pandora 不知道我们任何的音乐。
我想,“为什么不构建一个 Pandora 克隆应用来播放一些优秀的弥赛亚犹太歌曲呢?”它主要服务于我自己,但也服务于我们社区中的其他人。而且,它还能提高人们对一些优秀弥赛亚音乐的认识。
但更普遍的动机是,“为什么不呢?”无论如何,它都是一次很棒的学习体验,让一个人接触使用 Silverlight、WCF 和 Entity Framework 的完整客户端/服务器应用程序。这些技术目前很受欢迎,并且受到雇主的需求,所以它也是一份很棒的简历素材。
出于这些动机,Chavah 项目诞生了。Chavah 是我尝试构建一个播放弥赛亚犹太音乐的 Pandora 式克隆应用,也是本文的主题。
为什么选择 Silverlight?
在 2010 年专业开发者大会上,微软大肆宣传 IE9 及其 HTML 5 支持,而对 Silverlight 却鲜有评论。因此,有人猜测 Silverlight 是否会淡出,转而支持 HTML 5;人们质疑为什么要使用 Silverlight 而不是 HTML 5。
虽然微软后来重申了他们对 Silverlight 的长期支持,但对于像 Chavah 这样的 Web 应用程序来说,**为什么**要使用 Silverlight 仍然是一个非常现实的问题。
我最终选择 Silverlight 的原因很实际:
- Silverlight 比 HTML 5 具有更好的跨平台性。我的大多数目标受众仍然运行着对 HTML 5 支持有限或没有支持的浏览器。即使是少数拥有 HTML 5 兼容浏览器的用户,支持也并非跨浏览器一致,也不稳定。我的目标受众是 Windows 和 Mac 用户,而 Silverlight 目前在这两个平台上,在大多数任何 Web 浏览器中都能很好地运行。
- 工具棒极了。Visual Studio + Blend 是一个难以匹敌的组合。C# 是一种很棒的语言,拥有出色的工具和重构支持。动画、路径、柔和的 UI 边缘、发光效果、阴影效果以及所有性感的 UI 元素,使用 Blend 都能轻松实现。
- Silverlight 总是会领先 HTML 规范一步。等到 HTML 5 广泛普及之时,Silverlight 将拥有只有 HTML 6 才能带来的功能。这就是 HTML 规范委员会设计的天性。Silverlight 将不断创新并带来 HTML 缺乏的新功能,而且交付速度和普及范围都将超过 HTML 规范。
- Silverlight 是一个应用程序开发平台;HTML 的初衷是用于文档。Chavah 是一个应用程序,而不是一个文档。HTML + Javascript + CSS 技巧可以将文档变成应用程序,但最终,一个为应用程序而构建的平台对于像 Chavah 这样的应用程序来说更具吸引力。
- 由于 Chavah 是一个应用程序,我希望用户能够将 Chavah 本地安装到他们的桌面。凭借 Silverlight 的离线功能,用户可以选择将 Chavah 安装为本地应用程序。这在 HTML + CSS + Javascript 中是不可能实现的,除非使用特定于浏览器的 hack,如 IE9 的“固定网站”或 Google Chrome 的“应用程序快捷方式”。
\
关注点
在展示 Chavah 如何构建时,本文将向您展示如何模拟 Pandora 等应用程序的外观和感觉——流畅的 UI 布局、动画、流畅的体验——所有这些都在 Silverlight 4 中实现。我将向您展示一个真实世界的应用程序如何利用 Silverlight 中的一些新功能,例如离线功能、缓动动画和 GPU 加速。
此外,我们还将介绍与后端 WCF Web 服务通信,其中包含“根据用户的喜好选择歌曲”的逻辑。
我将向您展示如何使用 WCF、Entity Framework 和 SQLite 为您的应用程序添加一些有趣的社交功能。
此外,我还会涉及一些即将推出的热门技术——响应式扩展 (Reactive Extensions) 和代码契约 (Code Contracts)——并提供我对它们在现代应用中的可行性和实用性的看法。
需要注意的一点是:尽管 Chavah 的灵感来源于 Pandora,但请注意,与 Pandora 不同的是,Chavah 不使用音乐基因组计划来预测您会喜欢哪种音乐。这超出了本项目的范围。Chavah 会先播放所有歌曲,直到您开始对歌曲进行“赞”和“踩”操作,此时它会使用一种特殊的算法根据您的偏好选择歌曲。我们将在本文后面介绍这种更简单的算法。
Using the Code
说到视觉效果,Pandora 的核心是屏幕右侧出现的单个歌曲**磁贴**,它们流畅地移动到位。一旦到位,歌曲就开始播放。用户可以暂停歌曲、跳过歌曲、赞歌曲或踩歌曲。
这是我们要解决的第一部分——我们如何构建像上面图片中 Pandora 歌曲磁贴那样的 UI 控件?以及我们如何像那样流畅地将它移动到位?
构建歌曲磁贴
首要任务是构建一个歌曲磁贴。
作为 Silverlight 可扩展 UI 框架的证明,我们将完全使用 Silverlight 框架中的内置控件来构建歌曲磁贴控件。我们将为某些组件定制外观和感觉,但这里没有使用任何特殊的第三方工具包,所有功能都内置其中。
控件的轮廓非常简单
正如您所看到的,这里没有什么太花哨的东西:我们有一个 `Border` 容纳整个磁贴。实现圆角效果很简单,只需设置 Border 的 `CornerRadius` 属性即可。
此 UI 中唯一需要付出努力的部分是“赞”和“踩”按钮。从功能上讲,它们是 `RadioButtons`:如果一个被选中,另一个就会取消选中。(也就是说,您不能同时“赞”和“踩”一首歌。)
显然,内置 `RadioButton` 的外观和感觉不适用。我们需要改变单选按钮的外观以显示点赞/点踩。
为了实现这一点,您可以为单选按钮构建一个自定义 ControlTemplate。对于 Chavah 的“赞”按钮,它非常简单:
<ControlTemplate x:Key="ThumbUpStyle" TargetType="RadioButton">
<Grid>
<Image x:Name="imageUnchecked" Width="16" Height="16"
Source="http://judahhimango.com/Chavah/asyncImages/thumbUp.png" />
<Image x:Name="imageChecked" Width="16" Height="16"
Opacity="0" Source="http://judahhimango.com/Chavah/asyncImages/thumbUpChecked.png" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimation Duration="0:0:0.2" Storyboard.TargetName="imageChecked"
Storyboard.TargetProperty="(UIElement.Opacity)" To="1" />
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked">
<Storyboard>
<DoubleAnimation Duration="0:0:0.2" Storyboard.TargetName="imageChecked"
Storyboard.TargetProperty="(UIElement.Opacity)" To="0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
控件模板定义了 2 个图像:“选中”的赞和“未选中”的赞。当 RadioButton 从选中到未选中反之亦然时,我们只需使用不透明度动画隐藏一个图像并显示另一个。最终结果是一个外观不错的赞控件,在选中状态之间切换时具有很好的动画效果。
Silverlight 的控件模板使其非常简单地创建了一个类似于 Pandora 点赞控件的自定义单选按钮。
您会注意到在赞和踩按钮之间有一个数字。这是我们稍后会介绍的一个有趣社交功能的一部分。
将歌曲磁贴动画到指定位置
我之前提到,当 Pandora 播放歌曲时,歌曲磁贴不会突兀地突然出现,而是磁贴的边缘出现在屏幕右侧,并流畅地移动到指定位置。
这种效果是构建流畅 UI 的更广泛努力的一部分,在软件开发领域,尤其是在富互联网应用领域,这种努力越来越受欢迎。
我们如何在 Silverlight 中实现相同的效果?
我的第一个想法是使用 Silverlight 内置的动画系统来动画磁贴的边距:将歌曲标题的边距设置为一个大数字,使其超出屏幕,然后将其动画到指定位置。
在 WPF 中,我们可以通过使用 ThicknessAnimation 来动画歌曲控件的边距来实现此目的。
不幸的是,截至 Silverlight 4,Silverlight 没有 ThicknessAnimation。
咨询了强大的谷歌大神们后,我发现其他人通过使用自定义 ThicknessAnimation 解决了这个问题。我用一些缓动动画进一步定制了它,稍后会详细介绍。
public class ThicknessAnimation
{
// The time along the animation from 0-1
public static DependencyProperty TimeProperty = DependencyProperty.RegisterAttached("Time",
typeof(double), typeof(DoubleAnimation), new PropertyMetadata(OnTimeChanged));
// The object being animated
public static DependencyProperty TargetProperty = DependencyProperty.RegisterAttached("Target",
typeof(DependencyObject), typeof(ThicknessAnimation), null);
// The thickness we're animating to
public static DependencyProperty FromProperty = DependencyProperty.RegisterAttached("From",
typeof(Thickness), typeof(DependencyObject), null);
// The tickness we're animating from
public static DependencyProperty ToProperty = DependencyProperty.RegisterAttached("To",
typeof(Thickness), typeof(DependencyObject), null);
// The target property to animate to. Should have a property type of Thickness
public static DependencyProperty TargetPropertyProperty = DependencyProperty.RegisterAttached(
"TargetProperty", typeof(DependencyProperty), typeof(DependencyObject), null);
public static Timeline Create(DependencyObject target, DependencyProperty targetProperty,
Duration duration, Thickness from, Thickness to)
{
DoubleAnimation timeAnimation = new DoubleAnimation() { From = 0, To = 1, Duration = duration };
timeAnimation.EasingFunction = new ExponentialEase() { Exponent = 9,
EasingMode = System.Windows.Media.Animation.EasingMode.EaseOut };
timeAnimation.SetValue(TargetProperty, target);
timeAnimation.SetValue(TargetPropertyProperty, targetProperty);
timeAnimation.SetValue(FromProperty, from);
timeAnimation.SetValue(ToProperty, to);
Storyboard.SetTargetProperty(timeAnimation, new PropertyPath("(ThicknessAnimation.Time)"));
Storyboard.SetTarget(timeAnimation, timeAnimation);
return timeAnimation;
}
private static void OnTimeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DoubleAnimation animation = (DoubleAnimation)sender;
double time = GetTime(animation);
Thickness from = (Thickness)sender.GetValue(FromProperty);
Thickness to = (Thickness)sender.GetValue(ToProperty);
DependencyProperty targetProperty = (DependencyProperty)sender.GetValue(TargetPropertyProperty);
DependencyObject target = (DependencyObject)sender.GetValue(TargetProperty);
target.SetValue(targetProperty, new Thickness((to.Left - from.Left) * time + from.Left,
(to.Top - from.Top) * time + from.Top,
(to.Right - from.Right) * time + from.Right,
(to.Bottom - from.Bottom) * time + from.Bottom));
}
public static double GetTime(DoubleAnimation animation)
{
return (double)animation.GetValue(TimeProperty);
}
public static void SetTime(DoubleAnimation animation, double value)
{
animation.SetValue(TimeProperty, value);
}
}
使用 Silverlight 缓动动画尽情发挥
请注意创建动画的代码,您会发现我们正在使用 Silverlight 新的缓动动画。
timeAnimation.EasingFunction = new ExponentialEase()
{
Exponent = 9,
EasingMode = System.Windows.Media.Animation.EasingMode.EaseOut
};
缓动动画是一种动画,顾名思义,它从源状态到目标状态平滑过渡。缓动动画很重要,因为它有助于为应用程序构建流畅的感觉。
如果没有动画,歌曲磁贴凭空出现会让人感到突兀。
使用普通的动画,歌曲磁贴的滑入会显得机械。
只有使用缓动动画,我们才能实现那种让用户惊叹不已的温暖、模糊、粘稠的流畅感。
缓动动画由两部分组成
- 缓动函数
- 缓动模式
对于**缓动函数**,您有一系列令人印象深刻的选择(鸣谢 Mike Snow)
- BackEase – 元素先向后移动由其振幅指定的量,然后再向前移动。
- BounceEase – 创建类似弹跳元素的效果。
- CircleEase – 基于圆形函数加速动画。
- CubicEase – 基于三次函数加速动画。
- ElasticEase – 使用弹性与震荡进行动画。
- ExponentialEase – 基于指数值加速动画。
- PowerEase – 基于时间的幂加速动画。
- QuadraticEase – 基于时间的平方加速动画。
- QuarticEase – 基于时间的三次方加速动画。
- QuinticEase – 基于时间的五次方加速动画。
- SineEase – 沿着正弦波加速动画。
而对于**缓动模式**,
- EaseIn – 动画开始时缓慢,然后加速到全速
- EaseOut – 动画开始时全速,然后减速到结束
- EaseInOut – 动画开始时缓慢,加速到全速,然后减速到结束
对于 Chavah,我们将使用具有 EaseOut 模式的 ExponentialEase 函数。结果是歌曲磁贴迅速出现在 UI 中,然后慢慢地回到原位。既美观又流畅。用户看到后会惊叹不已。
使用动画
现在我们已经完成了定义 ThicknessAnimation 的艰巨工作,并且对缓动动画有了深入的了解,实际使用动画简直是小菜一碟。
创建一个故事板,将我们的厚度动画添加到故事板中,然后 .Begin() 动画。
var animationTime = TimeSpan.FromSeconds(1.5);
var timeline = ThicknessAnimation.Create(control, SongControl.MarginProperty,
new Duration(animationTime), new Thickness(5, 5, -150, 5), new Thickness(5, 5, 5, 5));
Storyboard storyBoard = new Storyboard();
storyBoard.Children.Add(timeline);
storyBoard.Begin();
我们将歌曲磁贴的右边距从 -150(例如,推到屏幕右侧)动画到 5,这样当歌曲添加到 UI 时,它会移动所有现有歌曲磁贴,平滑地为新的 UI 腾出空间。流畅而优美。
通过这些效果,我们模仿了 Pandora 的歌曲磁贴控件,包括其圆角外观和流畅动画,开发人员付出的努力非常少。纯粹的成功!
将 Silverlight 应用程序安装到用户桌面
由于 Chavah 是一个应用程序,我希望用户能够从他们的桌面运行该应用程序,而无需处理浏览器、地址、书签等问题。他们应该能够在不启动网络浏览器的情况下运行我的应用程序。
Silverlight 的一个相对较新的功能是“脱离浏览器”支持。通过“脱离浏览器”,您可以让用户有机会安装您的应用程序,并可选择在桌面和开始菜单上创建快捷方式。
对于 Chavah,我希望安装过程完全可选,并且从用户体验的角度来看,是隐蔽的。对于许多用户来说,直接跳出来的安装提示是可怕的,通常是桌面软件的一个痛点。我的目标受众可能不熟悉软件,因此我不想用安装提示吓跑他们。
为了避免那些令人害怕的安装提示,Chavah 只有在用户特意点击一个不引人注目的、微妙的链接时才会安装。
我们为该链接提供了一个工具提示:“将 Chavah 安装到您的桌面”。
点击该链接将触发安装代码。Silverlight 的离线安装代码非常简单。
if (!Application.Current.IsRunningOutOfBrowser &&
Application.Current.InstallState != InstallState.Installed)
{
var success = Application.Current.Install();
…
}
`Application.Current.Install()` 方法就足够了。调用后,Silverlight 将提示用户安装。令人担忧的安装提示来了。
还不错。再说一次,只有当用户特意尝试安装时,他们才会看到这个。
安装体验很好:安装只需几毫秒,提示没有关于我的应用程序会损害用户系统的可怕红色叉号,用户除了点击“确定”之外无需做任何操作。
这与典型的安装形成对比,典型的安装中,您会看到管理员权限提示,一个可怕的屏幕询问您是否确定要下载一个会摧毁您 C:\ 驱动器的 .exe 文件,一个多步骤的安装向导,EULA 协议,安装目录选择,安装组件自定义,以及其他多余的**东西**,这些都让用户害怕在 Windows 上安装桌面应用程序。
是的,Silverlight 在这方面有所进步,感谢上帝。它接近于 iPhone/iPad 应用程序安装的无痛、无摩擦体验。
如果用户点击“确定”安装,Chavah 将像典型应用程序一样安装,并在开始菜单和桌面创建快捷方式。
此外,该应用程序会像典型的原生应用程序一样显示在已安装程序列表中。
如果用户从桌面或开始菜单运行该应用程序,它看起来就像一个标准的桌面应用程序,无需浏览器边框。
借助 Silverlight 的离线功能,您可以将您的 Web 应用程序转变为桌面应用程序,具有原生窗口边框、开始菜单和桌面快捷方式、应用程序图标以及您对正常安装所期望的一切,而无需担心安全问题。
完美,正是我对 Chavah 的期望。
利用您的 500 美元显卡
与仅限 Windows 且完全访问 DirectX 和所有本地硬件功能的 WPF 不同,Silverlight 主要在 CPU 上运行。
但随着 Silverlight 的最新添加,您现在可以将部分 UI 卸载到 GPU,以获得更好的渲染性能。
关于如何做到这一点,网络上存在大量困惑和错误信息。(希望我没有加剧这种情况,干杯!
大多数人似乎认为,将“EnableGPUAcceleration=true”参数传递给 HTML 页面中的 Silverlight 对象就足够了。
这是一个错误的假设。
要使用 GPU,必须发生两件事:
- 将“EnableGPUAcceleration=true”参数传递给 HTML 页面中的 Silverlight 对象。(如果您正在离线运行,您还需要在 Visual Studio 的离线发布设置中设置“启用 GPU 加速”选项。)
- 将所有您希望通过 GPU 加速的 UI 元素(或顶级元素)的 CacheMode 设置为 BitmapCache。
对于 Chavah,我希望歌曲磁贴能够进行 GPU 加速;我正在对它们进行动画处理,我正在改变它们的不透明度等等。这似乎是 GPU 加速的好选择。
为此,我做了以下更改:
在包含 Chavah 应用程序的 HTML 文档中,我传入了指示 GPU 加速的参数。
<object data="data:application/x-silverlight-4,"
type="application/x-silverlight-4" width="100%" height="100%">
<param name="EnableGPUAcceleration" value="true" />
…
</object>
此外,我已经在我们的歌曲磁贴控件上设置了 CacheMode。
<UserControl x:Class="JudahHimango.Chavah.SongControl"
...
CacheMode="BitmapCache"
...
</UserControl>
此外,由于我们可能会在浏览器外运行,我们也需要在那里启用 GPU 加速。您可以通过 Silverlight 项目的属性页面完成此操作。
尽管 Silverlight GPU 加速非常出色,但并非一切都完美无缺;截至 Silverlight 4,仍有一些重要的注意事项:
某些 UI 组件**无法**进行硬件加速
- 效果(模糊、阴影等)
- 可写入位图 (WriteableBitmap)
- 透视变换 (PerspectiveTransforms)
- 非矩形裁剪
- 不透明度蒙版 (OpacityMasks)
此外,一些硬件/操作系统组合根本无法进行 GPU 加速。
- 在 Windows XP 上,如果您的显卡不是来自 NVidia、ATI 或 Intel,或者您的驱动程序日期早于 2004 年 11 月,您将无法获得任何 GPU 加速。
这些限制并不可怕。唯一影响 Chavah 的是效果限制——我们大量使用了 DropShadowEffect,这些效果将不会被加速。也就是说,屏幕上同时出现的效果很少,所以 CPU 的负载很小。事实上,在运行时,Chavah 通常运行在我 2.8 GHZ CPU 的 < 1% 资源下。一点也不差。
那么,有了所有这些注意事项和在 Silverlight 应用程序中实现 GPU 加速的步骤,您如何知道它是否正常工作?您如何知道您已正确设置所有内容,并且您想要的元素正在通过 GPU 管道?
答案是通过一个您可以发送给 HTML 中 Silverlight 对象的调试参数提供的。
<param name="enableCacheVisualization" value="true" />
启用该调试参数后,Silverlight 将对每个未进行硬件加速的元素着色。运行 Chavah 时,您会看到我们的歌曲磁贴确实得到了加速。
Silverlight 已对所有未加速的元素着色。正如您所看到的,歌曲磁贴没有着色(忽略已播放歌曲的不透明度)。这表明 Silverlight 正在通过 GPU 硬件加速管道发送这些 UI 元素位图缓存。
有关 Silverlight GPU 加速功能的更多信息,我建议您阅读 MSDN 的综合文章。
社交云协作 = 流行词过载(但功能很酷)
对于 Chavah,我希望比 Pandora 更进一步,通过软件促进社区意识。Chavah 面向的是一个小众社区,如果能有一些功能让社区共同赞扬好歌曲、淘汰差歌曲,那不是很好吗?
特别是,我想为 Chavah 添加一些社交功能,以促进社区发展并提高人们对优秀弥赛亚犹太音乐的认识:
- 弥赛亚社区每首歌的综合排名(赞 = +1,踩 = -1)
- 弥赛亚社区排名靠前的歌曲
- 弥赛亚社区热门歌曲(最近被赞的歌曲)
- 总播放歌曲数
当然,我们希望 Chavah 能够智能地播放更多用户喜欢的音乐,减少播放用户不喜欢的音乐。
为了实现这些功能,我在后端使用了 WCF Web 服务。这很好用;Silverlight 可以生成一个不错的异步 API 来执行服务器上的调用并返回结果,而不是编写代码与服务器通信。
对于服务器上的数据存储,因为 Chavah 不需要大规模,所以我不需要完整的 SQL Server。即使是 SQL Express 也超出了我的需求。
我真正想要的是一个影响小、单文件数据库,无需服务。另外,我不想手动将对象映射到数据库;我想要某种 ORM 在上面,使它变得非常容易。
虽然我曾考虑过使用 No-SQL,但最终对我奏效的是使用免费的 SQLite 数据库。其 ADO.NET 驱动程序也是免费的:System.Data.SQLite。
对于对象关系映射,我希望避免手动编写 ADO.NET 数据读取器代码。我惊喜地发现 Entity Framework 可以与 SQLite 配合使用,并提供完整的设计时支持。Entity Framework 将为您生成所有映射代码,让您可以编写简单的 LINQ 查询来获取数据。完美。
最终结果是这样干净整洁的代码
public Song[] GetTrendingSongs(int count)
{
using (var entities = new ChavahEntities())
{
var trendingSongs = from like in entities.Likes
where like.LikeStatus == true
orderby like.Id descending
select like;
var songs = from like in trendingSongs
where like.LikeStatus == true
join song in entities.SongInfos on like.SongId equals song.Id
select song;
return songs
.Take(count)
.ToArray();
}
}
看啊,妈妈,没有丑陋的 SQL 字符串!只有性感、智能感知友好的 LINQ。
由于 Entity Framework 以及 Silverlight 和 WCF 之间良好的集成,构建热门歌曲、社区排名、评分最高歌曲等社交功能变得轻而易举。
绝密歌曲选择算法
嘘。过来。对,就是你。你能保守秘密吗?好吧。
我不是算法向导,但我能够想出(阅读:从软件社区偷来)一种根据用户的喜好和厌恶选择歌曲的算法。
值得再次强调的是,与 Pandora 不同,Chavah 不使用音乐基因组计划来预测您会喜欢的音乐。相反,Chavah 使用一种更简单的算法,主要播放您喜欢的音乐,一些您未评分的音乐,并且很少播放您不喜欢的音乐。
引入歌曲权重算法。
为每首歌分配一个权重
- 喜欢的歌曲获得高权重(例如 5)
- 未排名的歌曲获得正常权重(例如 1)
- 不喜欢的歌曲获得低权重(例如 0.01)
要选择下一首要播放的歌曲,我们排好所有歌曲,将它们的权重相加,然后选择一个介于 0 和总和之间的随机数。
例如,假设我们的整个歌曲库中只有 3 首歌曲:Shalom、Blessings 和 King。
如果用户喜欢 Shalom,Blessings 未评分,King 不喜欢,我们的算法会为 Shalom 分配 0 到 5,为 Blessings 分配 5 到 6,为 King 分配 6 到 6.01。我们将生成一个介于 0 到 6.01 之间的随机数。如果随机数是 3,我们将播放 Shalom。如果是 5.5,我们将播放 Blessings。如果是 6.01,我们将播放 King。
简单但有效。结果是,喜欢的歌曲比普通歌曲更有可能播放,而普通歌曲比不喜欢的歌曲更有可能播放。此外,如果用户没有评价很多歌曲,他仍然会得到一个喜欢的歌曲和未评价歌曲的良好组合。控制“多多少”阈值只是调整喜欢、未评价和不喜欢的歌曲的权重常数的问题。
CPian 特色甜点:代码契约奶油,配 Rx 樱桃
虽然与构建 Pandora 克隆应用没有直接关系,但我想谈谈两个在未来 5 年内可能对 .NET 软件世界产生影响的主题:代码契约 (Code Contracts) 和响应式扩展 (Rx) 项目。在像 Chavah 这样的真实 Silverlight 应用中使用它们一直很有启发性,我希望将我的开悟的禅宗智慧传递给你们这些优秀的 CPian 们。
响应式扩展 (Rx)
响应式扩展 (Rx) 框架将 LINQ 的思想应用于事件。这里的“事件”不仅指标准的 .NET 事件,而是指任何以异步、不可预测方式传入的东西:队列消息、鼠标点击、按键、用户手势、对象更改通知、服务器消息,等等。
“响应式”是关键:Rx 不是像使用 LINQ 和 IEnumerable 那样轮询数据,而是让您通过观察 IObservable<T>(.NET 4 中的一种新类型)来**响应**数据的变化。
Rx 目前是微软的一个研究项目,但它的一些部分已经集成到 .NET 框架中。Rx 项目背后的几位杰出人物——Wes Dyer、Bart deSmet、Eric Meijer——是为我们带来 LINQ 的几位才华横溢的智者,LINQ 已被证明取得了巨大成功。
Rx 真正闪光的地方在于组合。想象一下拖放处理代码:你可能需要存储鼠标第一次按下的位置。你会监听鼠标按下事件。你会监听鼠标移动事件。你会监听鼠标抬起事件。你会协调整个过程。当你完成时,会有一大堆事件处理程序、可变状态和普遍的丑陋;你已经看不到森林,只看到树木了。
相比之下,Rx 可以通过一个单一的声明式调用将 MouseUp、MouseDown、MouseMove 事件组合在一起:瞧!您已经实现了拖放功能。
在 Chavah 中,我使用 Rx 观察我的视图模型的变化,从而取代了重复的更改通知管道。我还使用它在组件之间进行松散通信:我不是让视图模型 1 与视图模型 2 对话,而是向中介发布消息,然后感兴趣的各方可以组合和处理该消息。例如,要监听我们何时开始播放 Shalom 歌曲,我可以这样写:
// Use Rx to filter and react to messages, listening for the "Shalom" song to play. mediator.Messages .OfType<SongPlayingEventArgs><playsongeventargs>() .Where(s => s.Name == “Shalom”) .Subscribe(OnShalomPlaying);
Rx 将组合式 LINQ 的强大功能与事件结合起来。事实证明,这是一个强大的组合,在这个 Silverlight 项目中非常有用。
就像 LINQ 改变了我们处理数据的方式一样,我预测 Rx 将改变我们处理事件的方式。我现在已经到了这样的程度:很难看待一个问题而不将其视为对事件流的操作。
我建议微软在近期将 Rx 提升为 .NET 框架的一等公民。
代码契约
代码契约是 .NET 4 框架(和 Silverlight 4)的新增功能。它源于微软的另一个研究项目:Spec# 语言,旨在融合 C# 和契约式设计原则。然而,代码契约选择了语言无关的路径,仅作为 .NET 框架中的 API 实现,没有任何语言支持。
代码契约的概念并不新鲜:契约式编程,开发人员直接在代码中指定所需条件——关于方法、类、接口。然后,一个工具在编译期间运行,以验证您的条件永远不会被违反。
理论上,最终结果是代码错误更少,因为您的所有假设现在都已编码为契约并由工具验证。不再有 NullReferenceExceptions,不再有 ArgumentExceptions——那该多好?
在 Chavah 中,我主要在 Silverlight 端使用代码契约,结果喜忧参半。偶尔,代码契约会捕获到一个 bug——这很棒!但有时我写了很多契约,却感觉不到爱。
你看,当你控制整个 API 时,代码契约才能很好地工作。例如,如果你的整个项目都使用利用代码契约的 API,你将获得很棒的体验,并且代码中的错误会更少。
但这本质上就是问题所在:要让代码契约真正**发挥作用**,必须将整个“大海”煮沸。也就是说,所有你调用的 API 都需要使用代码契约,否则你会听到很多噪音。
尽管微软拥有“大海”,但他们并没有做太多“煮沸”的工作。
现实是,.Net 框架的很大一部分缺少契约;我个人遇到了一些并非不常见的 Silverlight API,它们明显缺少契约。结果,我不得不编写代码来向契约检查器保证某个 .NET API 确实会返回一个非负数。
此外,代码契约框架目前还没有广泛支持,这又带来了更多噪音。例如,Chavah 使用 Ninject 控制反转框架进行依赖解析和注入,但 Ninject 没有契约,所以为了避免噪音,我不得不再次编写更多代码来向契约工具保证天并没有塌下来。
代码契约的另一个困难是语言方面没有支持。C# 对代码契约不友好。例如,在接口上放置契约时,你必须费尽周折并编写更多琐碎的代码。另一个例子是契约检查器不理解 C# 构造,例如:
private readonly string foo = “bar”;
“哦不!”契约检查器说,“foo 可能是 null!”
呃……不,foo 只被赋值一次,但契约检查工具因为对自己不自信,所以喋喋不休地说 foo 可能是 null。再一次,C# 和代码契约彼此没有爱,他们的婚姻问题都转嫁给了你,开发者。
底线:代码契约有潜力,但在获得广泛框架支持之前,它会产生太多噪音。此外,它不够智能,无法识别某些事物不会为 null。(我真的必须检查某个事件处理程序的 EventArgs 不为 null 吗?真的吗?)一些语言集成也会有所帮助。
摘要
用 Silverlight 构建一个类似 Pandora 的音乐应用程序出奇地简单。我在一个周末内就写出了第一个版本。
Silverlight 结合 WCF 集成,可以用于创建在 Web 或桌面上运行良好的客户端/服务器应用程序。它是构建在 Web 上运行的应用程序的不错选择。
即使对于像我这样对设计有困难的程序员来说,在 Silverlight 中构建漂亮、流畅的 UI 也很自然。控件模板、数据模板、缓动动画和命令支持,用很少的代码就能创建出精美的 UI。
Entity Framework 正在成熟。将 Entity Framework 与 WCF 结合使用,让将数据返回给客户端变得异常简单。