《代码整洁之道》笔记04——数据结构

数据抽象

隐藏变量内部结构,可以通过抽象取值和设置的方法,让用户无须关心数据的实现而就能操作数据本体。

例如:

class Point {
    getX:function(){};
    getY:function(){};
 	setX:function(){};
    setY:function(){};
}

当然并不是一味的暴露取值和赋值器就可以完成。

例如:

class Vehicle {
    getFuelTankCapacityInGallons:function(){}
	getGallonsOfGasoline:function(){}
}
class Vehicle {
    getPercentFuelRemaining:function(){}
}

前者很明显是具象手段与机动车的燃料曾层进行通信;而后者则是通过抽象手段采用百分比来获得燃料的数据。

前者你很明显可以发现是变量的存取器;而后者是·并不能知道内部的数据形态。

这里明显后者为佳,因为我们不愿意暴露数据的细节,更愿意以抽象形态表述数据。

要以最好的方式呈现某个对象包含的数据,而不是一味的取值器与赋值器来完成。

数据、对象的反对称性

上面的2个例子表现了对象与数据结构的差异。

  1. 对象把数据隐藏于抽象之后,暴露操作数据的函数。

  2. 数据结构暴露其数据结构,没有提供有意义的函数

这2者的差距虽小,但是有着深远的意义。

  1. 过程式代码

    class Square {
        point:{x:9,y:0}
    	side:20
    }
    class Rectangle {
        point:{x:9,y:0}
    	height:10;
    	width:10;
    }
    class Geometry {
        Pi: 3.14
    	area(shape){
            if(shape instanceof Square) {
                let s = shape
                return s.side  * s.side
            }
            if(shape instanceof Rectangle) {
                let s = shape
                return s.height  * s.width / 2
            }
            throw new Error('不存在该图像')
        }
    }
    

    形状类只具有数据,而Geometry具有具体的函数。

    1. 优点:

    ​ 我们无论在Geometry内部中添加什么方法,都不会影响到外部的形状类的数据结构。

    1. 缺点:

    ​ 但是如果添加新的新形状,就需要在Geometry添加新的代码。

  2. 面向对象的方法

    class Square {
        point:{x:9,y:0}
    	side:20
    	area:function(){}
    }
    class Rectangle {
        point:{x:9,y:0}
    	height:10;
    	width:10;
    	area:function(){}
    }
    

    每一个形状类中都有自己的函数,这样的函数好处明显与前者相反,在新添加一个形状类的时候,其他类的函数不会受到影响。

这其中体现了数据结构与对象的二分原理:

过程式代码(使用数据结构的代码)便于在不改动既有的数据结构前提下添加新的函数;而面向对象代码便于在不改动既有函数的前提下添加新的类。

反过来看也说得通:

过程式代码难以添加新数据结构,因为必须修改所有函数。面向对象难以添加新的函数,因为需要修改所有类。

得墨忒尔律模块不应了解它所操作对象的内部情况

对象隐藏数据,暴露操作。这意味着对象不应该通过存取器来暴露操作,因为这样也会暴露其数据结构。

得墨忒尔认为:类C的方法 f 只应该调用以下对象的方法

  1. C
  2. 由于 f 创建的对象
  3. 作为参数传递给 f 的对象
  4. 由C的实体变量持有的对象

换句话而言,方法不应该调用由任何函数返回的对象的方法。只跟自己朋友说话,而不和陌生人说话。

我们通过几个反例来了解这个定律:

  1. 火车失事

    let opts = ctx.getOptions()
    let scratchDir = opts.getScratchDits()
    let outputDir = scratchDir.getAbsolutePath()
    

    这个很像火车意义,一节接着一节连接着。我们来看看如何违法了这个定律:

    模块ctx对象包含了许多选项,每一个选项都有许多目录,每一个目录对应一个路径。

    对于一个函数而言,它实在太丰富了。

    如果上面只是数据结构,当然该定律并不适用;而如果是对象的话,它就暴露了对象中的数据结构,不符合我们对象的隐藏数据结构,暴露操作数据的函数。

  2. 混杂

    这种混杂有时候会不幸导致混合结构,一半是对象,一半是数据结构。这种结构可能会同时出现提高添加新数据结构的难度,也出现提高添加新的类的难度。

  3. 隐藏结构

    就拿火车失事的例子来说明。如果我们需要隐藏数据结构同时,又要拿到临时目录的绝对路径,我们该如何实现呢?

    我们首先来看看取得绝对路径的需求,获得绝对路径是为了获得创建指定名称的临时文件,我们可以直接让ctx来操作

    let file = ctx.createScratchFileStream(classFileName)
    

    这样即隐藏了数据结构,也防止了当前函数浏览它不该知道的对象。

数据传递对象

DTO是最为精炼的数据结构,只有公共变量,没有函数的类。这种结构有时候被称为数据传递对象。

主要用于:数据库通信或者解析套接字传递的消息之类的场景。

小结

  1. 对象暴露行为,隐藏数据,便于添加新对象类型而不需要修改既有的行为,同时也难以在既有的对象中添加新的行为。

  2. 数据结构暴露数据,没有明显的行为,便于再既有的数据结构添加新的行为,同时也难以向新既有函数添加新的数据结构。

你可能感兴趣的:(一些学习的研究,代码整洁之道)