Pull to refresh

Comments 7

Кажется, что пример про UserCollection получился не python-way. Или мне одному в коллекции хочется видеть стандартный интерфейс __contains__, а не includes?

Спасибо за замечание, __contains__ здесь действительно лучше подходит. Добавил в UPD

UFO just landed and posted this here
В питоне принцип «клиенту не нужно иметь информации о конкретных типах объектов, которыми он пользуется, при условии, что все они имеют ожидаемый клиентом интерфейс;» реализован by default. Утиная типизация, всё такое.

Если у нас есть .get, то нам всё равно у какого он типа. Если у метода есть .next(), то мы можем его итерировать.

Пример с is_password_valid выглядит очень уродливо. Для таких вещей есть @staticmethod, а функция нас не «обманывает», так как в нормальном режиме (как член класса) она имеет ещё и self в аргументах. Если же self не нужен (для удобства тестирования) — то как раз @staticmethod поможет.

90% процентов джавовских паттернов разработки — всего лишь борьба с уродствами самой java. В питоне оно из коробки или реализуется в пол-пинка. В том числе опциональные аргументы, keyword-аргументы, или вот это, вот, с «интерфейсами».

Вот с этим соглашусь, а вот что написал автор в статье, я не особо-то и понял. Как-то не связанно и сумбурно повествует.

Например, у нас имеется простой класс:


class ColourMix:
    def __init__(self, colours):
        self.colours = colours

    def print_colours(self):
        for colour in self.colours:
            print(f"Colour RGB: ({colour.red}, {colour.green}, {colour.blue})")

При разработке другой части приложения клиент не хочет раскрывать реализацию и опирается только на интерфейс. Но интерфейс не говорит ничего о том, чем же является colours, и что он содержит внутри. Тогда единственным способом работы с данным классом является чтение его реализации и поиск всех методов, которые он дергает у colours и элементов colours. Это неэффективно и замедляет разработку. Если явно сказать, что colours — это список, содержащий объекты-значения, то в реализацию не нужно раскрывать:


from typing import NamedTuple, List

class Colour(NamedTuple):
    red: int
    green: int
    blue: int

class ColorMix:
    def __init__(self, colours: List[Colour]):
        self.colours = colours

    def print_colours(self):
        for colour in self.colours:
            print(f"Color RGB: ({colour.red}, {colour.green}, {colour.blue})")

Но при этом мы не лишаемся возможностей, которые предоставляет нам утиная типизация, только теперь нам известно, что для работы ColourMix нужен объект, который реализует интерфейс списка, причем не обязательно наследованием от списка.


Если у нас есть .get, то нам всё равно у какого он типа. Если у метода есть .next(), то мы можем его итерировать.

Это, конечно, верно. Статья о том, что интерфейс объекта должен явно говорить о наличии .get или .next.


Для таких вещей есть @staticmethod, а функция нас не «обманывает», так как в нормальном режиме (как член класса) она имеет ещё и self в аргументах.

self в аргументах ничем не поможет, если в конструкторе будет внешняя зависимость:


from utils import DatabaseConfig
# DatabaseConfig предоставляет доступ к конфигу,
# который хранится в БД

class PasswordUtils:
    def __init__(self):
        self.min_length = DatabaseConfig().password_min_length

    def is_password_valid(self, password: str) -> bool:
        return len(password) > self.min_length

Инстанцирование класса в "неправильном" окружении снова возбудит ошибку. Функция или метод класса — это инструменты для демонстрации примера внешней зависимости, которая раскрывает реализацию. Суть примера не в применяемых инструментах, а в необходимости явной передачи всех внешних зависимостей.


Паттерны разработки — это борьба со сложностью программ. Если в приложении в нескольких местах есть повторяющийся код инстанцирования и настройки объекта, я вынесу его в фабрику — единое место для получения "готового к работе" инстанса — и не важно, на каком языке я программирую. Необходимость инкапсулировать сложное создание объекта не зависит от языка, а вот реализация зависит. Паттерны рассказывают о том, как увидеть эту необходимость, а не о том, какой конкретно код нужно писать.

Я не могу понять ваш пример, потому что он не похож на питон код, и python3 со мной в этом согласен.

Второй пример, с password_min_length — либо у нас это статичное, и мы пишем self.min_length = DatabaseConfig.password_min_length, либо, если это берётся из базы данных, то мы пишем так:

def __init__(self, db):
self.min_length = db.password_min_length


Но я всё ещё не понимаю, при чём тут паттерны. Лично у меня от всего этого есть ощущение, что для борьбы со сложностью авторы разводят ещё большую сложность.
Only those users with full accounts are able to leave comments. Log in, please.