在OO(面向对象)时代长大的小伙伴们一定记得:

面向对象的基石:把数据和依赖该数据的行为封装在一起。

但我们经常遇到一个类依赖其它类的数据的情况。不多的话,正常,对象间势必存在交互,毕竟完全独立的类无法构建出复杂的业务系统。

太多依赖外部数据的话,可能是问题,也可能不是问题,而是故意为之。嗯?这不是反OO吗?莫急,先来看看两个例子,然后分析隐藏在后面的东西。

特性依恋

先看太多外部数据依赖是问题的情况,重构里面管这叫 特性依恋 。顾名思义,太过迷恋别人的东西。

case class Product(name: String, price: Float)
case class OrderItem(count: Int, product: Product) case class Order(items: List[OrderItem]) {
def cost: Float = {
items.sum(item => item.count * item.product.price)
}
}

每个订单项的花销之和,就是订单的花销。问题异常明显,订单项的花销是在订单层次计算的,导致订单过度依赖订单项的数据。

case class OrderItem(count: Int, product: Product) {
def cost = count * product.price
} case class Order(items: List[OrderItem]) {
def cost = items.sum(_.cost)
}

订单项的花销,订单项自己计算,订单的花销是所有订单项花销之和。代码比说明书清楚多了,OK。

行为构建在数据之上,对象作为载体封装二者。从上面的例子可以看出,不能错位,属于订单项的行为就不要放在订单里面,如此才能提高代码的可维护性和可重用性。

到目前为止,OO的世界依然和谐美好。

如此熟悉的反OO:访问者模式

再来一例。

case class Car(engine: Engine, body: Body, wheels: List[Wheel]) {
def engineerCheck() {
check(enigne)
check(body)
wheels.foreach(check(_))
} def washerWash() {
wash(body)
wheels.foreach(wash(_))
}
}

一辆车有一个引擎,一个车身,几个轮子。出厂/维修/保养的时候都需要找工程师检查,洗车的时候需要找洗车工清洗。工程师检查的行为一定是针对汽车的各组件,洗车工也是清洗的各汽车组件,行为和数据在一起组成对象,从OO的角度看,没啥问题。

如果来了一个外星人,以前没见过地球的汽车,觉得新奇,准备自己反向工程一辆,那简单:

case class Car(engine: Engine, body: Body, wheels: List[Wheel]) {
... def alienReverseEngineering() {
reverseEngineering(enigne)
reverseEngineering(body)
wheels.foreach(reverseEngineering(_))
}
}

小伙伴们发现没?汽车已经无辜到要关心外星人,职责太特么不单一了,即使它没有违反OO。重构的解决方案就是 访问者模式 ,把工程师/洗车工/外星人干的事情从汽车里面剥离出来。

trait Element {
def accept(v: Visitor)
} class Engine extends Element {
def accept(v: Visitor) {
v.visit(this)
}
} class Body extends Element {
def accept(v: Visitor) {
v.visit(this)
}
} class Wheel extends Element {
def accept(v: Visitor) {
v.visit(this)
}
} case class Car(engine: Engine, body: Body,
wheels: List[Wheel]) {
def accept(v: Visitor) {
engine.accept(v)
body.accept(v)
wheels.foreach(accept)
}
}

Elment代表的是需要被访问的元素,本例中就是汽车的各组件。Car容纳了所有组件,并隐藏组件间的结构。

trait Visitor {
def visit(engine: Engine)
def visit(body: Body)
def visit(wheel: Wheel)
} class Engineer extends Visitor {
def visit(engine: Engine) = { ... }
def visit(body: Body) = { ... }
def visit(wheel: Wheel) = { ... }
} class Washer extends Visitor {
def visit(engine: Engine) = { ... }
def visit(body: Body) = { ... }
def visit(wheel: Wheel) = { ... }
} class Alien extends Visitor {
def visit(engine: Engine) = { ... }
def visit(body: Body) = { ... }
def visit(wheel: Wheel) = { ... }
}

Visitor是所有对Car感兴趣的人,以及他们会对Car发生的行为。

Element/Car是数据,而Visitor是行为,访问者模式使得你可以在不修改Car的组件及结构的情况下,通过Visitor的方式定义新的行为。

细心的小伙伴们已经发现了,其实访问者模式分离了数据和行为,反OO了。

反不反OO呢?

一会支持OO,一会反OO,以后咋做设计呢?

如果一码说设计是门艺术,需要根据实际情况仔细权衡,小伙伴们一定会在心里使劲骂,说了句废话。

那一码不说虚的,来分析点实在的东西。既然两个例子无法在OO上达成一致,那咱往后退一层,来看看更基础的原则 单一职责不要重复

对于订单一例,只有把订单项的数据和行为(开销)放在一起,才算系统里面对一个概念的解释只在一处存在,满足 不要重复 的原则。对于汽车一例,只有把易于变化的行为和稳定的数据结构分离,才能做到一个个独立的职责 汽车/工程师/洗车工/外星人,才能做到易于维护和扩展。

能够把上面这一点想通,其实只是个开始而已。一码个人觉得,对于代码层面的设计而言:

  • 软件设计的基本原则是道,如:单一职责,不要重复,依赖倒置等
  • 范式及其背后的模式是术,如:面向对象及设计模式,函数式编程及Monads,泛型编程,元编程等

从代码设计的角度看,如果你会C#,那么不要再去学Java(反之亦然),而应该去学学Scheme的函数式编程,Ruby的元编程。只有掌握不同的术,才能让道逐渐丰满,也才能为具体问题找到最合适的设计方案。

推荐

消除过长方法

消除过长类

消除重复代码

答粉丝问

你的参数列表像蚯蚓一样让人厌恶吗

职责单一原则真的简单吗

防止“加个需求,到处改代码”

最新文章

  1. 谈谈Ruby中的类变量
  2. IOS第12天(3,UIViewController的生命周期)
  3. MYSQL #1064错误
  4. 第一个python程序hello.py
  5. NSString和data转换
  6. IOC框架Ninject实践总结
  7. chmod -x chmod的N种解法
  8. ORACLE客户端乱码
  9. 计算机学院大学生程序设计竞赛(2015’12) 1009 The Magic Tower
  10. oracle 归档日志满 报错ORA-00257: archiver error. Connect internal only, until freed
  11. Python的json and pickle序列化
  12. Linux error numbers
  13. 完成端口IOCP详解
  14. 修改Linux服务器的ttl值
  15. Forward团队-爬虫豆瓣top250项目-模块测试
  16. Beta阶段——第2篇 Scrum 冲刺博客
  17. 排序算法<No.6>【插入排序】
  18. hibernate 的evict 和clear
  19. js指定范围随机整数
  20. chm 文件生成器

热门文章

  1. Java微信开发_Exception_03_非微信官方网页-invalid signature
  2. Dedecms丨显示指定的导航栏
  3. leetcode 24. Swap Nodes in Pairs(链表)
  4. leetcode 3 Longest Substring Without Repeating Characters(滑动窗口)
  5. 如何用Mendeley引用目标期刊要求的参考文献格式
  6. freeMarker(五)——模板开发指南补充知识
  7. DevExpress源码编译总结
  8. 宽字符wchar_t和窄字符char区别和相互转换
  9. eclipse安装M2Eclipse插件
  10. tar 排除某个目录