• 代码整洁之道-读书笔记之对象和数据结构


    1. 数据抽象

    暴露给用户抽象接口,而不是具体实现(面向接口编程

    看两个例子:

    具象点

    public class Point{ 
    	public double x;
    	public double y;
    }
    
    • 1
    • 2
    • 3
    • 4

    抽象点

    public interface Point{ 
    	double getX();
    	double getY();
    	void setCartesian(double x, double y); 
    	double getR();
    	double getTheta();
       // 极坐标
    	void setPolar(double r, double theta);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    具象点

    public interface Vehicle {
    	// 获取燃油箱容量(加仑)
    	double getFuelTankCapacityInGallons(); 
    	// 获得加仑汽油
    	double getGallonsofGasoline();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    抽象点

    public interface Vehicle{
    	// 获取剩余燃油百分比
    	double getPercentFuelRemaining(); 
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    2. 数据、对象的反对称性

    面向过程编程和面向对象编程是对立的

    面向过程编程:便于在不改动既有数据结构的前提下添加新函数(难以添加新的数据结构,因为必须要修改所有函数)

    面向对象编程:便于在不改动既有函数的前提先添加新类(难以添加新函数,因为必须修改所有类)

    看一个例子

    面向过程编程

    public class Square{
    	public Point topLeft; 
    	public double side;
    }
    
    public class Rectangle{
    	public Point topLeft; 
    	public double heighta; 
    	public double width;
    }
    
    public class Circle{
    	public Point center; 
    	public double radius;
    }
    
    public class Geometry{
    	public final double PI = 3.141592653589793;
    
    	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 instanceof Circle) {
    			Circle c= (Circle) shape;
    			return PI * C.radius * c.radius;
    		}
    		throw new NoSuchShapeException ();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    面向对象编程

    public class Square implements Shape{ 
    	private Point topLeft;
    	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 implements Shape {
    	private Point center;
    	private double radius;
    	public final double PI=3.141592653589793;
    	public double area(){
    		return PI * radius * radius; 
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    3. 得墨忒耳律

    著名的得墨式耳律(The Law of Demeter) '认为,模块不应了解它所操作对象的内部情形。如上节所见,对象隐藏数据,曝露操作。这意味着对象不应通过存取器曝露其内部结构,因为这样更像是曝露而非隐藏其内部结构。

    更准确地说,得墨式耳律认为,类C的方法f只应该调用以下对象的方法:

    1.当前类C

    2.由f创建的对象;

    3.作为参数传递给f的对象;

    4.由C的实体变量(成员变量的一种)持有的对象。

    方法不应调用由任何函数返回的对象的方法。换言之,只跟朋友谈话,不与陌生人谈话。

    错误的调用

    final String outputDir = ctxt.getoptions().getscratchoir().getAbsolutePath(); 
    
    • 1

    3.1. 火车失事

    上面的代码被称作火车失事,因为看起来就像一列火车。

    一般可以改造为

    1.通过公共函数
    Options opts=ctxt.getoptions();
    File scratchDir=opts.getScratchoir();
    final String outputDir=scratchDir.getAbsolutePath();
    
    • 1
    • 2
    • 3
    • 4
    2.通过属性值
    final String outputDir = ctxt.options.scratchoir.absolutePath; 
    
    • 1
    • 2

    3.2 混乱

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

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

    3.3 隐藏结构

    假使ctxt、Options和ScratchDir是拥有真实行为的对象又怎样呢?由于对象应隐藏其内部结构,我们就不该能够看到内部结构。这样一来,如何才能取得临时目录的绝对路径呢?我们取得临时目录的绝对路径是为了干什么呢?

    String outFile = outputDir +"/" + className.replace('.','/')+".class";
    FileOutputStreamfout=new FileoutputStream(outfile):
    BufferedoutputStream bos=new BufferedoutputStream(fout); 
    
    • 1
    • 2
    • 3

    我们发现是为了创建指定名称的临时文件,那我们应该怎么做呢

    BufferedoutputStream bos=ctxt.createScratchFileStream(classFileName);
    
    • 1

    直接通过ctxt实现临时文件的创建,将创建的细节隐藏

    4. 数据传送对象

    最为精练的数据结构,是一个只有公共变量、没有函数的类。这种数据结构有时被称为数据传送对象,或DTO(Data Transfer Objects)。

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class JobBuildDetailDTO {
        private String disPlayName;
        private Integer lastBuildNumber;
        private Integer nextBuildNumber;
        private Boolean isQueue;
        private String lastBuildJobUrl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    5. 小结

    我们应该对外暴露操作,隐藏数据

    DTO对外暴露数据,不含有明显操作

    面向过程编程:便于在不改动既有数据结构的前提下添加新函数(难以添加新的数据结构,因为必须要修改所有函数)

    面向对象编程:便于在不改动既有函数的前提先添加新类(难以添加新函数,因为必须修改所有类)

  • 相关阅读:
    怎么给iPhone手机上的待办事项软件加上密码锁
    androidX org.eclipse.paho.android.service:报错Program type already present
    学习ASP.NET Core Blazor编程系列二——第一个Blazor应用程序(上)
    面试题复盘-2023/10/20
    10.28模拟赛总结
    uniapp在不需要后端数据的情况下 怎么记录用户进一次记录一次
    JAVA基础(三十六)——常用类之String类
    动态添加二级表头 You may have an infinite update loop in a component rende 9 function.
    【专栏】核心篇06| Redis 存储高可用背后的模式
    R语言中的取整函数
  • 原文地址:https://blog.csdn.net/constant_rain/article/details/127632625