Proposal: Default object method

Brasten Sager brasten at brasten.me
Mon Jan 28 00:04:53 UTC 2019


Apologies if this has been raised before. I was unable to locate anything similar.

Any thoughts or ideas on this proposal would be appreciated!

Original: https://gist.github.com/brasten/f87b9bb470973dd5ee9de0760f1c81c7 <https://gist.github.com/brasten/f87b9bb470973dd5ee9de0760f1c81c7>

-Brasten

—

# Proposal: Default object method #

Objects w/ default method can be invoked like a function.

## Problem ##

Objects that are well constrained (single responsibility)
can tend to end up with a single method, or at least a single method
that is important to most consumers. These methods tend to be named
by either verbing the class name (eg. `UserCreator.create()`) or with
some generic `handle` / `perform` / `doTheObviousThing`.

Whatever the name, downstream consumers of the object end up coupled to
two implementation details: 

   1) this thing-doer is an object and not a function
   2) this thing-doer's doing method is called `X`

### Example ###

Here we are going to create an object that can be used to
create a user later. Note that downstream consumers will only
care that this object does one thing: create a user. While it
make have other methods eventually for use in some limited
contexts, creating a user is its primary (and often sole-)
responsibility.

```js
class UserCreator {
  constructor(repository) {
    this.repository = repository; 
  }

  create(name) {
     return this.repository.createUser(name);
  }  
}

const userCreator = new UserCreator(userRepository);
```

At this point, the `userCreator` is just a single-method object.
It is useful for injecting into other objects that may need to 
create a user. But the fact that the `userCreator` is an object
with a single useful method is an implementation detail to which
consumers become coupled.

```js

// Consumer of `userCreator`. Although this could itself be a
// good example of a "UserCreator"-like object (due to `.handle()`).
//
class UserSignupHandler {
  constructor(userCreator) {
    this.userCreator = userCreator;
  }
  
  handle(userName) {
    // UserSignupHandler is aware of ".create" when it really doesn't have to be.
    //
    return this.userCreator.create(userName);
  }
}

const handler = new UserSignupHandler(userCreator);
```

Notably, if we were to change the implementation of UserCreator later to be 
a pure function, we would have to change all consumers of UserCreator when
conceptually it shouldn't be needed. There is still a thing-doer that has
the same input/output.


## Proposed Solution ##

An object instance can have a default method. This would allow an
object to be "invoked" exactly like a function, hiding the implementation
detail from consumers.

Note that there are several ways to define how the default method is
determined, and this proposal is less concerned with this aspect than with
what it looks like to invoke the object. We will demonstrate an option here,
but alternatives are welcome.

```js
// This particular implementataion would use a Symbol.
//

class UserCreator {
  constructor(repository) {
    this.repository = repository; 
  }

  [Symbol.apply](name) {
     return this.repository.createUser(name);
  }  
}

const userCreator = new UserCreator(userRepository);

class UserSignupHandler {
  constructor(userCreator) {
    // NOTE: at the consumer, it almost makes more sense to
    // name these with action verbs, as is done here.
    //
    this.createUser = userCreator;
  }
  
  handle(userName) {
    // UserSignupHandler is no longer coupled to the implementation details it doesn't need.
    //
    return this.createUser(userName);
  }
}

const handler = new UserSignupHandler(userCreator);
```

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20190127/2abd6f5f/attachment-0001.html>


More information about the es-discuss mailing list