Как бы вы обычно обрабатывали что-то подобное в Rust?
class Base(Model):
def do_something(self, x):
return {"base": x * 2}
class A(Model):
extend = Base
def do_something(self, x):
res = super(A, self).do_something()
res['test'] = x
return res
class B(Model):
extend = Base
def do_something(self, x):
if x > 5:
res = super(B, self).do_something()
else:
res = {}
return res
obj = Base()
obj.do_something(1)
>> {}
obj.do_something(6)
>> {'test': 6, 'base': 12}
В Python у меня было бы нечто подобное, когда класс Base
непрерывно расширяется с помощью метакласса.Вы всегда используете класс Base
, а остальные являются просто синтаксическим сахаром для расширения класса Base
.
Как вы можете видеть, в теле B.do_something
порядок, в котором вызываются методыуправляется телом функции, а не последовательно, поэтому невозможно просто сохранить список функций и затем вызвать их последовательно.
В Python большая часть логики скрыта за метаклассом, но идея состоит в том, чтобы предложить разработчикам расширений единственное место, добавляющее методы в модель, чтобы не знать, как какой-то другой модуль также присоединяется кта же модель.
Например, Base
ведет себя немного как прокси.Всякий раз, когда другой класс наследует его, он создает новый тип, основанный на верхнем типе.Когда вы вызываете метод, он делает что-то вроде return getattr(self._inner, name)(args)
и использует обычное наследование в Python для получения super
.Когда он расширяет его, все, что он делает, это self.top = type(self.name, (self.top,), args...)
, но поскольку у Rust нет наследования, я не уверен, как это можно сделать.
Есть ли способ достичь чего-то близкого в Rust?без того, чтобы сделать код полностью нечитаемым?
Как я понимаю, одним из способов достижения этого было бы создание объектов функторов
наподобие
struct Command {
name: String,
super: &command: Command,
func: Fn (&Object, &Command
, ArgumentMap) -> Any
}
struct Object {
name: String,
commands: Map<Command>,
}
impl Object {
fn insert_command(&self, command: Command) {
let super_command = self.commands.get(command.name)
if (super_command) {
command.super = super_command
}
self.commands.remove(super_command.name)
self.commands.set(command.name, command)
}
fn call(&self, name:String, args: ArgumentMap) -> Any {
let command = self.commands.get(command.name)
(command.func)(self, command.super, args)
}
fn extend(extension: Map<String, ?closure?>) {
// for each key add a command
}
}
user = Object {
name: "user",
commands: Map<Command> {}
}
user.extend({
"do_something": |self, super, args| {
// change args
let new_args = self.call("change_args", args)
let ret = (super.func)(self, super.super, new_args)
// do something with ret
},
})
user.extend({
"do_something": |self, super, args| {
// will call method previously defined above
let ret = (super.func)(self, super.super, args)
ret
}
})
Это во многомсделай это.Object
- это простой объект, который предназначен для хранения функций по мере необходимости.Это можно сделать лучше, сделав super
как вызываемый объект, который принимает args
в качестве аргумента только путем инкапсуляции (self, следующий супер метод).
Вот как я это сделаю, когда я неЯ не знаю, можно ли сделать подобные вещи без реализации объектоподобной структуры.Все это будет выполняться во время выполнения, но я полагаю, что иначе сделать невозможно, потому что существует необходимость в размышлении, если предполагается, что что-то вызывается извне с использованием запроса JSON.Давайте представим, что одной из точек входа является веб-сервер, который получает метод, вызываемый в модели.Как я понимаю, Rust не предлагает отражения, поэтому имеет смысл делать это так.