天天看点

精益编程:Write Lean Programs

oo makes code understandable by encapsulating moving parting, but fp makes code understandable by minimizing moving parts. -michael feathers
精益编程:Write Lean Programs

需求1:在仓库中查找所有颜色为红色的产品

指令式(imperative)

缺乏编译时类型安全性检查

实现类型

硬编码

重复设计

需求2:在仓库中查找所有颜色为绿色的产品

<code>copy-paste</code>是大部分程序员最容易犯的毛病,为此引入了大量的重复代码。

为了消灭<code>hard code</code>和重复代码,得到可重用的代码,可以引入简单的参数化设计。

需求3:查找所有重量小于10的所有产品

大部分程序员依然会使用<code>copy-paste</code>解决这个问题,拒绝<code>copy-paste</code>的陋习,最具实效的一个办法就是把<code>copy-paste</code>的快捷键失效,当每次尝试<code>copy-paste</code>时提醒自己做更好的设计。

为了消除两者重复的代码,通过简单的参数化往往不能完美解决这类问题,相反会引入额外的复杂度。

日常工作中这样的实现手法非常普遍,函数的参数列表随着需求增加不断增加,函数逻辑承担的职责越来越多,逻辑也变得越来越难以控制。

为此需要抽取出隐藏的概念,使其遍历的算法与查找的标准能够独立地变化,将行为参数化。

此刻<code>findproducts</code>的算法逻辑得到封闭。

通过可复用的<code>functor</code>来封装各种变化,让变化的因素控制在最小的范围内。

用户的接口也变得简单多了,而且富有表现力。

精益编程:Write Lean Programs

这是经典的<code>oo</code>设计,如果熟悉设计模式的读者对此已经习以为常了。设计模式是好东西,但常常被人依葫芦画瓢,死板照抄,甚至被滥用。事实上,引入或去除设计模式是一个很自然的过程。与大师们交流,问究此处为何引入设计模式,得到的答案:直觉。忘记所有设计模式吧,管它是不是模式,如果设计是简单的,它这就是模式。

至此,代码另外还有一个明显的坏味道,<code>colorspec</code>和<code>belowweightspec</code>都需要继承<code>productspec</code>,都需要定义一个构造函数和一个私有的字段,并重写<code>satisfy</code>方法,这是一种典型的重复现象:重复型结构。

因<code>java</code>缺乏闭包的支持,程序员不得不承受这样的烦恼,但此刻暂时不关心,继续前进。

需求4:查找所有颜色为红色或者绿色,并且重量小于10的产品

按照既有的代码结构,往往易于设计出类似<code>colorandbelowweightspec</code>的实现。

存在两个明显的坏味道:

类名中包含<code>and</code>往往是违背单一职责的信号灯

<code>colorandbelowweightspec</code>的实现与<code>colorspec</code>,<code>belowweightspec</code>之间存在明显的重复

此刻,需要寻找更本质的抽象来表达设计,引入<code>and/or</code>的语义模型。

composite spec: andspec, orspec

atomic spec:colorspec, beblowweightspec

精益编程:Write Lean Programs

可以通过<code>andspec</code>组合<code>colorspec, belowweightspec</code>来实现需求,简单漂亮,并且富有表达力。

此时设计存在两个严重的坏味道:

<code>andspec</code>与<code>orspec</code>存在明显的代码重复

大堆的<code>new</code>让人眼花缭乱

先尝试消除<code>andspec</code>与<code>orspec</code>存在的代码重复,<code>oo</code>设计的第一个直觉就是通过抽取基类。

通过参数化配置,复用<code>combinablespec</code>的实现。

如何评判<code>boolean</code>接口的使用呢?在不损伤可理解性的前提下,为了消除重复的设计是值得推荐的。<code>boolean</code>接口的可理解性关键依赖于调用点与函数接口之间的距离,如果在同一个文件,同一个类,并能在一个页面显示的,是完全可以接受的。

需求5:查找所有颜色为不是红色的产品

<code>notspec</code>是一种修饰了的<code>productspec</code>,同时也使得用户的接口也变得更加人性化了。

精益编程:Write Lean Programs

之前遗留了一个问题,一大堆眼花缭乱的<code>new</code>使得代码失去了部分的可读性。

可以引入<code>dsl</code>改善程序的可读性,让代码更具表达力。

上述的dsl可以使用<code>static factory</code>的设计手段简单实现。按照惯例,可以建立类似于<code>productspecs</code>的工具类,将这些工厂方法搬迁到工具类中去。

接口与对应工具类的对称性设计在<code>java</code>社区中应用非常广泛,例如标准库中的<code>java.util.collection/java.util.collections</code>的设计。

此外,使用匿名内部类,可以得到意外的惊喜。通过有限地引入闭包的概念,从而避免了类似firth attempt/sixth attempt的设计中引入多余的构造函数和成员变量的复杂度,从而消除了部分的结构性重复的坏味道。

当然,要让这些<code>static factory</code>可见,需要<code>import static</code>导入这些方法。

使用<code>java8</code>可以将这些工厂方法直接搬迁到<code>productspec</code>的接口中去,这样做至少得到两个好处。

可以删除<code>productspecs</code>的工具类

使的接口和静态方法(尤其静态工厂方法)关系更加紧密

<code>java8</code>并没有因为<code>comparing</code>等静态工厂方法的增强而建立<code>comparators</code>的工具类,而是直接将它们集成在<code>comparator</code>的接口中,这是自<code>java8</code>之后思维的一个新的转变(<code>comparator.comparing</code>的实现留作作业巩固今天所学知识)。

对于本例,可以将<code>productspecs</code>删除,将所有静态工厂方法搬迁到<code>productspec</code>中去。

需求6:无条件过滤掉或不过滤查找所有产品

至此,<code>productspec</code>存在如下一些类型:

composite specs: and, or

decorator specs: not

atomic specs: always, color, beblowweight

<code>java8</code>可以使用<code>lambda</code>表达式改善设计,增强表达力。

通过类型推演,可以进一步省略<code>labmda</code>表达式中参数的类型信息。

当然,你可以通过提取<code>static factory</code>,构造dsl复用这些<code>lambda</code>表达式。

其中,<code>@functionalinterface</code>注解标注了<code>productspec</code>是一个函数式接口,其抽象方法<code>boolean satisfy(product p)</code>的原型描述了<code>lambda</code>表达式的<code>function descriptor</code>。

遗留了一个问题: 如何替换匿名内部类,使用<code>lambda</code>实现 <code>and/or/not/always</code>的语义?

这里引入了<code>java8</code>一个重要的设计工具:<code>default method</code>,简单漂亮,并巧妙地实现<code>dsl</code>的设计,用户接口变得更加流畅、友好。

<code>java8</code>支持<code>default method</code>,扩展了<code>interface</code>原来的语义,从而隐式地支持了组合式设计,使的<code>oo</code>的设计更加完善和强大。

需求7:查找所有伪劣的产品

可以使用<code>method reference</code>进一步改善<code>lambda</code>的表达力。

泛化类型信息,让算法更具有通用性,并进一步增强代码的可复用性。

这样的实现存在一个明显的问题:泛型参数缺乏型变的能力。通过对泛型参数实施无限定类型通配符的修饰,从而使的算法实现更加具有弹性和通用性。

<code>and, or, not, always</code>在代数系统中具有稳定的抽象,为此需要进一步重构,以便最大化代码的可复用性。这样当需要建立诸如<code>numberspec, fruitspec</code>时无需重复地再写一遍<code>and, or, not, always</code>的实现。

为此,建立更为抽象的<code>predicate</code>的概念,并将通用的、抽象的<code>negate, and, or, always</code>搬迁到<code>predicate</code>中去,使其具有更大的可复用性。

同时,将领域内的<code>color, belowweight</code>等原子放回<code>productspecs</code>工具类中去(因为不存在<code>productspec</code>的接口了),让领域内的<code>lambda</code>表达式具有更大的复用性。

至此,可复用的基础设施便从领域中剥离出来,使其具有更高度的可重用性。

<code>java8</code>可以使用集合库的<code>stream</code>复用代码。

如果要支持并发,则可以构建<code>parallelstream</code>。

集合类通过<code>stream, parallelstream</code>工厂方法创建<code>stream</code>之后,其操作可分为<code>2</code>种基本类型:

transformation:其返回值为<code>stream</code>类型

action:其返回值不是<code>stream</code>类型

通过<code>stream</code>的机制,实现了集合类的惰性求值,直至<code>action</code>才真正地开始执行计算。<code>transformation</code>从某种意义上,可以看成是<code>stream</code>的<code>builder</code>,直至<code>action</code>启动执行。

<code>scala</code>语言是一门跨越<code>oo</code>和<code>fp</code>的一个混血儿,可以方便地与<code>java</code>进行互操作。在<code>scala</code>中,函数作为一等公民,使用<code>lambda</code>是一个很自然的过程。当你熟悉了<code>scala</code>,我相信你绝对会放弃<code>java</code>,放弃<code>java8</code>,犹如作者本人一样。

遗留了三个问题:

如何复用<code>lambda</code>表达式?

如何实现 <code>and/or/not</code>的语义?

如何实现 <code>always</code>的语义?

引入静态工厂方法及其操作符重载的机制构造内部<code>dsl</code>。

如何替换实现<code>???</code>,并让其具有<code>&amp;&amp;, ||, !</code>的语义呢?

<code>predicate</code>一个扩展匿名函数<code>a =&gt; boolean</code>的子类,其中,从面向对象的角度看,<code>a =&gt; boolean</code>的类型为<code>function[a, boolean]</code>。

其中<code>!</code>是一个一元操作符。

<code>always</code>静态工厂方法,可以搬迁到<code>predicate</code>的伴生对象中去。

<code>predicate</code>的设计既使用了<code>oo</code>的特性,又引入了<code>fp</code>的思维,<code>scala</code>使其两者如此和谐、完美,简直不可思议。

世界是多样性的,计算机工业也不仅仅只存在一种方法论。在我的哲学观里,oo和fp之间并不矛盾,而是一个和谐的,相互补充的统一体。

除了c++语言之外,使得我最偏爱scala,多范式,一个问题存在多种解决方案等等思维习惯,给了程序员最灵活、最自由的空间。

以标准库<code>collections.sort</code>,及其<code>comparator</code>在<code>java8</code>中的增强,及其<code>comparator.comparing</code>的泛型定义复习今天所学知识。

使用匿名内部类是<code>collectins.sort</code>最经典的使用方法之一。

可以通过<code>lambda</code>表达式替代匿名内部类,简化设计。

通过类型推演,但依然得到编译器类型安全的保护。

通过<code>comprator.compring</code>的静态工厂方法,改善表达力。

通过<code>function reference</code>的机制,进一步改善表达力。

其中,<code>comprator.compring</code>的实现为: