把大象放入冰箱与设计抽象性的思考 

Q:如何把大象放入冰箱

A:把冰箱打开,然后把大象放进去,再把冰箱关上

当然这是一个脑筋急转弯的笑话,但是对于系统设计者的启示是,如果你设计了一个足够抽象的接口,理论上这个接口可以代表近似于无限的实现,简单的说越抽象的接口,它可以衍生出来的实现就越多。

如果我们把上面的问题当作一个软件功能上的需求并对它进行建模,我们可能会推导出下面的UML模型:

Elephant

在上图中,接口Containable和Placeable表达了两个非常抽象的概念,一个代表可以放置Placeable的容器,一个代表可以放入Containable的物件,而类IceBox和Elephant 分别实现了这两个接口。

如果用代码来表示把大象放入冰箱的过程,可能的代码如下:

 1: void placeElephantInIceBox(IceBox box, Elephant e)
 2: {
 3: 
 4:     box.open();
 5:     box.place(e);
 6:     box.close();
 7: }

上面的思考主要是受最近在看的Java 2D Graphics一书的启发,Java 2D是Java提供的2D Graphics Library,它是一个全功能,桌面操作系统级别的2D绘图库,为了能够在实现复杂功能的同时保有一个尽可能简单的编程接口,并且允许用户继续对它进行扩充,Java 2D提供了一个高抽象度的模型,很多接口的抽象度基本就跟上面的Containable和Placeable类似。

比如说Stroke接口代表了笔画,但是它实际上是将一个输入的Shape转换成根据该笔画描绘出来的轮廓的Shape(Java 2D只处理图形填充,所谓描绘图形的轮廓实际上是先生成图形的轮廓图形,然后再对该轮廓图形进行填充)。

下面的代码基本等同于直接调用Graphics2D中的draw(graphics.draw(shape);):

 1: void drawShape(Graphics2D graphics, Shape shape)
 2: {
 3:     Stroke stroke = graphics.getStroke();
 4:     Shape strokedOutline = stroke.createStrokedShape(shape);
 5:     graphics.fill(strokedOutline);
 6: }

其中createStrokedShape就是Stroke接口的唯一的方法。