Tuesday, September 15, 2009

SOLID part 6 (bonus): how much solid Standard Php Library is?

This is a bonus part for the SOLID principles series. You can check it out to view the articles which explain all the principles or subscribe to the feed to be informed of new posts.

How that we have talked a lot about the five SOLID principles, it's time to apply them to a real project and see the good design ideas they introduce and what trade-offs are accepted in everyday coding. Since I have mainly a php audience, I have chosen the unique object-oriented library that the php core features natively: the Standard Php Library (SPL).

The Iterator interface and its implementations were the first Spl components to be included in php. This interface is very cohesive and it does not assume much about the source where items are coming from: for instance, in every implementation there's no need to write a method that counts the elements, since it is segregated in a Countable standard interface.
Iterator is extended by RecursiveIterator and OuterIterator, which manages respectively an iterator which items have children and a iterator who decorates another. These components adds only one or two methods to the parent interface and are also very cohesive: they are the hook for opening Spl to extensions.
The responsibilities of these classes are evenly distributed: the basic Iterator implementations have proven to be very useful (such as DirectoryIterator which produces the list of files contained in a folder), while other functionalities are kept in their own OuterIterator implementation, like LimitIterator and CachingIterator.
The primary use of Iterator (and of its parent interface Traversable) is in the foreach construct, where it can be passed as it were an array. The standard implementations can be swapped without problems in a foreach, and this means that Liskov Substitution Principle is more or less respected. What comes to mind is that different Iterators will return a different type of items, but php is a dynamic language and this precisation does not strictly affect the application of principle as long as the methods retain the same meaning and intent. To solve it completely the interface should use generics like in Java: Iterator.

Spl provides object-oriented way to access filesystem (SplFileInfo and similar ones), which is not a point of interest in this discussion since these classes are a wrapper to the basic functions such as opendir() and filesize(). The same goes for Exception and its tree of subclasses. The Serializable interface is also a good hook for extension of behavior, but we are not going to overanalyze the library.

What indeed raise attention is the SplObserver and SplSubject interfaces, which suggest a standard way to implement the observer pattern. These components are not useful in my opinion, and a comparison with static typed languages will show the difference.
In a static language like Java, when a parameter is passed to a method which have a determinate signature, only the contract defined by this signature could be used in the body of the method. This is the definition of SplObserver:
public function update(SplSubject $subject);
where SplSubject contains the methods attach(), detach() and notify(). Since the contract is defined from SplSubject interface, if php were a static language we could only call these three methods, which are totally unuseful to get the state of the subject (which can be thinked as some getXXX() methods to call on the subject); only the fact that php is a dynamic language, and does not complain if you call methods which can be unexistent,allows such a method call. Moreover, these interfaces force the developer to adopt a pull-style of Observer pattern even where a push-style would be simpler.
In sum, a design pattern, like the Observer one, is a common solution to a problem and should be implemented every time with different flavours, requiring different classes. SplObserver and SplSubject fail to satisfy the Dependency Inversion Principle since they won't decouple their implementors, that needs to know a large abstraction they leave out.

With the release of php 5.3, it has become clear that the goal of Spl is to provide fast components, implemented in C for particular purposes, and not a well designed object-oriented library. SplQueue is an example of this, along with its companion SplStack:
class SplQueue extends SplDoublyLinkedList implements Iterator, ArrayAccess, Countable { ...
SplDoublyLinkedList is a classic data structure, a general purpose list that is subclassed by SplQueue and SplStack. Since it implements Iterator and other Spl interfaces, their children inherits these features, but in my opinion it's not a good idea.
A queue can be accessed only one element at time; a stack has the same limitations, which are voluntarily imposed to incapsulate the internal data structure and limit the operations that can be done on it by a client class: I would rather have a SplQueue as an interface and not a concrete implementation, with just the enqueue() and dequeue() methods; SplStack as another interface with pop() and push(). SplDoublyLinkedList would be a generic implementation, which satisfies both interfaces.
Why I would prefer this design? Because it places functionalities in two compact interfaces; otherwise Interface Segregation Principle is not applied and no interfaces are even defined. SplQueue can't be a child of a SplDoublyLinkedList as it would be a violation of Liskov Substition Principle: a implementation of a queue can use a double linked list internally, but not inherit methods from it or it is not a queue anymore.
A benefit of a Queue interface would be real decoupling and its consequences: for instance, a mock of Queue can be used in testing to make sure only enqueue() and dequeue() are used, while mocking a SplQueue as it is will expose also all the other Iterator methods as empty ones, leading to strange results (and tying my high level modules to details instead of to a queue abstraction, infringing Dependency Inversion Principle).

I would say that Spl is a good product for the goals which it had been thinked for: a reliable and fast object-oriented layer, which can overload common constructs like count() and foreach; it has been a very successful feature of php. But its design could be vastly improved, also with application of SOLID principles.

The image at the top is an upside down library.
Did you like this series? Leave a comment or subscribe to the feed to stay in touch.

No comments:

Post a Comment