Posted On: 2021-09-20
In my previous work, my team often created what we called "wrapper objects" as a means of future-proofing our code. It was fairly simple (albeit tedious) to do, and, although it often didn't pay off, when we did need it, it was incredibly valuable. At the time, I didn't think much of it - the legacy code base used such wrappers extensively, and our lead architect would often encourage using them in a variety of different situations. Since then, I've come to realize that "wrapper objects" aren't an established programming pattern, but instead they are a twist on the traditional Proxy Pattern, which use the proxy to create the foundation for future use of the Facade Pattern. Knowing this, I find this to be quite the clever trick - by using a simpler pattern one can prepare for the possibility of much more complex changes down the line.
When programming, you often need to work with other resources that you don't or can't control (file handles, network connections, third-party objects, etc.) While you could simply reference the resource directly, it is often useful to put an intermediary in between that you control. That intermediary is a proxy: a class that has the same signature as the resource, and passes any calls along to the resource.
The most common use for a proxy class is to reduce duplication. If you have some action that you always want to perform whenever using the resource (ie. logging), then you can only need to add it in one place: the proxy. Proxies can also be used for a variety of other purposes, such as conforming to a new interface or, as this post demonstrates, acting as a placeholder for a different architectural pattern.
Programming often involves working at different levels of abstraction. Details that are essential for lower levels of abstraction often become overly fiddly when trying to work at higher levels. This can be especially acute when working with third-party libraries: which details are exposed and how they are configured are often far more detailed/complex than a particular application requires. The Facade Pattern presents a simple workaround to this problem: create a class (the facade) that exposes methods at the abstraction level you want to work in, but internally takes care of all the fiddly details you don't want to bother thinking about every time you try to use it. All the rest of your code should use the facade, thereby simplifying most of your code (and leaving all the complexity buried inside the facade's implementation.)
On my (previous) team, we often used proxies when there was no reason to do so. We'd dutifully create proxies that "wrapped around" all manner of things: database connections, third-party libraries, even our own code. These proxies would then pass along each of their method calls to the underlying resource without performing any additional action. To outsiders (or newcomers), it was rather perplexing: why did we create so many extra classes - so much extra work - if it was doing absolutely nothing?
To answer that, you need only look at the places where we didn't have time to do things "right". Perhaps some third-party code is misbehaving and needs to be replaced asap, or you need to start coding before knowing which of two competing dependencies will be used in the project. In these circumstances, the empty proxy suddenly becomes a massive time-saving tool. The proxy signature can stay exactly as-is, but the insides can be completely rewritten - effectively turning a proxy to one dependency into a facade for a different dependency. Additionally, in our (especially brittle) legacy applications, minimizing the surface area of changes helped deployment speed as well: more focused testing, fewer approval gates, and, ultimately, happier users in production.
While having "wrapper objects" change from proxies to facades helped us manage time when it was most precious (ie. during a production issue), the pattern does have disadvantages. There is an up-front cost to creating proxies in the first place: all that code won't write itself*. Additionally, having proxies that do nothing artificially increases the complexity of the code base, making maintenance of affected areas require a larger cognitive load. This can be mitigated by being able to accurately predict when a wrapper object will actually become a facade - but it's best not to count on that**.
On the other end of things, once a proxy has become a facade, it carries a significant maintainability cost. Facades are most useful when their signatures reflect the way a developer conceptualizes a problem, but these former-proxies-turned-facades often reflect a deprecated/removed dependency instead. While this is not terrible for developers who previously used the old dependency, it's an extra barrier for anyone new who joins the team. Ideally, one would update the facade so that it makes sense independent of what it once "wrapped around", but how often you can actually do so is dependent on your organization's programming discipline and risk tolerance.
As you can see, using proxies that will (potentially) become facades can save time when it's most precious. It does, however, come with a long-term cost in maintainability, so its appropriateness may vary by team/project. In my previous work, the way risk was managed meant that wrapper objects were particularly handy for hot-fix scenarios: they kept risk down and approvals smooth. In my current (solo) work, however, I find I have far less need for it: maintainability is particularly important (much more so than completing tasks arbitrarily fast) so I generally defer creating proxies/facades until I have an imminent need for one, rather than creating extras in advance. Thus, while I like using "wrapper objects", I haven't had the opportunity to do so in quite some time. Hopefully, this post has helped you appreciate them too - whether or not you ever have need of them.