3

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.

3

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:

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.

| improve this answer | |
  • As a quick follow-up, with python's unittest.mock module, especially the patch function, this type of explicit coupling doesn't really prevent unittesting. But then the unittest also gets coupled to the coupling... – Lagerbaer May 27 at 15:35
  • Another thought about this coupling is in incremental development. Let's say Foo was written in one pull request and Bar 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 of Bar while writing Foo? – Lagerbaer May 27 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 May 27 at 17:36
1

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.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.