隐藏实现并非只是在变量之间放上一个函数层那么简单。隐藏实现关乎抽象!类并不简单地用取值器和赋值器将其变量推向外间,而是暴露抽象接口,以便用户无需了解数据的实现就能操作数据本体。
- 具象点
public class Point{
public double x;
public duuble y;
}
-
抽象点
public interface Point{ double getX(); double getY(); void setCaresian(double x,double y); double getR(); double getTheta(); }
2的漂亮之处在于,你不知道该实现会在矩形坐标系中还是在极坐标系中。可能两个都不是,然而,该接口还是明白无误的呈现了一种数据结构。
public interface Vehicle{ double getFuelTankCapacityInGallons(); double getGallonsOfGasoline(); }
public interface Vehicle{ double getPercentFuelRemaining(); }
以上两段代码以后者为佳。我们不愿暴露数据细节,更愿意以抽象形态表述数据。这并不是用接口和、或赋值器,取值器就万事大吉,要以最好的方式呈现某个对象包含的数据,需要做严肃的思路。
过程式代码便于在不改动既有数据结构的前提下,添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类。
- 过程式代码
public class Square{ public Point topLeft; public doubel side; } public class Rectangle{ public Point topLeft; public double height; public double width; } public class Circle{ public Point center; public double radius; } public class Geometry{ public final double PI = 3.14159265358793; public double area(Object shape) throws NoSuchShapeException{ if(shape instanceof Square){ Square s = (square)shape; return s.side*s.side; } else if (shape instanceof Rectangle){ Rectangle r = (Rectangle)shape; return r.height*r.width; } else if (shape instance Circle){ Circle c =(Circle)shape; return PI*c.radius*c.radius; } throw new NoSuchShapeException(); } }
多态式形状
public class Square implements shape{ private Point toLeft; private double side; public double area(){ return side*side; } } public class Rectangle implements Shape{ private Point topLeft; private double height; private double width; public double area(){ return height*width; } } public class Circle implemnts Shape{ private Point center; priavte double radius; public final double PI = 3.141592653589793; public double area(){ return PI*radius*radius; } }
在任何一个复杂系统中,都会有需要添加新数据类型而不是新函数的时候。这时,对象和面向对象就比较合适。另一方面,也会有想要添加新函数而不是数据类型的时候。这时,过程式代码和数据结构更合适。
对象不应该通过存取器暴露其内部结构。
方法不应调用有任何函数返回的对象的方法。
下列代码违反了得墨忒耳律,因为他调用了getOptions()返回值的getScratchDir()函数,又调用了getScratchDir()返回值的getAbsolutePath()方法。
final String outputDIr = ctxt.getOptions().getScratchDir().getAbsolutePath();
上述代码常被称为火车失事。最好做类似如下的切分。
Options opts = ctxt.getOptions(); File scratchDir = opts.getScratchDir(); final String outputDir = scratchDir.getAbsolutePath();
这样又会违反得墨忒耳律。
属性访问器函数的使用把问题复杂化了。
如果数据结构只简单的拥有公共变量。没有函数,而对象则拥有是有变量和公共函数,这个问题就不那么混淆。然而,有些框架和标准甚至要求最简单的数据结构都要有房问题和改值器。
这种混淆有时会不幸导致混合结构,一半是对象,一半是数据结构。这种结构拥有执行操作的函数。也有公共变量或公共访问器及改值器。无论出于怎么样的初衷,公共访问器或改值器都把私有变量公开化,诱导外部函数以过程式程序使用数据结构的方式使用这些变量。
这类混杂增加了添加新函数的难度,也增加了添加新数据结构的难度,两面不讨好,应避免创造这种结构。他们的出现,展示了一种乱七八槽的设计,其作者不确定--或者更槽糕,完全无视-----他们是否需要函数或类型的保护。
假设ctxt,Options和ScratchDir是拥有真实行为的对象又怎么样呢?由于对象应隐藏其内部结构,我们就不该能够看到内部结构,这样一来,如何才能取得临时目录的绝对路径呢?
ctxt.getAbsolutePathofScratchDirectoryOption();
或者
ctxt.getScratchDirectoryOption().getAbsolutePath()
第一种方案可能导致ctxt对象中方法的暴露。第二种方案是假设getScratchDirectoryOption()返回一个数据结构而非对象。两种方案感觉都不好。
String outFile = outputDir +"/" + className.replace(".","/")+".class"; FileOutputStream fout = new FileOutputStream(outFile); BufferedOutputStream bos = new BufferedOutputStream(fout);
这种不同层级细节的混杂有点麻烦。句点,斜杠,文件扩展名和File对象不该如此随便的混杂在一起。不过,撇开这些毛病,我们发现,取得临时目录绝对路径的初衷是为了创建指定名称的临时文件。
所以直接让cxtx对象来做这事如何?
BufferedOutpurStream bos = ctxt.createScratchFileStream(classFileName);
这下看起来像是个对象做的事了! ctxt隐藏了其内部结构,防止当前函数因浏览他不知道的对象而违反得墨忒耳律。
最为精炼的数据结构,是一个只有公共变量,没有函数的类。这种数据结构有时被称为数据传送对象,或DTO(Data Transfer Objects)。DTO是非常有用的结构,尤其是在与数据库通信,或解析套接字传递的消息之类的场景中。
豆(bean)结构拥有有赋值器和取值器操作的私有变量。对豆结构的半封装会让某些OO纯化论者感觉舒服些,不过通常没有其他好处。
Active Record是一种特殊的DTO形式。他们是拥有公共(或可豆式访问的)变量的数据结构,但通常也会拥有类似save和find这样的可浏览方法。Active Record一般是对数据库表和其他数据源的其他翻译。开发者常常往这类数据结构中塞进业务规则方法。把这类数据结构当成对象来用。是不明智的行为。
解决方案就是把Active Record当做数据结构,并创建包含业务规则、隐藏内部数据的独立对象。
对象暴露行为,隐藏数据。便于添加新对象类型而无需修改既有行为,同时也难以在既有对象中添加新行为。
数据结构暴露数据,没有明显的行为。便于向既有数据结构添加新行为,同时也难以向既有函数添加新数据结构。
优秀的软件开发者不带成见地了解这两种情形,并依据手边工作的性质选择其中一种手段。