I have the following code:
class Car(object):
def __init__(self, my_id):
self.my_id = my_id
self.color = color
self.brand = brand
self.get_color()
self.get_brand()
def get_color():
return requests.get('https://example.com/{}/color'.format(self.my_id))
def get_brand():
return requests.get('https://example.com/{}/brand'.format(self.my_id))
def get_description(car):
return 'My {} is {}'.format(car.brand, car.color)
def get_color(car):
return 'My car is {}'.format(car.color)
def main():
c = Car(123)
print get_description(c)
It works quite well by first initializing a Car
object that gathers all necessary data, and then allowing me to work with that data through first class functions.
However, I'd like to move all those functions into a separate class Description
. This class, because it uses a bunch of data from Car, would be initialized as:
class Description(object):
def __init__(self, car):
self.car = car
self.description_text = description_text
self.color_text = color_text
where the car
instance is passed as an argument to the Description
object on instantiation.
I was told, however, that this approach is not the best since the two classes are now tightly coupled.
How should this example be reworked to use two classes? Should Description
inherit the data from Car
? Should I instantiate Description
by passing all data from Car
?
1 Answer 1
Reworking the classes with a mixin and properties
How should this example be reworked to use two classes? Should Description inherit the data from Car? Should I instantiate Description by passing all data from Car?
This is actually a great use-case for the has-a relationship of a mixin and properties. Don't prefix methods that execute immediately with get
, use @property
instead.
class Description_Mixin(object):
"""mixin assumes you have a brand and color"""
@property
def brand_and_color_description(self):
return 'My {} is {}'.format(self.brand, self.color)
@property
def type_and_color_description(self):
return 'My {} is {}'.format(type(self).__name__.lower(), self.color)
Don't use properties for the request methods, use regular methods to signify that this is a function that can (and will) take time to complete. Here, the code semantically says that the car "has-a" description.:
class Car(Description_Mixin):
def __init__(self, my_id):
self.my_id = my_id
self.color = self.get_color()
self.brand = self.get_brand()
def get_color(self):
return requests.get('https://example.com/{}/color'.format(self.my_id))
def get_brand(self):
return requests.get('https://example.com/{}/brand'.format(self.my_id))
And usage:
def main():
c = Car(123)
print c.brand_and_color_description
And you can reuse your mixin for other types that need those kinds of descriptions.
Follow up - Lazy loading the attributes:
If I want to have cli options that only return specific parts of the description, there is no need to load up all the resources for Car. For example, if I want to, based on a cli option, only get a car's type_and_color_description, there is no need to call get_brand(). Is there a way to modify this code to account for that (i.e. not assume I have all the data from Car)?
This is a matter of logistics. Here's an implementation that does this, but it makes the return of the color and brand attributes slow on the first retrieve:
class Car(Description_Mixin):
def __init__(self, my_id):
self.my_id = my_id
@property
def color(self):
try:
return self._color
except AttributeError:
self._color = requests.get(
'https://example.com/{}/color'.format(self.my_id))
return self._color
@property
def brand(self)
try:
return self._brand
except AttributeError:
self._brand = requests.get(
'https://example.com/{}/brand'.format(self.my_id))
return self._brand
-
This is perfect. Thank you! And one more follow-up Q, if I want to have cli options that only return specific parts of the description, there is no need to load up all the resources for Car. For example, if I want to, based on a cli option, only get a car's
type_and_color_description
, there is no need to callget_brand()
. Is there a way to modify this code to account for that (i.e. not assume I have all the data from Car)?mart1n– mart1n2016年03月11日 15:51:59 +00:00Commented Mar 11, 2016 at 15:51 -
@mart1n I think I got your followup.Aaron Hall– Aaron Hall2016年03月11日 16:12:23 +00:00Commented Mar 11, 2016 at 16:12
Description.getColor(car)
where the car is an argument and not a piece of state, but that's not always the best move. (also, resource requests are considered off-topic here, so I've edited out that last part of your question)