I need some help figuring out the right OOP design choice for my python class.
In my example, letters are appended to a string in separate steps. The string is then printed. For this application, it is not really important to the user to get the intermediate results.
So is it better to pass the string to the constructor and then use private methods for the operations and a dothings
method calling them all?
class A:
def __init__(self, string: str()):
self.string = string
def _append_a(self):
self.string += "a"
def _append_b(self):
self.string += "b"
def dothings(self):
_append_a()
_append_b()
def export(self):
print(self.string)
Or is it better to have the string passed to each method?
class AA:
@staticmethod
def append_a(string):
string += "a"
return string
@staticmethod
def append_b(string):
string += "b"
return string
@staticmethod
def export(string):
print(string)
The interface of A
looks a bit cleaner to me, one can just call dothings
and then export.
However, class A
would be a bit of a black box, while with class AA
the user has some more insights to what is happening.
Is there a 'right' choice for this?
AA
is easily dismissed. There is nothing object-oriented about it: it's just three regular functions collected into a single namespace. There's no shared state operated on by a set of methods. There's no suggestion that only the output of one function is a valid input to another. For example, the intention is probably to write something like
export(append_a(append_b("foo"))) # fooba
but nothing requires this pattern be followed. The functions aren't related to each other in anyway.
A
has some things in common with the builder pattern. Given an initial string, you can append a
s and b
s to it, but nothing else (without violating encapsulation provided by the methods. Eventually, you get the "final" value by calling export
, so the work flow it represents is something like:
a = A("foo")
a.append_a()
a.append_a()
a.append_b()
a.append_b()
a.append_a()
a.export() # fooaabba
The class as shown is almost trivially simple, but demonstrates how to provide a well defined interface to building a string value from an initial seed. You can't just do anything you like with it: you can't prepend values, you can't remove existing characters, etc.
To conform more closely to the builder pattern, I would modify A
as follows:
class A:
def __init__(self, string: str):
self.string = string
def append_a(self):
self.string += "a"
def append_b(self):
self.string += "b"
def append_ab(self):
self.append_a()
self.append_b()
def export(self):
return self.string + "c"
As long as you don't access the string
attribute directly, this class limits the kind of string you can build:
__init__
)a
to the stringb
to the stringab
to the string (but this is just a convenient shortcut for calling append_a
followed by append_b
, as the implementation implies)c
You get your final value by calling export
(which I modified just to make the point that you cannot add a c
at any point, so there's no way to follow a c
with another a
, for example).
In some sense, it's kind of a dual to a regular expression: instead of recognizing whether or not a string matches the regular expression .*(a|b)*c
, you create a string that will match the regular expression.