2

我正在尝试将我的框架的一部分从 Python 转换为 D,并且正在努力解决一个细节:但首先我必须扩大范围。该组件就像一个 ORM。我的用户定义类,哪些实例将在数据库中持久存在。用户 API 应该尽可能简单。例如

class Person:
  identity _id,
  string name,
  date birthdate,
  ...

class Car:
  indentity _id,
  string ident,
  int power,
  Person owner
  ...

myCar = Car.load(ident="DEAD-BEAF")
print myCar.power
print myCar.owner.name

加载函数从数据库加载实例数据。但是车主的加载应该推迟到实例被使用,因为大多数应用程序都工作在汽车上,车主很少使用。

在 Python 中,我可以使用Descriptor-Protocol来实现这种行为。我有一组“字段”类,它们是描述符。例如

class StringField(object):

    def __get__(self,obj,objtype):
        # get the value from obj

    def __set__(self,obj,value):                    
        # set value in obj

EntityClass 有一个适当的元类,它连接所需的连接。用户在 Python 中定义:

class Person(EntityClass):      
    name = StringField()
    birthdate = DateField()
    ...

class Car(EntityClass):
  ident = StringField()
  power = IntField()
  owner = RelationField(Person)
  ...

并使用类:

myCar = Car.load(ident="DEAD-BEAF")
print myCar.power      (#1)
print myCar.owner.name (#2)

在引擎盖下,对 myCar.power 的调用扩展到

Car.power.__get__(myCar,Car)

如果我从数据库加载汽车,我只加载所有者 ID。如果一个使用所有者

theowner = myCar.owner

我可以从数据库中加载延迟的 Person 实例

class RelationField(object):

    def __get__(self,obj,objtype):
        if not instance in obj.cache:
            load instance
            add instance to obj.cache
        return instance from obj.cache

将 ORM 转换为 DI 尝试了不同的实现。对于简单的基本类型,将用户定义的属性 (UDA) 与模板和统一调用语法结合使用非常简单:

struct Persistent {};

save(T)(ref T obj)
  {
  ...
  }

T load(T)(...) 

class Person
  {
  @Persistent string name;
  @Persistent Date birthday;
  ...
  }

class Car
  {      
  @Persistent string ident;
  @Persistent int power;
  @Persistent Person owner; //???
  ...
  }

auto myCar = load!Car(...);
writeln(myCar.power);
writeln(myCar.owner.name)

这个 API 和 Python-API 一样简单,但是我不知道如何实现 owner 的延迟加载。我需要的是用属性函数替换所有者成员,但我不知道如何使用编译时元编程来做到这一点。那么这要怎么做呢?还是有惯用的方法?

4

1 回答 1

2

您可以使用opDispatch。属性名称在编译时可用,因此您可以使用通用代码处理它们,或者您也可以像下面的代码一样创建专门化,在行if后使用template

struct Person {
    int id;
    string name;
}

Person[int] person_by_id;

struct Car {
    int id;
    int power;
    int owner_id;

    // https://dlang.org/spec/operatoroverloading.html#dispatch
    template opDispatch(string property)
    if (property == "owner")
    {
        @property ref Person opDispatch() {
            return person_by_id[owner_id];
        }
    }
}

void main() {
    auto p = Person(1, "Joe");
    person_by_id[p.id] = p;
    auto c = Car(123, 900, 1);

    assert(c.owner.name == "Joe");
    c.owner.name = "Average Joe";

    assert(person_by_id[1].name == "Average Joe");
}

opDispatch模板可以由 mixin 模板生成,因此用户应该编写如下内容:

struct Car {
    int id;
    int power;
    int owner_id;
    mixin addMyHandlers!Car;
}

在这里,addMyHandlermixin 模板应该opDispatch使用传入结构的自省来生成上面的函数。如果您希望保持结构整洁,可以在结构之外生成函数,并利用统一函数调用语法 (UFCS):

struct Car {
    int id;
    int power;
    int owner_id;
}

@property ref Person owner(const ref Car car) {
    return person_by_id[car.owner_id];
}

这个owner函数也可以使用 mixin 模板生成,使用 introspection onCar来调查生成什么。(类似于您的 python 代码。)

于 2016-04-16T08:15:45.763 回答