Question

What is a tear-off? I ran across the term reading Flutter documentation:

Returns a CallbackHandle that can be provided to PluginUtilities.getCallbackFromHandle to retrieve a tear-off of the original callback.

Googling the term only seems to yield "tear-off menus" which I understand but are unrelated.

Was it helpful?

Solution

What is the “tear-off” pattern?

The idea of a "tear-off" is that we have some functionality provided by an object, but the thing actually providing the functionality does not have identity with the service provider you're talking to.

Cast your mind back to the 1990s and imagine that you're a COM programmer. COM is a binary standard for doing object-oriented programming from languages that do not necessarily support objects directly, like C. The foundational method in COM programming is IUnknown::QueryInterface, which takes a number that uniquely identifies an interface and either fails, or succeeds by providing a pointer to an object that implements that interface. All COM interfaces inherit from IUnknown, and therefore all COM objects implement QueryInterface on all their interfaces.

So suppose you are implementing your COM server -- remember, a server is any object that provides a service; don't think of a server as a machine -- as a C++ class and you want it to implement IFoo and IBar, both of course deriving from IUnknown. Typically we do this by creating abstract classes for IFoo and IBar, assigning each a globally unique number, and then implementing QueryInterface on a C++ class such that it casts the this pointer to the appropriate abstract class and returns the cast pointer. The standard way to implement multiple interfaces is multiple inheritance of abstract base classes. (Note that even though we logically are preserving referential identity, in practice the pointers are allowed to be unequal and usually are.)

But all that is required of QueryInterface is that on success it returns a valid pointer to an object that has the right vtable. There is not a requirement that QueryInterface be implemented via casting to interfaces that are multiply inherited. It would be entirely legal for you to implement your contract by having QueryInterface return a different object entirely when QI'd for IFoo and a different object entirely when QI'd for IBar. (There are then some complications regarding what happens when you QI the IFoo object for IBar, but perhaps you can see how those are handled.)

There are performance and program architecture reasons to prefer this implementation choice, which is called "tear-off interfaces". The key thing is that when asked for a service, the object hands you back a different object that implements that service, rather than implementing the service itself.

From a "design patterns" perspective, I think of the "tear-off" pattern as being the first step towards more complex solutions to this problem such as "chain of responsibility". In "chain of responsibility" you ask an object for a service and it either provides it, or it says "I can't provide the service you want, but I know someone else who might..." and forwards the request along a chain of providers. (In COM, "chain of responsibility" is represented by IServiceProvider::QueryService.)

It appears that Dart is using "tear-off" to mean something similar, but only for a single function, not for an entire interface. That is, a "tear off" is an object that is distinct from the original object, but represents a service provided by that object, namely, the ability to call a method. I would think of this in C# as a delegate.

Licensed under: CC-BY-SA with attribution
scroll top