发现许多作者交替使用术语类型和类。某些涵盖基于对象的模型的教科书也涵盖了术语接口。
有人可以根据一般的面向对象编程,特别是 C++/Java/面向对象的数据库,用简单的术语来解释这些。
发现许多作者交替使用术语类型和类。某些涵盖基于对象的模型的教科书也涵盖了术语接口。
有人可以根据一般的面向对象编程,特别是 C++/Java/面向对象的数据库,用简单的术语来解释这些。
在 OOP 用语中,可以互换使用术语类型和类是很常见的,尤其是在谈论某些语言时,例如 java 和/或 C++。
一般来说,当您定义一个 Class 时,您实际上是在定义一个Type 模板,您所定义的Type的对象将被创建和/或实例化。
接口的概念有点复杂,当你定义一个接口时,你基本上是在指定一种特定的方式来处理使用它的任何类型的对象。也就是说,您的对象必须来自实现该接口的类。接口是一种通过指定原始类/类型规范中未包含的附加行为来增强类/类型的方法。
出于许多实际目的,您可以将对象的接口视为接口类型,这可能就是术语接口与术语类型互换使用的原因。
请参阅抽象数据类型背后的理论以获得进一步的见解。
类型是一段数据的分类,告诉您其允许的值和允许的操作是什么。(几乎?)所有编程语言都有类型,尽管打字规则因一种语言而异。
类是 OOP 语言中的一种特定类型,它是用语言本身中的特定语法定义的(与诸如 Java等由语言本身定义的所谓“本机类型”相反) . 一个类通常根据内存布局和数据编码(所谓的成员变量)以及对它们起作用的函数(所谓的成员函数或方法)来定义。int
float
接口* 是对类型必须实现哪些操作才能被视为给定相似类型集的一部分的规范,但它没有指定允许的值、内存布局等。
这是一个非常、非常、非常简短的概述,是几种语言处理这些方法的简化“平均形式”。它忽略了一些边缘情况以及诸如 C++ 在接口和类之间制作部分内容的能力。它还忽略了 Haskell 等函数式语言中的类,因为进一步损害你的大脑并不是这里的目标。;)
编辑以添加一些示例
这里有一些类似于 Java的声明来帮助巩固这些概念。
int myVariable1;
这个变量myVariable1
——是一个本机(或原始)类型,由一个以 2s 补码表示法编码的 32 位有符号整数值组成。它有一个已知的范围(大约 -20 亿到 +20 亿)和一组已知的可用运算(乘法、加法、除法、模数、减法、各种转换等)。
class MyClass
{
int myMemberVariable;
int myOtherMemberVariable;
int myMethod(int p) { myMemberVariable += p; myOtherMemberVariable = p; }
}
MyClass myVariable2 = new MyClass();
这myVariable2
是类定义的类型 MyClass
。 MyClass
定义内存布局(在这种情况下由两个 32 位有符号整数组成,采用 2s 补码表示法)以及myMethod()
将其参数添加到该参数myMemberVariable
并设置myOtherMemberVariable
到该参数的单个操作。
interface MyInterface
{
int myInterfaceMethod(int p, int q);
}
这里MyInterface
只声明了一组操作(在这种情况下由单个函数组成myInterfaceMethod()
),没有任何成员变量,也没有任何实现。它只告诉您,任何实现此接口的类都保证有一个方法,该方法具有名称 + 返回值 + 参数的特定签名。要使用它,您必须创建一个实现接口的类。
class MyOtherClass implements MyInterface
{
int myMember1;
int myMember2;
int myMember3;
int myInterfaceMethod(int p, int q) { myMember1 = p; myMember2 = q; myMember3 = p - q; }
int myNonInterfaceMethod() { return myMember1; }
}
MyOtherClass myVariable3 = new MyOtherClass();
NowmyVariable3
被定义为具有由三个带符号的 32 位整数和两个操作组成的内存布局的类型。其中一项操作是由于整个部分而必须执行的操作。implements MyInterface
这样,任何期望(抽象)MyInterface
类型的东西都可以使用(具体)MyOtherClass
类型,因为操作保证在那里。另一种方法————不是来自 MyInterface,所以只期望 a 的东西myNonInterfaceMethod()
不能使用它,因为它不知道它的存在。MyInterface
进一步 编辑以根据要求添加一些真实世界的东西
如果你曾经在你使用过类型的程序中使用过整数值、浮点值、字符串或类似的东西。类型可以说是计算的东西,我们所做的一切都是对给定类型的值的操作。因此,我将重点关注类和接口的 OOP 特定概念。
任何时候你有数据和对这些数据的操作,你就有可能成为一个类。以银行账户为例。除其他外,银行帐户将具有帐号,当前余额,交易限额等。代表此的类(很糟糕,仅用于解释概念)可能如下所示:
class BankAccount
{
String accountNumber;
float balance; /* DO NOT USE FLOATING POINT IN REAL FINANCIAL CODE! */
int transaction_limit;
float transaction(float change) {
balance += change > transaction_limit ? transaction_limit : change;
return balance;
}
}
现在您可以创建这种类型的变量并知道它将携带一个帐号(它本身就是一种String
类型)、一个余额(它本身就是一种float
类型——但不要在真实世界的金融代码中使用浮点数! ) 和交易限额(它本身就是一种int
类型)。您还知道您可以执行交易(创造性地称为transaction
),该交易将根据交易限制检查更改并修改余额。(一个真正的类会包含更多内容,并且会包含很多我出于教学目的而删除的模糊保护内容。)
现在假设您处于一个更复杂的金融环境中,其中有多种交易,而不仅仅是银行账户。让我们进一步说,您有一些代码将处理不关心底层类型的细节是什么的事务。一个离线的交易批处理器,比如说,它涵盖了银行账户、应收账款账户等。与其让它知道书中的每一种交易,我们可以这样做:
interface Transactable
{
float transaction(float change);
}
class BankAccount implements Transactable
{
/* interior is identical */
}
class ReceivablesAccount implements Transactable
{
float balance;
float transaction(float change) { balance += change; }
}
现在任何知道Transactable
类型的东西都可以使用你BankAccount
的实例和你ReceivablesAccount
的实例。他们不必知道银行账户有交易限额,而应收账款账户没有。他们不必了解数据的内部表示。他们不必知道任何事情的特殊情况。他们只需要通过名称 ( ) 了解一个函数,仅transaction()
此而已。Iterable
(如果您想要更具体的实际使用,请查看集合类如何使用Java 中的接口,更不用说“for in”循环了。)
就个人而言,我认为如果你用具体的术语来描述它会更容易理解。例如,假设我们要构建一个程序来跟踪厨房中的物品库存。
大多数语言只知道整数、字符和字节等非常简单、原始的“类型”。我们完全可以使用这些原始类型来描述厨房中事物的类型。例如,您可以构造一个由字符数组组成的数组,其中数组中的每个索引代表某物的一个属性。就像,也许索引 0 代表您要描述的事物的“类型”。但是,当然,这种技术很快就会变得非常复杂。
因此,使用面向对象技术,我们可以通过组合原始类型轻松定义新类型。你这样做的方法是创建一个类定义。将类视为新类型的蓝图。
在厨房示例中,我们有“食物”类型、“电器”类型和“家具”类型的概念。为了在程序中使用这些新类型,我们需要为每个类型创建一个类定义:
public class Food {...}
public class Appliance {...}
public class Furniture {...}
接口描述了我们如何与某物进行交互。例如,我们想在厨房里创建一个物品清单。我们希望能够“添加”到我们的库存中,从我们的库存中“删除”东西,并可能“迭代”我们的库存。“添加”、“删除”和“迭代”操作几乎适用于任何列表(不仅仅是我们的库存)。为此,Java 提供了一个名为“List”的接口。通过使用非常流行和常见的列表“接口”创建一个清单对象,我们可以免费获得所有操作,其他程序员可以查看我们的程序并确切地知道如何与“清单”对象交互:
...
java.util.List inventory = new ArrayList();
...
//later in our program, we can interact with inventory
// with any of methods on the list interface (because ArrayList "implements" List)
inventory.add(new Furniture("chair"));
inventory.add(new Appliance("refrigerator"));
inventory.add(new Food("ice cream"));
...
因此,在上面的示例中,库存是包含 ArrayList 类型实例的变量。由于 ArrayList 类定义实现了“List”接口,因此 ArrayList 类型的所有实例都必须遵守“List”接口中定义的约定。换句话说,我们可以像这样轻松地创建inventory
LinkedList(而不是 ArrayList):
java.util.List inventory = new LinkedList();
请记住将接口视为公开要执行的特定操作集的合同。因为 LinkedList 和 ArrayList 都实现了“List”接口,所以它们都有合同义务实现“List”接口中的所有方法。
这样做的好处是,任何对使用我们的“库存”变量感兴趣的代码都不会对“添加”、“删除”或“迭代器”操作是如何实现的。他们所关心的是变量“inventory”服从“List”接口。
换句话说,LinkedList 实现的“add”方法与 ArrayList 略有不同。但这对于任何想要使用我们的“库存”变量的代码都无关紧要。
如果一些数学高手想出一种革命性的方式来存储列表。也许他们想出了如何在月球上而不是在计算机内存中存储列表,结果证明这比 ArrayList 快 10 倍。那么我们需要做的就是:
java.util.List inventory = new CrazyMoonList();
All the other code that uses inventory can stay exactly the same because CrazyMoonList is guaranteed to provide "add", "remove", etc.
Hope this helps clarify the concepts!
类是对象的模板,对象是用户定义的数据类型,包含变量、属性和方法。
//defines a simple class describing all types of fruit
public class Fruit {
//insert fields here...
Fruit(){//constructor method
//insert constructor code here...
}
//insert class methods here....
}
当我们将这个类实例化为一个对象时,我们需要让编译器知道它将是什么类型的对象。在这种情况下,我们的对象将是 Fruit 类型。
//The initialisation might look something like this.
Fruit myFruit = new Fruit();
上一行代码告诉编译器构造一个 Fruit 类型的新对象。
所以简单来说,“类”定义了一个对象的特征(它的数据),其中“类型”是指当它被实例化时我们正在查看的对象类型,无论是水果、汽车还是办公桌!
这个概念可以通过进一步阅读继承和多态的主题来扩展。
我希望这会有所帮助。