Slide 1

Slide 1 text

PRESENTED BY JEFF CAROUTH @jcarouth Anti-Patterns Found in your Object oriented codebase

Slide 2

Slide 2 text

Today we'll look at several patterns found in object- oriented codebases which may be considered undesirable.

Slide 3

Slide 3 text

anti pattern A common solution to a recurring problem which might seem like an appropriate response but often brings more consequences than benefits.

Slide 4

Slide 4 text

null reference

Slide 5

Slide 5 text

null Word of the day adjective 1. the absence of a known value 2. empty

Slide 6

Slide 6 text

doSomethingMagical(); Pop Quiz 1. What does the following code snippet output? a) An Elephpant b) null c) Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM) d) Fatal error: Call to a member function doSomethingMagical() on null e) All of the above f) None of the above g) I do not know

Slide 7

Slide 7 text

! php null.php Fatal error: Call to a member function doSomethingMagical() on null in /Users/jcarouth/null.php on line 3 Call Stack: 0.0006 227176 1. {main}() /Users/jcarouth/null.php:0

Slide 8

Slide 8 text

Right now you're thinking: but I would never assign null to an variable and try to use it like it's an object.

Slide 9

Slide 9 text

db->prepare( 'SELECT * FROM users WHERE email = :email' ); $statement->execute(['email' => $email]); if ($statement->rowCount() === 0) { return null; } return new User($statement->fetch()); } }

Slide 10

Slide 10 text

rowCount() === 0) { return null; }

Slide 11

Slide 11 text

Why does it matter? $userRepository = new UserRepository(new PDO(/*...*/)); $email = '[email protected]'; $user = $userRepository->findByEmail($email); if (null === $user) { header("HTTP/1.1 404 Not Found"); exit; } //do something magical with the $user object

Slide 12

Slide 12 text

Why does it matter? $userRepository $email $user if } //do something magical with the $user object if (null === $user) { header("HTTP/1.1 404 Not Found"); exit; }

Slide 13

Slide 13 text

Why does it matter? $userRepository $email $user if } //do something magical with the $user object $email = 'doesnotexist'; $user = $userRepository->findByEmail($email);

Slide 14

Slide 14 text

Why does it matter? $userRepository $email $user if } //do something magical with the $user object $email = 'doesnotexist'; $user = null;

Slide 15

Slide 15 text

Semantic Method Names class UserRepository { /** * @return User|null */ public function findByEmail($email) { /*...*/ } }

Slide 16

Slide 16 text

Semantic Method Names class UserRepository { /** * @return User|null */ public function findByEmailOrNullIfNotFound($email) { /*...*/ } }

Slide 17

Slide 17 text

Alternative #1: Throw Exceptions Instead of returning a null you can use built-in exception handling to properly handle the situation for you. Object-oriented languages have exception handling to get away from the imperative style error handling.

Slide 18

Slide 18 text

class ResourceNotFoundException extends \Exception { } class UserRepository { public function findByEmail($email) { $statement = $this->db->prepare( 'SELECT * FROM users WHERE email = :email' ); $statement->execute(['email' => $email]); if ($statement->rowCount() === 0) { throw new ResourceNotFoundException(); } return new User($statement->fetch()); } }

Slide 19

Slide 19 text

class class { } if ($statement->rowCount() === 0) { throw new ResourceNotFoundException(); }

Slide 20

Slide 20 text

How does that change the client code? $userRepository = new UserRepository(new PDO(/*...*/)); $email = '[email protected]'; $user = $userRepository->findByEmail($email); if (null === $user) { header("HTTP/1.1 404 Not Found"); exit; } //do something magical with the $user object

Slide 21

Slide 21 text

How does that change the client code? try { $userRepository = new UserRepository(new PDO(/*...*/)); $email = '[email protected]'; $user = $userRepository->findByEmail($email); $user->doSomethingMagical(); } catch (ResourceNotFoundException $e) { header("HTTP/1.1 404 Not Found"); exit; }

Slide 22

Slide 22 text

Using exceptions allows you to let the caller or other upstream callers handle the situation appropriately.

Slide 23

Slide 23 text

How does that change the client code? try { $userRepository = new UserRepository(new PDO(/*...*/)); $email = '[email protected]'; $user = $userRepository->findByEmail($email); $user->doSomethingMagical(); } catch (ResourceNotFoundException $e) { header("HTTP/1.1 404 Not Found"); exit; }

Slide 24

Slide 24 text

Alternative #2: Return a Null Object The Null Object pattern is sometimes useful when you can use an object which implements neutral behavior in place of another object.

Slide 25

Slide 25 text

Using a Null User to Represent Guests class User { public function isRegistered() { return true; } } class NullUser { public function isRegistered() { return false; } }

Slide 26

Slide 26 text

Using a Null User to Represent Guests class UserRepository { public function findByEmail($email) { $statement = $this->db->prepare( 'SELECT * FROM users WHERE email = :email' ); $statement->execute(['email' => $email]); if ($statement->rowCount() === 0) { return new NullUser(); } return new User($statement->fetch()); } }

Slide 27

Slide 27 text

utility classes

Slide 28

Slide 28 text

There's nothing inherently wrong with utility classes. They just aren't OOP.

Slide 29

Slide 29 text

class StatTracker { public function __construct($trackerFile) { $this->trackerFile = $trackerFile; } public function trackEvent($event, $value) { $data = json_encode( ['event' => $event, 'value' => $value] ); FileUtils::write($this->trackerFile, $data); } }

Slide 30

Slide 30 text

class { [ ) } FileUtils::write($this->trackerFile, $data);

Slide 31

Slide 31 text

class FileUtils { public static function write($file, $content) { $fileHandle = fopen($file, 'a'); fwrite($fileHandle, $content); fclose($fileHandle); } }

Slide 32

Slide 32 text

class FileUtils { public static function write($file, $content) { $fileHandle = fopen($file, 'a'); fwrite($fileHandle, $content); fclose($fileHandle); } } $statTracker = new StatTracker('/path/to/stats.txt'); $statTracker->trackEvent('used a util class', 'jcarouth');

Slide 33

Slide 33 text

What's the problem then? Right now, nothing. Unless you're worried about testability. It's a minor inconvenience but you'll have to have a real filesystem because of the static method call.

Slide 34

Slide 34 text

Alternative: Create an Object to deal with your FileSystem abstraction FileUtility::write() becomes (new FileManager())->write()

Slide 35

Slide 35 text

class FileManager { public function __construct($file) { $this->path = $file; } public function write($content) { fwrite($this->handle, $content); } public function open($mode = 'r') { $this->handle = fopen($this->file, $mode); return $this; } public function close($handle) { fclose($this->handle); } }

Slide 36

Slide 36 text

class StatTracker { public function __construct($trackerFile) { $this->trackerFile = $trackerFile; } public function trackEvent($event, $value) { $data = json_encode( ['event' => $event, 'value' => $value] ); $fileManager = new FileManager($this->trackerFile); $fileManager->open('a')->write($data); } }

Slide 37

Slide 37 text

class FileManager { public function __construct($file) { $this->path = $file; } public function write($content) { fwrite($this->handle, $content); } public function open($mode = 'r') { $this->handle = fopen($this->file, $mode); return $this; } public function close($handle) { fclose($this->handle); } }

Slide 38

Slide 38 text

class TextFile { protected $path; public function __construct($file) { $this->file = $file; } public function write($content) { $file = $this->open('a'); fwrite($file, $content); $this->close($file); } protected function open($mode = 'r') { /** snip **/ } protected function close($handle) { /** snip **/ } }

Slide 39

Slide 39 text

(new FileManager('/path/to/file.txt')) ->open('a') ->write('i know details'); (new TextFile('/path/to/file.txt')) ->write('better');

Slide 40

Slide 40 text

class StatTracker { public function __construct(TextFile $file) { $this->trackerFile = $file; } public function trackEvent($event, $value) { $data = json_encode( ['event' => $event, 'value' => $value] ); $this->trackerFile->write($data); } }

Slide 41

Slide 41 text

class { [ ) } public function __construct(TextFile $file) $this->trackerFile->write($data);

Slide 42

Slide 42 text

class { [ ) } $statTracker = new StatTracker(new TextFile('/path/to/stats.txt')); $statTracker->trackEvent('made a real object', 'jcarouth');

Slide 43

Slide 43 text

Fear of Adding Classes

Slide 44

Slide 44 text

The manifestation of this anti pattern is in classes that have large complexity due to poor abstraction.

Slide 45

Slide 45 text

class DataExport { public function __construct($type, $fileName = "report") { $this->fileName = $fileName; $this->outputType = strtolower($type); switch ($this->outputType) { case 'csv': case 'txt': $this->contentType = 'application/csv'; break; case 'xls': $this->contentType = 'application/vnd.ms-excel'; break; default: throw new Exception('Unsupported export type'); } } }

Slide 46

Slide 46 text

class DataExport { public function outputFromArray($data) { $firstRow = current($data); $columns = array_keys($firstRow); switch ($this->outputType) { case 'csv': case 'txt': //create text body break; case 'xls': //build xls file break; } } }

Slide 47

Slide 47 text

Alternative: Extract objects from the switch statements

Slide 48

Slide 48 text

class DataExport { public function outputFromArray($data) { $firstRow = current($data); $columns = array_keys($firstRow); switch ($this->outputType) { case 'csv': case 'txt': //create text body break; case 'xls': //build xls file break; } } }

Slide 49

Slide 49 text

interface FileExport { public function outputFromArray($data); } class CsvExportFile implements FileExport { public function __construct($name) { $this->name = $name; } public function outputFromArray($data) { //create the text body } } class XlsExportFile implements FileExport {}

Slide 50

Slide 50 text

class FileExportFactory { public static function create($type, $name) { switch (strtolower($type)) { case 'csv': case 'txt': return new CsvExportFile($name); break; case 'xls': return new XlsExportFile($name); break; } } }

Slide 51

Slide 51 text

exposing getters and setters

Slide 52

Slide 52 text

Getters and setters are not always wrong, but often they lead to other problems.

Slide 53

Slide 53 text

class Product { protected $id; protected $price; public function __construct($id, Price $price) { $this->id = $id; $this->price = $price; } public function getPrice() { return $this->price; } public function setPrice(Price $price) { $this->price = $price; } }

Slide 54

Slide 54 text

class Price { public $value; public $currency; public function __construct($value, $currency) { $this->value = $value; $this->currency = $currency; } }

Slide 55

Slide 55 text

How do you reduce the price of a Product? Let's say we want to put a particular object on sale for 20% off.

Slide 56

Slide 56 text

$product = new Product('12345', new Price(9999, 'USD')); $salePrice = new Price( intval(round($product->getPrice()->value * 0.80, 0)), $product->getPrice()->currency ); $product->setPrice($salePrice);

Slide 57

Slide 57 text

Wouldn't it be cool if I could just tell a product to reduce its price by 20 percent? Why can't I?

Slide 58

Slide 58 text

class Product { public function price() { return $this->price; } public function reducePriceByPercentage($salePercent) { $multiplier = 1 - ($salePercent / 100.00); $this->price = new Price( intval( round( $product->getPrice()->value * $multiplier, 0 ) ), $this->price->value ); } }

Slide 59

Slide 59 text

$product = new Product('12345', new Price(9999, 'USD')); $product->reducePriceByPercentage(20);

Slide 60

Slide 60 text

recap

Slide 61

Slide 61 text

null references can be problematic for client code and in an object- oriented world can be mitigated with use of exceptions or null objects

Slide 62

Slide 62 text

Purely utility objects are sometimes damaging to object-oriented codebases and should be extracted to proper abstractions when possible

Slide 63

Slide 63 text

Do not be afraid to think about places where you are adding complexity by avoiding adding classes. Classes are not the enemy of object-oriented code.

Slide 64

Slide 64 text

Exposing internals of objects can leak details and increase surface area of objects. Codifying the behaviors you expect as the interface avoids this situation entirely.

Slide 65

Slide 65 text

Thank You @jcarouth