Breaking Down Large PHP Problems into Classes: A Class Should Only have a Single Responsibility

UXA Little UX and HCI

I’ve been working with a student doing experiments based on Gestalt psychology and User Experience (UX) and Human Computer Interaction (HCI). She wanted to find out whether the Gestalt concepts of similarity and proximity could be applied to a user interface (UI). So she set up an experiment to see what happened.

In summary, she tested using UIs with similarity/no similarity and proximity/no proximity. The subjects were given a task that would test each condition and their times were compared. After each test, the times were saved in a MySQL database and then retrieved to compare the mean times (average amount of time) that each task took. As predicted, the raw data showed that when the Gestalt principles were observed, the task took less time than when they were not.

While just looking at the data showed a difference, she did not know whether the differences were significant. The most appropriate way to find whether the differences are significant is to conduct a statistical test called a “means test” or “t-test” that compares two means. The t-test would tell us with a precise degree of probability whether the differences were random or not. Use the following buttons to view the outcome and download the files:
PlayDownload

The T-Test

stat

Figure 1: The t-test

Figure 1 shows the statistic used for the t-test. Let’s break down the t-test:

  • t: This is the value generated by the t-test. Using it, we use a statistical look-up table to see to what degree this is significant or not. (Because the sample size is 50, the degrees of freedom is 49, and so the t value must be 3.2651 or greater for the difference to be significant at the .001 level of significance. That means there’s about 1 chance in 1000 that these differences would be random.)
  • x1, x2: The x1 and x2 values are the means. This value is easy to calculate. You just divide the total of the values in the array using the function array_sum($array) by the number of cases–count($array). (In the diagram you see the x’es with a line over them and the numbers are subscripted.)
  • Triangle: The triangle (or delta) is the symbol used for difference in the samples. The null hypothesis is that there will be no differences, and so the value of delta is zero.
  • s1,s2: The s values are standard deviations. These are the amount of differences between the observed value and the mean. In the formula, the s values are squared–s^2.The standard deviation algorithm can be found in your PHP Manual.
  • n1, n2: The n values are the total number of values in the samples. In PHP, those values can easily be determined by the count($array) function.
  • Square root symbol: We need to divide the top part of the formula by the square root of the standard deviations divided by the total number of cases in each sample. Again, that’s easy using the sqrt($calc) function built into PHP.

By breaking down the problem in this manner, you can quickly see that you need only three values and two arrays:

  1. Mean
  2. Standard Deviation
  3. Number of cases

The arrays need to be numeric, and the delta value in this case is zero; so that could be left out of the formula if we wanted. However, just to keep it in mind, it will be represented by the literal 0.

Making Simple

The purpose of this post is to illustrate the OOP principle,

A class should only have a single responsibility.

The principle is both to modularize problem solving and create reusable parts. Most PHP programmers don’t want to “waste time” creating additional code, and OOP programming that modularizes problem solving certainly requires more code than one that does not. However, the more code in a file or class, the more particularized it is and the more difficult it is to reuse. So, rather than “wasting time,” in the long run you save time by having reusable code. Keep in mind that businesses that hired coders encouraged OOP programming because over time, they spend less effort starting all over every time they wanted to change a program or write a new one. Modularized coding allowed many parts to be reused and changes could be made to even the most complex programs.
oneThing

Keeping this in mind, we can modularize the t-test into parts that not only solve the problem at hand and make it simpler to understand but is flexible enough to be reusable.

Figure 2 shows the class diagram for the t-test. As with the Chi Square example a while back, this example also uses the Template Method design pattern. However, it breaks the solution down into more parts.

Figure 2: Class diagram for means test.

Figure 2: Class diagram for means test.

The MeansTest class implements the IMeansTest interface (an abstract class) that includes a templateMethod() function to order the steps in the means test. The steps in the algorithm are cast as abstract functions to be implemented in the child class. First, it gets the means from the Mean class, then, using the mean value, it gets the standard deviation from the StandardDeviation class, and finally the MeansTest object puts them together for the t-test value.

The Abstract Parent and MeansTest Implementation

The IMeansTest abstract class outlines the t-test using a Template Method. The focus is on three steps: 1) finding the mean, 2) finding the standard deviation and 3) computing the t-test.

<?php
abstract class IMeansTest
{
   abstract protected function getMean();
   abstract protected function getSD();
   abstract protected function gettTest();
 
    protected function templateMethod()
    {
        $this->getMean();
        $this->getSD();
        $this->gettTest();
    }
}
?>

The rest of the program is now clearly ordered through the IMeansTest interface. The program gets the mean value, which is used in calculating the standard deviation, which in turn is used to find the t-value.

The implementation of the MeansTest does not mean that it must do all of the work. It must follow the route laid out in the template method, but other classes can help out and later be reused. It calculates the t-value but it gets the mean and standard deviations from other objects.

<?php
class MeansTest extends IMeansTest
{
    private $compare1= array();
    private $compare2= array();
    private $n1;
    private $n2;
    private $mean1;
    private $mean2;
    private $standardDev1;
    private $standardDev2;
    private $tTest;
 
    public function compareMeans(array $data1,array $data2)
    {  
        $this->compare1=$data1;
        $this->compare2=$data2;     
        $this->n1=count($this->compare1);
        $this->n2=count($this->compare2);
        $this->templateMethod();
        return $this->tTest;
    }
 
    protected function getMean()
    {
        $useMean=new Mean();
        $this->mean1=$useMean->meanCalc($this->compare1);
        $this->mean2=$useMean->meanCalc($this->compare2);
    }
 
    protected function getSD()
    {
        $useSD=new StandardDeviation(); 
        $this->standardDev1=$useSD->doSD($this->compare1,$this->mean1);
        $this->standardDev2=$useSD->doSD($this->compare2,$this->mean2);
    }
 
    protected function gettTest()
    {
        $this->tTest=($this->mean1 - $this->mean2-0);
        $sdPow1=pow($this->standardDev1,2)/$this->n1;
        $sdPow2=pow($this->standardDev1,2)/$this->n2;
        $sqSd=sqrt($sdPow1+$sdPow2);
        $this->tTest /=$sqSd;
    }
}
?>

Notice how the methods for getting the mean and standard deviation simply make requests from other objects and pass the returns to private variables.

Two Easy Classes

We could have calculated the mean and standard deviation in the MeansTest class and point out that the class only does one thing—namely, calculate the t-vaule for the means test. However, by breaking down the task of finding the t-test value into two further classes, the calculation of the t-value is clarified and simplified. Furthermore, you can create two classes that can be reused in other calculations.

<?php
class Mean
{
    private $meanNow;
    private $meanArray;
 
    public function meanCalc(array $tocalc)
    {
        $this->meanArray=$tocalc;
 
        $this->meanNow=array_sum($this->meanArray)/count($this->meanArray);
        return $this->meanNow;   
    }
}
?>

The Mean class calculates the mean, and it clearly only does one thing. It is set up so that reuse is simple and it can be reused with any number of other objects that use the value of a mean.

The StandardDeviation class has two methods, one public and one private, and no constructor function. The request is issued by passing two arguments; one for an array and the other, the mean of that array. In this particular case the mean passed from the requesting object is calculated by the Mean class, but could have been calculated in any manner. What is important is that the class is flexible and can be used with just about any application requiring a standard deviation.

<?php
class StandardDeviation
{
    private $stanD;
    private $mean;
    private $data=array();
 
    public function doSD(array $dataNow, $meanNow)
    {
        $this->data=$dataNow;
        $this->mean=$meanNow;
        return $this->sdCalc();
    }
 
    private function sdCalc()
    {
        $deviation=0.0;
        foreach($this->data as $element)
        {
            $deviation += pow($element - $this->mean,2);
        }
        $deviation /= count($this->data);
        return (float) sqrt($deviation);               
    }
}
?>

Again, these two classes are quite simple, and their algorithms could have been part of a larger class. However, we return to the OOP principle that a class should only do one thing. Further, keep in mind that OOP’s focus is on the relationships between classes and not on how much you can cram into a single class.

The Client as an Analogy

In this particular example, the client is really an analogy. The arrays with 50 elements each represent data from a database table. So, in looking at this client (the Client class), just assume that the actual client would most likely be a request that pulls data from a MySQL database, rolls it into a PHP array and sends requests to the MeansTest object. The constructor function simulates passing array data to an encapsulated Client property (private array variable). The actual request is made by the doTtest() method.

<?php
function __autoload($class_name) 
{
    include $class_name . '.php';
}
class Client
{
   private $tTest;
   private $expGroupS;
   private $conGroupS;
   private $expGroupP;
   private $conGroupP;
 
    public function __construct()
    {
        $s1 = array(1.822,8.289,3.93,4.346,8.016,4.263,8.062,8.029,6.665,3.782,1.708,3.17,6.533,5.446,5.223,2.485,5.126,3.838,
4.288,6.626,3.469,7.535,3.214,1.802,3.337,2.82,2.018,1.889,4.34,2.169,3.348,2.469,4.13,1.888,2.882,2.857,3.051,7.888,2.894,4.099,
3.28,5.17,4.03,4.199,6.563,2.781,4.14,3.556,3.874,3.121);
        $s2 = array(8.147,5.279,7.591,6.726,6.279,3.681,5.231,6.11,3.932,5.958,5.118,4.6,7.204,7.992,4.085,1.925,4.663,8.432,5.113,
4.383,5.067,3.052,3.719,2.372,11.702,7.119,5.174,10.382,3.547,6.988,3.181,11.46,2.043,12.678,5.766,8.291,2.314,7.951,3.829,5.049,
10.259,7.799,5.255,5.605,10.781,8.495,5.327,2.901,5.199,7.769);
        $p1 = array(1.979,9.729,2.349,5.975,1.905,2.331,3.464,4.992,4.861,7.679,1.538,1.686,6.722,5.247,2.483,4.939,4.567,3.243,
2.942,4.566,7.465,11.384,5.986,3.54,4.355,1.698,1.371,1.608,4.808,6.421,2.137,1.694,1.41,1.962,5.259,5.532,2.058,3.462,1.482,2.32,
1.876,1.571,2.961,1.932,2.779,2.29,4.618,4.424,5.799,3.256);
        $p2 = array(5.238,6.951,6.459,11.341,9.112,8.362,5.76,8.381,4.127,11.063,7.259,4.926,9.318,5.471,8.913,6.839,4.64,10.233,
8.568,8.765,8.399,14.871,7.7,3.853,10.743,8.11,7.581,9.942,5.893,7.458,5.743,8.175,6.47,4.856,5.955,7.283,5.237,8.763,8.823,9.826,
6.131,3.147,7.447,6.925,6.747,6.914,10.586,6.753,8.76,7.879);
 
        $this->expGroupS=$s1;
        $this->conGroupS=$s2;
 
        $this->expGroupP=$p1;
        $this->conGroupP=$p2;  
    }
 
    public function doTtest()
    {
       $df=count($this->expGroupS)-1;
        $mt=new MeansTest();
        $this->tTest=$mt->compareMeans($this->expGroupS,$this->conGroupS);
        echo "Similarity T-Test value:  " . abs(round($this->tTest,4)) . " ( >3.2651 required for significance at 0.001 level wtih $df df) <br />";
        $this->tTest=$mt->compareMeans($this->expGroupP,$this->conGroupP);
        echo "Proximity T-Test value:  " . abs(round($this->tTest,4)) . " ( >3.2651 required for significance at 0.001 level wtih $df df)<br />";   
    }
}
$worker=new Client();
$worker->doTtest();
?>

Assume that the four arrays are not actually shown in the Client class, and you can see how little it has to do. After all, it is just a requesting client and other than making a request is not an integral part of the Template Method design pattern.

Granularity

Jamming up a class with everything but the kitchen sink is ultimately a clumsy and self-defeating way to program. The extent to which a program is broken down into encapsulated objects speaks of its granularity. Of course this leads to the question of, How much granularity? In general, the granularity unit is that of the method; so we’re not talking about statement or operator level granularity. In the Mean class, a single method handled a simple operation and in the StandardDeviation class, two methods were able to handle the standard deviation calculation. In both cases, though, each class had one and only one responsibility.

The issue of granularity is hotly debated in everything from the number of comments to put in a program to compile time overhead. Another way to think about granularity is to ask, Is this reusable? and Will this be flexible enough for change? Too much granularity and you end up with modules becoming little more than statements, while big lumbering programs (think Jaba the Hut ) are resistant to reuse and change. Be practical with granularity and seek the right balance to get things done in both development and change.

Share

Copyright © 2013 William Sanders. All Rights Reserved.

0 Responses to “Breaking Down Large PHP Problems into Classes: A Class Should Only have a Single Responsibility”


  • No Comments

Leave a Reply