SOLID
Design Principles introduced by Robert C. Martin (uncle Bob)
Nota inclusion de boost using CLion
Esto funciona ojo con <nombre del proyecto> debe aparecer en dos partes
en este caso liskov_substitution
cmake_minimum_required(VERSION 3.9)
project(liskov_substitution)
find_package(Boost 1.66.0 COMPONENTS system filesystem REQUIRED)
include_directories(${Boost_INCLUDE_DIRS})
set(CMAKE_CXX_STANDARD 11)
add_executable(liskov_substitution main.cpp)
target_link_libraries(liskov_substitution ${Boost_LIBRARIES})
Single Responsibility Principle
Class should have just one Responsibility.
Pastelero a tus pasteles.
boost::lexical_cast
En el ejemplo este weón usa boost::lexical_cast
CLion dice: Use std::to_string() instead of
boost::lexical_cast<std::string> .
https://theboostcpplibraries.com/boost.lexical_cast
Boost.LexicalCast provides a cast operator, boost::lexical_cast , that can
convert numbers from strings to numeric types like int or double and vice versa.
boost::lexical_cast is an alternative to functions like std::stoi() ,
std::stod() , and std::to_string() , which were added to the standard library
in C++11.
Journal es una clase en si, no tiene que preocuparse de guardar archivos y cosas relativas, eso lo hace
PersistenceManager
int main()
{
Journal journal{"Dear Diary"};
journal.print();
journal.add("I ate a bug");
journal.add("I cried today");
//journal.save("diary.txt"); NO!
PersistenceManager pm;
pm.save(journal, "diary.txt");
return 0;
}
Open-Closed Principle
Open for extensions but closed for modifications.
struct ProductFilter {
// filtering by color
vector<Product *> by_color(vector<Product *> items, Color color) {
...
}
// filtering by size
vector<Product *> by_size(vector<Product *> items, Size size) {
...
}
// filtering by size and color
vector<Product *> by_size_and_color(vector<Product *> items, Size size,
Color color) {
...
}
};
El weón usa el Specification Patterns que es un Enterprise pattern.
Liskov substitution principle
Subtypes should be immediately substitutable for their base types.
https://www.tomdalling.com/blog/software-design/solid-class-design-the-liskov-substitution-principle/
Ejemplo esto asume que todas las aves vuelan
class Bird {
public:
virtual void setLocation(double longitude, double latitude) = 0;
virtual void setAltitude(double altitude) = 0;
virtual void draw() = 0;
};
No onstante los pinguinos no vuelan
void Penguin::setAltitude(double altitude)
{
//altitude can't be set because penguins can't fly
//this function does nothing
}
If an override method does nothing or just throws an exception, then you’re probably violating the LSP.
El drama es que hay que andar buscando que clases violan esta especificacion y hacer arreglos parches.
La forma correcta es:
//Solution 3: Proper inheritance
class Bird {
public:
virtual void draw() = 0;
virtual void setLocation(double longitude, double latitude) = 0;
};
class FlightfulBird : public Bird {
public:
virtual void setAltitude(double altitude) = 0;
};
Interface Segregation Principle
Not create interfaces that are too large.
Example a Scanner does not FAX.
/* Too general if given as an API for example*/
struct IMachine {
virtual void print(Document &doc) = 0;
virtual void scan(Document &doc) = 0;
virtual void fax(Document &doc) = 0;
};
struct MFP: IMachine {
void print(Document &doc) override {}
void scan(Document &doc) override {}
void fax(Document &doc) override {}
};
struct Scanner:IMachine {
void print(Document &doc) override {}
void scan(Document &doc) override {}
void fax(Document &doc) override {}
};
Instead smaller interfaces
struct IPrinter {
virtual void print(Document &doc) = 0;
};
struct IScanner {
virtual void scan(Document &doc) = 0;
};
struct IFax {
virtual void fax(Document &doc) = 0;
};
// Si quiero algo mas general simplemente
struct IMachine: IPrinter, IScanner {};
struct Machine: IMachine {
void print(Document &doc) override {}
void scan(Document &doc) override {}
};
Dependency Inversion
It’s not about INJECTION, it’s about forming dependencies between objects.
1. High-level modules should not depend on low-level modules.
Both should depend on abstractions.
2. Abstractions should not depend on details.
Details should depend on abstractions.
*Abstractions are Interfaces or Base Classes.
struct RelationShip { // low-level module (or low-level construct)
vector<tuple<Person, Relationship, Person>> relations;
void add_parent_and_child(const Person &parent,const Person &child) {
relations.push_back({parent, Relationship ::parent, child});
relations.push_back({child, Relationship ::child, parent});
}
};
...
...
struct Research { // high-level
// aca esta el problema
// here a high-level is depending on a low-level
Research(RelationShipS &relationships) {
auto &relations = relationships.relations;
// C++17
for (auto &&[first, rel, second] : relations) {
if (first.name == "John" && rel == Relationship::parent) {
cout << "John has a child called " << second.name << endl;
}
}
}
*/
};
Aca arriba Research (which is high-level) depends on RelationShipS (which is low-level). Cambios
en RelationShipS pueden estopearlo todo.
Solucion, incorporar esta abstraccion:
struct RelationshipBrowser {
virtual vector<Person> find_all_children_of(const string &name) = 0;
};
y ahora
struct RelationShipS: RelationshipBrowser {
vector<Person> find_all_children_of(const string &name) {
vector<Person> result;
for(auto &&[first, rel, second]: relations) {
if (first.name == "John" && rel == Relationship::parent) {
result.push_back(second);
}
}
return result;
}
}
y asi Research (que es high-level) deja de depender de RelationShip
struct Research { // high-level
// arreglo, ahora solo depende de RelationshipBrowser
Research(RelationshipBrowser &browser) {
for (auto &child: browser.find_all_children_of("John")) {
cout << "John has a child called " << child.name << endl;
}
}
};