0

Consider a case, in which a class needs an attribute only in a very limited context. (For example, only during initialization, which is handled by a factory) I do not want to include such an attribute in the class, in order to keep it as simple as possible.

In the concrete example, I have a sorted list of tree nodes with given depth. Every node is direct child of the last node that has a smaller depth and comes before the child node itself. Once the tree is properly built and all parents contain links to their children, the depth is no longer required.

Here are the options I have considered in python:

  • Just including the attribute in the class (ugly)
  • Monkey patching the attribute in the factory method and removing it afterwards (uglier)
  • Using the following dataclass in the factory method:
@dataclass
class NodeAndDepth:
    depth: int
    node: Node
  • Using the following class, and than "casting" the objects into the simpler one:
class NodeWithDepth(Node):
    def __init__(self, depth, ...):
        ...
Ali Rasim Kocal
  • 992
  • 1
  • 6
  • 11
  • 3
    The best practice is to just add the attribute. – Telastyn Dec 04 '21 at 04:57
  • @Telastyn would you mind explaning why? – Ali Rasim Kocal Dec 04 '21 at 05:05
  • 3
    Because “ugliness” isn’t a reasonable metric. Can someone understand it? Can someone maintain it? A plain attribute is the clearest expression of what is going on of your alternatives. – Telastyn Dec 04 '21 at 05:09
  • @Telastyn: monkey patching makes even more clear that the attribute is only required during initialization, so why not this? – Doc Brown Dec 04 '21 at 06:36
  • 1
    ... Personally, I would prefer the `NodeAndDepth` approach for this case, but I think this 100% opinionated, and may be influenced by the fact I have used strongly typed languages way more often than Python. – Doc Brown Dec 04 '21 at 06:40
  • 1
    @DocBrown - I am skeptical that “only used during initialization” is a useful thing to convey or is unlikely to change in the future. If it is a trait of the class, just put it with the class. – Telastyn Dec 04 '21 at 17:05
  • I have never known a tree node class to require knowledge of depth, even in initialization. Are you sure this is required? – Daniel T. Dec 05 '21 at 13:29

2 Answers2

6
  1. If an attribute is only needed for the time of a very specific operation (e.g. initialization), it is not really an attribute but a parameter or a local variable of that operation. Don’t bother.

  2. If the operation is complex to the point that it could be encapsulated in a class (e.g. a builder, factory, a strategy, or a visitor) then you could want to make it an attribute of that operation class. Two main reason can justify this choice: 1) the operation is decomposed into several simpler operations, each requiring access to this attribute; 2) the attribute is reused between successive invocation of the operation. But if there is no need, don’t do it: YAGNI.

  3. If there is a semantic link between the attribute and the class, i.e if independently of the operation the attribute means something relevant, you may want to keep it. It could save you time sooner or later. The depth of a tree is a perfect candidate, if you were able to easily keep it up-to-date. If not, go back to 1.

Christophe
  • 74,672
  • 10
  • 115
  • 187
1

If the attribute is volatile and not always needed, look at how databases do this..

In databases, you implement occasional, loose relations between data by using junction table records, connecting records from one table with other records in another table.

In this case, you'd have a ClassInstances table with an index named ClassInstanceId, and you'll have a table Attributes with an AttributeId. You introduce new table named InstanceAttribute only containing the indexes you want to connect..

enter image description here

Advantages of this approach, also when it is used in memory: (1) you don't have to change your Class declaration to add Attributes.. (2) you can have an arbitrary number of attributes per instance and (3) you could store attribute sets only once, share them.

Goodies
  • 115
  • 2
  • 2
    But writing the code isn't the same as a database schema, right? There is no straightforward way to implement something like a temporary junction table to an OOP model. The closest equivalent would be to use a dictionary that maps the ID (memory location) of objects to the additional data. So instead of `self.depth` one might access `depths[id(self)]`. I don't think that's a good design though. – amon Dec 04 '21 at 21:36
  • In memory, you don't need an "Id" index, you'll have a reference to the instance, and a reference to the attribute instance. The junction has its own Junction class with two reference pointers. Database things and "writing code" are related.. a method like the above will translate very easily into "writing code" as soon as you're used to LINQ and Entity Framework. In EF applications, database tables are accessed as in memory sets anyway. – Goodies Dec 05 '21 at 16:04