Refactoring, Separation of Concerns

Everyone has different interests and strengths. While there are some people that claim to excel at "multi-tasking", and some people are jacks-of-all-trades, most people generally do better when focusing on one task at a time, or very narrow set of related tasks. One of the primary tenants of object-oriented programming is to assign purpose to classes, limiting the scope of any one class. Each object performs its role, avoids meddling in the affairs of other objects, and what generally results is a system that is reasonably easy to understand and predict. This approach exemplifies a major theme of computer science: break down a problem into smaller, more easily solved problems.

People and programs are very different things. Computer programs do not "care" about being interrupted in the same sense that humans do. From personal experience I know that when I am working on an important project, the last thing I want is for some unrelated demand to ambush my productivity and momentum. The process of stopping, shifting mental gears, and then returning to the original task is time consuming and mentally taxing.

For a computer program on the other hand, the difference between being interrupted with B while doing A, and completing A and then doing B is typically a trivial matter of microseconds and memory allocation. One complex "object", a traditional procedural program can do most if not all tasks just as well, if not occasionally more efficiently than a well designed system of objects. The difference again comes from the human factor. A well designed system of objects is often easier to develop, maintain, and expand than a single amorphous blob of code.

The right level at which to introduce objects depends heavily on the project configuration (the set of design choices dictated to or by the project). Projects in Java for example are innately completely object oriented, while projects in other languages may not have the capacity for true objects. PHP is a particularly flexible language with growing support for object-oriented features. The size of the project also may necessitate the use of objects to make the code manageable, or might preclude the usefulness of implementing classes. All but the most rudimentary programs can benefit from leveraging object-oriented techniques, but the expertise to make objects helpful rather than a hindrance is important.

In the previous post, the boot strap code included a file that procedurally outlined the first few steps of the process. Before going much further let's revisit that line and instead of including a file, instantiate an object:

//Now for our reguarly scheduled program.
$controller = new TwitteRdf('TwitteRdf_View_Www','_config/sysnews.xml');

To understand what is happening here, first one must understand the autoloading capabilities of the Zend_Loader class (included in code from the previous blog post).

The Zend Framework includes a wide variety of classes that are useful in various types of PHP development. Among these is the Loader class, which is a helper class (AKA utility class, a collection of static methods). One of the methods of this class inserts a process into PHP's method of locating class declarations such that if a class is instantiated that has not been declared, PHP will search for a file and include it automatically to try and secure a declaration for the class before throwing a fatal error. If the class is found a declared, execution can proceed as normal.

This "autoloading" method allows the programmer to instantiate classes without having to manually include files and packages. So long as the classes are saved in a file structure following the conventions used by the autoloader, it will find them automatically. The Zend Framework includes coding conventions, one of which sets forth the naming convention for classes such that the autoloader can find them.

In the case of the class TwitteRdf, the autoloader will expect to find a TwitteRdf.php file directly in the include path(s). It is important to note that while the name of objects should follow some logical grouping by function, they do not reflect class inheritance. As an example, the constructor for TwitteRdf takes the name of a view class, and a file path to a config file. The class name of the view used for web output in this sample code is "TwitteRdf_View_Www".

When the code later attempts to instantiate the class TwitteRdf_View_Www it will start in the include path(s) and look for a file named "TwitteRdf/View/Www.php". The class defined inside that file isn't neccesarily a sub-class of TwitteRdf, rather as one might infer from the name and usage in the constructor for the TwitteRdf class it is a associated with a "View" class or pattern. Other views for the application might logically be stored in the same View folder in different PHP files.

Given what is known so far about the TwitteRdf class, a shell for it might look something like:

/**
 * File documentation
 * @package TwitteRdf
 * @version 0.0.1
 */
 
 /**
  * Class documentation
  */
class TwitteRdf
{

    /**
     * All non-constant properties should be protected or private...
     */
    protected _property = NULL;
   
    /**
     * The PHP constructor method
     */
    public function __construct($viewName, $configFileName)
    {
   
    }
   
    /**
     *
     */
    private function createView($viewName)
    {
   
    }

    /**
     *
     */
    private function loadConfig($configXml)
    {
   
    }
   
    /**
     *
     */
    private function run()
    {
        $currentRdfUrl = '';
        $pendingRdfUrl = '';
        $message = '';
       
        $currentNews = new TwitteRdf_Rdf_Extractor($currentRdfUrl);
       
        $pendingNews = new TwitteRdf_Rdf_Extractor($pendingRdfUrl);
       
        $newsList = array_merge($currentNews->toArray(), $pendingNews->toArray());
       
        $publishedItems = unserialize(file_get_contents('_cache/TwitteRdfCache.pser'));
       
        $potentialTweetItems = array_diff_ukey($newsList,$publishedItems);
       
        return $message;
    }
}

With this new shell in place, some other classes that are needed to make the code run are TwitteRdf_View_Www and TwitteRdf_Rdf_Extractor. In addition a configuration file must be generated and the cache file has to be initialized. The code to this point should technically run since the constructor does not attempt to call the "run" method, but since it makes no attempt to output the result would be a completely blank page with no output.

In order to test the program in any capacity, some output is needed. The sample code has error reporting turned on. While one might assume that if there were any errors they would be output to the browser, it is not a very safe assumption. To generate output while properly following "separation of concerns", the code will need to employ a view class.

The view classes in this application are an implementation of the previously discussed View Pattern. This is the class that will handle the decision as to what should be output, though the Controller Pattern makes the ultimate decision on when to output and even which View class or object to use, and how to use it. The implementation of the Controller Pattern in the sample code is the TwitteRdf class itself.

To be even more specific, the views used by the application will be an adapter for the Zend_View class. The application will only touch on a fraction of the features of Zend_View, and creating an adapter helps make the code more portable should the Zend Framework not fit well into a later project configuration that might want to use code from TwitteRdf.

The sample view adapter in "TwitteRdf/View/Www.php" looks something like this:

<?php
/**
 *
 */
class TwitteRdf_View_Www{
    /**
     * The view object
     */
    protected $_view = NULL;
   
    /**
     * The path inside of _template (if any) and the file name
     * excluding the .php file type suffix of the file to use
     * for the view script. Should not start with a '/'.
     */
    protected $_template = '';
   
    /**
     * The data set to be available to the template via $this->data
     */
    protected $_dataArray = array();
   
    /**
     * Switch used to ensure output only happens once.
     * When this property is true, the render method will
     * output nothing.
     */
    protected $_mute = false;
   
    /**
     * Characters not allowed in the template name.
     */
    protected static $_notAllowed = array(
        '.','\\','+','*','?','[',']','(',')','^','$'
    );
   
    /**
     * Constructor takes an array by reference and an optional
     * template name. If no template name is specified 'www' is
     * used.
     */
    public function __construct(&$data, $template='www'){
        $this->_view = new Zend_View();
        $this->_view->setScriptPath('.');
        $this->_dataArray =& $data;
        // don't allow '.' and other funny characters in $template
        $template = str_replace(self::_notAllowed,'',$template);
        $this->_template = '_template/'.$template.'.php';
    }
   
    /**
     * Ouputs the view to the browser unless it has already been
     * called once. Returns a boolean indicating if output occured.
     */
    public function render(){
        $outputOccured = false;
        if(!$this->_mute){
            $this->_view->data = $this->_dataArray;
            print($this->_view->render($this->_template));
            $outputOccured = true;
        }
        $this->_mute = true;
        return $outputOccured;
    }
}


To use this object there must be an _template directory in place and for this example a www.php file is needed.

<html>
<head>
<title><?php print($this->data['title']); ?></title>
<?php if($this->data['reload']){
    if(is_array($this->data['reloadTime'])){
        $this->data['reloadTime'] = max($this->data['reloadTime']);
    }
?>

<script language='javascript'>
setTimeout( "window.location.reload()", <?php print(max(1,$this->data['reloadTime'])); ?>*60*1000 );
</script>
<?php } ?>

</head>
<body>
<?php print($this->data['message']); ?>
</body>
</html>


The html portions will look familiar if you have looked at the original example. The Zend_View class merges data, presumably from some Model Pattern with a template script of some sort. The adapter above simply alters the behavior of Zend_View by abstracting it with an adapter better tailored to the needs of this application and extended slightly to restrict where the template script may come from.

Now in the TwitteRdf controller's constructor the new adapter can be called and assigned some data:

    public function __construct($viewName,$configXml){
        $dataArray = array(
            'title'=>'Need A Title',
            'reload'=>false,
            'reloadTime'=>60,
            'message'=>'<p>So Far So Good...</p>'
        );
        $this->_viewAdapter = new TwitteRdf_View_Www($dataArray);
        $this->_viewAdapter->render();
    }

While far from finished, the program is starting to take shape. It should be pretty clear by now that one downside to this method of development is it will result in behavioral and declarative code separated out across multiple files. In the next post I'll introduce the Zend_Config class and explore the difference between procedural programming and declarative programming.

Comments [0]

Trackback URL: http://blogs.lib.ncsu.edu/fabulousit/entry/refactoring_separation_of_concerns
Comments:

Post a Comment:

Name:
E-Mail:
URL:

Your Comment:

HTML Syntax: Allowed