Ответы пользователя по тегу Программирование
  • Bestpractices по ООП и паттернам в js?

    @bromzh
    Drugs-driven development
    Вопрос из серии "почему в функциональных языках нет циклов и переменных и как с этим жить". Так вот, жить можно вполне хорошо. Просто немного по-другому.
    Советую изучить, что же такое ООП и какое оно бывает. За пару минут можно найти инфу, что в жаваскрипте ООП - прототипное, а в питоне "классоориентированое". Т.е. в первом всё есть объект, и создавать новые объекты можно только клонируя основной объект, все методы и поля ищутся сперва у объекта, а потом у всех его прототипов. Во втором случае есть разделение на классы и объекты - экземпляры этих классов. Там диспетчеризация устроена по-другому: методы и поля ищутся у класса, а потом у всех его предков.
    И обходиться без наследования и метаклассов можно довольно легко, если знать, для чего же вообще нужно наследование и метаклассы. Метакласс позволяет манипулировать создаваемым классом на уровне его создания. Т.е. в питоне есть 2 понятия: класс и его экземпляр. В конструкторе класса можно определить поведение создаваемых экземпляров (через __new__ и __init__). Чтобы изменить поведение самого класса (а не его экземпляра) и нужны метаклассы: там, в функциях __init__ и __new__ можно переопределить поведение при создании класса. Так как в жаваскрипте такого разделения нет, то и отпадает само понятие метакласса: каждый объект создаётся путём клонирования от базового объекта (Object в js, хотя можно клонировать и любой другой объект: Array, Function, твой собственный). Такая же фигня и для наследования: там ты просто указываешь, какие поля могут выступать в качестве прототипов для всех "дочерних" объектов, это когда пишешь MyClass.prototype.someMethod = function(args) { ... }. В данном случае, someMethod будет сперва искаться среди самого объекта. Если там такого нет, то ищется в прототипе, т.е. в MyClass. Ну "классы" там объявляются функциями, потому что синтаксис не предусматривает создание класса. Да и то, функция-класс - просто синтаксический сахар: при вызове оператора new MyClass эта функция исполняется и возвращает копию объекта Object, над которой применили некие операции (которые ты как раз и описываешь в ней). Для каждой функции создаётся свой локальный контекст, который хранится в переменной this. Ты можешь в функции-классе описать, какие поля добавить в this, и она вернёт тебе как раз-таки этот изменённый this. Вот в lua нет даже оператора new, и ничего, вполне удобное ООП.

    У каждого подхода есть и плюсы и минусы. Мне удобно писать в обоих стилях. Единственное, могут возникнуть проблемы, когда в классе вытаешься присвоить полю какую-то функцию (callback). This внутри этого колбека будет иметь локальный контекст этой функции, а объекта, в котором находится это поле. На помощь приходит функция bind.

    P.S. Важно понимать, что функции являются объектами (в обоих языках). Но в js использование функций как переменных встречается гораздо чаще. Поэтому, строго говоря, нет никаких методов и полей в js. У объекта просто есть члены, которые могут быть как простыми данными и объектами, так и функциями. Собственно, для питона это тоже верно, ведь сами классы тоже являются объектами (экземпляры типа type). Просто там есть 2 понятия, как бы двухуровневая система. Ну и там куда реже члену объекта присваивают функцию, нежели в JS.
    Ответ написан
    1 комментарий
  • Как реализовать систему эффектов (модификаторов) накладываемых на игрока?

    @bromzh
    Drugs-driven development
    Так как ты не сказал, на каком языке пишешь, скину код на питоне:
    class Effect:
    
        def __init__(self, name, mods):
            self.name = name
            self.mods = mods
            # mods - это словарь с модификаторами
            # ключ - имя поля в классе User
            # значение - функция, которая принимает экземпляр класса User и промежуточное значение поля
            # а возвращает изменённое значение поля
    
    
    class User:
    
        def __init__(self, name, strength, agility):
            # Инициируем начальные значения
            self.name = name
            self.strength = strength
            self.health = strength * 100
            self.agility = agility
            self.effects = list()
    
        # в самом классе храним только независимые значения
        # но любой доступ к параметрам должен идти через геттер
        # для каждого необходимого параметра создаём функцию-геттер
        # которая будет учитывать применение эффектов
        # можно вместо этого использовать @property
        def get_strength(self):
            strength = self.strength
            for effect in self.effects:
                if effect.mods.get('strength'):
                    strength = effect.mods['strength'](strength, self)  # вызываем функцию - модификатор
            return strength
    
        def get_agility(self):
            agility = self.agility
            for effect in self.effects:
                if effect.mods.get('agility'):
                    agility = effect.mods['agility'](agility, self)  # вызываем функцию - модификатор
            return agility
    
        def get_health(self):
            health = self.health
            for effect in self.effects:
                if effect.mods.get('health'):
                    health = effect.mods['health'](health, self)  # вызываем функцию - модификатор
            return health
    
        # Это зависимый параметр
        def get_max_health(self):
            max_health = self.get_strength() * 100  # первоначальное значение вычисляется на основе силы
            for effect in self.effects:
                if effect.mods.get('max_health'):
                    max_health = effect.mods['max_health'](max_health, self)  # вызываем функцию - модификатор
            return max_health
    
    
    if __name__ == '__main__':
        foo = User('Foo', 10, 10)
        god_strength = Effect('God strength', {
            'strength': lambda s, u: s + 10
        })
    
        def _life_power_func(value, user):
            return value + 100
    
        life_power = Effect('Life Power', {
            'max_health': _life_power_func
        })
    
        def _extra_agility_func(value, user):
            return value + 10
    
        # этот эффект влияет сразу на 2 параметра
        extra_agility = Effect('Extra agility', {
            'agility': _extra_agility_func,
            'max_health': lambda h, u: h - 400
        })
        print(foo.get_strength(), foo.get_max_health(), foo.get_health(), foo.get_agility(), [e.name for e in foo.effects])
        foo.effects.append(god_strength)
        print(foo.get_strength(), foo.get_max_health(), foo.get_health(), foo.get_agility(), [e.name for e in foo.effects])
        foo.effects.append(life_power)
        print(foo.get_strength(), foo.get_max_health(), foo.get_health(), foo.get_agility(), [e.name for e in foo.effects])
        foo.effects.append(extra_agility)
        print(foo.get_strength(), foo.get_max_health(), foo.get_health(), foo.get_agility(), [e.name for e in foo.effects])

    Результат:
    10 1000 1000 10 []
    20 2000 1000 10 ['God strength']
    20 2100 1000 10 ['God strength', 'Life Power']
    20 1700 1000 20 ['God strength', 'Life Power', 'Extra agility']
    Ответ написан
    3 комментария
  • Что входит в обязанности "тонкого контроллера" и как при нем происходит связь вида с моделью?

    @bromzh
    Drugs-driven development
    Но в объектно-ориентированном программировании используется активная модель MVC, где модель — это не только совокупность кода доступа к данным и СУБД, но и вся бизнес-логика. Следует отметить возможность модели инкапсулировать в себе другие модели. В свою очередь, контроллеры представляют собой лишь элементы системы, в чьи непосредственные обязанности входит приём данных из запроса и передача их другим элементам системы. Только в этом случае контроллер становится «тонким» и выполняет исключительно функцию связующего звена (glue layer) между отдельными компонентами системы.


    Как это можно было не заметить на википедии?
    Ответ написан