При разработке часто приходится управлять различными объектами. В некоторых случаях возникает ситуация, когда объект может отсутствовать или его значение не определено. Это может привести к ошибкам, если не учесть данную возможность. Особенно часто такая проблема возникает при работе с системами аутентификации.
Проблема проверки существования объекта
Представим сайт, на котором есть возможность аутентификации. Внутри такой системы всегда присутствует понятие «текущий пользователь». Это тот пользователь, который аутентифицировался при помощи логина и пароля.
Текущий пользователь активно используется для вывода различных блоков информации, например, для отображения блога этого пользователя. Такой код обычно выглядит так:
# Где-то в воображаемом коде
if is_authenticated and current_user.has_articles():
for article in current_user.get_articles():
# Здесь выводим статьи
Тут мы сталкиваемся с проверкой аутентификации пользователя. Если ее не сделать, то код упадет с ошибкой, потому что вызывается метод has_articles()
у None
, так как пользователь отсутствует, если он не залогинился.
Если таких проверок одна-две, то ничего страшного. Если их много, то код быстро захламляется. Кроме того, такую проверку очень легко забыть вставить.
Чтобы управлять подобными ситуациями, существует несколько подходов, одним из которых является паттерн Null Object.
Использование Null Object Pattern
Null Object Pattern — это шаблон проектирования, который используется для обработки нулевых или отсутствующих значений. Вместо того чтобы возвращать null
или None
и затем проверять его на каждом шаге, мы создаем объект, который представляет отсутствующее значение и имеет тот же интерфейс, что и остальные объекты.
Это упрощает код, поскольку нам не нужно постоянно проверять, является ли объект null
или None
. Мы можем просто вызывать методы, как если бы это был обычный объект.
Представим, что у нас есть система аутентификации, которая возвращает текущего пользователя. Этот пользователь может быть либо аутентифицирован, либо нет:
class User:
def __init__(self, name):
self.name = name
def get_name(self):
return self.name
def has_articles(self):
return True
def get_articles(self):
# воображаемый код, делающий запрос к базе и получающий все статьи
return db.get_articles(self)
class Guest:
def get_name(self):
return "Guest"
def has_articles(self):
return False
def get_articles(self):
return []
Здесь User
— это класс для аутентифицированного пользователя, а Guest
— это класс для неаутентифицированного пользователя. Guest
представляет собой «нулевой объект», у которого тот же интерфейс, что и User
, но возвращает значения по умолчанию.
Большинство методов Null Object возвращает False
либо пустые списки, так как у этого пользователя ничего нет.
Теперь напишем функцию, которая возвращает текущего пользователя:
def get_current_user(authenticated):
if authenticated:
return User("Alice")
else:
return Guest()
Эта функция возвращает либо аутентифицированного пользователя, либо нулевой объект. Мы можем использовать ее, чтобы выводить информацию о текущем пользователе без проверки, является ли пользователь null
или None
:
current_user = get_current_user(authenticated=True)
print(current_user.get_name()) # => Alice
print(current_user.get_articles()) # => ['article1', 'article2', 'article3']
current_user = get_current_user(authenticated=False)
print(current_user.get_name()) # => Guest
print(current_user.get_articles()) # => []
Здесь мы вызываем функцию get_current_user()
с разными значениями параметра authenticated
, который указывает, является ли пользователь аутентифицированным. В зависимости от значения этого параметра функция возвращает либо объект класса User
с определенными данными, либо объект класса Guest
.
Мы видим, что мы без проблем вызываем методы get_name()
и get_articles()
. При этом во втором случае мы работаем с экземпляром класса Guest
. Именно в этом и заключается основное преимущество паттерна Null Object: нам не нужно проверять, является ли объект null
или None
, перед тем как вызвать его методы.
# где-то дальше в коде
# нам не нужно беспокоиться о том зарегистрированный это пользователь или нет
# потому что метод has_arctiles есть как у User, так и у Guest
if current_user.has_articles():
for article in current_user.get_articles():
# Здесь выводим статьи
Выводы
Паттерн Null Object помогает упростить код и избежать постоянных проверок на null
или None
. Он особенно полезен в языках программирования, где null
или None
может вызвать ошибку при попытке вызвать методы.
Несмотря на удобство и простоту использования паттерна Null Object, его стоит применять осторожно. Большое количество нулевых объектов может затруднить отладку и понимание кода, поскольку они скрывают отсутствие значения. Важно найти баланс между удобством использования и ясностью кода.
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.