I sometimes find myself writing classes (in Python, in my case) that are used like this:
MyClass(some_args).go()
In other words, there's only one method in the class that any external code would call, and it will only ever get called once. There might be a return value from go()
but often there isn't, and when there isn't, I could even call go
from the class's __init__
.
So why even bother with the class, instead of just writing go()
as a standalone function? Because what goes on inside go
is complicated. There may be several levels of nested function calls, and there are certain pieces of information that are needed in different spots. If I try to write all this without a class, I end up with a many of the functions returning multiple values. Sometimes the argument lists also get uncomfortably long. If I wrap everything up inside MyClass
, then I can put all this information in member variables of the MyClass
instance. These become not-quite-global variables, and they're generally stuff that feels like it make sense to store in a class.
Does this practice have a name? Is it a pattern, or an anti-pattern? And if the latter, how should I structure my code instead?
4 Answers 4
This is a natural progress of encapsulation, abstraction, and general code organization. As you extract pieces of code from a function into their own separate functions, you may notice that they share a lot of state that requires passing numerous parameters around. Promoting this state to fields of a class -- and said functions to methods of the class -- helps to reduce clutter and improve readability.
Note that in language like Python, where standalone functions are allowed, you may want to hide the existence of your class behind a function that both instantiates the object and calls its go
method. This way, the class becomes an implementation detail and your API remains the same, even if e.g. the logic becomes complicated enough to warrant a whole subpackage.
Like Ixrec said - put the implementation in another file. But since you need a state that you don't want to make global - put that state in a class!
OK, that came up a bit confusing. What I mean is to keep the class, but hide it from the user. The user will only see a global function that internally creates the class and call it's go
method. Actually - ditch the go
. Since all it does is calling other functions, you probably should do that from that external function.
So, instead of
class MyClass:
def __init__(self):
self.x = 1
self.y = 2
def op1(self):
self.x += self.y
def op2(self):
self.y += self.x
def go(self):
self.op1()
self.op2()
return self.x * self.y
Write this:
class __MyClass:
def __init__(self):
self.x = 1
self.y = 2
def op1(self):
self.x += self.y
def op2(self):
self.y += self.x
def calc_result(self):
return self.x * self.y
def my_function():
my_object = __MyClass()
my_object.op1()
my_object.op2()
return my_object.calc_result()
That way, you still have a class that holds all the data, but the user sees a single function.
-
+1 for not letting the abstraction leak any more than it really must. Which coincidentally also makes misuse considerably harder.Deduplicator– Deduplicator06/14/2015 15:22:04Commented Jun 14, 2015 at 15:22
-
if using Python 3 another option would be nested functions and nonlocal variables.Kelly Thomas– Kelly Thomas06/15/2015 03:31:25Commented Jun 15, 2015 at 3:31
I do not think this practice has a special name, I would call it "encapsulation", "creating an abstraction by utilizing a class", "class design", or simply "OO programming". And it is definitely not an anti-pattern.
-
I would add that this sounds an awful lot like a function object or lambda. One public method that hides underlying complexity is a common theme in several frameworks and languages.user22815– user2281506/14/2015 09:40:18Commented Jun 14, 2015 at 9:40
-
Well, "leaky encapsulatio" "java-esque OO programming (forced class)" and the like seem to fit better.Deduplicator– Deduplicator06/14/2015 15:24:35Commented Jun 14, 2015 at 15:24
Uncomfortably long argument lists and multiple return values can usually be addressed by grouping values into structured data types or encapsulating them in classes. If there are no clear groupings, hiding that by sweeping it into object state with other unrelated values is only furtherly detrimental.
Methods (even methods intended to be private) should leave objects in a consistent state when they return. That becomes more difficult to achieve as the object's state gets more complex. Often it means factoring out some code into external/static functions so that you have: 1) a method that transitions the object from one valid state to another, which is accomplished by 2) functions that implement well-defined procedures, which may be passed a subset of values from the object state, but are separately testable.
Lastly, a function should be exposed as a function. That means clients should be calling my_function(some_args)
and not MyClass(some_args).go()
. If my_function
is implemented as the latter, you are at least isolating client code from its ambiguities.
Explore related questions
See similar questions with these tags.
MyClass
instance handles that for me.