I'm wondering if there's a standardized name for the following refactoring:
class Foo:
def do_something_awesome(self):
my_bar = Bar(42)
return my_bar.reticulate_splines()
Here class Foo
is explicitly coupled to class Bar
because it relies on that class name to create the my_bar
object. If I don't like this explicit coupling, I'd go
class Foo:
def __init__(self, bar_generator):
self.bar_generator = bar_generator
def do_something_awesome(self):
my_bar = self.bar_generator.create_bar(42)
return my_bar.reticulate_splines()
I can swear I read this in either Martin Fowler's refactoring book or in Kerievski's refactoring to pattern book, but can't seem to find it.
Apart from that, would this be considered a reasonable refactoring? I feel it's a mix of Factory Object and Dependency Injection.
2 Answers 2
Your bar_generator
is called a Factory, and designing a class Foo
to use such an "injected" factory (instead of contructing the objects on its own) is called "Dependency Injection" - this is what you already mentioned. However, this is not how the refactoring steps are called.
Looking at Fowler's refactoring catalog, the transformation can be achieved three steps:
Replace Constructor with Factory Function will extract the
bar
creation into a new member functioncreate_bar
, still insideFoo
Replace Function with Command will move
create_bar
into the newly created classbar_generator
. Still thebar_generator
is created insideFoo
.Parameterize Function can be applied to the constructor which makes
bar_generator
a parameter of it.
Apart from that, would this be considered a reasonable refactoring?
It is reasonable when the decoupling from Bar
fulfills some reasonable purpose. "I don't like this explicit coupling" is IMHO a very weak reason. "The explicit coupling hinders me from unit testing the class" or "The explicit coupling prevents reusage in a different context" would be way better reasons, for example.
-
As a quick follow-up, with python's
unittest.mock
module, especially thepatch
function, this type of explicit coupling doesn't really prevent unittesting. But then the unittest also gets coupled to the coupling...Lagerbaer– Lagerbaer2020年05月27日 15:35:10 +00:00Commented May 27, 2020 at 15:35 -
Another thought about this coupling is in incremental development. Let's say
Foo
was written in one pull request andBar
will be fleshed out in another. Then right now the code wouldn't work, whereas with factory injection it would. Is that a good reason for this refactoring, or would the advice there be to just provide a minimal stub implementation ofBar
while writingFoo
?Lagerbaer– Lagerbaer2020年05月27日 15:36:22 +00:00Commented May 27, 2020 at 15:36 -
1@Lagerbaer: honestly, I would recommend to try out what works best for you and your team. DI by using a factory has several use cases, but also some alternatives, which are sometimes simpler. Don't overthink this, keep things as simple as possible, but not more.Doc Brown– Doc Brown2020年05月27日 17:36:28 +00:00Commented May 27, 2020 at 17:36
It is difficult to tell with generic names, but if bar_generator
is not returning a polymorphic object, that is it does not return multiple different types of objects, then it really might not be a factory object. Factory objects in their purist form are used in conjunction with polymorphism. The objects they return are usually cast up to an interface or abstract class, and the concrete type is determined at runtime.
If the type of my_bar
is not changed at runtime, and is always the same type, then a Service Locator might be a better name for this (anti) pattern. It is not always an anti pattern, but given your conceptual code it looks like an anti pattern.
If bar_generator.create_bar
does not require runtime data from the Foo class itself, traditional dependency injection would be the better technique:
foo = Foo()
bar = Bar(42)
foo.do_something_awesome(bar)
If the Bar
object is only needed in do_something_awesome
consider making it an argument to the method, rather than a field on the class. Think of it as a dependency of the method rather than the entire class.
Explore related questions
See similar questions with these tags.