之前写过一篇随笔《剪刀剪纸》是给一些新同事讲面向对象时用的,当时就感觉有些不顺畅,不过用来给新同事入门足够了就没多想,最近看书时偶尔走神把这件事想起来了,顺便群里讨论时谈到聚合之间的方法调用,于是决定写一篇博客纠正一下那篇随笔里的问题。
开头先声明一下,以下只是个例子,只是用来说明对象间交互的解耦,怎么样交互我觉得更好,但是如果是真的要写一个剪刀剪纸的程序,之前随笔的做法并不一定就是不好的,有些耦合只是在需要解的时候才应该去解。另外,以下做法只是理想的做法,但是现实的项目总会有各种各样的妥协,所以主要还是随机应变,没有最好的做法,只有最合适的做法,声明暂时结束。
在剪刀剪纸的随笔里写了:剪刀只需要关心自己发出了剪的动作,不需要关心纸,于是给纸抽象了一个东西的基类。但是回头细看其实这并没有解决问题,剪刀虽然没有和纸发生耦合,但是和东西发生了耦合,剪刀本身并不一定要剪东西,说不定就是个工艺品呢,剪刀剪纸随笔中剪子的Cut(Thing thing)方法参数选择了Thing等于将剪刀内部的逻辑对Thing公开了一部分,这种做法已经破坏了剪子的封装,同时也表达了一种业务逻辑,既剪刀剪东西,而且Thing也已经不是单纯的东西的概念,而是被剪子剪的东西。如果是一个初学者做项目,这么做问题或许不大,但是深究这种做法从某种角度来说是错的。
上面一段说出的问题概括一下:
1.剪刀和Thing(以下所说的Thing都代表”被剪的东西“这个概念)的耦合是多余的,如何让剪刀和Thing各自独立;
2.在需要的时候,剪子和Thing可以交互完成剪东西这一业务。
上面说了Cut的参数使用Thing破坏了封装,如果做好封装的话就可以解决上面的第一个问题。做好封装也就是说剪刀只包含自己的职责,也就是抽象好自己的职责,封装和抽象是相辅相成的存在。仔细思考下,剪子的职责应该是什么的,可以用来剪东西只是说明了它可以做什么,诺基亚还可以用来砸核桃(其实还有用来开核桃的模特模型,你懂得。。。),但你能说砸核桃是诺基亚的职责么,那剪刀的职责是什么,很明显没Thing那就一定是剪了,怎么表达这个剪呢,那就是发出一个剪的作用力,所以,我们可以增加一个作用力的对象,当然如果非要较真,作用力和剪刀还是有耦合,不过剪刀本就是接受并传递作用力的,作用力只是参数,而且作用力在这个业务场景中也是在一个工作单元里传递业务状态变化的根本,整个业务其实就是由作用力驱动的,其实这个作用力用命令模式的命令来表达或许更贴切,不过这里就不细说了。
//关于命名,我是故意的,原因不想说,你们千万别学,作用力的意思 public class KineticEnergy { public int Size { get; set; } public struct Point { public int X { get; set; } public int Y { get; set; } } }
然后是剪刀的:
public class Scissors { private KineticEnergy _kineticEnergyBeUsed { get; set; } public void Stress(KineticEnergy kineticEnergy) { _kineticEnergyBeUsed = kineticEnergy; } public KineticEnergy SendCutEnergy() { return SwitchKineticEnergy(); } private KineticEnergy SwitchKineticEnergy() { KineticEnergy kineticEnergyBeCut = _kineticEnergyBeUsed; //作用点和力的大小等变化 return kineticEnergyBeCut; } }
至于Thing还是看剪纸那篇随笔的,基本就是那意思,代码就不改个名重贴一遍这么费事了。
下面就是第二个问题了,想必大家也看出来了,第二个问题基本上也不是问题了,剪刀剪一次纸的过程我们可以用一个工作单元来表示(我偷懒,传递的力一直在改变,但是反正重点不在这,就不要在意这些细节了):
//工作单元UOW三个单词不是这么写的,看到的注意,别学!!! public class CutThingsWorkUnit { private KineticEnergy _kineticEnergy; private Scissors _scissors; private Thing _thing; //剪刀和Thing应该由业务场景的Context来提供 public CutThingsWorkUnit(Scissors scissors, Thing thing) { _scissors = scissors; _thing = thing; } public void Execute() { //这里的人,那篇随笔我已经给抽象掉了,放这里就是个意思,随便实例化一下,这种细节就不要在意了 Person person = new Person(); _kineticEnergy = person.UseScissorsKineticEnergy(); _scissors.Stress(_kineticEnergy); _kineticEnergy = _scissors.SendCutEnergy(); _thing.Stress(_kineticEnergy); } }
最后,至于这东西被剪的结果,剪纸那篇随笔说过,就不再说了。