数据抽象
隐藏变量内部结构,可以通过抽象取值和设置的方法,让用户无须关心数据的实现而就能操作数据本体。
例如:
class Point {
getX:function(){};
getY:function(){};
setX:function(){};
setY:function(){};
}
当然并不是一味的暴露取值和赋值器就可以完成。
例如:
class Vehicle {
getFuelTankCapacityInGallons:function(){}
getGallonsOfGasoline:function(){}
}
class Vehicle {
getPercentFuelRemaining:function(){}
}
前者很明显是具象手段与机动车的燃料曾层进行通信;而后者则是通过抽象手段采用百分比来获得燃料的数据。
前者你很明显可以发现是变量的存取器;而后者是·并不能知道内部的数据形态。
这里明显后者为佳,因为我们不愿意暴露数据的细节,更愿意以抽象形态表述数据。
要以最好的方式呈现某个对象包含的数据,而不是一味的取值器与赋值器来完成。
数据、对象的反对称性
上面的2个例子表现了对象与数据结构的差异。
-
对象把数据隐藏于抽象之后,暴露操作数据的函数。
-
数据结构暴露其数据结构,没有提供有意义的函数
这2者的差距虽小,但是有着深远的意义。
-
过程式代码
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
具有具体的函数。- 优点:
我们无论在
Geometry
内部中添加什么方法,都不会影响到外部的形状类的数据结构。- 缺点:
但是如果添加新的新形状,就需要在
Geometry
添加新的代码。 -
面向对象的方法
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 只应该调用以下对象的方法
- C
- 由于 f 创建的对象
- 作为参数传递给 f 的对象
- 由C的实体变量持有的对象
换句话而言,方法不应该调用由任何函数返回的对象的方法。只跟自己朋友说话,而不和陌生人说话。
我们通过几个反例来了解这个定律:
-
火车失事
let opts = ctx.getOptions() let scratchDir = opts.getScratchDits() let outputDir = scratchDir.getAbsolutePath()
这个很像火车意义,一节接着一节连接着。我们来看看如何违法了这个定律:
模块
ctx
对象包含了许多选项,每一个选项都有许多目录,每一个目录对应一个路径。对于一个函数而言,它实在太丰富了。
如果上面只是数据结构,当然该定律并不适用;而如果是对象的话,它就暴露了对象中的数据结构,不符合我们对象的隐藏数据结构,暴露操作数据的函数。
-
混杂
这种混杂有时候会不幸导致混合结构,一半是对象,一半是数据结构。这种结构可能会同时出现提高添加新数据结构的难度,也出现提高添加新的类的难度。
-
隐藏结构
就拿火车失事的例子来说明。如果我们需要隐藏数据结构同时,又要拿到临时目录的绝对路径,我们该如何实现呢?
我们首先来看看取得绝对路径的需求,获得绝对路径是为了获得创建指定名称的临时文件,我们可以直接让
ctx
来操作let file = ctx.createScratchFileStream(classFileName)
这样即隐藏了数据结构,也防止了当前函数浏览它不该知道的对象。
数据传递对象
DTO是最为精炼的数据结构,只有公共变量,没有函数的类。这种结构有时候被称为数据传递对象。
主要用于:数据库通信或者解析套接字传递的消息之类的场景。
小结
-
对象暴露行为,隐藏数据,便于添加新对象类型而不需要修改既有的行为,同时也难以在既有的对象中添加新的行为。
-
数据结构暴露数据,没有明显的行为,便于再既有的数据结构添加新的行为,同时也难以向新既有函数添加新的数据结构。