IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 代码整洁之道【5】-- 对象和数据结构 -> 正文阅读

[数据结构与算法]代码整洁之道【5】-- 对象和数据结构

坦白来讲,这章如果不仔细读两遍的话,不是那么好懂。

其实这章只要理解了作者说的“对象”和“数据结构”这两个概念,就好懂了,说下我的理解:

“对象”:暴露行为(方法),隐藏数据(成员private,没有get/set)。
“数据结构”:暴露数据(成员public,或者有get/set),没有明显的行为(方法)。

好,进入正题:

将变量设置为私有(private)有一个理由:我们不想其他人依赖这些变量。但是还是有很多程序员给对象自动添加get/set方法,将私有变量公之于众、如同他们根本就是公共变量一般。

一、数据抽象

举例,如下是两段表示Point的数据结构的代码。

public class Point {
    public double x;
    public double y;
}
public interface Point {
  double getX();
  double getY();
  void setCartesian(double x, double y);
  double getR();
  double getTheta();
  void setPolar(double r, double theta);
}

第二段代码的精妙之处在于,你不知道该实现会是在矩阵坐标系中还是极坐标系中,或者可能是其他的什么坐标系。然而,该接口还是明白无误的呈现出了Point这种数据结构。而第一段代码要求我们直接对x,y坐标进行操作,这其实暴露了Point的内部结构。实际上,即使变量设置成private,但因为我们也通过get、set方法使用变量,其结构仍然暴露了。

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

举个例子解释上面这段话,假设要计算机动车的剩余油量百分比,有以下两段代码:

public interface Vehicle {
  double getFuelTankCapacityInGallons();
  double getGallonsOfGasoline();
}
public interface Vehicle {
  double getPercentFuelRemaining();
}

以上两段代码第二种更好。第一段代码中直接暴露了燃油车的数据结构,你可以直接看出方法是哪些字段的get方法。而第二段代码中采用了百分比计算方法的抽象,隐藏了机动车的数据结构,直接获取剩余油量百分比。

写代码的过程中,我们不愿意暴露数据细节,更愿意以抽象形态表述数据(例如上面获取剩余油量百分比的方法)。无脑地添加get/set方法,是最坏的选择。

二、数据、对象的反对称性

对象把数据隐藏于抽象之后,暴露操作数据的函数。数据结构暴露其数据,没有提供有意义的函数。举例说明:

下面这段代码是过程式代码的范例。Geometry类操作了三个形状类。形状类都是简单的数据结构,没有任何行为(方法)。所有行为都在Geometry类中。

public class Square {
	public Point topLeft;
	public double 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.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();
	}
}

想想看,如果给Geometry类添加一个primeter()函数会怎样?现有的形状类根本不会受到影响。另一方面,如果添加一个新形状,那就得修改Geometry类中的所有函数来处理它了!

再来看下面这段面向对象方法的解决方案,这里area()方法是多态的,不需要有Geometry类。所以添加一个新形状的类,现有的函数一个也不会受到影响,而当添加新函数时所有的形状都得做修改。

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;

	}
}

对象与数据之间的二分原理:过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数,面向对象代码便于在不改动既有函数的前提下添加新类。

反过来说也说得通:过程式代码难以添加新数据结构,因为必须修改所有函数,面向对象代码难以添加新函数,因为必须修改所有类。所以,对于面向对象较难的事,对于过程式代码却较容易,反之亦然。这个就是数据和对象的反对称性。

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

三、迪米特法则

迪米特法则(Law of Demeter)又叫作最少知识原则,英文简写为: LoD。迪米特法则认为一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。

如上一节所说,对象隐藏数据,暴露操作。这也就意味着对象不应该通过get/set方法暴露其内部结构,因为这样更像是暴露结构而不是隐藏内部结构。

举个例子:

下面这个代码违反了迪米特法则,因为它调用了getOptions()方法返回值的getScratchDir()方法,又调用了getScratchDir()方法返回值的getAbsolutePath()方法。

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

get方法把这个问题搞复杂了。如果我们用下面代码这种形式,就不涉及到违反迪米特法则的问题了。

final String outputDir = ctxt.options.scratchDirs.absolutePath;

所以这里可以做个小总结:如果数据结构只简单地拥有公共变量,没有函数;对象拥有私有变量和公共方法,那么就很容易判断是不是符合迪米特法则,问题就不容易混淆。

但是很不幸,有些代码一半是对象、另一半是数据结构。这将会导致这种代码既增加了添加新函数的难度,又增加了添加新数据结构的难度,两边不讨好。所以应该尽量避免这种结构的代码。

四、数据传送对象(DTO)

最为精炼的数据结构,是一个只有公共变量、没有函数的类。这种数据结构有时被称为数据传送对象(Data Transfer Object,DTO)。DTO非常有用,尤其是在与数据库通信等应用场景下,用于将原始数据转化为数据库中数据。

Active Record是一种特殊形式的DTO形式。它们拥有公共变量的数据结构,但通常也会有类似save、find这样的方法。Active Record是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。这类数据结构中不应该塞进业务方法,不然会导致数据结构和对象混杂,造成之前提到过的两面不讨好的问题。

参考
《代码整洁之道》

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-10-02 15:06:48  更:2021-10-02 15:07:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 11:12:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码