Object Oriented Design Patterns Book Switch
This past week I've been reading Head First Design Patterns as my primary learning resource and refresher for OO design patterns. I've found I don't click well at all with it's teaching style, though. I prefer a teaching method that first details foundation, rules, and theory, then gives brief practical example, preferably also with a following challenge problem for practice.
Head First, however, teaches largely by example, breaking up the foundations over long, drawn out, slowly built examples, sometimes starting with, "done the wrong way," examples. This style is too drawn out for me and too similiar to how online courses generally teach, in which I feel I lose too much foundational knowledge in exchange for time that could be better spent than on fifty page or four hour video examples. Thus, dropping this book for now, and moving to replacement, Dive Into Design Patterns instead.
Object Oriented Design Patterns Notes
Program to Interface, Not Implementation
Programming to implementation makes code rigid and with high cost of change. Responsibilities get mixed, dependancies grow many, and simplicity fades. A base design principle is to program to the interface instead of implementation. While concrete classes set details more in stone, interfaces provide a contract with many implementations. High-level classes no longer need to worry about what low-level classes do. All they care about is the interfaces used to access the logic of the lower-level classes.
The following shows the benefit of this principle, first with some "bad" tightly coupled, non-flexible code, followed by loosely coupled code, programmed to an interface.
//AnimalFeeder.php
//heavy coupling, many depedencies
require_once 'Cat.php';
require_once 'Dog.php';
require_once 'Panda.php';
class AnimalFeeder{
private $dog;
private $cat;
private $panda;
public function __construct(){
//no flexibility in Animal type
$this->dog = new Dog();
$this->cat = new Cat();
$this->panda = new Panda();
}
public function feedAnimals(){
/*non-uniform and complex
interface */
$this->dog->eatKibble();
$this->cat->eatFish();
$this->panda->eatBamboo();
}
}
//test.php
require_once 'AnimalFeeder.php';
$feeder = new AnimalFeeder();
$feeder->feedAnimals();
//Cat.php
class Cat{
public function eatFish(){
echo "Cat is eating fish\n";
}
}
//Dog.php
class Dog{
public function eatKibble(){
echo "Dog is eating kibble\n";
}
}
//Panda.php
class Panda{
public function eatBamboo(){
echo "Panda is eating bamboo\n";
}
}
In the second example, now with cleaner code, note how both the high-level and low-level classes are both dependent on the interface. test.php references Animals and does not care what type of Animal they are. Likewise, all implementations of Animal are dependent on the Animal interface. This allows for high flexibility, extensibility, and results in clean, decoupled code
class AnimalFeeder{
private $animals = [];
public function __construct(){
/*allows any num of animal
to be passed in*/
$args = func_get_args();
foreach($args as $animal){
$this->animals[] = $animal;
}
}
public function feedAnimals(){
/*does not care about
type of animal*/
foreach($this->animals as $animal){
$animal->eat();
}
}
}
//test.php
//Depenencies in endpoint instead of low-level
require_once 'AnimalFeeder.php';
require_once 'Cat.php';
require_once 'Dog.php';
require_once 'Panda.php';
$dog = new Dog();
$panda = new Panda();
$cat = new Cat();
//flexible args 1-n
$feeder = new AnimalFeeder($dog, $panda, $cat);
$feeder->feedAnimals();
//Animal.php
interface Animal{
public function eat();
}
//Cat.php
require_once 'Animal.php';
class Cat implements Animal{
public function eat(){
echo "Cat is eating fish\n";
}
}
//Dog.php
require_once 'Animal.php';
class Dog implements Animal{
public function eat(){
echo "Dog is eating kibble\n";
}
}
//Dog.php
require_once 'Animal.php';
class Panda implements Animal{
public function eat(){
echo "Panda is eating bamboo\n";
}
}