首页 > 解决方案 > 可以用 Python 实现关联类吗?

问题描述

我刚刚开始学习软件开发,并且正在用 UML 类图对我的系统进行建模。我不确定如何在代码中实现这一点。

为了简单起见,我们假设以下示例:有一个 Room 和一个 Guest 类,关联 Room(0.. )-Guest(0.. ) 和一个关联类 RoomBooking,其中包含预订详细信息。如果我的系统想要查看特定客人的所有房间预订,我将如何在 Python 中对此进行建模?

标签: pythonclassoopuml

解决方案


从 UML 设计开发的大多数 Python 应用程序都由关系数据库支持,通常通过 ORM。在这种情况下,您的设计非常简单:您RoomBooking是数据库中的一个表,而查找RoomBooking给定所有对象的Guest方式只是一个 ORM 查询。保持模糊而不是使用特定的 ORM 语法,如下所示:

bookings = RoomBooking.select(Guest=guest)

使用 RDBMS 但没有 ORM,它并没有太大的不同。像这样的东西:

sql = 'SELECT Room, Guest, Charge, Paid FROM RoomBooking WHERE Guest = ?'
cur = db.execute(sql, (guest.id))
bookings = [RoomBooking(*row) for row in cur]

这表明如果您使用 RDBMS,您会做什么:任何将存储为具有外键的表的关系都将作为某种 dict 存储在内存中。

例如,您可能有一个将客人映射到房间预订集的字典:

bookings = guest_booking[guest]

或者,如果您没有大量酒店,您可能会隐式使用此映射,每个酒店都有客人到预订的一对一映射:

bookings = [hotel.bookings[guest] for hotel in hotels]

由于您从 UML 开始,您可能在考虑严格的 OO 术语,因此您需要将此 dict 封装在某个类中,在一些 mutator 和 accessor 方法后面,这样您可以确保您不会意外中断任何不变量。

有几个明显的地方可以放置它——一个BookingManager对象对于客人到预订集的映射是有意义的,而且它Hotel本身对于每家酒店的客人到预订来说是如此明显的地方,以至于我使用了它不考虑上面。

但是另一个更接近ORM设计的地方是在RoomBooking类型上的一个类属性中,由类方法访问。如果您以后需要,这也允许您扩展事物,例如,按酒店查找事物 - 然后您将两个 dicts 作为类属性,并确保单个方法始终更新它们,所以您知道它们是始终一致。

那么,让我们看一下:

class RoomBooking
    guest_mapping = collections.defaultdict(set)
    hotel_mapping = collections.defaultdict(set)
    def __init__(self, guest, room):
        self.guest, self.room = guest, room
    @classmethod
    def find_by_guest(cls, guest):
        return cls.guest_mapping[guest]
    @classmethod
    def find_by_hotel(cls, hotel):
        return cls.hotel_mapping[hotel]
    @classmethod
    def add_booking(cls, guest, room):
        booking = cls(guest, room)
        cls.guest_mapping[guest].add(booking)
        cls.hotel_mapping[room.hotel].add(booking)

当然,您的Hotel实例可能还需要添加预订,因此如果两个不同的预订在重叠日期覆盖同一个房间,它可能会引发异常,无论这种情况发生在RoomBooking.add_booking,还是在调用两者的更高级别的函数中Hotel.add_bookingRoomBooking.add_booking

如果这是多线程的(这似乎是一个很好的可能性,因为您正朝着受 Java 启发的设计路径走这么远),您将需要一个大锁或一系列细粒度的锁整个交易。

对于持久性,您可能希望将这些映射与公共对象一起存储。但是对于足够小的数据集,或者对于很少重新启动的服务器,只保留公共对象并在加载时重建映射可能更简单,方法是在加载过程中执行一堆add_booking调用。


如果你想让它更像 ORM 风格,你可以有一个find方法,它接受关键字参数并以一种简单的方式手动执行“查询计划”:

    @classmethod
    def find(cls, guest=None, hotel=None):
        if guest is None and hotel is None:
            return {booking for bookings in cls.guest_mapping.values()
                    for booking in bookings}
        elif hotel is None:
            return cls.guest_mapping[guest]
        elif guest is None:
            return cls.hotel_mapping[hotel]
        else:
            return {booking for booking in cls.guest_mapping[guest] 
                    if booking.room.hotel == hotel}

但这已经把事情推到了一个地步,你可能想回去问问你一开始不使用 ORM 是否正确。如果这对于您的简单玩具应用程序来说听起来非常繁重,请查看sqlite3数据库(Python 附带,使用它比想出一种方法picklejson所有数据进行持久化所需的工作更少)和SqlAlchemyORM . 没有太多的学习曲线,也没有太多的运行时开销或编码时间样板。


推荐阅读