Предыдущая статья — Введение в объектно-ориентированное программирование: много клякс.
Добро пожаловать в следующую часть нашей серии статей про объектно-ориентированное программирование. В этой статье мы обсудим модульность написанного нами класса.
Одними из основных признаков хорошего кода являются поддерживаемость, масштабируемость и модульность.
Пока мы не представили ничего, что с течением времени сделало бы наш код слишком сложным для поддержки или масштабирования, по крайней мере, в рамках того, что мы можем делать с библиотекой PyGame. А как насчет того, чтобы сделать его модульным? Для этого есть действительно простой тест, давайте попробуем его импортировать!
Для этого мы разобьем наш код на два файла. Давайте скопируем класс Blob
и импорт библиотеки random
в новый файл blob.py
:
import random class Blob: def __init__(self, color): self.x = random.randrange(0, WIDTH) self.y = random.randrange(0, HEIGHT) self.size = random.randrange(4,8) self.color = color def move(self): self.move_x = random.randrange(-1,2) self.move_y = random.randrange(-1,2) self.x += self.move_x self.y += self.move_y if self.x < 0: self.x = 0 elif self.x > WIDTH: self.x = WIDTH if self.y < 0: self.y = 0 elif self.y > HEIGHT: self.y = HEIGHT
В первоначальном файле удалим класс Blob
, а затем импортируем его из модуля blob.py
:
import pygame import random from blob import Blob STARTING_BLUE_BLOBS = 10 STARTING_RED_BLOBS = 3 WIDTH = 800 HEIGHT = 600 WHITE = (255, 255, 255) BLUE = (0, 0, 255) RED = (255, 0, 0) game_display = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Blob World") clock = pygame.time.Clock() def draw_environment(blob_list): game_display.fill(WHITE) for blob_dict in blob_list: for blob_id in blob_dict: blob = blob_dict[blob_id] pygame.draw.circle(game_display, blob.color, [blob.x, blob.y], blob.size) blob.move() pygame.display.update() def main(): blue_blobs = dict(enumerate([Blob(BLUE) for i in range(STARTING_BLUE_BLOBS)])) red_blobs = dict(enumerate([Blob(RED) for i in range(STARTING_RED_BLOBS)])) while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() draw_environment([blue_blobs,red_blobs]) clock.tick(60) if __name__ == '__main__': main()
Сразу же мы получаем ошибку в модуле blob.py
относительно нашего класса Blob
, где у нас появилось несколько неопределенных переменных. Это явно проблема с написанием классов: вам следует избегать использования констант или переменных вне класса. Давайте добавим эти значения в метод __init__
, а затем изменим все части, в которых мы использовали эти константы.
Итак, обновим файл blob.py
следующим образом:
import random class Blob: def __init__(self, color, x_boundary, y_boundary): self.x_boundary = x_boundary self.y_boundary = y_boundary self.x = random.randrange(0, self.x_boundary) self.y = random.randrange(0, self.y_boundary) self.size = random.randrange(4,8) self.color = color def move(self): self.move_x = random.randrange(-1,2) self.move_y = random.randrange(-1,2) self.x += self.move_x self.y += self.move_y if self.x < 0: self.x = 0 elif self.x > self.x_boundary: self.x = self.x_boundary if self.y < 0: self.y = 0 elif self.y > self.y_boundary: self.y = self.y_boundary
Теперь в нашем исходном файле класс Blob
при вызове ожидает определенных значений для этих аргументов, поэтому их нужно добавить в главную функцию:
def main(): blue_blobs = dict(enumerate([Blob(BLUE,WIDTH,HEIGHT) for i in range(STARTING_BLUE_BLOBS)])) red_blobs = dict(enumerate([Blob(RED,WIDTH,HEIGHT) for i in range(STARTING_RED_BLOBS)])) while True: ...
Отлично, теперь наш класс Blob
можно как минимум импортировать, так что он уже является модульным по своей сути! Есть еще одна хорошая идея: попробовать дать как можно больше возможностей разработчику, использующему ваш код, а также сделать ваш класс максимально универсальным.
Есть по крайней мере один момент, в котором мы определенно могли бы дать несколько больше свободы программисту, использующему этот класс. Это определение размера нашей кляксы:
self.size = random.randrange(4,8)
Есть ли хоть одна причина, по которой не стоит давать программисту простой способ это менять? Мы таких причин не видим. Однако, в отличие от значений x_boundary
и y_boundary
, нам не обязательно нужен программист, который даст нам значение размера, поскольку мы можем, по крайней мере, использовать разумное начальное значение по умолчанию. То есть мы можем сделать так:
class Blob: def __init__(self, color, x_boundary, y_boundary, size_range=(4,8)): self.x_boundary = x_boundary self.y_boundary = y_boundary self.x = random.randrange(0, self.x_boundary) self.y = random.randrange(0, self.y_boundary) self.size = random.randrange(size_range[0],size_range[1]) self.color = color
Теперь, если программист захочет изменить размер, он сможет это сделать (но может и не делать). Мы также разрешили программисту изменять скорость кляксы, если есть такое желание:
import random class Blob: def __init__(self, color, x_boundary, y_boundary, size_range=(4,8), movement_range=(-1,2)): self.size = random.randrange(size_range[0],size_range[1]) self.color = color self.x_boundary = x_boundary self.y_boundary = y_boundary self.x = random.randrange(0, self.x_boundary) self.y = random.randrange(0, self.y_boundary) self.movement_range = movement_range def move(self): self.move_x = random.randrange(self.movement_range[0],self.movement_range[1]) self.move_y = random.randrange(self.movement_range[0],self.movement_range[1]) self.x += self.move_x self.y += self.move_y if self.x < 0: self.x = 0 elif self.x > self.x_boundary: self.x = self.x_boundary if self.y < 0: self.y = 0 elif self.y > self.y_boundary: self.y = self.y_boundary
Итак, мы немного разобрали наш класс Blob
. Что-нибудь еще бросается в глаза? Нам кажется, что еще надо подумать о границах, в которых наша клякса может существовать.
Могут ли быть примеры, в которых мы бы хотели, чтобы кляксы могли свободно перемещаться вне поля зрения? Безусловно! Но полезен ли этот код для создания границ? Возможно ли, что программисты захотят использовать его довольно часто? Безусловно!
В общем, имеет смысл либо совсем не иметь такого кода, либо выделить его в отдельный собственный метод, например:
import random class Blob: def __init__(self, color, x_boundary, y_boundary, size_range=(4,8), movement_range=(-1,2)): self.size = random.randrange(size_range[0],size_range[1]) self.color = color self.x_boundary = x_boundary self.y_boundary = y_boundary self.x = random.randrange(0, self.x_boundary) self.y = random.randrange(0, self.y_boundary) self.movement_range = movement_range def move(self): self.move_x = random.randrange(self.movement_range[0],self.movement_range[1]) self.move_y = random.randrange(self.movement_range[0],self.movement_range[1]) self.x += self.move_x self.y += self.move_y def check_bounds(self): if self.x < 0: self.x = 0 elif self.x > self.x_boundary: self.x = self.x_boundary if self.y < 0: self.y = 0 elif self.y > self.y_boundary: self.y = self.y_boundary
Теперь программист может самостоятельно решить, использовать ему эти границы или нет. Вы также можете добавить некий новый аргумент в метод move
. И если этот аргумент будет принимать значение True
, то тогда границы будут применены.
В следующей статье серии, посвященной объектно-ориентированному программированию, мы обсудим наследование.
Следующая статья — Введение в объектно-ориентированное программирование: наследование.