3

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.

  • 1
    I was curious so I googled a bit; it's seems to be Dart terminology. For example, here it says "If you refer to a method on an object but omit the parentheses, Dart gives you a “tear-off”—a closure that takes the same parameters as the method and invokes it when you call it.". – Filip Milovanović Dec 16 '19 at 17:24
  • 1
    Also, the Dart Language Spec ch16.22 says: "Function objects derived from members via closurization are colloquially known as tear-offs", where instance method closurization converts a method into a function object. – Filip Milovanović Dec 16 '19 at 17:24
  • 1
    @RobertHarvey: The notion of a tear-off goes back at least to 1990s COM programming, which is where I first encountered it. But Dart appears to be using it in a much more restricted and specific way than a COM programmer would. – Eric Lippert Dec 16 '19 at 18:19
5

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.

| improve this answer | |
  • It always weirded me out that it's called Component Object Model and yet all you worked with was a bunch of interfaces; no objects in sight. That really complicated object agregation on the implementer's side for not many gains. – Joker_vD Dec 16 '19 at 20:17
  • @Joker_vD: I'm not following your argument; how are objects that implement interfaces not objects? (This link might be germane: softwareengineering.stackexchange.com/questions/46592/…) – Eric Lippert Dec 16 '19 at 20:23
  • 1
    @Joker_vD: It's just a name. Microsoft wasn't great at naming things anyway. – Robert Harvey Dec 16 '19 at 21:00
  • 2
    @RobertHarvey: twitter.com/ericlippert/status/1205372172534874112 – Eric Lippert Dec 16 '19 at 21:38
  • 2
    So, if I understood you well, the "tear-off" you described is a way to return a different object in a way that's transparent to clients (they don't know it's a different object, it's an implementation detail), but the way Dart uses the term is different not only due to the tear-off interface being a restricted to a single function, but also due to the fact that clients know it's a different object (and it behaves within the language as such). – Filip Milovanović Dec 16 '19 at 22:36

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.