Skip to content

Latest commit

 

History

History
216 lines (149 loc) · 7.98 KB

第6章 对象和数据结构.md

File metadata and controls

216 lines (149 loc) · 7.98 KB

第6章 对象和数据结构

6.1 数据抽象

隐藏实现并非只是在变量之间放上一个函数层那么简单。隐藏实现关乎抽象!类并不简单地用取值器和赋值器将其变量推向外间,而是暴露抽象接口,以便用户无需了解数据的实现就能操作数据本体。

  1. 具象点
public class Point{
    public double x;
    public duuble y;
}
  1. 抽象点

    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();
    }

    以上两段代码以后者为佳。我们不愿暴露数据细节,更愿意以抽象形态表述数据。这并不是用接口和、或赋值器,取值器就万事大吉,要以最好的方式呈现某个对象包含的数据,需要做严肃的思路。

    6.2 数据、对象的反对称性

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

    1. 过程式代码
    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;
          }
        }

    在任何一个复杂系统中,都会有需要添加新数据类型而不是新函数的时候。这时,对象和面向对象就比较合适。另一方面,也会有想要添加新函数而不是数据类型的时候。这时,过程式代码和数据结构更合适。

    6.3 得墨忒耳律

    对象不应该通过存取器暴露其内部结构。

    方法不应调用有任何函数返回的对象的方法。

    下列代码违反了得墨忒耳律,因为他调用了getOptions()返回值的getScratchDir()函数,又调用了getScratchDir()返回值的getAbsolutePath()方法。

    final String outputDIr = ctxt.getOptions().getScratchDir().getAbsolutePath();
    6.3.1 火车失事

    上述代码常被称为火车失事。最好做类似如下的切分。

    Options opts = ctxt.getOptions();
    File scratchDir = opts.getScratchDir();
    final String outputDir = scratchDir.getAbsolutePath();

    这样又会违反得墨忒耳律。

    属性访问器函数的使用把问题复杂化了。

    如果数据结构只简单的拥有公共变量。没有函数,而对象则拥有是有变量和公共函数,这个问题就不那么混淆。然而,有些框架和标准甚至要求最简单的数据结构都要有房问题和改值器。

    6.3.2 混杂

    这种混淆有时会不幸导致混合结构,一半是对象,一半是数据结构。这种结构拥有执行操作的函数。也有公共变量或公共访问器及改值器。无论出于怎么样的初衷,公共访问器或改值器都把私有变量公开化,诱导外部函数以过程式程序使用数据结构的方式使用这些变量。

    这类混杂增加了添加新函数的难度,也增加了添加新数据结构的难度,两面不讨好,应避免创造这种结构。他们的出现,展示了一种乱七八槽的设计,其作者不确定--或者更槽糕,完全无视-----他们是否需要函数或类型的保护。

    6.3.3 隐藏结构

    假设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隐藏了其内部结构,防止当前函数因浏览他不知道的对象而违反得墨忒耳律。

    6.4 数据传送对象

    最为精炼的数据结构,是一个只有公共变量,没有函数的类。这种数据结构有时被称为数据传送对象,或DTO(Data Transfer Objects)。DTO是非常有用的结构,尤其是在与数据库通信,或解析套接字传递的消息之类的场景中。

    豆(bean)结构拥有有赋值器和取值器操作的私有变量。对豆结构的半封装会让某些OO纯化论者感觉舒服些,不过通常没有其他好处。

    Active Record是一种特殊的DTO形式。他们是拥有公共(或可豆式访问的)变量的数据结构,但通常也会拥有类似save和find这样的可浏览方法。Active Record一般是对数据库表和其他数据源的其他翻译。开发者常常往这类数据结构中塞进业务规则方法。把这类数据结构当成对象来用。是不明智的行为。

    解决方案就是把Active Record当做数据结构,并创建包含业务规则、隐藏内部数据的独立对象。

    6.5 小结

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

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

    优秀的软件开发者不带成见地了解这两种情形,并依据手边工作的性质选择其中一种手段。