Extension methods: Swiss army knives without the anti-pattern

at 2009-08-29 in Experiments5.8-SERIESRFCs by friebe (0 comments)

Swiss Army KnifeThe following calls a method called "sorted" on an instance of the lang.types.ArrayList class. The ArrayList class doesn't have such a method, and we'll thus get a nice "call to undefined method" error shortly before our program is terminated.

<?php 
$array= new ArrayList(3, 1, 2);
$sorted= $array->sorted();
?>

We could add this method to the class, but the next request would be to have filter(), map(), join(), collect(), partition(), and whatever else methods in this class, which would turn it into the "swiss army knife" anti-pattern.

Of course adding functionality can also be accomplished by subclassing or by creating an adapter which
delegates all calls to the ArrayList class but the ones it wants to implement on top.

The subclass


<?php 
class SortableArrayList extends ArrayList {
public
static function from(ArrayList $in) {
$s= new self();
$s->values= $in->values;
$s->size= $in->size;
return
$s;
}
public
function sorted() { ... }
}

$array= new ArrayList(3, 1, 2);
$sorted= SortableArrayList::from($array)->sorted();
?>

The delegate


<?php 
class SortableArrayList extends Object {
public
function __construct(ArrayList $delegate) {
$this->delegate= $delegate;
}
public
function __call($name, $args) {
return call_user_func_array
(array($this->delegate, $name), $args);
}
public
function sorted() { ... }
}

$array= new ArrayList(3, 1, 2);
$sorted= create(new SortableArrayList($array))->sorted();
?>

Following the "one class, one responsibility" we could also refactor the code out into an ArrayHelper class with all kinds of static helper functions or into an ArraySorter, an ArrayFilterer, and so on. We wouldn't really be adding functionality in these cases, but let's have a look at them nevertheless:

The helper class


<?php 
class Arrays extends Object {
public
static function sort(ArrayList $in) { ... }
}

$array= new ArrayList(3, 1, 2);
$sorted= Arrays::sort($array);
?>

The sorter


<?php 
class ArraySorter extends Object {
public
function sort(ArrayList $in) { ... }
}

$array= new ArrayList(3, 1, 2);
$sorted= create(new ArraySorter())->sort($array);
?>

In any of these situations though, we have to write more sourcecode. More sourcecode not only for the extension itself but also when using it.

Fact


Programmers can be safely said to be of the type of person who are lazy (that's why we suffer from the copy and paste syndrome, hate having to feed throws Throwable to the Java compiler to stop it from complaining about checked exceptions and most important of all: otherwise we'd be shifting the bytes ourselves and not letting a programming language do the job, right?).

Random Number code

Writing less code to solve a problem is not only fundamental to a programmer's motivation: It also circumvents possible bugs. In this spirit, we'd like to accomplish the following:

  • add functionality to an existing type without modifying it,
  • import this functionality on demand to prevent bloat,
  • write shortest amount of code when using the extension.


Anatomy


Extension methods let you "add" methods to existing types without having to change the respective type itself. They are declared in a regular class as static methods, albeit being called as if they were instance
methods on objects of the type they extend. Seen from the outside, using extension methods does not differ from calling methods actually defined inside that type.

Declaration


Given this, to implement an extension method "sorted" for the ArrayList class so that it can be used as in the example from the beginning, we declare the following:

<?php 
class ArraySortingExtension extends Object {

static function __static() {
xp::extensions
('lang.types.ArrayList', __CLASS__);
}

public
static function sorted(ArrayList $self) {
// Implementation here
}
}
?>

There are two essential parts in this declaration: First of all, the static initializer, which registers this class as a provider for extension methods for the lang.types.ArrayList class. Second, the extension method
itself, which is declared public static and receives the ArrayList instance to work on as its first parameter.

Using it


Although extension methods are defined static, they are called using instance method syntax. However, extension methods are only available if they're explicitly imported! This can be done by adding the extension
method class to the uses() list:

<?php 
uses
('com.example.extensions.ArraySortingExtension');
?>

Once this is achieved our example will work and the sorted() call will yield an ArrayList containing the sorted values 1, 2, 3.

Restrictions


Because of the way they're implemented, extension methods can only operate on a type's public API. Extension methods are really only syntactic sugar, backed by static method calls to an extension class. Thus, the following two calls are equivalent from a protection level point of view.

<?php 
$sorted= $array->sorted();
$sorted= ArraySortingExtension::sorted($array);
?>

If you look at the second it becomes clear why only public methods can be called!

Furthermore, extension methods can only add functionality. Extension methods cannot overwrite existing methods. Thus, the following extension method will never be executed because the XPClass class already has a getName() method:

<?php 
class XPClassExtensions extends Object {
public
static function getName(XPClass $self) { ... }
}
?>

The same applies for the methods defined in lang.Object itself, every other object inherits these: hashCode(), equals(), getClassName(), getClass() and toString().

Summing it up


Extension methods can be helpful in many situations, helping us get rid of Util and Helper classes as just one example. To start toying with them: They're implemented in the 5_8 branch!

Coming up next: Extension method use-cases. Stay tuned!



Subscribe

You can subscribe to the XP framework's news by using RSS syndication.


Categories

News
General
PHP5
Announcements
RFCs
Further reading
Examples
Editorial
EASC
Experiments
Unittests
Databases
5.8-SERIES

Related

Find related articles by a search for «Extension».