Organizing Classes

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Organizing Classes

Post by Benjamin »

Well, you're all going to be happy with me. I have decided to fully transition to OO. I have held out as long as I can. I am currently reviewing best practices and putting a lot of thought into how classes should be structured and organized. I have a lot of questions, but for now I'm going to start with this.

I've created a very simple, perhaps too simple example below. It's a calculator that can add and subtract. Adding new classes to divide or multiple is very simple. But what if I want to add the ability for the calculator to accept an arbitrary number of values so that an average can be calculated? Did I build this wrong from the start?

Code: Select all

 
abstract class Calc {
    abstract function calculate();
 
    public function setValues($x, $y) {
        $this->x = $x;
        $this->y = $y;
    }
 
    public function getAnswer() {
        return $this->calculate();
    }
}
 
class Calc_Add extends Calc {
    public function calculate() {
        return $this->x + $this->y;
    }
}
 
class Calc_Subtract extends Calc {
    public function calculate() {
        return $this->x - $this->y;
    }
}
 
 
$calc = new Calc_Add();
$calc->setValues(10, 10);
echo $calc->getAnswer();
 
User avatar
novice4eva
Forum Contributor
Posts: 327
Joined: Thu Mar 29, 2007 3:48 am
Location: Nepal

Re: Organizing Classes

Post by novice4eva »

Using func_num_args( ) and func_get_args( ) would make that possible, but then you would have to move your values to an array.
Since this is in theory and design, this might not be the type of solution you are looking for!!
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Re: Organizing Classes

Post by Benjamin »

novice4eva wrote:Using func_num_args( ) and func_get_args( ) would make that possible, but then you would have to move your values to an array.
Since this is in theory and design, this might not be the type of solution you are looking for!!
Right, I'm actually looking for more abstract solutions. Thank you.
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Re: Organizing Classes

Post by alex.barylski »

Congrats on making the switch. :)

For a trivial calculator, you have applied polymorphism and solved the problem elgantly. The bigger problem is with your latter question, on how to allow arbitrary values.

You could create a child class and override it's calculate() and setValues() to allow arbitrary values but IMHO you would be better off solving the problem implementing an expression evaluator, although in PHP you could just pass the equation to eval() for pedagogical reasons, that probably isn't what you after.

The problem with solving an expression like you are doing, using nothing but objects, is that the interface (and interaction) does not really allow for complex expression evaluation, as you are experiencing.

Complex math equations are best described using a DSL (domain specific language) in the form of infix, not chaining or piping objets togather.

If the excersize is for the developer only and no user would ever need to enter equations, than you could probably get away with implementing a calculator using objects. I'm not sure how best to implement an arbitrary number evaluator but it seems deriving from a child class and overriding the calculate() and setValues() would do the basic trick.

Code: Select all

class Average extend BaseCalculator{
  function setValues($arr)
  {
     
   }
}
You could maybe use the objects to emulate an infix equation like:

Code: Select all

$res = new Div(10, new Sub(5, new Add(1, 2)));
This would essentially be the same as:

Code: Select all

10 / (5 - (1 + 2))
Each object would need to check whether an argument was a constant or evaluation result. If the object model supported operator overloading you could emulate infix almost indentically, but you essentially would be re-inventing the wheel.

As you have experienced, expressing an mathematical equation using a programming interface is quite challenging (whether you use objects or just fucntions) as an alternative, you could implement a calculator using the reverse polish notation.

http://en.wikipedia.org/wiki/Reverse_Polish_notation

It 'was' (and maybe still is) a commonly used technique to easily implement calculators, as it's far better suited for a push/pop stack based calculator and less CPU/memory intensive as there is no parsing of the expression required (ie: infix).

Calculators are always bad examples of the flexing power of OO but then again, they make a whole lot more sense than duck and car analogies.

Truth be told, if you follow OOD principles/practices:

http://ootips.org/ood-principles.html

Inheritence will rarely be used and it is only a small part of the overall OO paradigm. As a developer just coming onto OOP you will go through 3 phases:

1. Begin wrapping everything up in classes as a form of functions on steriods (ie: namespace emulation). The GD API is a good example as each function requires a global resource handle, which is an excellent candidate for a private member variable.

2. Start applying inheritence to solve every problem (try and avoid this). The difference between private, public and protected will click and you will develop codebases with deep, fragile hierarchies.

3. Frustrated by problems you begin to learn and truly appreciate the OOD principles and applying them to assist in solving problems. Inter-object dependencies are minimized or removed outright, dependency injection is favoured over static composition, etc.

You will begin to see problems as a series of objects, not algorithms, you are more concerned with interface and interaction, and not so much the implementation.

Cheers,
Alex
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Organizing Classes

Post by Christopher »

I think it is a good start. But the first thing you have to be able to see is that your use case is procedural:

Code: Select all

$calc = new Calc_Add();
$calc->setValues(10, 10);
echo $calc->getAnswer();
 
 
A calculator might not be a good simple example, because a real calculator might break down into a different set of classes. I'd need to think about that more. But in your simple case, I think a Calculator is the same as a Number. Here is an example of what I am talking about in OO. Start with this:

Code: Select all

class Calculator {
    protected $x;
}
That's the place to start because objects are data with methods that act on that data.

Now add methods to act on the data:

Code: Select all

class Calculator {
    protected $x; 
    public function __construct($value=0) {
        return $this->x = $value;
    }
 
    public function add($value) {
        $this->x += $value;
    }
 
    public function subtract($value) {
        $this->x -= $value;
    }
 
    public function equals() {
        return $this->x;
    }
}

Code: Select all

$calc = new Calculator(10);
$calc->add(10);
$calc->subtract(5);
echo $calc->equals();
It may seem a small difference, but notice that the object is really data in this example. Here it is initialized a reasonable default (or by your value) when created. So it is always useful. That's important and different from procedural where you do things step-by-step and things aren't useful until all the steps are done. And with an object, you ask it to do things to itself and it does them. It is self contained.

PS - I know you are playing around, but abstract is not necessary that often. It's really for providing a partial implementation and an interface to child classes. This is usually because you want to create variations -- all with the base functionality/interface. But abstract is really for later. ;)
(#10850)
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: Organizing Classes

Post by josh »

The OO way of doing things can seem to go against the procedural way of doing things, just like the examples that were just posted a real OO system seems like its comprised entirely of extraneous indirection. Thing is the cost comes with a benefit, instead of setting some values and hitting a "go" button", your program becomes more cohesive, you're writing components that can be used together to perform complex tasks, analogous to piping output from a GNU linux command into another command
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Re: Organizing Classes

Post by Benjamin »

Ok, lots of good stuff here. Thank you for the detailed responses. arborint, whether intentional or not, I believe you actually hit the nail on the head.

So there's two things here:

1. My original question wasn't answered, but that's ok because I wasn't clear. The question is this. When you have multiple classes extending an abstract class, is it common for these classes to have methods that are unique? For example with the code I originally posted an average class would have had to contain a function allowing for multiple values to be loaded into the calculator, while none of the other classes would contain this method.

2. The code in my first post was a simple example, but it had critical flaws. I have modified it and improved it. Each number used by the calculator is now an instance of the number class. This allows for the calculator to add any combination of numbers in any base. For example you could add a hexadecimal number to a binary number and get the answer as a base 10 number. Average should not be an extension of the calculator class, because it's actually a user of the calculator.

Code: Select all

 
class calculator {
    private $_n;
 
    public function __construct(number $n) {
        $this->_n = $n;
    }
 
    public function add(number $n) {
        $this->_n->setBase10($this->_n->getBase10() + $n->getBase10());
    }
 
    public function subtract(number $n) {
        $this->_n->setBase10($this->_n->getBase10() - $n->getBase10());
    }
 
    public function divide(number $n) {
        $this->_n->setBase10($this->_n->getBase10() / $n->getBase10());
    }
 
    public function multiply(number $n) {
        $this->_n->setBase10($this->_n->getBase10() * $n->getBase10());
    }
 
    public function equals() {
        return $this->_n;
    }
}
 
class number {
    private $_n;
 
    public function __construct($n = null, $baseFrom = 10) {
        $this->_n = ($n !== null) ? $this->_n = base_convert($n, $baseFrom, 10) : 0;
    }
 
    public function setBase10($n) {
        $this->_n = $n;
    }
 
    public function setBase16($n) {
        $this->_n = base_convert($n, 16, 10);
    }
 
    public function setBase8($n) {
        $this->_n = base_convert($n, 8, 10);
    }
 
    public function setBase2($n) {
        $this->_n = base_convert($n, 2, 10);
    }
 
    public function getBase10() {
        return $this->_n;
    }
 
    public function getBase16() {
        return base_convert($this->_n, 10, 16);
    }
 
    public function getBase8() {
        return base_convert($this->_n, 10, 8);
    }
 
    public function getBase2() {
        return base_convert($this->_n, 10, 2);
    }
}
 
class average {
    private $_numbers = array();
 
    public function addValue(number $number) {
        $this->_numbers[] = $number;
    }
 
    public function calculate() {
        $calculator = new calculator(new number());
 
        foreach ($this->_numbers as $number) {
            $calculator->add($number);
        }
 
        $calculator->divide(new number(count($this->_numbers)));
 
        return $calculator->equals();
    }
}
 
Here are some sample use cases:

Calculate an Average

Code: Select all

 
$average = new average();
$numbers = range(1, 500);
 
foreach ($numbers as $n) {
    $average->addValue(new number($n));
}
 
$answer = $average->calculate();
 
echo "The average of all numbers from 1 to 500 is {$answer->getBase10()}\n";
 
Calculate an Average using numbers with different bases

Code: Select all

 
$numbers   = array();
$numbers[] = new number(32, 16);      // 32 is base16 for 50
$numbers[] = new number('110010', 2); // 110010 is base2 for 50
$numbers[] = new number(62, 8);       // 62 is base8 for 50
 
$average = new average();
 
foreach ($numbers as $n) {
    $average->addValue($n);
}
 
$answer = $average->calculate();
echo "The average of 32(16), 110010(2), and 62(8) is {$answer->getBase10()}\n";
 
Add numbers with different bases

Code: Select all

 
$numbers   = array();
$numbers[] = new number(32, 16);      // 32 is base16 for 50
$numbers[] = new number('110010', 2); // 110010 is base2 for 50
$numbers[] = new number(62, 8);       // 62 is base8 for 50
 
$calculator = new calculator(new number());
 
foreach ($numbers as $n) {
    $calculator->add($n);
}
 
$result = $calculator->equals();
 
echo "The total of 32(16) + 110010(2) + 62(8) is {$result->getBase10()}\n";
 

Resulting Output

Code: Select all

 
The average of all numbers from 1 to 500 is 250.5
The average of 32(16), 110010(2), and 62(8) is 50
The total of 32(16) + 110010(2) + 62(8) is 150
 
So I think this is a lot better, but it still doesn't seem completely right, especially the average class. What are the issues with using new number() in the constructor of the calculator class rather than forcing the user to pass along an instance? Does it matter either way?
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: Organizing Classes

Post by josh »

astions wrote: 1. My original question wasn't answered, but that's ok because I wasn't clear. The question is this. When you have multiple classes extending an abstract class, is it common for these classes to have methods that are unique?
Yes in practice it is common, but a recurring design goal should be to refactor things to as common an interface as practically possible
astions wrote:For example with the code I originally posted an average class would have had to contain a function allowing for multiple values to be loaded into the calculator, while none of the other classes would contain this method.
Well in this case the akwardness was the class telling you it wanted to be designed differently, like the infix notation arborint and pcspectra alluded to
astions wrote: 2. The code in my first post was a simple example, but it had critical flaws. I have modified it and improved it. Each number used by the calculator is now an instance of the number class. This allows for the calculator to add any combination of numbers in any base. For example you could add a hexadecimal number to a binary number and get the answer as a base 10 number. Average should not be an extension of the calculator class, because it's actually a user of the calculator.
that's the beaty of the OO design, the calculator class if implemented should be as dumb as possible. Actual values of the same type ( or more accurately phenomena ) should be plug + play with eachother. Adding inches to centimeters should be possible without the programmer worrying about converting. If the programmer tryed adding weight to inches it should throw an exception

Code: Select all

  What are the issues with using new number() in the constructor of the calculator class rather than forcing the user to pass along an instance?  Does it matter either way?[/quote]
If you add more types you have to update the class. Read Martin Fowlers article on dependency injection
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Organizing Classes

Post by Christopher »

Looks like you are heading in the right direction ... and you have some interesting code happening. One change I would make is a naming one, but it would show thinking in an OO fashion. Remember that the object is usually the thing, not the operation done on the thing. So I would change your names to this:

Code: Select all

class NumberSet {
    private $_numbers = array();
 
    public function addNumber(number $number) {
        $this->_numbers[] = $number;
    }
 
    public function average() {
        $calculator = new calculator(new number());
 
        foreach ($this->_numbers as $number) {
            $calculator->add($number);
        }
 
        $calculator->divide(new number(count($this->_numbers)));
 
        return $calculator->equals();
    }
}
Now you could provide mean, median, mode, max, min, etc. methods...
(#10850)
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Re: Organizing Classes

Post by Benjamin »

josh wrote:Read Martin Fowlers article on dependency injection
I read about half of it, but I'm really zeroing in on individual components like a laser right now and learning each piece one at a time. Can you summarize that article in how it relates specifically to the calculator class requiring each number to be an instance of a number?
arborint wrote:Remember that the object is usually the thing, not the operation done on the thing.
That's perfect. If I play with this for another few hours I'll have one hell of a powerful calculator. I was telling someone I made a calculator that can add different bases and they were like, "Are you sure it works? That's very difficult!". So that's pretty neat.

More to follow...
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: Organizing Classes

Post by josh »

astions wrote:I read about half of it, but I'm really zeroing in on individual components like a laser right now and learning each piece one at a time. Can you summarize that article in how it relates specifically to the calculator class requiring each number to be an instance of a number?
Unfortunately that article doesn't even begin to get into programming to interfaces and composition, for that I'd recommend reading the gang of four book, when you're done thinking outside the laser :D :D
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Re: Organizing Classes

Post by Benjamin »

??
astions wrote:What are the issues with using new number() in the constructor of the calculator class rather than forcing the user to pass along an instance? Does it matter either way?
josh wrote:If you add more types you have to update the class. Read Martin Fowlers article on dependency injection
astions wrote:Can you summarize that article in how it relates specifically to the calculator class requiring each number to be an instance of a number?
josh wrote:Unfortunately that article doesn't even begin to get into programming to interfaces and composition, for that I'd recommend reading the gang of four book
I thought this was a pretty simple question.
User avatar
Eran
DevNet Master
Posts: 3549
Joined: Fri Jan 18, 2008 12:36 am
Location: Israel, ME

Re: Organizing Classes

Post by Eran »

I don't think updating the class is such a terrible occurrence. I relish refactoring, and personally think dependency injection for this calculator is over-design. No need to pour all design tools at every problem. What I'm saying, is first encounter the need for a new type and not plan for every possibility.
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Re: Organizing Classes

Post by alex.barylski »

I was telling someone I made a calculator that can add different bases and they were like, "Are you sure it works? That's very difficult!". So that's pretty neat.
Try and implement one that supports infix and you'll see what they mean. :)
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Re: Organizing Classes

Post by Benjamin »

Ok, let's use that as an example then. Let's say this is in production and the client comes along and requires infix to be integrated. I don't really understand what it is, but I read the "short" wikipedia page on it.

The calculator would have to parse something like this?

Code: Select all

 
5 * (10 / 3) + (6 - ((10 + 69) / 2) * 10)
 
It seems that the infix parser would need to be a distinct class that uses the number and calculator class. Is that right?
Post Reply