tags:

views:

42

answers:

2

Here's a design problem that I run into sometimes. Say I have a set of related classes, with some common features and some unique ones;

interface TableCell {
    public function setContent($content);
}
interface HTMLTableCell extends TableCell {
    public function setID($id);
    public function setClass($class);
}
interface CSVTableCell extends TableCell {
    public function setQuoted($quoted);
}

Then I have some classes that need to interact with any object from the set. It's easy to use the common methods. But I want the class to be able to use the unique methods as well, when they're available:

class NumberCell {
    public function render(TableCell $cell) {
        $cell->setContent("123.45");
        // if this is a HTMLTableCell
        $cell->setClass("number");
    }
}

What's the best way to handle this? Checking the type at runtime seems kind of hacky. I'm open to changing the design.

+1  A: 

Since CSV and HTML are both different output formats, it makes sense to have dedicated NumberCells for those formats. In other words HTMLNumberCell and CSVNumberCell:

class HTMLNumberCell {
    public function render(TableCell $cell) {
        $cell->setContent("123.45");
        $cell->setClass("number");
    }
}

or use a Decorator though then you have to find a way to instantiate it somehow before rendering.

Gordon
+1  A: 

An interface is the highest possible level of restriction in OO. That is to say, you cannot have something use an interface for methods it doesn't know about. This is why all of its methods are public. For HTMLTableCell, sedID() and setClass() are essentially tied together. Instead you should have a higher execution method that wraps the calls to these methods (and the corresponding setQuoted() for CSV).

This is an example of the Strategy pattern. Really the answer is that the TableCell interfaces are not encapsulated enough. The methods of the TableCell classes should be totally transparent to the Context (caller, in this case NumberCell).

tandu
Do you mean that I should try to make things like setID and setQuoted part of the generic TableCell interface? The problem I'm having is that some of them really only make sense for a certain sub-interface (e.g. HTML or CSV). So I'm hesitant to clutter up the generic interface with implementation-specific details.
JW
No, that is not what I'm saying. Interfaces should be very sparse. What I am saying is you should have a method such as "setContent()" or what have you. For its implementation in Html class it will call setID() and setClass(), and for the CSV it will call setQuoted(), for example.
tandu