3

In Python, I have a class C which embeds objects from classes A and B. Is it considered good practice to creates shortcuts from the properties of embedded objects of classes A and B as attributes of container class C?

The goal is to create a simpler interface in class C, abstracting the implementation details of classes A and B:

A minimal example:

class A:
    def __init__(self):
        self._a = 1
@property
def a(self):  # Read-only property
    return self._a


class B: def init(self): self._b = 2

@property
def b(self):  # Read-write property
    return self._b

@b.setter
def b(self, value):
    self._b = max([value, 0])


class C: def init(self): self.a = A() self.b = B()

    # The following attributes provide "shortcuts" 
    # to the properties of the contained objects
    self.a_val = self.a.a
    self.b_val = self.b.b

blunova
  • 398

2 Answers2

4

This is one way of mitigating violations of law of Demeter. I would provide caution on two things:

  1. When you assign the subattributes of A and B to attributes, you're taking a snapshot in time. Futher changes to a.a won't be reflected in a. This may or may not be desirable

    It might be preferable to make a method which returns the current value of a.a on-demand, rather than storing its value at the time of initialization.

  2. In some cases this process of exposing constituent properties makes sense to do conceptually, but in others it doesn't.

    Consider a car, which contains driving controls and an engine.

    This would be bad:

    drivingControls.engine.openThrottleBody()
    

    This would be preferred:

    drivingControls.acceleratorPedalPressed()
    
Alexander
  • 5,185
2

Since this is Python, you could implement the "shortcuts" to A.a and B.b as properties of C, so that changes in the underlying instances of A and B are instantly reflected when you query C.a or C.b, respectively. This avoids the first issue raised by @Alexander.

Example:

class C:
    def __init__(self):
        self.a = A()
        self.b = B()
@property
def a(self):
    return self.a.a

@property
def b(self):
    return self.b.b

@b.setter
def b(self, value):
    self.b.b = value

I said could, because as you can see, this leads to some verbosity - you'd have to add properties (and potentially their setters) for every attribute you want to expose. You should really evaluate whether this makes sense conceptually, or whether instances of C could instead have methods that use A.a and B.b internally, without never exposing them directly. For example:

class C:
    def __init__(self):
        self.a = A()
        self.b = B()
def do_foo(self):
    return self.a.a + self.b.b

Alternatively, if all you need is the data held by A or B, but none of their functionality, you can simply relay the data over to C upon construction. This can be done either by passing in A and B instances to C's __init__:

class C:
    def __init__(self, a_instance, b_instance):
        self.a = a_instance.a
        self.b = b_instance.b

a = A() b = B() c = C(a_instance=a, b_instance=b)

Or passing in the values directly:

class C:
    def __init__(self, a, b):
        self.a = a
        self.b = b
a = A()
b = B()
c = C(a=a.a, b=b.b)

This is often the simplest solution, because it keeps A and B isolated from C, while only relaying the necessary data. But whether this works for you or not depends on your specific use case.

jfaccioni
  • 516