at 2009-08-29
in Experiments, 5.8-SERIES, RFCs
by friebe
(0 comments)
The 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?).
 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) { } } ?> 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.
CategoriesNews General PHP5 Announcements RFCs Further reading Examples Editorial EASC Experiments Unittests Databases 5.8-SERIES
RelatedFind related articles by a search for «Extension».
|