Question Details

No question body available.

Tags

design-patterns

Answers (2)

February 24, 2026 Score: 1 Rep: 165,607 Quality: Medium Completeness: 60%

The biggest question I'd add to this is: how are you going to test your program? This tilts me heavily towards the "composable simple blocks" direction, even for building large complex things.

One huge advantage of small, pure functions is that it's really easy to write unit tests for them. If they have no external dependencies, the tests will run really quickly. You can get up to "okay" test coverage testing the computational parts of your system even if you need a heavy-weight integration test for things like database handling and network I/O.

A very recent professional project for me involved distributing some work across a cluster of machines, with Kubernetes-cluster dependencies (and not in Go). There was a sequence of tasks, and each task unblocked some number of other tasks, and any of the unblocked tasks could run in parallel. Here I wrote a standalone piece to build the execution plan, which was a pure in-memory directed acyclic graph (DAG) of tuples ("taskname", objectnumber). Then I could write tests that the DAG was being constructed correctly, and that completing some tasks unblocked others, without actually having the external dependencies.


The other possible piece you're missing is a dependency injection system, coupled with abstract interfaces. Let's say you have an object-storage system

public interface ObjectStorage {
  public String get(int id);
  public void put(int id, String contents);
}

Now in reality this is probably going to be backed by a database or an in-memory store or a cloud service or a filesystem. But your code doesn't need to know that. So long as you have some instance of the storage interface, you don't need to pass along credentials or connection pools or anything else.

public class MyClass {
  private ObjectStorage objectStorage;

public MyClass(ObjectStorage objectStorage) { this.objectStorage = objectStorage; }

public void addSomeText(int id, String text) { String oldText = objectStorage.get(id); String newText = oldText + text; objectStorage.put(id, newText); } }

Now remember that storage system? You could trivially implement that as a HashMap. And if you do that, then you can easily write a unit test for MyClass, without either test-time database dependencies or invasive mocks.

But now we can abstract that

public interface TextAdder {
  void addSomeText(int id, String text);
}

public class MyClass implements TextAdder { ... }

and use that in a different class

public class Greeter {
  private TextAdder textAdder;

public Greeter(TextAdder textAdder) { this.textAdder = textAdder; }

public void toTheWorld(int id) { textAdder.addSomeText(", world!"); } }

The important thing about this last setup is that you don't have dozens of parameters in your top-level object for external object storage and whatever other interfaces, but you've encapsulated those in other objects that have exposed functions you're calling.

You can construct this object graph by hand, and would likely do this in tests

ObjectStorage objectStorage = new MemoryObjectStorage();
MyClass myClass = new MyClass(objectStorage);
Greeter greeter = new Greeter(greeter);

objectStorage.put(1, "hello"); greeter.toTheWorld(1); System.out.println(objectStorage.get(1));

but where the automated dependency-injection system comes in is that it can construct this object graph for you, and reuse dependencies across different objects as you need to.


...and sometimes you're just forced to pass down lots of parameters.

It's not hard to write a complex subsystem that intrinsically does have lots of options and settings. These might be at a per-call scope that your DI system can't necessarily handle. Sometimes you can pack those up into an object of common settings, or an execution object that captures the settings. But if you have parameters A, B, C, and D, and step 1 needs A-B-C and step 2 needs A-C-D and step 3 only needs B-D, you might wind up passing lots of these values down unmodified.

This is a little unpleasant. If your language has static typing then you can at least catch problems of forgetting to pass along one of the values. I try to avoid it when I can, but you can't always necessarily.

February 24, 2026 Score: 0 Rep: 17,469 Quality: Low Completeness: 30%

It turns out there's two styles of designers: Lumpers and Splitters.

https://www.youtube.com/watch?v=3gib0hKYjB0&t=2383s