zoukankan      html  css  js  c++  java
  • 大话重构 之 原来反OO天天见

    在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的元编程。只有掌握不同的术,才能让道逐渐丰满,也才能为具体问题找到最合适的设计方案。

    推荐

    消除过长方法

    消除过长类

    消除重复代码

    答粉丝问

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

    职责单一原则真的简单吗

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

    图片二维码

  • 相关阅读:
    jar
    8月21日23:38
    WPF之UI虚拟化
    (转)Windows系统白名单以及UAC机制
    C#获取文件版本信息
    命名实体识别,使用pyltp提取文本中的地址
    (转载)完成端口(Completion Port, I/OCP)详解
    全国各城市地名抓取,包含街道、村落、小区、商店、景点等
    关于Python打包运行的一些思路
    关于批判性思维(Critical Thinking)
  • 原文地址:https://www.cnblogs.com/yoyaprogrammer/p/not_only_oo.html
Copyright © 2011-2022 走看看