Home > OS >  How can I encapsulate a private mutable object of a class such that it can expose its attributes pub
How can I encapsulate a private mutable object of a class such that it can expose its attributes pub

Time:01-31

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

CodePudding user response:

One way to achieve this is to use the Proxy pattern. The Proxy pattern is a structural design pattern that provides a surrogate or placeholder object for another object and controls access to it.

In this case, you can create a proxy class that wraps the DownloadItem object and exposes its attributes publicly. The proxy class should have methods to get and set the attributes of the DownloadItem object, but should not allow any direct assignment to the DownloadItem object itself.

For example:

class DownloadItemProxy:
    def __init__(self, download_item):
        self._download_item = download_item

    def get_filesize(self):
        return self._download_item.filesize

    def set_filesize(self, filesize):
        self._download_item.filesize = filesize

Then, in the Downloader class, you can return an instance of the DownloadItemProxy class instead of the DownloadItem object itself. This way, the Downloader class can still access and modify the DownloadItem object, but the user of the Downloader class will only be able to access and modify the attributes of the DownloadItem object through the proxy. Hope this helped.

  • Related