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
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...");
}
}