defectos que causan daño al sw:
-poner mas codigo/funcionalidad no relacionada en una unica clase
-hacer depender una clase de otra (estrechamente acopladas) el cambio en una afectará a la otra
-meter codigo duplicado
soluciones:
-elegir arquitectura correcta (mvc,etc)
-seguir principios de diseño ...solid
-elegir patrones de diseño
--------------------------------------------
SOLID
-acrónimo: cada letra representa un principio.
-si se sigue hará de nuestro sw uno escalable en el tiempo.
-tiene una relación con los conceptos de:
alta cohesión y bajo acoplamiento
alta cohesión: la información que almacena una clase debe ser coherente y estar relacionada con la clase.
bajo acoplamiento: tener las clases lo menos ligadas entre si. de tal modo que si se modifica una de ellas, no afecte o afecte poco al resto de clases.
SOLID, principios:
S: responsabilidad única
0: abierto o cerrado
L: sustitución de Liskov
I: segregación de interfaces
D: inversión de dependencia
un buen diseño nos ayuda a hacer más sencillo el mantenimiento del sw, para que cada vez que nos pidan una nueva funcionalidad no tener que estar tocando media aplicación de código antiguo.
reducimos así el costo de mantenimiento de sw.
Principio de Responsabilidad Unica:
-cada modulo de sw debe tener una sola razón para cambiar (no como una navaja suiza)
ejemplo
class Usuario{
public function pagarMisDeudas(){
if(tieneDineroEnELBanco(id_usuario){
...
}
}
public function tieneDineroEnELBanco(id_usuario){
...
}
}
-----------------------
la responsabilidad de la funcion tieneDineroEnELBanco(id_usuario)
no le pertenece directamenreal Usuario...
pues esa misma logica la vamos a querer usar en otra parte de la aplicacion y terminemos copiando y pegando ese codigo, haciendo luego inmantenible.
entonces, una solución sencilla sería:
class Usuario{
Banco $banco;
public Usuario(Banco banco){
$this->banco = banco;
}
public function pagarMisDeudas(){
if($this->banco->tieneDineroEnELBanco(id_usuario){
...
}
}
Principio Abierto Cerrado
un modulo o clase de sw está abierto para extensión cerrado para modificación.
entonces si por ejemplo tenemos
class AreaService{
public sumarAreas(Rectangulo[] rectangulos){
//recorremos el arreglo de rectangulos ,
/por cada rectangulo, segun su base y altura y calculamos el area y lo acumulamos en una variable
//devolvemos el valor de la variable de acumulacion
}
}
si ahora tambien ademas de rectangulos tenemos circulos,
una solución podria ser
hacer una clase Figura de la cual extender Rectangulo y Circulo
entonces
modificamos el metodo
public sumarAreas(Figura[] figura){
//recorremos el arreglo de figuras ,
/por cada figura, segun la instancia de què objeto sea calculamos su area y lo acumulamos en una variable
//devolvemos el valor de la variable de acumulacion
}
pero que pasa si se agregan mas tipos de Figura?
entonces tendriamos que modificar el metodo de sumarAreas para poder calcular el area
segun cada uno de los nuevos tipos de figuras.
se viola lo de "cerrado para modificación" del método..
entonces lo que se puede hacer es que el método que calcula el área debe estar implementado en cada una de las clases y ser llamadas dentro del mètodo de sumarAreas
PRINCIPIO DE LISKOV
asegurarse de que la clase derivada no modifique el comportamiento de la clase padre.
class FileManager{
writeFiles(File[] file){
file->write();
}
}
donde File tiene 2 metodos: read y write
si luego, nos indican que hay Files que no pueden escribirse.
se podria crear FileReadeable para que extiendan de File y reescriba el metodo write
donde el metodo write de FileReadeable lance una exception.
esto viola el principio, ya que el hijo ya no hace lo del padre.
entonces lo que vamos a hacer es crear 2 interfaces
IFileReadeable con el unico metodo read
IFileWritable con el unico metodo write
donde File implementaria ambas interfaces
entonces, FileManager quedaria asi:
class FileManager{
writeFiles(IFileWriteable[] file){
file->write();
}
}
principio de Segregación de Interfaces
es preferible muchas interfaces con pocos metodos
que pocas con muchos.
asi no obligamos a un cliente a implementar metodos que no usa.
por ejemplo
tenemos que una emplresa hay un TeamLead que puede crear una tarea, asignar una tarea y trabajarenunatarea.
entonces podemos crear la interface ILead
con los metodos
createTask()
assignTask()
WorkOnTask()
y luego creamos la clase TeamLead que implementa ILead
luego nos dicen que hay un Manager con similares capacidades que el TeamLead pero que no puede trabajar en ninguna tarea,
entonces podemos crear una clase Manager que implemente ILead
y en el metodo workOnTask() que lance una excepcion indicando un error.
esta soluciòn viola el principio, pues se està obligando a Manager a implementar un método que no usa.
ahora imaginemos que tambien tenemos un programador que solo puede trabajar en una tarea,
lo que tendriamos que hacer es crear 3 interfaces
IProgrammer con el metodo workOnTask()
IManager con el metodo createTask() y assignTask()
entonces :
La clase TeamLead implementa IProgrammer y IManager
La clase Programmer implementa IProgrammer
La clase Manager implementa IManager
Principio de inversión de dependencias
Los modulos/clases de alto nivel no deberian depender de las de bajo nivel.
supongamos que tenemos
class Logger{
logMessage(string message){
}
}
class DBLogger{
logMessage(string message){
}
}
y tenemos una
class ExceptionLoggerManager{
logearEnFile(Exception e){
log =new FileLoger()
log->logMessage(e->getMessage())
}
logearEnDB(Exception e){
}
log = new DBLoger();
log->logMessage(e->getMessage())
}
class exportarData(){
try{
}catch(IOException e){
new ExceptionLogerManager->logearEnDb(e);
}catch(Exception e){
new ExceptionLogerManager->logearEnFile(e)
}
}
el problema aca es que cada vez que nos pidan logear en otro lado, la clase ExceptionLogerManager
va a tener que modificarse. Eso pasa porque depende mucho de clase de bajo nivel.
la solucion:
creamos una interface ILoger metodo log(string)
y cada log distinto que la implemente
entonces
la nueva interfaz se pasa como parametro en el constructor de la clase ExcpetionLogerManager
class ExceptionLogerManager{
ILoger _iloger;
ExceptionLogerManager(Iloger iloger)
{
_iloger = iloger;
}
}
entonces ahora la clase de exportar quedaria asi:
class exportarData(){
try{
}catch(IOException e){
new ExceptionLogerManager(new DBLOger())->log(e);
}catch(Exception e){
new ExceptionLogerManager(new FIleLOger())->loge(e)
}
}