A type - это классификация фрагмента данных, сообщающая вам, каковы его допустимые значения и допустимые операции. (Почти?) Все языки программирования имеют типы, хотя дисциплина набора значительно варьируется от одного языка к другому.
A class - это определенный вид типа в языках ООП, который определяется с помощью специального синтаксиса в самом языке (в отличие, скажем, от так называемых «нативных типов»). "как Java int
или float
или тому подобное, которые определяются самим языком). Класс обычно определяется в терминах структуры памяти и кодирования данных (так называемые переменные-члены ) и функций, которые над ними работают (так называемые функции-члены или методы ).
Интерфейс * - это спецификация операций, которые должен выполнять тип, чтобы считаться частью данного набора похожих типов, но в котором не указываются допустимые значения, схемы памяти и т. Д.
Это очень, очень, очень краткий обзор, который является своего рода упрощенной «средней формой» подхода нескольких языков к ним. Он игнорирует некоторые крайние случаи и такие вещи, как способность C ++ создавать вещи, которые являются промежуточным звеном между интерфейсом и классом. Он также игнорирует классы на функциональных языках, таких как Haskell, потому что дальнейшее повреждение вашего мозга здесь не является целью. ;)
отредактировано для добавления некоторых примеров
Вот некоторые декларации Java- , такие как , чтобы помочь закрепить концепции.
int myVariable1;
Эта переменная & mdash; myVariable1
& mdash; является собственным (или примитивным) типом, состоящим из 32-разрядного целочисленного значения со знаком, закодированного в нотации 2-дополнения. Он имеет известный диапазон (приблизительно от -2 миллиардов до +2 миллиардов) и известный набор операций (умножение, сложение, деление, модуль, вычитание, различные преобразования и т. Д.), Доступных для него.
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()
) без каких-либо переменных-членов и без какой-либо реализации. Это только говорит вам, что любой класс, который реализует этот интерфейс, гарантированно имеет метод с этой конкретной сигнатурой name + return value + arguments. Чтобы использовать его, вы должны создать класс, который реализует интерфейс.
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();
Теперь myVariable3
определяется как тип с макетом памяти, состоящим из трех 32-разрядных целых чисел со знаком и двух операций. Это одна из тех операций, которую должен осуществить из-за всей части implements MyInterface
. Таким образом, все, что ожидает (абстрактный) тип MyInterface
, может использовать (конкретный) тип MyOtherClass
, поскольку операция гарантированно будет там. Другой метод & mdash; myNonInterfaceMethod()
& mdash; не исходит от MyInterface, поэтому что-то, что ожидает только MyInterface
, не может использовать его, потому что не может знать, что оно существует.
далее отредактировано для добавления некоторых реальных вещей по запросу
Если вы когда-либо использовали целочисленное значение, значение с плавающей запятой, строку или что-то подобное в программе, которую вы использовали. Типы, возможно, являются предметом вычислений, и все, что мы делаем, это манипулирование значениями данных типов. Поэтому я сосредоточусь на ООП-специфических понятиях классов и интерфейсов.
Каждый раз, когда у вас есть данные и операции с этими данными, у вас есть потенциал для класса. Взять, к примеру, банковский счет. Банковский счет будет, помимо прочего, иметь номер счета, текущий баланс, лимит транзакций и т. Д. Класс, представляющий это (плохо и показан только для объяснения концепций), может выглядеть так:
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()
) и все. (Если вы хотите более конкретно использовать это в реальных условиях, посмотрите, как классы коллекций, не говоря уже о цикле for, используют интерфейс Iterable
в Java.)