Este documento presenta varios principios clave para el diseño de sistemas de software. Explica que es importante separar el proceso de construcción del sistema de su uso posterior mediante técnicas como el patrón de fábrica abstracta e inyección de dependencias. También enfatiza la importancia de un desarrollo incremental y la necesidad de refactorizar y expandir el sistema de forma iterativa a medida que se implementan nuevas funcionalidades. Otro concepto clave es el de "test driven architecture", donde la arquitectura del sistema puede guiarse por pruebas al
2. Organización de una
clase
• Public static constants
• Private static constants
• Private instance variables
• Public functions
• Private utilities called by a public function right after
3. Clases pequeñas
• La primera regla es que las clases deben ser
pequeñas.
• La segunda regla es que deben ser más pequeñas
que eso.
4. Nombres de las clases
• Describe la responsabilidad que cumple.
• Si no podemos deducir un nombre concreto, es
demasiado grande.
• Cuanto más ambiguo es el nombre, más probable
es que tenga varias responsabilidades (ej.
Processor o Manafer
5. SRP: Single
Responsibility Principle
• Un método/función/clase/módulo/microservicio
solo cambia por un, y solo un, motivo (no significa
“tiene una sola responsabilidad”).
• Agrupa lo que cambia por el mismo motivo.
• Uno de los conceptos más importantes en el
diseño OO.
6. SRP: Single
Responsibility Principle
• Objetivo principal en el manejo de la complejidad:
• Organizarla para que sepamos dónde buscar
para encontrar las cosas.
• Necesidad de comprender solo la complejidad
directamente afectada en un momento dado.
• Muchas clases pequeñas, no pocas muy grandes.
7. Cohesión
"Pon junto lo que cambia junto”, "pon junto lo que
cambia con la misma frecuencia".
8. Cohesión
• Grado de utilización de las variables de instancia
por parte de los métodos.
• Cohesión alta == métodos y variables de la clase
son dependientes y actúan como una unidad
lógica.
• Mantener la cohesión conlleva tener muchas clases
pequeñas.
• Separamos las clases cuando pierden cohesión.
9. Aislar los cambios
• El cambio es algo constante.
• Cada cambio supone el riesgo de que el resto del
sistema deje de funcionar.
• Organizamos las clases para reducir el riesgo de
cambiar cosas.
• Modificar una clase introduce un riesgo.
10. OCP: Open/Closed
principle
• Las clases deben estar abiertas a extensión y
cerradas a modificación.
• No cambies la implementación, permite que se
extienda.
11. OCP: Open/Closed
principle
The Shape Abstraction
Consider the following example. We have an application that must be able to draw circles
and squares on a standard GUI. The circles and squares must be drawn in a particular
order. A list of the circles and squares will be created in the appropriate order and the pro-
gram must walk the list in that order and draw each circle or square.
In C, using procedural techniques that do not conform to the open-closed principle,
we might solve this problem as shown in Listing 1. Here we see a set of data structures
that have the same first element, but are different beyond that. The first element of each is
a type code that identifies the data structure as either a circle or a square. The function
DrawAllShapes walks an array of pointers to these data structures, examining the type
code and then calling the appropriate function (either DrawCircle or DrawSquare).
Listing 1
Procedural Solution to the Square/Circle Problem
enum ShapeType {circle, square};
struct Shape
{
ShapeType itsType;
};
struct Circle
{
ShapeType itsType;
double itsRadius;
Point itsCenter;
};
The Shape Abstraction
The function DrawAllShapes does not conform to the open-closed principl
because it cannot be closed against new kinds of shapes. If I wanted to extend this functio
struct Square
{
ShapeType itsType;
double itsSide;
Point itsTopLeft;
};
//
// These functions are implemented elsewhere
//
void DrawSquare(struct Square*)
void DrawCircle(struct Circle*);
typedef struct Shape *ShapePointer;
void DrawAllShapes(ShapePointer list[], int n)
{
int i;
for (i=0; i<n; i++)
{
struct Shape* s = list[i];
switch (s->itsType)
{
case square:
DrawSquare((struct Square*)s);
break;
case circle:
DrawCircle((struct Circle*)s);
break;
}
}
}
Listing 1 (Continued)
Procedural Solution to the Square/Circle Problem
12. OCP: Open/Closed
principle
Thus the problem of finding and understanding all the places where the new shape needs
to be added can be non-trivial.
Listing 2 shows the code for a solution to the square/circle problem that conforms to
the open-closed principle. In this case an abstract Shape class is created. This abstract
class has a single pure-virtual function called Draw. Both Circle and Square are
derivatives of the Shape class.
Note that if we want to extend the behavior of the DrawAllShapes function in
Listing 2 to draw a new kind of shape, all we need do is add a new derivative of the
Listing 2
OOD solution to Square/Circle problem.
class Shape
{
public:
virtual void Draw() const = 0;
};
class Square : public Shape
{
public:
virtual void Draw() const;
};
class Circle : public Shape
{
public:
virtual void Draw() const;
};
void DrawAllShapes(Set<Shape*>& list)
{
for (Iterator<Shape*>i(list); i; i++)
(*i)->Draw();
}
14. Aislarse del cambio
• Necesidades cambian -> código cambia
• Si una clase depende de detalles concretos, está
en riesgo si esos detalles cambian.
• Si tengo bajo acoplamiento, los elementos de
nuestro sistema están mejor aislados unos de otros
y por tanto, del cambio.
15. DIP: Dependency
Inversion Principle
• Las clases deben depender de abstracciones, no
de detalles.
• ¿Quién manda? Si un cambio en el mandado, hace
que cambies el que manda, invierte la
dependencia.
16. Resumen
• Todo tiene que ver con razonar sobre el cambio.
• El SRP es "el método/función/clase/modulo/microservicio solo cambia por un
motivo" (el SRP no significa "tiene una sola responsabilidad" eso no significa nada).
• La cohesión es "pon junto lo que cambia junto", "pon junto lo que cambia con la
misma frecuencia".
• El acoplamiento ocurre cuando "si cambio en un sitio tambien tengo que cambiar en
el otro".
• Organizar el código correctamente a nivel de cambio impacta en la velocidad que
desarrollo si lo pienso a nivel de módulo (conjunto de clases y funciones). Esto es,
intento agrupar clases que:
- tienen mucha interacción entre si (cohesión).
- hay pocos motivos externos que los hacen cambiar (acoplamiento).
- cambian por un solo motivo.
17. Resumen
• Quiero valor temprano porque me da feedback y retorna la
inversión antes:
• y el feedback reduce el riesgo de desviarme y aumenta las
posibilidades de entregar software valioso $$$
• esto implica desarrollo iterativo e incremental
• lo cuál significa básicamente que creo algo y lo CAMBIO
constantemente
• así que mi principal preocupación es razonar sobre cómo
cambian las cosas.
18. Resumen
• CUIDADO: el objetivo no es predecir el cambio. Eso implica preparar el código
para cosas que igual no pasan. Pierdo el tiempo y genero complejidad.
• el objetivo es que cuando sepa el cambio que quiero hacer, pueda reorganizar la
menor cantidad de código posible y hacer el cambio.
• es como tener organizado un cajón en el que no sabes qué vas a tener que
coger de él, pero sabes que cuando tengas que coger algo "está todo a mano”.
• Si no hay duplicidad, cuando cambio algo no lo tengo que cambiar en varios
sitios
• Si hay pocas dependencias, cuando cambio algo no se rompen las cosas en
mil sitios....
• Para que este "todo a mano" y sea fácil de entender hace falta: naming,
buenas abstracciones, y una gestión explícita y aislada de los side-effects.
19. Resumen
• Principios que me ayudan a razonar sobre cómo organizar el código en función del cambio:
• SRP - agrupa lo que cambia por el mismo motivo
• OCP - no cambies la implementación, permite que se extienda
• LSP - permite cambiar las clases hijas por las padres y que siga teniendo sentido
• ISP - si tengo que cambiar la interfaz de una cosa, que no tenga métodos mezclados de otras cosas
• DIP: ¿quién manda? Si un cambio en el mandado, hace que cambies el que manda, invierte la dependencia.
• Law of demeter, local retention, encapsulation, tell don´t ask: <--- oculta los detalles, así si cambias los
detalles solo lo tienes que cambiar en un sitio.
• El diseño de código va sobre hacer desarrollar más barato (menos costoso, menos dinero).
• En desarrollo iterativo/incremental, el coste de programar es el coste de cambiar.
• Razona sobre el coste.
• Indirectamente aumentarás la velocidad a la que aportas valor. (mejorando el flow = valor/coste)
21. Separar el proceso de construcción
del sistema de su uso
• Separar el proceso de inicio, cuando los objetos de
la aplicación se construyen y las dependencias
están "conectadas", de la lógica de ejecución de
después del arranque.
• Necesitamos un sitio centralizado donde abordar la
construcción y resolver las dependencias. Una
estrategia posible es generar todo desde main para
después pasárselo a la aplicación.
22. Separar el proceso de construcción
del sistema de su uso
• Patrón Factoría Abstracta
• Separar en una clase la responsabilidad de crear un
objeto concreto para esconder los detalles de la creación.
• Inyección de dependencias
• Aplicación de Inversión de Control (IoC) al manejo de
dependencias.
• Un objeto no es responsable de instanciar sus
dependencias, lo delega a un sistema autorizado
(normalmente main o un contenedor autorizado).
23. Escalando
• Desarrollo iterativo e incremental, no bien hecho a
la primera.
• Implementar las historias de hoy, refactorizar y
expandir el sistema para implementar las historias
de mañana.
• Un sistema software no es un sistema físico. Su
arquitectura puede crecer incrementalmente, SI
mantenemos la correcta separación de conceptos.
24. Test Driven Architecture
• Una arquitectura óptima consiste en dominios
modularizados de conceptos.
• Los diferentes dominios están conjuntamente
integrados con la mínima interferencia de
herramientas. Esta arquitectura puede ser guiada
por los tests, igual que el código.