03.拍摄 UFO - 单一职责原则(Single Responsibility Principle)

3.1 新手机

  • 时间: 2月28日18点   地点: 小菜大鸟居住的校区附近   人物: 小菜, 大鸟      大鸟小菜晚上晚饭过后, 在外面散步.
         大鸟: “小菜, 刚换的手机感觉如何?”
         小菜: “哈, 当然是怎个爽字了得, 可以听音乐, 玩儿游戏, 拍照, 摄像, 功能全着呢!”
         大鸟: “你们这些小年轻, 只会赶时髦, 手机要那么多功能干嘛? 能打电话就可以了.”
         小菜: “这你就不懂了吧, 比如你出门旅游, 数码相机一定要的吧, 拍照时最起码的旅游需求; 有摄像机会更好, 动的影像不是更有保留价值吗; 一路上无聊的时候, 打打游戏总是需要的, 游戏机要准备; 坐在大巴士上, 看着窗外的美景, 听听音乐应该也属于正常需求吧, MP3一定要带着了; 有时获取还需要什么GPS 定位, 上网看看新闻, 发发邮件, 查查股票行情, 这些需求如何办,总不能带着笔记本电脑在路上跑吧. 这些东西且不说本身就很重, 很麻烦, 就说这些东西的充电器, 就是五花八门, 估计单就带着些东西, 你就得累个半死了. “
         大鸟: “说的没错, 现在电子产品能玩儿的东西太多了…..”
         小菜: “啊, 大鸟! 快看!”

3.2 拍摄

     小菜惊呼, 左手拉住大鸟, 右手指向了天空.
     大鸟跟着抬头一看, “那应该是架飞机吧!”
     “不可能,” 小菜坚决地说, “飞机哪有没翅膀的, 那个东西飞的很奇怪, 你看你看, 他停在空中, 普通飞机怎么会在空中停下来.”
     “是不太象飞机, 飞碟?!—傻菜, 快用你的手机录像呀!”
     “是是是, 啊, 这手机怎么….等等,” 小菜手忙脚乱.
     “看你慌得,” 大鸟说, “快些, 马上可能就没了.”
     “好了好了,” 小菜终于打开了手机的摄像功能, 对准了天空, “主要是对新功能不熟悉, 你看, 这家伙飞的多快.”
     “恩, 应该是飞碟, 不然不可能这种样子的, 以前也不是没听过这玩儿意儿,” 大鸟肯定到, “还好你这手机可以摄像—啊, 他飞跑了, 你拍下来没有?”
     “好了, 我都拍下来了, 有点不太清楚, 回去放电脑上看看吧.” 小菜很开心, 他的新手机发挥大作用了, “头一次看到UFO, 就拍到了, 这些可是大新闻了.”
     “是呀, 我也投一次看到, 我们太幸运了.”

3.3 没用的东西

     回到家中. 小菜将手机文件传入电脑.
     “这什么呀, 黑乎乎的, 什么也看不清.” 大鸟大为失望.
     “那不是有一个小白点吗?” 小菜想极力申辩.
     那白点就和液晶显示器里的坏点一样, 这如何看得出来是 UFO 呢, 说给别人谁信呀.
     “嗨! 是的.” 小菜也承认了这个事实, “这手机拍出来的东西没办法看呀, 根本算不上是UFO 的证据. “
     小菜拿起手机, 一脸苦相, 对着他说道: “狗屁, 要你这么多功能有鸟用, 关键时刻就掉链子, 我砸…..” 小菜举起手机欲往地上砸去.

3.4 单一职责原则

     “砸呀, 你砸呀!” 大鸟笑嘻嘻的看着小菜, “哼哼, 我就知道你舍不得, 不过你的手机的确是有点没用, 这么好的机遇, 都没有录成, 如果是摄像机, 效果一定不会差, 因为当时我们眼睛看的很清楚呀. 这下说给谁, 谁也不相信呀!大多时候, 一件产品简单一些, 职责单一些, 或许是更好的选择. 这就和设计模式中的一大原则 — 单一职责的道理是一样的.”
     “哦, 听字面意思, 单一职责原则, 意思就是说, 功能要单一?”
     “哈, 可以简单的这么理解, 他的准确定义是, 就一个类而言, 应该仅有一个引起他变化的原因[ASD]. 我们在做编程的时候, 很自然的就会给一个类加各种各样的功能, 比如我们写一个窗体应用程序, 一般都会生成一个 Form1 这样的类, 于是我们就把各种各样的代码, 像某种商业运算的算法啦, 项数据访问的 sql 语句什么的都写到这样的类当中, 这就意味着, 无论任何需求要来, 你都需要更改这个窗体类, 这其实是很糟糕的, 维护麻烦, 复用不可能, 也缺乏灵活性.”
     “是的, 我写代码一般刚开始就是把所有的方法直接写在窗体类的代码当中.”

单一职责原则(Simple Response Protocol. SRP), 就一个类而言, 应该仅有一个引起他变化的原因.

3. 5 方块游戏的设计

     “我们再来举些例子, 比如就拿手机里的俄罗斯方块游戏为例. 要是让你开发这个小游戏, 你如何考虑?” 大鸟问道.
     “我想想, 首先方块下落动画的原理是画四个小方块, 擦掉, 然后再在下一行话四个方块. 不断的绘出和擦掉就形成了动画, 所以应该要有画和擦方块的代码. 然后左右键实现了左右移动, 下键实现加速, 上键实现旋转, 这其实都应该是函数, 当然左右移动需考虑碰撞的问题, 下一需要考虑堆积和消除的问题.”
     “OK, 你也说了不少了. 如果就用 WinForm 的范式开发, 你打算怎么开发呢?”
     “那淡然是先建立一个窗体 From, 然后加一个用于游戏框的空间, 比如 Panel 或者是 PictureBox, 一个按钮 Button 来控制 ‘开始’, 最后再放一个 Timer 空间用于分时动画的编程. 写代码当然就是编写 Timer_Tick 时间来绘出和擦除方块, 并作出堆积和消除的判断. 在编写控件的键盘事件, 按了左箭头则左移, 右箭头则右移等等, 对了, 还需要用到GDI+ 技术的方法来画出方块和擦方块.”
     “你能不能就这些代码划分一下类呢?”
     “分类? 这里好像关键在于各种事件代码如何写吧, 这里有什么分类可言呢?”
     “看来你的面向过程开发已经根深蒂固了. 你把所有的代码都写在了 Form1.cs 这个类里面, 你觉得这个合理吗?”
     “可能不合理, 但是我实在没想出怎么分离他.”
     “打个比方, 如果现在要你写的是手机版的俄罗斯方块程序, 即 Pocket PC 或者是 Windows CE 上运行的程序, 他们可以安装 .NET 框架的精简版, 运行 C# 语言编写的应用程序, 但 PC 上的普通的 WinForm 界面的程序不能使用. 那你现在这个代码有什么可以复用的嘛?”
     “你都已经说过了, 不能使用, 我当然就没法使用了. Copy 过去, 在针对代码做些改进吧.”
     “但这当中, 有些东西始终没变的.”
     “你是说, 下落, 旋转, 碰撞判断, 移动, 堆积这些游戏逻辑吧?”
     “说的没错, 这些都是和游戏有关的逻辑, 和界面如何表示没有什么关系, 为什么要写在一个类里面呢? 如果一个类承担的职责过多, 就等于把这些职责耦合在一起, 一个这则的变化可能会削弱或者抑制这个类完成其他这则的能力. 这种耦合会导致脆弱的设计, 当变化发生时, 设计会遭到意想不到的破坏[ASD]. 事实上, 你完全可以找出哪些是界面, 那些是游戏逻辑, 然后进行分离.”
     “但我还是不明白, 如何分离开.”
     “你仔细想想看, 方块的可以动的游戏区域, 可以设计为一个二维整形数组用来表示坐标, 宽10, 高20, 比如 ‘int[,] arraySquare = new int[20, 20];’ 那么整个方块的移动其实就是数组的小编变化, 比如原方块在 arraySquare[3,5] 上, 则下一时变成 arraySquare[3, 6], 如果下移同时还按了左键, 则是 arraySquare[2,6]. 每个数组的值就是是否存在方块的标志, 存在为1, 不存在时缺省为0. 这下你该明白, 所谓的碰撞判断, 其实就是什么?”
     “我知道了, 能否左移, 就是判断 arraySquare[x,y] 中的 x-1 是否小于0, 否则就碰撞了. 或者 arraySquare[x-1,y] 是否等于1, 否则就说明左侧有堆积的方块. 所谓堆积, 不过是判断 arraySquare[x, y+1] 是否等于 1 的过程, 如果是, 则将自己 arraySquare[x, y] 的值改为1. 那么消层, 其实就是arraySquare[x,y] 中循环x 由 0 到 9, 判断 arraySquare[x, y] 是否都等于1, 是则此行数据清零, 并将其上方的数组值遍历下移一位.”
     “那你就应该明白了, 所谓游戏逻辑, 不过就是数组的每一项变化的问题, 下落, 旋转, 碰撞, 判断, 移动, 堆积, 这些都是在做数组具体项的值的变化. 而界面表示逻辑, 不过是根据数组的数据进行绘制和擦除, 或者根据键盘命令调用数组的相应的方法进行改变. 因此, 至少应该考虑将此程序分为两个类, 一个是游戏逻辑的类, 一个是 WinForm 窗体的类. 当有一天要改变界面, 或者换界面时, 不过是窗体类的变化, 和游戏逻辑无关, 以此达到复用的目的.”
     “这个听起来容易, 真正要做起来还是有难度的哦!”
     “当然, 软件设计真正要做的许多内容, 就是发现职责并把那些职责相互分离[ASD]. 其实要去判断是否应该分离出来, 也不难, 那就是如果你能够想到多余一个的动机去改变一个类, 那么这个类就具有多于一个的职责[ASD],也就是该考虑职责的分离.”
     “的确是这样, 界面的变化是和游戏本身没有关系的, 界面时容易变化的, 而游戏逻辑是不太容易变化的, 将他们分离开有利于界面的改动.”

3.6 手机的职责过多吗?

     “这回你知道你的手机为什么不能拍摄好 UFO 的原因了吧?” 大鸟笑道.
     “如果手机只用来接听电话, DV 用来拍摄, 职责的分离是可以把事情做的更好. 不过这其实不是一回事哦, 现在的智能手机承担的职责多, 并不等于就不可以做好, 只不过现在的科技还不能让手机在摄像时超过 DV 而已.” 小菜分析说.
     “整合当然是一种很好的思想. 比如 Google 最初的理想就是将一切的需求都整合到一个文本框里提交, 用干净的页面来吸引用户, 导致互联网的一场变革. 但现在分类信息, 垂直搜索又开始流行, 这却是单一职责思想的体现. 现在智能手机整合了很多功能的原因是以因为 DV , DC , MP3 等产品的体积也太大了. 手机携带很方便, 所以才有了这样的过渡产品, 如果, 每一样数码产品都缩小100倍, 就像放在包里的一张卡片, 一支笔那么简单, 而功能和质量都不发生变化, 你还会觉得他们很麻烦吗?” 大鸟总结道, “总的来说, 手机的发展有它的特点, 而编程时, 我们确实要在类的职责分离上多思考, 做到单一职责, 这样你的代码才是真正的已维护, 易扩展, 衣复用, 灵活多样.”

~感谢捧场,您的支持将鼓励我继续创作~