对象和数据结构有啥区别?
Posted
技术标签:
【中文标题】对象和数据结构有啥区别?【英文标题】:Whats the difference between objects and data structures?对象和数据结构有什么区别? 【发布时间】:2014-06-17 20:22:54 【问题描述】:我一直在阅读 Clean Code: A Handbook of Agile Software Craftsmanship 这本书,在第六章第 95-98 页中,它阐明了对象和数据结构之间的区别:
对象将其数据隐藏在抽象之后,并公开操作该数据的函数。数据结构暴露了它们的数据并且没有有意义的功能。
对象公开行为并隐藏数据。这使得在不改变现有行为的情况下添加新类型的对象变得容易。这也使得向现有对象添加新行为变得困难。
数据结构公开数据并且没有重要的行为。这使得向现有数据结构添加新行为变得容易,但向现有函数添加新数据结构变得困难。
我有点困惑某些类是对象还是数据结构。例如 java.util 中的 HashMaps,它们是对象吗? (因为它的 put()、get() 等方法,我们不知道它们的内部工作原理)还是它们是数据结构? (我一直认为它是数据结构,因为它是一个 Map)。
字符串也是,它们是数据结构还是对象?
到目前为止,我编写的大部分代码都是所谓的“混合类”,它们也尝试充当对象和数据结构。关于如何避免它们的任何提示?
【问题讨论】:
也许只是我个人的看法,但听起来作者在混淆数据结构和抽象数据类型。见the difference。我还假设(嗯,希望)有一些前言说明这些是最佳实践,而不是那些必然总是正确的 - 我无法想象任何理智的人会称HashMap
为数据结构,然后称它为某事否则(或至少不是数据结构),例如,如果您将 table
公开。
没有看书的朋友,请看同一作者的这篇文章:sites.google.com/site/unclebobconsultingllc/…
【参考方案1】:
数据结构和类/对象之间的区别在 Java 中比在 C++ 中更难解释。在 C 中,没有类,只有数据结构,它们只不过是类型化和命名字段的“容器”。 C++ 继承了这些“结构”,因此您可以同时拥有“经典”数据结构和“真实对象”。
在 Java 中,您可以使用没有方法且只有公共字段的类“模拟”C 风格的数据结构:
public class VehicleStruct
public Engine engine;
public Wheel[] wheels;
VehicleStruct
的用户知道车辆的部件,并且可以直接与这些部件进行交互。行为,即函数,必须在类之外定义。这就是改变行为很容易的原因:添加新功能不需要更改现有代码。另一方面,更改数据需要更改与VehicleStruct
交互的几乎每个函数。它违反了封装!
OOP 背后的想法是隐藏数据并公开行为。它着重于您可以对车辆做什么,而无需知道它是否有发动机或安装了多少个***:
public class Vehicle
private Details hidden;
public void startEngine() ...
public void shiftInto(int gear) ...
public void accelerate(double amount) ...
public void brake(double amount) ...
请注意Vehicle
可能是摩托车、汽车、卡车或坦克——您无需了解详细信息。更改数据很容易——班外没有人知道数据,因此不需要更改班级的用户。改变行为是困难的:当一个新的(抽象)函数被添加到类中时,所有的子类都必须调整。
现在,遵循“封装规则”,您可以将隐藏数据理解为简单地将字段设为私有并将访问器方法添加到VehicleStruct
:
public class VehicleStruct
private Engine engine;
private Wheel[] wheels;
public Engine getEngine() return engine;
public Wheel[] getWheels() return wheels;
在他的书中,鲍勃叔叔认为,通过这样做,您仍然拥有数据结构而不是对象。您仍然只是将车辆建模为其各部分的总和,并使用方法公开这些部分。它本质上与具有公共字段和普通旧 C struct
的版本相同——因此是数据结构。隐藏数据和公开方法不足以创建对象,您必须考虑这些方法实际上是公开行为还是仅公开数据!
当您混合使用这两种方法时,例如暴露getEngine()
和startEngine()
,你最终会得到一个“混合”。我手头没有 Martin 的书,但我记得他根本不推荐混合,因为你最终会得到两全其美的结果:数据和行为都很难改变的对象。
您关于 HashMaps 和 Strings 的问题有点棘手,因为它们的级别非常低,不太适合您将为应用程序编写的类。不过,使用上面给出的定义,您应该能够回答它们。
HashMap
是一个对象。它向您展示其行为并隐藏所有令人讨厌的散列细节。你把它告诉put
和get
数据,而不关心使用哪个散列函数,有多少“桶”,以及如何处理冲突。实际上,您仅通过其Map
接口使用HashMap
,这很好地表明了抽象和“真实”对象。
您可以使用 Map 的 instances 作为数据结构的替代品,不要感到困惑!
// A data structure
public class Point
public int x;
public int y;
// A Map _instance_ used instead of a data structure!
Map<String, Integer> data = new HashMap<>();
data.put("x", 1);
data.put("y", 2);
另一方面,String
几乎是一个字符数组,不会试图隐藏太多。我想可以将其称为数据结构,但老实说,我不确定是否会以一种或另一种方式获得很多。
【讨论】:
很好的解释@ferdinard,谢谢!我发现了这篇文章 - hackernoon.com/objects-vs-data-structures-e380b962c1d2,它使用了你所有的解释,非常详细的例子。 @kay am see:感谢您分享链接,这确实是这个主题的一个非常好的资源! 创建一个数据结构,然后为您要公开的每个行为创建一个单独的对象类。您可以将数据结构传递到每个对象中以根据需要对其进行操作。您最终将在对象和数据结构之间得到很好的分离。您最终将拥有许多对象类,但您也会有适当的关注点分离! 关于两者的混合,鲍勃叔叔在他的《干净的代码》一书中说过:“这种混合使得添加新功能变得困难,但也使得添加新数据结构变得困难。它们是两全其美。避免创建它们。它们表明设计混乱,其作者不确定——或者更糟的是,不知道——是否需要保护它们免受函数或类型的影响。” 如果控制器不知道封装在模型中的细节,我试图从 MVC 模式中理解这一点?它如何序列化模型内部的细节并传回给演示者?【参考方案2】:这就是我相信的,罗伯特。 C. Martin 试图传达:
数据结构是简单地充当结构化数据容器的类。例如:
public class Point
public double x;
public double y;
另一方面,对象用于创建抽象。一个抽象被理解为:
对隐藏在The Law of Leaky Abstractions, Joel on Software的更复杂的事情进行了简化
因此,对象隐藏了它们的所有基础,只允许您以一种简化的方式操纵它们的数据的本质。例如:
public interface Point
double getX();
double getY();
void setCartesian(double x, double y);
double getR();
double getTheta();
void setPolar(double r, double theta);
我们不知道 Point 是如何实现,但我们知道如何使用它。
【讨论】:
Martin 书中的微妙之处在于您的界面Point
仍然暴露了它的部分,而不是行为。它的任何方法都不允许您对这一点做一些有意义的事情!我并不是说这很糟糕,一个点可能是一个典型的例子,当它更喜欢数据结构而不是对象时,但这可能不是最好的例子。
@FerdinandBeyer,代码是从 Martin 的书中复制的(第 94 页)。看到getX()
和getY()
被曝光后,我最初的反应和你一样。 Martin 说:“美妙之处在于,您无法判断实现是在直角坐标还是极坐标中。可能两者都不是!但接口仍然明确无误地代表了一个数据结构。但它代表的不仅仅是只是一个数据结构。这些方法强制执行访问策略。您可以独立读取各个坐标,但必须将坐标设置为原子操作。"【参考方案3】:
在我看来,Robert Martin 试图传达的是,对象不应通过 getter 和 setter 公开其数据,除非它们的唯一目的是充当简单的数据容器。此类容器的良好示例可能是 java bean、实体对象(来自 DB 实体的对象映射)等。
然而,Java 集合框架类并不是他所指的一个很好的例子,因为它们并没有真正公开它们的内部数据(在很多情况下是基本数组)。它提供了抽象,让您可以检索它们包含的对象。因此(在我的 POV 中)它们属于“对象”类别。
你从书中添加的引文中说明了原因,但还有更多充分的理由可以避免暴露内部。例如,提供 getter 和 setter 的类会违反 Demeter 法则。最重要的是,了解某个类的状态结构(知道它具有哪些 getter/setter)会降低抽象该类实现的能力。这类原因还有很多。
【讨论】:
【参考方案4】:对象是类的实例。 一个类可以对现实世界中的各种事物进行建模。它是某种事物的抽象(汽车、插座、地图、连接、学生、老师,你可以说)。
数据结构是以某种方式组织某些数据的结构。 您可以以与使用类不同的方式实现结构(这就是您在不支持 OOP 的语言中所做的事情,例如,您仍然可以在 C 中实现数据结构)。
Java 中的 HashMap 是一个使用基于哈希的实现对地图数据结构进行建模的类,这就是它被称为 HashMap 的原因。
Java 中的套接字是一个类,它不模拟数据结构,而是模拟其他东西(套接字)。
【讨论】:
String 是 Java 中的一个类。字符串 - 好的,如果您将其视为有序的字符序列,您也可以将其视为数据结构。或者您可以将 String 视为比数据结构更简单的东西——只是 Java 中的一种数据类型。可能第一次治疗更好(用你的话)。 @TristanMilan “字符串”是一种数据结构(在某些语言中也可能是一个类)。在 C 中,以空值结尾的 ASCII 值数组。在 Pascal 中,一个大小后跟该数量的 ASCII 值。等等…… 好吧,Clean Code 中的章节描述了何时应该将 类 视为对象或数据结构,而不是它是否模型 数据结构,即它的实例是否可以用作数据结构。它们是有区别的!HashMap
类应该被视为一个对象,因为它只公开行为,而不公开其内部数据。
@FerdinandBeyer 我不确定我是否明白你的意思。当然,任何类的任何实例都是对象,但并非每个类都对数据结构建模。例如。类 Socket 不为数据结构建模。那是我的观点。似乎您出于某种原因不喜欢这里的“模型”一词。
@peter.petrov 我猜您还没有阅读问题所指的“清洁代码”中的章节。作者并没有将“对象”仅仅定义为类的一个实例,而是研究了对象隐藏数据和暴露行为的原始 OOP 定义。根据本书,只有公共字段且没有方法的类应该被视为数据结构,类似于普通的旧 C 结构。在运行时可以替代结构的类建模不被视为数据结构。 HashMap 不是本书定义的数据结构。【参考方案5】:
数据结构只是一种抽象,一种表示数据的特殊方式。它们只是人造结构,有助于降低高层的复杂性,即不能在低层工作。一个对象可能看起来意味着同样的事情,但对象和数据结构之间的主要区别在于一个对象可以抽象任何东西。它还提供行为。数据结构没有任何行为,因为它只是保存数据的内存。
Map、List等库类。是类,它们表示数据结构。它们实现并设置了一个数据结构,以便您可以通过创建它们的实例(即对象)轻松地在程序中使用它们。
【讨论】:
【参考方案6】:数据结构(DS)是一种抽象的方式,表示结构包含一些数据'。带有一些键值对的 HashMap 是 Java 中的一种数据结构。关联数组在 php 等中也类似。对象比 DS 级别低一点。您的哈希图是一种数据结构。现在要使用哈希图,您可以创建它的“对象”并使用 put 方法将数据添加到该对象。我可以拥有自己的 Employee 类,它有数据,因此对我来说是一个 DS。但是要使用此 DS 执行一些操作,例如查看员工是男性还是女性同事,我需要一个 Employee 实例并测试其性别属性。
不要将对象与数据结构混淆。
【讨论】:
不要将 C 中使用struct
关键字创建的“静态”数据结构与 HashMap
的 instances 表示的“动态”数据结构混淆。这里的问题是 HashMap
类是否应该被视为遵循引用书的定义的对象或数据结构,而不是您是否可以使用 Map
实例来替换用 struct
声明的自定义数据结构或class
关键字!
@FerdinandBeyer 有什么不同吗?结构是静态的还是动态的与 IMO 无关。 DS 是编译时或代码时构造。我们对其进行编码以保存一些数据。该构造的实际运行时实现是对象。要在执行上下文中与它交互,您需要它的一个对象,您的静态或动态构造对于运行时执行上下文是概念性的。【参考方案7】:
你的问题被标记为 Java,所以我在这里只引用 Java。 对象是 Java 中的 Eve 类;也就是说Java中的一切都扩展了Object,而object是一个类。
因此,所有数据结构都是对象,但并非所有对象都是数据结构。
区别的关键在于封装一词。
当您在 Java 中创建对象时,将所有数据成员设为私有被认为是最佳实践。您这样做是为了保护他们免受任何使用该课程的人的伤害。
但是,您希望人们能够访问数据,有时还要对其进行更改。因此,您提供称为访问器和修改器的公共方法来允许它们这样做,也称为 getter 和 setter。此外,您可能希望他们以您选择的格式查看整个对象,因此您可以定义一个 toString 方法;这将返回一个表示对象数据的字符串。
结构略有不同。
这是一门课。
它是一个对象。
但它通常在另一个类中是私有的;由于节点在树中是私有的,因此树的用户不应直接访问。但是,在树对象内部,节点数据成员是公开可见的。节点本身不需要访问器和修改器,因为这些函数受树对象的信任和保护。
研究关键词:封装、可见性修饰符
【讨论】:
“所有数据结构都是对象,但并非所有对象都是数据结构”非常接近真实,但您的其余答案让我迷失了方向。可见性与某物是否是数据结构完全无关。将 DS 类的成员变量公开可能是不好的做法(和/或破坏),但许多人认为将 any 类的成员变量公开是不好的做法。而且“[一个数据结构???]通常在另一个类中是私有的”听起来是错误的。我不会将树的节点本身称为 DS。不知道“对象是 Eve 类”是什么意思。【参考方案8】:对象是类的实例。一个类可以定义该类的每个实例/对象继承的一组属性/字段。数据结构是一种组织和存储数据的方式。从技术上讲,数据结构是一个对象,但它是一个具有特定用途的对象,用于保存其他对象(Java 中的一切都是对象,甚至是原始类型)。
为了回答你的问题,字符串是一个对象和一个数据结构。您创建的每个 String 对象都是 String 类的一个实例。字符串,正如 Java 内部表示的那样,本质上是一个字符数组,而数组是一种数据结构。
并非所有的类都是数据结构的蓝图,但是所有数据结构在技术上都是对象 AKA 类的实例(专门设计用于存储数据),如果这有意义的话。
【讨论】:
抱歉,您完全没有抓住重点。这本书和这个问题更多地是关于面向对象编程的理论,其中对象的定义不仅仅是“类的实例”。此外,Java 中的原始类型是 not 对象!它们可以在对象中“装箱”,但int
类型的值与 Integer
类型的 object 不同!以上是关于对象和数据结构有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章