SOLID

Singleton se inventó lara tener una única instancia de un objeto no lara que fuera accesible desde todos los sitios

1. Single Responsibility Principle (SRP)

  • Definition: A class should have one and only one reason to change, meaning that a class should have only one job.

  • Ex: A class that calculates area of a shape also manages authentication, connection to db and rendering. WRONG!

  • Problems of GOD class: dependencies, different logics, not decoupled

  • Solution: Use layers, proper naming, DI

2. Open/Closed Principle (OCP):

  • Definition: Software entities (classes, modules, functions) should be open for extension but closed for modification

  • How to accomplish

    • Avoid depending on specific implementations, making use of abstract classes or interfaces.

  • Example in PHP:

class LoginService
{
    public function login($user)
    {
        if ($user instanceof User) {
            $this->authenticateUser($user);
        } else if ($user instanceOf ThirdPartyUser) {
            $this->authenticateThirdPartyUser($user);
        }
    }
}

class LoginService
{
    public function login(LoginInterface $loginService, UserInterface $user)
    {
        $loginService->authenticateUser($user);
    }
}

interface LoginInterface
{
    public function authenticateUser(UserInterface $user);
}

class UserAuthentication implements LoginInterface
{
    public function authenticateUser(UserInterface $user)
    {
        // TODO: Implement authenticateUser() method.
    }
}

class ThirdPartyUserAuthentication implements LoginInterface
{
    public function authenticateUser(UserInterface $user)
    {
        // TODO: Implement authenticateUser() method.
    }
}

3. Liskov Substitution Principle (LSP):

  • Definition: Objects of a derived class should be able to replace objects of the base class without affecting the correctness of the program.

  • A subtype can either be a class extending another class, or a class implementing an interface.

  • If we have a Tesla is a subtype of a Car, then I should be able to drive either a Car or a Tesla the same way.

  • Classes extensions must use the same prototype (method signatures) as their parents. When implementing an Interface there is a contract in the input but should be a contract on the output to. (Also exceptions)

    • You can widen the types of method arguments in child classes and interface implementations, but not make them more narrow.

    • You can make the return types in child classes and interface implementations more narrow, but not widen them.

    • In subtypes, you should try to avoid throwing exceptions that are not thrown by the parent class or documented on the interface being implemented (where possible).

    • You should consider closing off your constructor method arguments from modification in subtypes by making final

  • Example in PHP:

class Bird {
    public function fly() {
        // Logic for flying
    }
}

class OstrichAvestruz extends Bird {
    public function fly() {
        // Ostrich can't fly, so this method does nothing
			throw \\Exception();
    }
}

function makeBirdFly(Bird $bird) {
    $bird->fly();
}

$ostrich = new OstrichAvestruz();
makeBirdFly($ostrich); // This should not lead to unexpected behavior

https://madewithlove.com/blog/liskov-substitution-principle-explained/#:~:text=Applying the Liskov Substitution Principle,narrow%2C but not widen them.

4. Interface Segregation Principle (ISP)

  • Definition: A client should never be forced to implement an interface that it doesn’t use

  • Many client-specific interfaces are better than one general-purpose interface

  • Solution:

    • Use dependencies

    • Split the class into => interfaces + abstractions

    • Implement abstractions in a way that don’t knoe about other classses’ behavior

interface WorkerInterface {
	public function work(); 
	public function sleep();
}
class Worker implements WorkerInterface { 
	public function work(){} 
	public function sleep(){}
}
class Android implements WorkerInterface {
	public function work(){} 
	public function sleep(){ return null; }
}
class Captain {
	 public function manage (WorkerInterface $worker) {
		$worker->work();
		$worker->sleep();
	}
}

=>

interface WorkableInterface {
	public function work(); 
}
interface SleepableInterface {
	public function sleep();
}
class Worker implements WorkableInterface, SleepableInterface{ 
	public function work(){} 
	public function sleep(){}
}
class Android implements WorkableInterface {
	public function work(){} 
}
class Captain {
	 public function manage (¿?¿?¿? $worker) {
		$worker->work();
		$worker->sleep();
	}
}

=>

interface ManageableInterface {
	public function beManaged(); 
}
interface WorkableInterface {
	public function work(); 
}
interface SleepableInterface {
	public function sleep();
}
class Worker implements WorkableInterface, SleepableInterface, ManageableInterface{ 
	public function work(){} 
	public function sleep(){}
	public function beManaged(){
		$this->work();
		$this->sleep(); 
	}	
}
class Android implements WorkableInterface, ManageableInterface {
	public function work(){} 
	public function beManaged(){
		$this->work();
	}
}
class Captain {
	 public function manage (ManageableInterface $worker) {
		$worker->beManaged();
	}
}

5. Dependency Inversion Principle (DIP)

  • Definition: high-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details, details houls depend on abstractions

  • It encourages the use of interfaces or abstract classes to decouple components.

class PasswordReminder {
    private $dbConnection;

    public function __construct(MySQLConnection $dbConnection) {
        $this->dbConnection = $dbConnection;
    }
}
interface DBConnectionInterface {
    public function connect();
}
class MySQLConnection implements DBConnectionInterface {
    public function connect() {
        return "Database connection";
    }
}

class PasswordReminder {
    private $dbConnection;

    public function __construct(DBConnectionInterface $dbConnection) {
        $this->dbConnection = $dbConnection;
    }
}

High-level modules typically have higher-level responsibilities and are responsible for coordinating or managing various lower-level components or modules.
Low-level modules usually have lower-level responsibilities and handle specific, fine-grained tasks or operations.

PasswordReminder => hig level

MySQLConnection => low level

class Lamp {
    //TRUE = on, FALSE = Off
    protected bool $currentState = FALSE;

    public function turnOn() {
        $this->currentState = TRUE;
    }

    public function turnOff() {
        $this->currentState = FALSE;
    }

    public function getState() {
        return $this->currentState;
    }

    public function getStateString(): string {
        if ($this->currentState) {
            return 'On';
        } else {
            return 'Off';
        }
    }
}

class Button {
    protected Lamp $lamp;

    public function __construct(Lamp $l) {
        $this->lamp = $l;
    }

    public function On() {
        $this->lamp->turnOn();
    }

    public function Off() {
        $this->lamp->turnOff();
    }
}

$l = new Lamp();
$b = new Button($l);
echo $l->getStateString(); //Off

$b->On();
echo $l->getStateString(); //On
interface DeviceInterface {
    public function turnOn();
    public function turnOff();
}

class Lamp implements DeviceInterface {
		// same
}

interface ButtonInterface {
    public function On();
    public function Off();
}

class Button implements ButtonInterface {
    protected DeviceInterface $di;

    public function __construct(DeviceInterface $di) {
        $this->di = $di;
    }

    public function On() {
        $this->di->turnOn();
    }

    public function Off() {
        $this->di->turnOff();
    }
}

$l = new Lamp();
$b = new Button($l);
echo $l->getStateString(); //Off

$b->On();
echo $l->getStateString(); //On

"Abstractions should not depend on details, details should depend on abstractions."

interface DataProvider {
  public function getData(); 
}

class MySQLDataProvider implements DataProvider {
  
  private $db;
  
  public function __construct() {
    $this->db = new MySQLDB();
  }

  public function getData() {
    return $this->db->query("SELECT..."); 
  }
}

Last updated