【问题标题】:Can an association class be implemented in Python?可以用 Python 实现关联类吗?
【发布时间】:2018-06-06 21:43:11
【问题描述】:

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

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

【问题讨论】:

  • 最简单的做法是在某处有一个字典,将Guests 映射到RoomBookings。如果您想隐藏该字典以进行封装,您可以将其放入RoomManager 对象或现有Hotel 对象中,或者仅在RoomBooking 上使用类方法(如果它是系统范围而不是酒店)-宽的东西。
  • 但与此同时,除非您将类映射到关系数据库——在这种情况下,答案只是使用您正在使用的任何 ORM 框架进行查询——我不知道我会首先使用RoomBooking 类。
  • 在 Python 中,关联被称为“映射”,它有一个名为 dict 的内置关联,这听起来像是您想要的(因此您不必自己实现它)。
  • @abarnert 你为什么不从你的 cmets 中回答?这似乎是一个完整的答案。添加一个代码示例,它就完美了。
  • 在 UML 中,关联类可以有自己的属性和操作(我猜它是 Python 中的方法),那么如何使用字典来实现呢?那么该方法是否必须只在房间或客人中?例如,如果该方法根据总逗留时间计算客人逗留的工作日数。

标签: python class oop uml


【解决方案1】:

大多数从 UML 设计开发的 Python 应用程序都由关系数据库支持,通常通过 ORM。在这种情况下,您的设计非常简单:您的RoomBooking 是数据库中的一个表,而您查找给定Guest 的所有RoomBooking 对象的方式只是一个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 all 的方法相比,它的使用工作更少用于持久性的数据)和SqlAlchemy 用于 ORM。没有太多的学习曲线,也没有太多的运行时开销或编码时间样板。

【讨论】:

    【解决方案2】:

    当然你可以在 Python 中实现它。但是没有单一的方法。通常您有一个数据库层,其中关联类与两个外键一起使用(在您的情况下为RoomGuest 的主键)。因此,为了进行搜索,您只需编写要发送的相应 SQL。如果你想缓存这个表,你可以像这样(或类似地)使用关联数组对其进行编码:

    from collections import defaultdict
    
    class Room():
        def __init__(self, num):
            self.room_number = num
    
        def key(self):
            return str(self.room_number)
    
    class Guest():
        def __init__(self, name):
            self.name = name
    
        def key(self):
            return self.name
    
    def nested_dict(n, type):
        if n == 1:
            return defaultdict(type)
        else:
            return defaultdict(lambda: nested_dict(n-1, type))
    room_booking = nested_dict(2, str)
    
    class Room_Booking():
        def __init__(self, date):
            self.date = date
    
    
    room1 = Room(1)
    guest1 = Guest("Joe")
    
    room_booking[room1.key()][guest1.key()] = Room_Booking("some date")
    
    print(room_booking[room1.key()][guest1.key()])
    

    【讨论】:

      猜你喜欢
      • 2022-05-26
      • 1970-01-01
      • 2022-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-15
      • 1970-01-01
      相关资源
      最近更新 更多