При выделении объектов необходимо абстрагироваться (отвлечься) от большинства присущих им свойств и сконцентрироваться на свойствах, значимых для задачи..
Выделяемые объекты необязательно должны походить на физические объекты — ведь это абстракции, за которыми скрываются процессы, взаимодействия, отношения.
Удачная декомпозиция стоит многого. От нее зависят не только количественные характеристики кода (быстродействие, занимаемая память), но и трудоемкость дальнейшего развития и сопровождения. При отсутствии соответствующего опыта лучше не загадывать будущих путей развития программы, а делать ее как можно проще, под конкретную задачу.
Даже если просто перечислить все существительные, встретившиеся в описании задачи (явно или неявно), получится неплохой список кандидатов в классы.
При процедурном подходе тоже используется декомпозиция, но при объектно–ориентированном подходе производится декомпозиция не самого алгоритма на более мелкие части, а предметной области на классы объектов.
Объекты
До этой лекции объекты Python встречались много раз: ведь каждое число, строка, функция, модуль и т.п. — это объекты. Некоторые встроенные объекты имеют в Python синтаксическую поддержку (для задания литералов). Таковы числа, строки, списки, кортежи и некоторые другие типы.
Теперь следует посмотреть на них в свете только что приведенных определений. Пример:
Листинг
a = 3
b = 4.0
c = a + b
Здесь происходит следующее. Сначала имя «a» связывается в локальном пространстве имен с объектом–числом 3 (целое число). Затем «b» связывается с объектом–числом 4.0 (число с плавающей точкой). После этого над объектами 3 и 4.0 выполняется операция сложения, и имя «c» связывается с получившимся объектом. Кстати, операциями, в основном, будут называться методы, которые имеют в Python синтаксическую поддержку, в данном случае — инфиксную запись. То же самое можно записать как:
Листинг
c = a.__add__(b)
Здесь __add__() - метод объекта a, который реализует операцию + между этим объектом и другим объектом.
Узнать набор методов некоторого объекта можно с помощью встроенной функции dir():
Листинг
>>> dir(a)
['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__',
'__delattr__', '__div__', '__divmod__', '__doc__', '__float__',
'__floordiv__', '__getattribute__', '__getnewargs__', '__hash__',
'__hex__', '__init__', '__int__', '__invert__', '__long__',
'__lshift__', '__mod__', '__mul__', '__neg__', '__new__',
'__nonzero__', '__oct__', '__or__', '__pos__', '__pow__',
'__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__',
'__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__',
'__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__',
'__rshift__', '__rsub__', '__rtruediv__', '__rxor__',
'__setattr__', '__str__', '__sub__', '__truediv__', '__xor__']
Здесь стоит указать на еще одну особенность Python. Не только инфиксные операции, но и встроенные функции ожидают наличия некоторых методов у объекта. Например, можно записать:
Листинг
abs(c)
А функция abs() на самом деле использует метод переданного ей объекта:
Листинг
c.__abs__()
Объекты появляются в результате вызова функций–фабрик или конструкторов классов (об этом ниже), а заканчивают свое существование при удалении последней ссылки на объект. Оператор del удаляет имя (а значит, и одну ссылку на объект) из пространства имен:
Листинг
a = 1
# …
del a
# имени a больше нет
Типы и классы
Тип определяет область допустимых значений объекта и набор операций над ним. В ООП тип тесно связан с поведением — действиями объекта, состоящими в изменении внутреннего состояния и вызовами методов других объектов.
Ранее в языке Python встроенные типы данных не являлись экземплярами класса, поэтому считалось, что это были просто объекты определенного типа. Теперь ситуация изменилась, и объекты встроенных типов имеют классы, к которым они принадлежат. Таким образом, тип и класс в Python становятся синонимами.
Интерпретатор языка Python всегда может сказать, к какому типу относится объект. Однако с точки зрения применимости объекта в операции его принадлежность к классу не играет решающей роли: гораздо важнее, какие методы поддерживает объект.
Примечание:
Пока что в Python есть «классические» и «новые» классы. Первые классы определяются сами по себе, а вторые обязательно ведут свою родословную от класса object. Для целей данного изложения разница между этими видами классов не имеет значения.
Экземпляры классов могут появляться в программе не только из литералов или в результате операций. Обычно для получения объекта класса достаточно вызвать конструктор этого класса с некоторыми параметрами. Объект–класс, как и объект–функция, может быть вызван. Это и будет вызовом конструктора:
Листинг
>>> import sets
>>> s = sets.Set([1, 2, 3])
В этом примере модуль sets содержит определение класса Set. Вызывается конструктор этого класса с параметром [1, 2, 3]. В результате с именем s будет связан объект–множество из трех элементов 1, 2, 3.
Следует заметить, что, кроме конструктора, определенные классы имеют и деструктор — метод, который вызывается при уничтожении объекта. В языке Python объект уничтожается в случае удаления последней ссылки на него либо в результате сборки мусора, если объект оказался в неиспользуемом цикле ссылок. Так как Python сам управляет распределением памяти, деструкторы в нем нужны очень редко. Обычно в том случае, когда объект управляет ресурсом, который нужно корректно вернуть в определенное состояние.
Еще один способ получить объект некоторого типа — использование функций–фабрик. По синтаксису вызов функции–фабрики не отличается от вызова конструктора класса.
Определение класса
Пусть в ходе анализа данной предметной области необходимо определить класс Граф. Граф — это множество вершин и набор ребер, попарно соединяющий эти вершины. Над графом можно проделывать операции, такие как добавление вершины, ребра, проверка наличия ребра в графе и т.п. На языке Python определение класса может выглядеть так:
Листинг
from sets import Set as set # тип для множества
class G:
def __init__(self, V, E):
self.vertices = set(V)
self.edges = set(E)
def add_vertex(self, v):
self.vertices.add(v)
def add_edge(self, (v1, v2)):
self.vertices.add(v1)
self.vertices.add(v2)
self.edges.add((v1, v2))
def has_edge(self, (v1, v2)):
return (v1, v2) in self.edges
def __str__(self):
return "%s; %s» % (self.vertices, self.edges)
Использовать класс можно следующим образом:
Листинг
g = G([1, 2, 3, 4], [(1, 2), (2, 3), (2, 4)])
print g
g.add_vertex(5)
g.add_edge((5,6))
print g.has_edge((1,6))
print g
что даст в результате
Листинг
Set([1, 2, 3, 4]); Set([(2, 4), (1, 2), (2, 3)])
False
Set([1, 2, 3, 4, 5, 6]); Set([(2, 4), (1, 2), (5, 6), (2, 3)])
Как видно из предыдущего примера, определить класс не так уж сложно. Конструктор класса имеет специальное имя __init__. (Деструктор здесь не нужен, но он бы имел имя __del__.) Методы класса определяются в пространстве имен класса. В качестве первого формального аргумента метода принято использовать self. Кроме методов в объекте класса имеются два атрибута: vertices (вершины) и edges (ребра). Для представления объекта G в виде строки используется специальный метод __str__().
Принадлежность классу можно выяснить с помощью встроенной функции isinstance():