jeudi 26 janvier 2023

How can I encapsulate a private mutable object of a class such that it can expose its attributes publicly through an inmutable object?

To be more specific I am designing a Downloader Class that has as member variable a reference to an array of DownloadItem objects which represents resources that the user wants to download over the network. The idea is that a Downloader object will handle the details regarding connecting to the server that hosts the resource, fetching the file data/metadata and writing to disk while also exposing the state of the DownloadItems when queried.

The DownloadItem stores information about the file such as filename, URL, file size, etc. plus other metadata such as the status, progress, when the download started, etc. Some of this information is not known before instantiation therefore the class itself should be mutable to allow for the Downloader object to modify it, but only the Downloader object that created it.

In short I want the properties of the DownloadItem to be accessible through the Downloader object like so:

> DownloaderObj = Downloader()
> unique_id = DownloaderObj.download(url='https://path/to/resource', start_inmediate=False)
> print(DownloaderObj._download_queue)
  [<Class: DownloadItem url: https://path/to/resource filesize: -1>]
> DownloaderObj.start_download(unique_id)  # Handler thread updates metadata on the background
> print(DownloaderObj.get_download_by_id(unique_id).filesize)
  1024
> DowloaderObj.get_download_by_id(unique_id).filesize = 1024 # Should raise NotAllowed exception

One could have a lot of boilerplate code in the Downloader class that exposes those attributes but that increases the coupling between the two classes and makes the class less maintainable if I want to later extend the DownloadItem class to support other fields. Any thoughts or ideas on how I can accomplish this?

Side note: This problem is mostly for my own learning of OOP patterns and design choices so feel free to critique and add as much context as possible.

I tried doing something like:

class InmutableWrapper:
    def __init__(self, obj):
        self._obj = obj

    def __getattr__(self, val):
        return self._obj.__getattr__(val)

then returning InmutableDownloadItemObj = InmutableWrapper(DownloadItemObj) on the call to Downloader.get_download_by_id() but I could still do assignments which would be reflected when the property was queried:

> print(Downloader.get_download_by_id(unique_id).filesize)
  1024
> Downloader.get_download_by_id(unique_id).filesize = 2    # Assigment goes through
> print(Downloader.get_download_by_id(unique_id)._obj.filesize) # Underlying object remains unchanged
  1024 
> print(Downloader.get_download_by_id(unique_id).filesize)
  2

Aucun commentaire:

Enregistrer un commentaire