Sometimes the problem that you’re solving is as simple as “I don’t have the interface that I want.” Two of the patterns in Design Patterns solve this problem: Adapter takes one type and produces an interface to some other type. Façade creates an interface to a set of classes, simply to provide a more comfortable way to deal with a library or bundle of resources.
When you’ve got this, and you need that, Adapter solves the problem. The only requirement is to produce a that, and there are a number of ways you can accomplish this adaptation:
# adapter.py
# Variations on the Adapter pattern.
from typing import Any
class WhatIHave:
def g(self) -> None: pass
def h(self) -> None: pass
class WhatIWant:
def f(self) -> None: pass
class ProxyAdapter(WhatIWant):
def __init__(self, what_i_have: Any) -> None:
self.what_i_have = what_i_have
def f(self) -> None:
# Implement behavior using
# methods in WhatIHave:
self.what_i_have.g()
self.what_i_have.h()
class WhatIUse:
def op(self, what_i_want: Any, /) -> None:
what_i_want.f()
# Approach 2: build adapter use into op():
class WhatIUse2(WhatIUse):
def op(self, what_i_have: Any) -> None:
ProxyAdapter(what_i_have).f()
# Approach 3: build adapter into WhatIHave:
class WhatIHave2(WhatIHave, WhatIWant):
def f(self) -> None:
self.g()
self.h()
# Approach 4: use an inner class:
class WhatIHave3(WhatIHave):
class InnerAdapter(WhatIWant):
def __init__(self, outer: Any) -> None:
self.outer = outer
def f(self) -> None:
self.outer.g()
self.outer.h()
def what_i_want(self) -> WhatIWant:
return WhatIHave3.InnerAdapter(self)
what_i_use = WhatIUse()
what_i_have = WhatIHave()
adapt = ProxyAdapter(what_i_have)
what_i_use2 = WhatIUse2()
what_i_have2 = WhatIHave2()
what_i_have3 = WhatIHave3()
what_i_use.op(adapt)
# Approach 2:
what_i_use2.op(what_i_have)
# Approach 3:
what_i_use.op(what_i_have2)
# Approach 4:
what_i_use.op(what_i_have3.what_i_want())I’m taking liberties with the term “proxy” here, because in Design Patterns they assert that a proxy must have an identical interface with the object that it is a surrogate for.
The four variations above are Java habits. Python is dynamically
typed: WhatIUse.op() only calls f(), so it
accepts any object that has an f(). You do not
need a shared base class or a declared interface, only the method.
The common adapter need is “forward most calls unchanged, and add or
change a few.” __getattr__ does the forwarding, so the
adapter stays tiny:
# getattr_adapter.py
# The usual adapter need: forward most calls, change a few.
# __getattr__ delegates everything you do not override, so the
# wrapper stays small.
from typing import Any
class WhatIHave:
def g(self) -> str: return "g"
def h(self) -> str: return "h"
class Adapter:
def __init__(self, adaptee: WhatIHave) -> None:
self._adaptee = adaptee
def f(self) -> str: # the new interface
return self._adaptee.g() + self._adaptee.h()
def __getattr__(self, name: str) -> Any: # forwards the rest
return getattr(self._adaptee, name)
a = Adapter(WhatIHave())
print(a.f()) # adapted method
print(a.g()) # forwarded to the adaptee unchanged__getattr__ runs only for attributes Python does not
find normally, so f() uses the adapter’s own version
while everything else falls through to the adaptee. This is the
idiomatic Python adapter: a thin wrapper, not a hierarchy.
A test pins down both halves of that behavior: the new
f() combines the adaptee’s methods, and unoverridden
calls forward straight through to the wrapped object:
# test_adapter.py
from getattr_adapter import Adapter, WhatIHave
def test_new_interface_combines_methods() -> None:
assert Adapter(WhatIHave()).f() == "gh"
def test_getattr_forwards_existing_methods_unchanged() -> None:
a = Adapter(WhatIHave())
assert a.g() == "g"
assert a.h() == "h"
def test_forwarding_targets_the_wrapped_object() -> None:
have = WhatIHave()
a = Adapter(have)
assert a.g.__self__ is have # __getattr__ delegates to adapteeA general principle that I apply when I’m casting about trying to mold requirements into a first-cut object is “If something is ugly, hide it inside an object.” This is basically what Façade accomplishes. If you have a rather confusing collection of classes and interactions that the client programmer doesn’t really need to see, then you can create an interface that is useful for the client programmer and that only presents what’s necessary.
Façade is often implemented as a singleton abstract factory. You
can easily get this effect by creating a class containing
static factory methods:
# facade.py
class A:
def __init__(self, x: object) -> None: pass
class B:
def __init__(self, x: object) -> None: pass
class C:
def __init__(self, x: object) -> None: pass
# Other classes that aren't exposed by the
# facade go here ...
class Facade:
@staticmethod
def make_a(x: object) -> A:
return A(x)
@staticmethod
def make_b(x: object) -> B:
return B(x)
@staticmethod
def make_c(x: object) -> C:
return C(x)
# The client programmer gets the objects
# by calling the static methods:
a = Facade.make_a(1)
b = Facade.make_b(1)
c = Facade.make_c(1.0)The cleaner Python façade is a module. A module already
presents a curated set of names over whatever tangle of classes
lives behind it, and, as the Singleton
chapter notes, it is imported once and shared everywhere. Put
the friendly functions and the few classes you want to expose at
module level, keep the messy internals private (a leading
underscore, by convention), and the import is
the façade. A Facade class full of static methods only
reproduces, with more ceremony, what a module gives you for
free.