The hanging attribute
I have not practiced writing for the long time, so this might be not my best text, but here we go.
The problem I named "The hanging attribute" can be highlighted with the following example. Let's assume that we have a class Item which we sell:
class Item:
item_id: int
price: float
category: str
name: str
dimensions: {"weight": x, "volume": y}
And we want to have ability to select stuff.
(note: often people do client-side selection, we do it server-side)
Here we have some options:
1) Selection abstraction
from collections import defaultdict
selection = defaultdict(int)
item_id = 1
quantity = 5
selection[item_id] += quantity
2) SelectedItem abstraction
class SelectedItem(Item):
quantity: int
How to choose which option to pick? I have hard times in finding the traits that make one option be more attractive over another.
Let's write some user-code of server-side selection (some pseudo code involved):
1) Selection abstraction
items: list[Item] = get_items_from_db(...)
send_items_to_client(items)
# ...
selected_item_id, selected_quantity = receive_selection()
selection[selected_item_id] = selected_quantity
2) SelectedItem abstraction
items: list[Item] = get_items_from_db(...)
send_items_to_client(items)
# ...
selectable_items = make_selectable_items(items) # default quantity == 0
selected_item_id, selected_quantity = receive_selection()
selected_item = [i for i in selectable_items if i.item_id == selected_item_id][0]
selected_item.quantity = selected_quantity
Given that both solutions lack input validation, so the second option seems a little more complicated.
Let's now add a constrain - we want some rules being calculated on top off current selection.
For example we want to limit max_weight
of the order to be below 100 kilos.
Let's extend the code:
1) Selection abstraction
items: list[Item] = get_items_from_db(...)
send_items_to_client(items)
# ...
selected_item_id, selected_quantity = receive_selection()
selection[selected_item_id] = selected_quantity
# ...
quantity = int # for better signature readability
selected_items: list[tuple[Item, quantity]] = []
for selected_item_id, selected_quantity in selection.items():
selected_item = get_item_by_id(selected_item_id)
selected_items.append((selected_item, selected_quantity))
selected_weight = sum(item.dimensions.weight * q for item, q in selected_items)
if selected_weight > max_weight:
raise SelectedWeightError()
2) SelectedItem abstraction
items: list[Item] = get_items_from_db(...)
send_items_to_client(items)
# ...
selectable_items = make_selectable_items(items) # default quantity == 0
selected_item_id, selected_quantity = receive_selection()
selected_item = [i for i in selectable_items if i.item_id == selected_item_id][0]
selected_item.quantity = selected_quantity
# ...
selected_weight = sum(item.dimensions.weight * item.quantity for item in selected_items)
if selected_weight > max_weight:
raise SelectedWeightError()
So we seem to benefit from SelectedItem as long as we need to operate on Item's attributes and on quantity at the same time. Packing quantity and Item together seems to be wanted.
In my opinion we went to selected_items
way more straight forward in 2)
snippet.
You might argue :
Hey, I could have written a short way to make SelectedItem out of snippet
1)
And actually I won't persuade you in the opposite. What I wanted to state that SelectedItem abstraction seems beneficial as it couples together Item and quantity.
So a way to reasons about this is in terms of coupling - we are likely to use Item and quantity together, so -
Don't let quantity attribute hang in the tuple - time to create SelectedItem
[1]
I have made small search on this problem and found a reference to a similar problem at refactoring.guru. Don't hesitate to check it.