Refactoring
Code Smells
Es una indicación de un posible problema
- Se encuentra en el código fuente
- Puede indicar un problema
- Estilo
- Implementación
- Diseño
- Análisis de Dominio
Refactoring
Transformaciones de código que preservan
el comportamiento
- No corrigen errores
- No agregan funcionalidad
Pueden mejorar algún aspecto no funcional del código
- Configurabilidad (a patrones o a frameworks)
- Performance
- Uso de memoria
- Legibilidad
Soporte de herramientas (basados en ATS)
- Renaming: classes, variables, methods
- Extrating Method class & component
- Pushing UP/DOWN: Methods&vars
-
Renombrar
idx
aindex
:- Línea 5:
int idx;
->int index;
- Línea 11:
idx = result;
->index = result;
- Línea 15:
if(idx >= source.length)
->if(index >= source.length)
- Línea 16:
idx = 0;
->index = 0;
- Línea 17:
result = idx++;
->result = index++;
- Línea 5:
-
Renombrar
result
aposition
:- Línea 14:
char result;
->char position;
- Línea 17:
result = idx++;
->position = index++;
- Línea 18:
return source[result];
->return source[position];
- Línea 14:
Patrón
La mejor solución a un problema recurrente
Engine Flameout
- Es un incidente (recurrente) que resulta en el malfuncionamiento de un motor jet
- El entrenamiento de un piloto incluye conocer la mejor solución a este tipo de problemas
- Diferentes aviones tienen diferentes procedimientos
- Diferentes momentos del vuelo requieren diferentes procedimientos
- Carreteo
- Despegue
- Vuelo
⚠️ Importante:
- Reconocer el problema de manera exacta
- Aplicar la mejor solución (correcta… patrón)
- Aplicar la solución de manera apropiada
IB6012 (18/08/2015) solución correcta, bien aplicada
TNA235 (04/02/2015) solución correcta, mal aplicada
Design Patterns: smells más comunes
Patrones
Proceso de resolución de Problemas
- Reconocer el problema de manera exacta
- Encontrar la solución correcta
- Aplicar la solución de manera apropiada
Un Patrón es la mejor solución a un problema recurrente…
Proceso de resolución de Problemas con Patrones
- Reconocer el problema de manera exacta
- Encontrar el patrón que aplica al problema
- Evaluar ventajas y desventajas
- Aplicar la solución de manera apropiada
Suele ocurrir que un patrón soluciona parcialmente el universo del problema, y se debe seguir resolviendo problemas (menores) de manera iterativa
Framework
- Un framework es en sí mismo un módulo de software reutilizable.
- Muchas aplicaciones se construirán sobre él (instanciaciones).
- El framework será independiente de las aplicaciones. Sin embargo, las aplicaciones dependen de que la interfaz (conjunto de puntos de instanciación) y el comportamiento del framework se mantengan estables.
Conceptos elementales
- Instaciación vs extensión.
- instanciación(framework) = aplicación
- extensión(framework) = framework
- Inversión de control
- WhiteBox frameworks: las instanciaciones completan el loop de control (estáticamente)
- BlackBox: las instanciaciones configuran el framework (composición) y agregan objetos que responden a protocolos requeridos.
(deep into) Refactoring
Los refactoring de código son:
- Cualquier transformación de código fuente
- Cambios en el código que no alteran el comportamiento
- Cambios en los datos del sistema para que funcione bien
- Cambios en el código fuente que elimina code smells
- Cambios en el código que mejoran el sistema
- Transtornaciones del programador que arreglan el sistema
- Cambios en el código que mantienen la funcionalidad
- Mejoras en la funcionalidad sin cambios de código
Refactoring
- Rename Variable
- Rename Method
- Extract Method
- Move Method to Component
- Replace Conditional with Polymorphism
- Replace temp with query
Code Smells
- Duplicate Code
- Large Class
- Long Method
- Data Class
- Feature Envy
- Long Parameter List
- Switch Statements
- Primitive Obsession
- Oddball Solution
- Lazy Class
Patrones de Diseño
- Template Method
- Strategy
- State
- Composite
- Façade
- Adapter/Decorator
- Command
- Builder / Factory Method
Code Smells → Refactoring (cuales se relacionan?)
- Duplicate Code
- Large Class
- Long Method
- Data Class
- Feature Envy
- Long Parameter List
- Switch Statements
- Rename Variable
- Rename Method
- Extract Method
- Move Method to Component
- Replace Conditional with Polymorphism
- Replace temp with query
- Variable move up
- Method move up (+ variables)
Refactorings Patterns
- Form Template Method
- Extract Adapter
- Replace Implicit Tree with Composite
- Replace Conditional Logic with Strategy
- Replace State-altering conditionals with State
Refactorings
- Transformaciones de código que preservan el comportamiento
- Preservar el comportamiento
- (Hace lo que hacía antes) & (No hace lo que no hacía antes) *
- Se puede contar todas las maneras de escribir?
- operación (función),
- Clase
- Librería, Framework
- Programa
- Elija su opción: Finito, Infinito contable o Infinito Incontable
Qué debería usarse para poder analizar (cualquier) código (dado un lenguaje de programación)?
Abstracción
- Creada desde el código
- Uniquivoca
- Manipulable por
- Algoritmos
- => Arboles !
Syntax Trees
- Parsing
- Gramatica
- Abstract (AST)
- Concrete (CST)
EXPRESSION ::= NUMERAL | “(“NUMERAL”)”
“(“ EXPRESSION OPERATOR EXPRESSION “)”
OPERATOR ::= “+” | “-”
NUMERAL ::= DIGIT | DIGIT NUMERAL
DIGIT ::= “0” | “1” | “2” | “3” | “4” | “5” | “6” | “7” | “8” | “9”
Ejemplo: (4 - (3 + 2))
((4) - (3 + 2))
Concrete ST: cada derivation rule es un subarbol
Abstract ST: estructura relevante
Refactoring(Tree)
EXPRESSION ::= NUMERAL | (NUMERAL)
“(“ EXPRESSION OPERATOR EXPRESSION “)”
(NUMERAL) ::= “(“ NUMERAL ”)”
OPERATOR ::= “+” | “-”
NUMERAL ::= DIGIT | DIGIT NUMERAL
DIGIT ::= “0” | “1” | “2” | “3” | “4” | “5” | “6” | “7” | “8” | “9”
Transformación(Tree) = Tree’
- Tree EQUIV Tree’
Refactorizar
- Transformaciones de AST que preservan la funcionalidad
- Son todas las expresiones equivalentes a ((4) - (3+2))? Cuales son las reglas de transformación que preservan funcionalidad?
- ((4) - (3 + 2))
- (4 - (3 + 2))
- 4 - (3 + 2)
- (4) - (3 + 2)
- (4) - 3 + 2
- 4 - 3 + 2
- 4 - 3 - 2
Para pensar…
Qué implica preservar el comportamiento?
- Variable temporal (de una operación)
- Variable de instancia privada de una clase
- Variable de instancia publica de una clase
- Método público de una clase
- Método abstracto de una clase
- Método público de una clase (pública) de una librería
Para cuando estan desvelados:
- Semántica Denotacional
- Lógica de Reescritura + Resolverdores de Teoremas
Select Orders.OrderID, Customers.CustomerName
From Orders, Customers
WHERE Customers.CustomerID = Orders.OrderID
Refactoring SQL: alias de tablas
SELECT Orders.OrderID, Orders.OrderDate, Customers.CustomerName
FROM Customers, Orders
WHERE Customers.CustomerName='Around the Horn' AND Customers.CustomerID=Orders.CustomerID;
- Verificar precondiciones
- Agregar alias a la declaración de la tabla
- Cambiar la ocurrencia de la tabla por su alias
Ejemplo: Customers →“c”. Orders →“o”
SELECT o.OrderID, o.OrderDate, c.CustomerName
FROM Customers AS c, Orders AS o
WHERE c.CustomerName='Around the Horn' AND c.CustomerID=o.CustomerID;
antlr
ANTLR (ANother Tool for Language Recognition)
- Generador de analizadores para leer, procesar, ejecutar o traducir texto estructurado o archivos binarios.
- Se usa ampliamente para crear lenguajes, herramientas y Frameworks.
- A partir de una gramática, ANTLR genera un analizador que puede construir y recorrer árboles de análisis.
- http://lab.antlr.org/
Antlr lab: herramiento de pruebas
Configuraciones
Ejemplos de uso
- Asignación como statement
- Expresiones aritméticas con precedencia de operadores
f(x,y) {
a = 3+foo;
x* x + y * x;
}
- Condicional simple (exp1 ? exp2)
- Condicional completo (exp1 ? exp2 : exp3 )
- Asignación y Condicionales
f(x,y) {
x ? x+1 ;
y ? y+ 1 : y-1;
z = y ? y+ 1 : y-1;
}
Acceso a estructura
f(x,y) {
x.foo;
x.foo = 4;
}
- Envio de mensajes sin argumento
- Envio de mensajes con argumento
- Envio de mensajes con argumentos
f(x,y) {
x.foo();
x.foo(4);
x.bar(x.foo, 4);
}
Otro ejemplo
Practica
Ejercicio 1.1
-
Nombres de Métodos Crípticos (Inconsistent Naming):
- Los nombres de los métodos (
lmtCrdt
,mtFcE
,mtCbE
) son crípticos y no son claros respecto a lo que hacen. Utilizan abreviaciones que dificultan la lectura y comprensión del código. - Esto es un mal olor llamado “Nombres Inconsistentes o Crípticos”, que puede llevar a errores en el uso del código, ya que otros desarrolladores tendrán dificultades para entender lo que hacen estos métodos.
- Los nombres de los métodos (
-
Falta de Cohesión (Low Cohesion):
- Los métodos
mtFcE()
ymtCbE()
operan sobre rangos de fechas y parecen ser parte de una lógica de facturación o cobro que podría pertenecer a una clase separada. Este es un indicativo de que la claseCliente
puede estar asumiendo más responsabilidades de las que debería. - Esto es un mal olor conocido como “Falta de Cohesión” donde la clase
Cliente
podría estar haciendo más de lo que debería según el Principio de Responsabilidad Única (SRP).
- Los métodos
-
Interfaz Inflada (Large Class / Bloated Interface):
- Si la clase
Cliente
tiene más métodos además de estos, podría convertirse en una clase demasiado grande, manejando múltiples responsabilidades. Las interfaces infladas dificultan el mantenimiento, las pruebas y la reutilización del código. - Este es un mal olor de “Interfaz Inflada” que puede ser mitigado mediante la división de responsabilidades.
- Si la clase
Refactorings Sugeridos
-
Renombrar Métodos (Rename Method):
- Refactoring Aplicado: Cambiar los nombres de los métodos a algo más descriptivo y consistente.
- Justificación: Nombres claros y consistentes ayudan a mejorar la legibilidad y mantenibilidad del código. Además, el uso de nombres claros disminuye la curva de aprendizaje para nuevos desarrolladores.
protected double obtenerLimiteCredito() { ... } protected double calcularMontoFacturadoEntreFechas(LocalDate fechaInicio, LocalDate fechaFin) { ... } protected double calcularMontoCobradoEntreFechas(LocalDate fechaInicio, LocalDate fechaFin) { ... }
-
Extraer Clase (Extract Class):
- Refactoring Aplicado: Extraer los métodos
calcularMontoFacturadoEntreFechas()
ycalcularMontoCobradoEntreFechas()
a una nueva clase, quizás llamadaFactura
oCobranzas
. - Justificación: Esta clase
Factura
oCobranzas
puede encargarse de toda la lógica de facturación y cobro, siguiendo el Principio de Responsabilidad Única (SRP). Esto hace que la claseCliente
sea más cohesiva y se centre únicamente en información relacionada con el cliente.
public class Factura { public double calcularMontoFacturadoEntreFechas(Cliente cliente, LocalDate fechaInicio, LocalDate fechaFin) { ... } public double calcularMontoCobradoEntreFechas(Cliente cliente, LocalDate fechaInicio, LocalDate fechaFin) { ... } }
- Refactoring Aplicado: Extraer los métodos
-
Crear Interfaces Específicas (Introduce Interface):
- Refactoring Aplicado: Si existen más métodos similares en la clase
Cliente
, se pueden crear interfaces específicas para diferentes responsabilidades. Por ejemplo, una interfazFacturable
oCobrable
. - Justificación: Esto reduciría la complejidad de la clase
Cliente
y mejoraría la modularidad y reusabilidad del código.
public interface Facturable { double calcularMontoFacturadoEntreFechas(LocalDate fechaInicio, LocalDate fechaFin); } public interface Cobrable { double calcularMontoCobradoEntreFechas(LocalDate fechaInicio, LocalDate fechaFin); }
- Refactoring Aplicado: Si existen más métodos similares en la clase
-
Reducción del Alcance de los Métodos (Reduce Method Scope):
- Refactoring Aplicado: Evaluar si los métodos necesitan ser
protected
o si podrían serprivate
en una nueva clase separada. - Justificación: Reducir el alcance de los métodos mejora el encapsulamiento y reduce la exposición innecesaria de métodos.
- Refactoring Aplicado: Evaluar si los métodos necesitan ser
Ejercicio 1.2
1. Malos Olores Identificados en el Diseño Inicial (Figura 1)
-
Inversión de Dependencia (Inappropriate Intimacy):
- En el diseño inicial, la clase
Persona
tiene un métodoparticipaEnProyecto(Proyeto p)
que depende de la claseProyecto
para determinar si la persona participa en el proyecto. Esto significa que la clasePersona
tiene conocimiento interno de la estructura y el funcionamiento de la claseProyecto
. - Este es un mal olor de “intimidad inapropiada” porque
Persona
depende de los detalles de implementación deProyecto
, lo que crea un acoplamiento fuerte entre las dos clases. Esto hace que el código sea más difícil de mantener y modificar.
- En el diseño inicial, la clase
-
Falta de Cohesión (Low Cohesion):
- El método
participaEnProyecto(Proyeto p)
en la clasePersona
no es cohesivo con el propósito de la clasePersona
. El propósito dePersona
debe ser representar a una persona, y no es responsabilidad de esta clase gestionar la lógica de participación en un proyecto. - La falta de cohesión en las responsabilidades de la clase
Persona
puede llevar a un código que es difícil de entender y modificar.
- El método
-
Violación del Principio de Responsabilidad Única (Single Responsibility Principle, SRP):
- La clase
Persona
está asumiendo más de una responsabilidad: representar a una persona y manejar la participación en proyectos. Esto viola el SRP, ya que la clase debería tener una única razón para cambiar.
- La clase
2. Cambios Realizados en el Diseño Modificado (Figura 2)
En el diseño revisado, se realizaron los siguientes cambios:
- Se movió el método
participaEnProyecto()
de la clasePersona
a la claseProyecto
. El método ahora se llamaparticipa(Persona p)
y está dentro de la claseProyecto
. - La lógica de verificación de participación se ha simplificado y ahora la clase
Proyecto
contiene un métodoparticipa(Persona p)
que verifica si la persona dada está contenida en la lista departicipantes
.
3. Refactorings Aplicados en el Diseño Modificado
-
Mover Método (Move Method):
- Refactoring Aplicado: El método
participaEnProyecto()
se movió de la clasePersona
a la claseProyecto
. - Justificación: Esto elimina la dependencia inapropiada de
Persona
sobreProyecto
y hace que la lógica de participación esté dentro de la clase que realmente debería conocer sus participantes. Ahora,Proyecto
maneja su propia lista de participantes y verifica la participación internamente.
- Refactoring Aplicado: El método
-
Aumentar Cohesión (Increase Cohesion):
- Refactoring Aplicado: Al mover el método
participaEnProyecto()
aProyecto
, aumentamos la cohesión en ambas clases.Persona
ahora solo contiene información relacionada con la persona (comoid
) y no tiene métodos que gestionen proyectos. - Justificación: Esto hace que las clases sean más cohesivas.
Persona
se encarga únicamente de la información personal, yProyecto
se encarga de manejar la lógica relacionada con los proyectos, incluido quién participa en ellos.
- Refactoring Aplicado: Al mover el método
-
Cumplir con el Principio de Responsabilidad Única (SRP):
- Refactoring Aplicado: Separando las responsabilidades entre las dos clases.
Persona
ya no es responsable de la lógica del proyecto, yProyecto
maneja exclusivamente la lógica relacionada con la participación en proyectos. - Justificación: Esto alinea mejor el diseño de las clases con el SRP, haciendo que el sistema sea más fácil de mantener y modificar.
- Refactoring Aplicado: Separando las responsabilidades entre las dos clases.
4. ¿Es Apropiado el Cambio Realizado?
Sí, el cambio es apropiado. Las razones son:
- Reducción de Acoplamiento: Ahora
Persona
no está acoplada aProyecto
, lo que hace que las clases sean menos dependientes entre sí y más fáciles de modificar de manera independiente. - Mejora de la Cohesión: Cada clase ahora tiene una responsabilidad más clara y está alineada con su propósito.
- Simplificación del Diseño: El nuevo diseño es más fácil de entender porque
Proyecto
maneja toda la lógica relacionada con los proyectos y sus participantes.
Ejercicio 1.3
1. Malos Olores Identificados en el Código Original
-
Método Largo (Long Method):
- El método
imprimirValores()
realiza múltiples operaciones:- Calcula la suma de edades.
- Calcula la suma de salarios.
- Calcula el promedio de edades.
- Formatea e imprime un mensaje.
- Este es un indicativo de una violación del Principio de Responsabilidad Única (SRP), ya que el método está manejando más de una responsabilidad.
- El método
-
Duplicación de Código (Duplicated Code):
- Dentro del bucle
for
, el cálculo de la suma de edades y la suma de salarios sigue el mismo patrón repetitivo. - Este tipo de duplicación es un mal olor porque aumenta la posibilidad de errores y dificulta el mantenimiento. Si la lógica cambia, tendrías que modificar el mismo código en varios lugares.
- Dentro del bucle
-
Falta de Abstracción (Lack of Abstraction):
- El método
imprimirValores()
está demasiado atado a la lógica específica de los cálculos. No utiliza métodos separados para encapsular la lógica de sumar edades o salarios, lo cual hace que el código sea menos modular y más difícil de probar.
- El método
-
Cálculo de Promedio no Intuitivo:
- La división para calcular el promedio de edades es un poco críptica en el contexto del método
imprimirValores()
. Sería más claro si este cálculo se realizara en un método separado.
- La división para calcular el promedio de edades es un poco críptica en el contexto del método
2. Refactorings Aplicados
-
Extracción de Métodos (Extract Method):
- Refactoring Aplicado: Extraer el código de cálculo de la suma de edades y la suma de salarios en métodos separados:
calcularSumaEdades()
ycalcularSumaSalarios()
. - Justificación: Esto hace que el código sea más modular, fácil de entender y reutilizable. Además, se reduce la duplicación de código y se aísla la lógica de los cálculos en métodos específicos.
private int calcularSumaEdades(List<Empleado> personal) private double calcularSumaSalarios(List<Empleado> personal)
- Refactoring Aplicado: Extraer el código de cálculo de la suma de edades y la suma de salarios en métodos separados:
-
Extracción de Métodos para el Cálculo del Promedio (Extract Method):
- Refactoring Aplicado: Extraer la lógica de cálculo del promedio de edades a un método separado.
- Justificación: Aunque el cálculo del promedio se realiza en línea dentro de
imprimirValores()
, se hace explícito que este cálculo es una operación separada. Esto no solo mejora la legibilidad del método principal, sino que también facilita la prueba y el mantenimiento del código.
private double calcularPromedioEdades(List<Empleado> personal) { int totalEdades = calcularSumaEdades(personal); return (double) totalEdades / personal.size(); }
-
Separación de Lógica de Presentación (Separate Presentation Logic):
- Refactoring Aplicado: Mantuve la separación lógica de los cálculos y la presentación en la consola.
imprimirValores()
ahora solo coordina las operaciones y presenta el resultado. - Justificación: Separar la lógica de presentación de la lógica de negocio permite que el código sea más flexible y fácil de modificar o cambiar la salida en el futuro.
- Refactoring Aplicado: Mantuve la separación lógica de los cálculos y la presentación en la consola.
-
Renombrado de Variables para Claridad (Rename Variable):
- Refactoring Aplicado: Aclaré los nombres de los métodos (
calcularSumaEdades
,calcularSumaSalarios
) para que sean más descriptivos y fáciles de entender. - Justificación: Nombres más claros y precisos mejoran la legibilidad del código y reducen la necesidad de comentarios explicativos.
- Refactoring Aplicado: Aclaré los nombres de los métodos (
-
Conversión Explícita de Tipos para Promedio (Explicit Type Conversion):
- Refactoring Aplicado: Se dejó la conversión explícita a
double
al calcular el promedio ((double) totalEdades / personal.size()
) para asegurar que el cálculo del promedio se realice correctamente. - Justificación: Este cambio asegura que el comportamiento del código sea el mismo que el original, sin perder precisión.
- Refactoring Aplicado: Se dejó la conversión explícita a
Ejercicio 2
1. Malos Olores Identificados
-
Duplicación de Código (Duplicated Code):
- Las clases
EmpleadoTemporario
,EmpleadoPlanta
yEmpleadoPasante
tienen atributos y métodos muy similares. Los atributosnombre
,apellido
, ysueldoBasico
se repiten en cada clase. - El cálculo de
sueldo()
también tiene una lógica redundante para la deducción del 13% delsueldoBasico
.
- Las clases
-
Falta de Herencia (Missing Abstraction):
- Todas las clases de empleados (
EmpleadoTemporario
,EmpleadoPlanta
,EmpleadoPasante
) comparten características comunes, como nombre, apellido y sueldo básico, y deberían heredar de una clase base común (por ejemplo,Empleado
). - Este es un mal olor de “Duplicación de Código” y “Código Rígido”, donde falta una jerarquía de herencia adecuada que permita compartir atributos y métodos comunes.
- Todas las clases de empleados (
-
Código Frágil (Fragile Code):
- Si se necesita cambiar la lógica para calcular el sueldo (por ejemplo, cambiar la deducción de impuestos), se tendría que actualizar en múltiples lugares, lo cual es propenso a errores.
-
Interfaz Inflada (Bloated Class):
- Las clases pueden volverse más grandes y difíciles de mantener si se siguen agregando nuevos atributos o métodos específicos para diferentes tipos de empleados, en lugar de aprovechar la herencia y/o composición.
Refactorings Sugeridos
-
Extraer Clase Base Común (Extract Superclass):
- Refactoring Aplicado: Crear una clase base
Empleado
que contenga los atributos y métodos comunes (nombre
,apellido
,sueldoBasico
, y un método abstractosueldo()
). - Justificación: Esto elimina la duplicación de código, hace que el código sea más mantenible y facilita futuros cambios en la lógica de los empleados.
- Refactoring Aplicado: Crear una clase base
-
Uso de Métodos Abstractos para Comportamientos Específicos (Pull Up Method / Abstract Method):
- Refactoring Aplicado: Mover el método
sueldo()
común a la clase baseEmpleado
y convertirlo en un método abstracto para que cada subclase (EmpleadoTemporario
,EmpleadoPlanta
,EmpleadoPasante
) pueda implementar su propia lógica específica. - Justificación: Esto permite que cada clase hija tenga su propia implementación de cálculo de sueldo mientras se comparte la lógica común.
- Refactoring Aplicado: Mover el método
-
Eliminar Duplicación de Código (Remove Duplicated Code):
- Refactoring Aplicado: Utilizar métodos en la clase base
Empleado
para manejar la deducción de impuestos común a todos los empleados. - Justificación: Esto hace que el código sea más limpio, más fácil de mantener y menos propenso a errores.
- Refactoring Aplicado: Utilizar métodos en la clase base
Código Refactorizado
Clase Base Empleado
public abstract class Empleado {
protected String nombre;
protected String apellido;
protected double sueldoBasico;
public Empleado(String nombre, String apellido, double sueldoBasico) {
this.nombre = nombre;
this.apellido = apellido;
this.sueldoBasico = sueldoBasico;
}
// Método común para calcular la deducción de impuestos
protected double calcularDeduccionImpuestos() {
return this.sueldoBasico * 0.13;
}
// Método abstracto que será implementado por cada subclase
public abstract double sueldo();
}
Clase EmpleadoTemporario
public class EmpleadoTemporario extends Empleado {
private double horasTrabajadas;
private int cantidadHijos;
public EmpleadoTemporario(String nombre, String apellido, double sueldoBasico, double horasTrabajadas, int cantidadHijos) {
super(nombre, apellido, sueldoBasico);
this.horasTrabajadas = horasTrabajadas;
this.cantidadHijos = cantidadHijos;
}
@Override
public double sueldo() {
return this.sueldoBasico + (this.horasTrabajadas * 500) + (this.cantidadHijos * 1000) - calcularDeduccionImpuestos();
}
}
Clase EmpleadoPlanta
public class EmpleadoPlanta extends Empleado {
private int cantidadHijos;
public EmpleadoPlanta(String nombre, String apellido, double sueldoBasico, int cantidadHijos) {
super(nombre, apellido, sueldoBasico);
this.cantidadHijos = cantidadHijos;
}
@Override
public double sueldo() {
return this.sueldoBasico + (this.cantidadHijos * 2000) - calcularDeduccionImpuestos();
}
}
Clase EmpleadoPasante
public class EmpleadoPasante extends Empleado {
public EmpleadoPasante(String nombre, String apellido, double sueldoBasico) {
super(nombre, apellido, sueldoBasico);
}
@Override
public double sueldo() {
return this.sueldoBasico - calcularDeduccionImpuestos();
}
}
2.2 Juego
1. Malos Olores Identificados
-
Datos Expuestos (Data Clump):
- La clase
Jugador
tiene sus atributosnombre
,apellido
, ypuntuacion
definidos comopublic
, lo cual expone estos datos directamente a otras clases. - Este es un mal olor llamado “Campos Públicos Expuestos” porque rompe el principio de Encapsulamiento. Exponer los campos directamente permite que cualquier clase acceda y modifique el estado interno de
Jugador
sin ningún control, lo que puede conducir a errores y comportamientos inesperados.
- La clase
-
Inapropiada Intimidad (Inappropriate Intimacy):
- La clase
Juego
accede directamente al atributopuntuacion
deJugador
para incrementar o decrementar su valor. Esto indica un acoplamiento innecesario entre las dos clases. - Este es un mal olor conocido como “Inapropiada Intimidad”, ya que la clase
Juego
depende de los detalles internos de la claseJugador
.
- La clase
-
Falta de Encapsulación (Missing Encapsulation):
- No hay métodos que encapsulen la lógica de manipulación de la puntuación en la clase
Jugador
. Esto lleva a una falta de control sobre cómo se accede y se modifica la puntuación del jugador.
- No hay métodos que encapsulen la lógica de manipulación de la puntuación en la clase
-
Abuso de Funcionalidad de Clase (Feature Envy):
- Los métodos
incrementar()
ydecrementar()
en la claseJuego
muestran que esta clase está “celosa de características” de la claseJugador
, porque dependen del conocimiento interno de cómo manipular la puntuación del jugador. - Este es un mal olor de “Abuso de Funcionalidad de Clase”.
- Los métodos
Refactorings Sugeridos
-
Encapsular Campos (Encapsulate Field):
- Refactoring Aplicado: Cambiar los campos
public
de la claseJugador
aprivate
y proporcionar métodosget
yset
(si es necesario) para acceder a ellos. - Justificación: Esto sigue el principio de Encapsulamiento y proporciona un control sobre cómo se accede y modifica el estado del objeto
Jugador
.
- Refactoring Aplicado: Cambiar los campos
-
Mover Métodos (Move Method):
- Refactoring Aplicado: Mover los métodos
incrementar()
ydecrementar()
a la claseJugador
. La manipulación de la puntuación debería ser responsabilidad de la claseJugador
, no deJuego
. - Justificación: Esto elimina la “Inapropiada Intimidad” entre
Juego
yJugador
y asegura queJugador
controle cómo se cambia su puntuación.
- Refactoring Aplicado: Mover los métodos
-
Proteger Datos Internos (Hide Data):
- Refactoring Aplicado: Asegurar que las clases externas no accedan directamente a los atributos de
Jugador
y que toda manipulación se haga a través de métodos de instancia deJugador
. - Justificación: Esto garantiza la integridad del objeto y hace que el código sea más fácil de mantener y modificar.
- Refactoring Aplicado: Asegurar que las clases externas no accedan directamente a los atributos de
Código Refactorizado
Clase Jugador
public class Jugador {
private String nombre;
private String apellido;
private int puntuacion = 0;
public Jugador(String nombre, String apellido) {
this.nombre = nombre;
this.apellido = apellido;
}
// Métodos getter y setter para nombre y apellido
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getApellido() {
return apellido;
}
public void setApellido(String apellido) {
this.apellido = apellido;
}
public int getPuntuacion() {
return puntuacion;
}
// Método para incrementar la puntuación
public void incrementarPuntuacion(int puntos) {
this.puntuacion += puntos;
}
// Método para decrementar la puntuación
public void decrementarPuntuacion(int puntos) {
this.puntuacion -= puntos;
}
}
Clase Juego
public class Juego {
// Métodos para incrementar y decrementar utilizando los métodos de Jugador
public void incrementar(Jugador j) {
j.incrementarPuntuacion(100); // Delegar la responsabilidad a Jugador
}
public void decrementar(Jugador j) {
j.decrementarPuntuacion(50); // Delegar la responsabilidad a Jugador
}
}
Clase 2.3
Análisis del Código Proporcionado
El método ultimosPosts(Usuario user, int cantidad)
en la clase PostApp
devuelve una lista de los últimos N
posts que no pertenecen al usuario especificado, ordenados por fecha de forma descendente.
Malos Olores Identificados
-
Código Duplicado / Repetitivo (Duplicated Code):
- El código tiene dos bucles anidados que implementan manualmente el algoritmo de ordenación (un ordenamiento de selección, en este caso) para ordenar los posts por fecha. Este es un código repetitivo y es innecesario dado que Java proporciona métodos de ordenación más eficientes y legibles en su API estándar.
- Este es un mal olor llamado “Código Duplicado” porque la lógica de ordenación puede simplificarse utilizando las utilidades proporcionadas por la biblioteca estándar de Java.
-
Complejidad de Código (Long Method):
- El método
ultimosPosts
es demasiado largo y realiza múltiples tareas:- Filtra los posts que no pertenecen al usuario dado.
- Ordena los posts filtrados por fecha.
- Limita la lista de posts a la cantidad solicitada.
- Este es un mal olor conocido como “Método Largo (Long Method)” y “Falta de Separación de Responsabilidades”, ya que el método maneja múltiples responsabilidades, lo que lo hace difícil de entender, probar y mantener.
- El método
-
Interfaz Inflada (Bloated Interface):
- La clase
PostApp
tiene un método que realiza demasiadas tareas. Este mal olor puede llevar a una interfaz inflada, haciendo que la clase tenga más responsabilidades de las que debería. - Esto indica que se podría dividir la lógica en métodos más pequeños y específicos.
- La clase
-
Código Imperativo en Lugar de Declarativo (Imperative Programming):
- El uso de bucles y condiciones explícitas para filtrar y ordenar es más imperativo que declarativo. Con las modernas API de Java, como
Stream
, se puede hacer el código más declarativo y legible.
- El uso de bucles y condiciones explícitas para filtrar y ordenar es más imperativo que declarativo. Con las modernas API de Java, como
Refactorings Sugeridos
-
Usar API de Streams de Java (Replace Loop with Stream):
- Refactoring Aplicado: Utilizar la API de
Stream
de Java para filtrar, ordenar y limitar los posts. Esto hace el código más conciso, fácil de leer y mantener. - Justificación: Los Streams proporcionan un enfoque más declarativo y eficiente para manejar colecciones en Java.
- Refactoring Aplicado: Utilizar la API de
-
Dividir Métodos (Extract Method):
- Refactoring Aplicado: Extraer la lógica de filtrado y ordenación en métodos separados. Esto reduce el tamaño del método
ultimosPosts
y mejora la legibilidad. - Justificación: Hace que cada método sea más cohesivo, siguiendo el Principio de Responsabilidad Única (SRP).
- Refactoring Aplicado: Extraer la lógica de filtrado y ordenación en métodos separados. Esto reduce el tamaño del método
-
Eliminar Código Duplicado (Remove Duplicated Code):
- Refactoring Aplicado: Usar el método
sort()
proporcionado por Java en lugar de implementar manualmente la ordenación. - Justificación: Reduce la duplicación de código y utiliza soluciones probadas y más eficientes.
- Refactoring Aplicado: Usar el método
Código Refactorizado
Clase PostApp
Refactorizada
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
public class PostApp {
private List<Post> posts; // Suponemos que esta lista se inicializa en algún lugar del código
/**
* Retorna los últimos N posts que no pertenecen al usuario user.
*/
public List<Post> ultimosPosts(Usuario user, int cantidad) {
return filtrarYOrdenarPosts(user)
.stream()
.limit(cantidad)
.collect(Collectors.toList());
}
/**
* Método para filtrar y ordenar los posts que no pertenecen al usuario dado.
*/
private List<Post> filtrarYOrdenarPosts(Usuario user) {
return posts.stream()
.filter(post -> !post.getUsuario().equals(user))
.sorted(Comparator.comparing(Post::getFecha).reversed()) // Orden descendente por fecha
.collect(Collectors.toList());
}
}
Cambios Realizados
:
-
Uso de Streams:
- Reemplacé los bucles manuales y la lógica de ordenación con una combinación de
filter()
,sorted()
ylimit()
de la API deStream
. - Beneficio: Código más conciso, legible y fácil de mantener.
- Reemplacé los bucles manuales y la lógica de ordenación con una combinación de
-
Separación de Responsabilidades:
- Creé un método
filtrarYOrdenarPosts()
que encapsula la lógica de filtrado y ordenación, mejorando la cohesión del código. - Beneficio: Cada método tiene una única responsabilidad, mejorando la mantenibilidad.
- Creé un método
-
Eliminación de Código Duplicado:
- La lógica de ordenación ahora utiliza el método
sorted()
de la API deStream
, eliminando el código repetitivo. - Beneficio: El código se simplifica y utiliza métodos estándar, lo que también mejora el rendimiento.
- La lógica de ordenación ahora utiliza el método
Ejercicio 2.4
-
Feature Envy (Envidia de Características):
- El método
total()
en la claseCarrito
depende de los detalles internos de la claseItemCarrito
para calcular el total del carrito. Está utilizando directamenteitem.getProducto().getPrecio() * item.getCantidad()
en lugar de delegar esta responsabilidad a la claseItemCarrito
. - Refactoring: Mover el método de cálculo de subtotal a
ItemCarrito
y luego usarlo enCarrito
. Esto hará que la claseCarrito
sea más cohesiva y queItemCarrito
sea responsable de manejar su propio estado.
- El método
-
Long Method (Método Largo):
- Aunque el método
total()
no es excesivamente largo en términos de líneas de código, su complejidad proviene del hecho de que realiza múltiples operaciones a nivel de detalle de objetos internos. - Refactoring: Extraer métodos para dividir las responsabilidades. Crear métodos más pequeños y cohesivos ayuda a simplificar y hacer el código más legible.
- Aunque el método
-
Primitive Obsession (Obsesión por Tipos Primitivos):
- Aunque el código utiliza objetos como
Producto
yItemCarrito
, podría ser más expresivo utilizando métodos y objetos que representen comportamientos en lugar de solo datos. - Refactoring: Añadir métodos de conveniencia que encapsulen operaciones frecuentes (por ejemplo,
subtotal()
enItemCarrito
).
- Aunque el código utiliza objetos como
-
Data Class (Clase de Datos):
- La clase
Producto
es esencialmente una “clase de datos”, ya que solo tiene atributos y getters, pero ningún comportamiento. Esto puede ser un mal olor cuando podría beneficiarse de agregar métodos que proporcionen comportamiento en lugar de solo datos. - Refactoring: Considerar si
Producto
necesita tener algún método de comportamiento (aunque en este caso puede ser más limitado y no necesariamente un problema).
- La clase
Refactorings Aplicados
-
Mover Método (Move Method):
- Se ha movido la lógica de cálculo de subtotal del carrito (
item.getProducto().getPrecio() * item.getCantidad()
) al propioItemCarrito
. Esto se ha logrado agregando un métodosubtotal()
a la claseItemCarrito
.
- Se ha movido la lógica de cálculo de subtotal del carrito (
-
Añadir Métodos de Conveniencia (Add Convenience Method):
- Se ha añadido el método
subtotal()
aItemCarrito
para que encapsule la lógica de multiplicación decantidad
yprecio
deProducto
.
- Se ha añadido el método
-
Refactorización del Método
total()
enCarrito
:- Después de mover la lógica de cálculo del subtotal a
ItemCarrito
, el métodototal()
enCarrito
ahora se vuelve más simple y legible.
- Después de mover la lógica de cálculo del subtotal a
Código Refactorizado
Clase Producto
El código de la clase Producto
permanece igual, ya que no presenta malos olores críticos:
public class Producto {
private String nombre;
private double precio;
public double getPrecio() {
return this.precio;
}
}
Clase ItemCarrito
Se añade un método subtotal()
que encapsula la lógica del cálculo del subtotal:
public class ItemCarrito {
private Producto producto;
private int cantidad;
public Producto getProducto() {
return this.producto;
}
public int getCantidad() {
return this.cantidad;
}
// Método de conveniencia que calcula el subtotal del ItemCarrito
public double subtotal() {
return this.producto.getPrecio() * this.cantidad;
}
}
Clase Carrito
El método total()
se simplifica para usar el método subtotal()
de ItemCarrito
:
import java.util.List;
public class Carrito {
private List<ItemCarrito> items;
public double total() {
return this.items.stream()
.mapToDouble(ItemCarrito::subtotal) // Usa el método subtotal() de ItemCarrito
.sum();
}
}
Ejercicio 2.5
Malos Olores Identificados
-
Envidia de Características (Feature Envy):
- El método
getDireccionFormateada()
en la claseCliente
está utilizando intensamente los detalles internos de la claseDireccion
(sus atributoslocalidad
,calle
,numero
,departamento
) para crear la dirección formateada. Esto muestra queCliente
está haciendo trabajo que debería ser responsabilidad deDireccion
. - Este es un mal olor llamado “Feature Envy” porque la lógica relacionada con
Direccion
debería estar encapsulada dentro de la propia claseDireccion
.
- El método
-
Duplicación Potencial de Código (Potential Duplicate Code):
- El formato de la dirección puede necesitar reutilización en otros contextos (por ejemplo, otros métodos que requieran una dirección formateada). Si esto no se mueve a
Direccion
, existe la posibilidad de que esta lógica de formateo se duplique.
- El formato de la dirección puede necesitar reutilización en otros contextos (por ejemplo, otros métodos que requieran una dirección formateada). Si esto no se mueve a
-
Falta de Encapsulamiento (Lack of Encapsulation):
- El método
getDireccionFormateada()
no está encapsulando la lógica donde pertenece; la lógica de formateo de dirección debería estar en la claseDireccion
.
- El método
Refactorings Sugeridos
-
Mover Método (Move Method):
- Refactoring Aplicado: Mover el método
getDireccionFormateada()
deCliente
aDireccion
. - Justificación: Encapsula el comportamiento de formateo en la clase que contiene los datos necesarios (
Direccion
), mejorando la cohesión y reduciendo la envidia de características.
- Refactoring Aplicado: Mover el método
-
Renombrar Método para Mayor Claridad (Rename Method):
- Refactoring Aplicado: Renombrar
getDireccionFormateada()
aformatearDireccion()
para dejar claro que el método realiza una acción (formatear). - Justificación: Mejora la legibilidad del código y hace que el nombre del método sea más descriptivo.
- Refactoring Aplicado: Renombrar
Código Refactorizado
Clase Direccion
El método getDireccionFormateada()
se mueve a la clase Direccion
y se renombra a formatearDireccion()
:
public class Direccion {
private String localidad;
private String calle;
private String numero;
private String departamento;
// Refactoring: Move Method and Rename Method
public String formatearDireccion() {
return localidad + ", " + calle + ", " + numero + ", " + departamento;
}
// Getters y setters para los atributos (si es necesario)
public String getLocalidad() {
return localidad;
}
public String getCalle() {
return calle;
}
public String getNumero() {
return numero;
}
public String getDepartamento() {
return departamento;
}
}
Clase Cliente
El método getDireccionFormateada()
se elimina de Cliente
y se llama al método de Direccion
:
public class Cliente {
private Direccion direccion;
// Método para obtener la dirección formateada ahora delega a Direccion
public String getDireccionFormateada() {
return this.direccion.formatearDireccion(); // Refactoring: Delegación del método
}
// Otros métodos y atributos
}
Clase Supermercado
La clase Supermercado
permanece igual, ya que no presenta malos olores significativos. La única modificación es el nombre del método que se llama para obtener la dirección formateada:
import java.text.MessageFormat;
public class Supermercado {
public void notificarPedido(long nroPedido, Cliente cliente) {
String notificacion = MessageFormat.format(
"Estimado cliente, se le informa que hemos recibido su pedido con número {0}, el cual será enviado a la dirección {1}",
nroPedido, cliente.getDireccionFormateada()
);
// lo imprimimos en pantalla, podría ser un mail, SMS, etc..
System.out.println(notificacion);
}
}
Resultado Final
Con esta refactorización:
-
Eliminación de la “Envidia de Características” (Feature Envy): El método
formatearDireccion()
se encuentra ahora en la clase que contiene los datos necesarios (Direccion
), reduciendo la dependencia en los detalles internos de otras clases. -
Mejora de la Cohesión y Encapsulamiento: La clase
Direccion
ahora es más cohesiva, ya que incluye la lógica relevante para su propósito (formateo de la dirección). -
Código más legible y mantenible: La lógica se distribuye apropiadamente entre las clases, y el código es más fácil de entender y modificar.
Conclusión
Los malos olores de Envidia de Características y Falta de Encapsulamiento se resolvieron mediante los refactorings Mover Método (Move Method) y Renombrar Método (Rename Method). El resultado es un código más limpio, modular y fácil de mantener.
Ejercicio 2.6
Malos Olores Identificados
-
Condicionales Anidados (Long Conditional / Conditional Complexity):
- El método
calcularCostoPelicula()
en la claseUsuario
utiliza una serie de condicionales (if-else
) para determinar el costo de una película según el tipo de suscripción del usuario. Esto genera complejidad y es difícil de mantener.
- El método
-
Falta de Polimorfismo (Missing Polymorphism):
- En lugar de usar condicionales para decidir el comportamiento basado en el tipo de suscripción, se puede usar polimorfismo para encapsular el comportamiento en clases diferentes de suscripción.
-
Uso de Comparación de Strings con
==
(Primitive Obsession):- Comparar
String
usando==
es un error común en Java y puede llevar a un comportamiento inesperado. Debería utilizarse el método.equals()
para compararString
.
- Comparar
-
Acoplamiento Fuerte (Feature Envy):
- La clase
Usuario
sabe demasiado sobre cómo calcular los costos adicionales de una película. Esto es responsabilidad de la suscripción o de la propia clasePelicula
.
- La clase
Refactorings Aplicados con Nombres Específicos
-
Reemplazar Condicionales con Polimorfismo (Replace Conditional with Polymorphism):
- Refactoring Aplicado: Crear una jerarquía de clases para diferentes tipos de suscripciones (
Basico
,Familia
,Plus
,Premium
), cada una con su propia implementación del cálculo de costos. - Justificación: Esto elimina los condicionales y permite que cada tipo de suscripción maneje su propio comportamiento, siguiendo el principio de Abierto/Cerrado (Open/Closed Principle, OCP).
- Refactoring Aplicado: Crear una jerarquía de clases para diferentes tipos de suscripciones (
-
Extraer Jerarquía de Clases (Extract Class Hierarchy):
- Refactoring Aplicado: Crear una clase base
Subscripcion
y subclases derivadas comoBasico
,Familia
,Plus
, yPremium
que implementen el métodocalcularCosto(Pelicula pelicula)
. - Justificación: Encapsula el comportamiento de cada tipo de suscripción y hace que el código sea más fácil de mantener y expandir. Las subclases son responsables de su comportamiento específico.
- Refactoring Aplicado: Crear una clase base
-
Reemplazar Comparación de
String
con Polimorfismo (Replace String with Polymorphism):- Refactoring Aplicado: En lugar de comparar
String
para decidir el tipo de suscripción, se crea una jerarquía de clases que maneja estos comportamientos. - Justificación: Mejora la legibilidad y reduce errores debido a comparaciones incorrectas de
String
.
- Refactoring Aplicado: En lugar de comparar
-
Mover Método a la Clase Correcta (Move Method):
- Refactoring Aplicado: El método
calcularCostoPelicula()
que pertenece aUsuario
ahora se mueve a la jerarquía de clasesSubscripcion
. Esto se denomina Mover Método porque mueve la lógica al lugar correcto. - Justificación: Este refactoring ayuda a reducir el acoplamiento y la “envidia de características” (Feature Envy) entre las clases, y mejora la cohesión.
- Refactoring Aplicado: El método
Clase Usuario
La clase Usuario
ahora contiene una referencia a Subscripcion
, que maneja el cálculo del costo:
public class Usuario {
private Subscripcion subscripcion; // Reemplazamos tipoSubscripcion con una clase
// Refactoring: Encapsulate Field
public void setSubscripcion(Subscripcion subscripcion) {
this.subscripcion = subscripcion;
}
// Refactoring: Move Method
public double calcularCostoPelicula(Pelicula pelicula) {
return subscripcion.calcularCosto(pelicula); // Delegamos el cálculo a la suscripción
}
}
Clase Base Subscripcion
y Subclases Específicas
Creamos una clase base Subscripcion
y subclases para cada tipo de suscripción:
// Refactoring: Extract Class Hierarchy
public abstract class Subscripcion {
// Refactoring: Replace Conditional with Polymorphism
public abstract double calcularCosto(Pelicula pelicula);
}
// Refactoring: Extract Subclass for Basico
public class Basico extends Subscripcion {
@Override
public double calcularCosto(Pelicula pelicula) {
return pelicula.getCosto() + pelicula.calcularCargoExtraPorEstreno();
}
}
// Refactoring: Extract Subclass for Familia
public class Familia extends Subscripcion {
@Override
public double calcularCosto(Pelicula pelicula) {
return (pelicula.getCosto() + pelicula.calcularCargoExtraPorEstreno()) * 0.90;
}
}
// Refactoring: Extract Subclass for Plus
public class Plus extends Subscripcion {
@Override
public double calcularCosto(Pelicula pelicula) {
return pelicula.getCosto();
}
}
// Refactoring: Extract Subclass for Premium
public class Premium extends Subscripcion {
@Override
public double calcularCosto(Pelicula pelicula) {
return pelicula.getCosto() * 0.75;
}
}
Clase Pelicula
La clase Pelicula
permanece igual, ya que no presenta malos olores significativos:
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class Pelicula {
private String nombre;
private String genero;
private LocalDate fechaEstreno;
private double costo;
public double getCosto() {
return this.costo;
}
// Refactoring: Replace Conditional Logic with Method
public double calcularCargoExtraPorEstreno(){
// Si la Película se estrenó 30 días antes de la fecha actual, retorna un cargo de 0$, caso contrario, retorna un cargo extra de 300$
return (ChronoUnit.DAYS.between(this.fechaEstreno, LocalDate.now())) > 30 ? 0 : 300;
}
}
Resultado Final
Con esta refactorización:
-
Reemplazar Condicionales con Polimorfismo: Se han eliminado las estructuras condicionales complejas utilizando polimorfismo. Cada subclase de
Subscripcion
maneja su propio comportamiento. -
Extraer Jerarquía de Clases: Se ha creado una jerarquía de clases específica para representar cada tipo de suscripción, haciendo que el código sea más modular y mantenible.
-
Mover Método a la Clase Correcta: Se movió la lógica del cálculo de costos a las clases de suscripción correspondientes, mejorando la cohesión y reduciendo el acoplamiento.
Ejercicio 3
Requerimientos de Extensión
El diseño original tenía ciertas limitaciones que se hicieron evidentes con los nuevos requerimientos:
- Flexibilidad insuficiente: El diseño original no permite que una persona pueda tener múltiples roles (e.g., ser tanto
Profesor
comoMentor
). - Dificultad para agregar nuevos roles: En el diseño original, agregar un nuevo rol requeriría añadir nuevas subclases de
Person
, complicando la jerarquía de herencia.
Diseño Propuesto
-
Nuevas Clases:
Party
: Nueva clase abstracta que representa a una persona o entidad en el sistema.Role
: Nueva clase que representa un rol que puede tener unaParty
.Profesor
yStudent
ya no son subclases dePerson
sino que sonRole
.
-
Nuevas Relaciones:
Party
puede tener uno o muchosRole
.Course
se relaciona conRole
en lugar de conProfesor
oStudent
directamente.
¿Por qué la Transformación no es un Refactoring?
-
Se Añade Nueva Funcionalidad:
- El nuevo diseño introduce la capacidad de que una
Party
pueda tener múltiplesRole
al mismo tiempo. Por ejemplo, una persona ahora puede serProfesor
yMentor
, lo cual no era posible en el diseño original. - Añadir esta funcionalidad va más allá del simple refactoring porque un refactoring debe preservar el comportamiento existente sin agregar nuevas capacidades.
- El nuevo diseño introduce la capacidad de que una
-
Cambio en el Modelo Conceptual:
- El diseño original usaba una jerarquía de herencia para definir los roles (
Profesor
yStudent
) de unaPerson
. El diseño propuesto cambia este modelo a una composición deParty
yRole
, lo que modifica significativamente cómo se entienden y representan las relaciones entre los objetos. - Este cambio en la abstracción del modelo implica una nueva forma de operar y extender el sistema, más allá de lo que permite un refactoring.
- El diseño original usaba una jerarquía de herencia para definir los roles (
-
Impacto en la Extensibilidad y Mantenimiento:
- Aunque la propuesta mejora la extensibilidad y flexibilidad del sistema al permitir múltiples roles y facilita la adición de nuevos roles, estos cambios también introducen nuevas estructuras de datos y asociaciones que no existían en el diseño original. Esta mejora en la extensibilidad implica cambios significativos en cómo funciona el sistema.
Conclusión
La transformación propuesta no es un refactoring porque:
- Agrega nueva funcionalidad: Permite que una
Party
tenga múltiplesRole
, lo cual es una nueva capacidad que no existía en el diseño original. - Cambia la estructura conceptual del modelo: Introduce nuevas clases (
Party
yRole
) y modifica las relaciones existentes, lo que cambia el modelo conceptual del dominio. - Impacto en el comportamiento: Aunque la funcionalidad existente (e.g., cursos que tienen profesores y estudiantes) puede ser preservada, la forma de manejar esa funcionalidad cambia significativamente.
Alternativas de Diseño
En lugar de llamar a esto un “refactoring”, sería más preciso considerarlo como una reingeniería de diseño o rediseño del sistema que permite nuevas funcionalidades y mejora la extensibilidad del sistema. Esto es parte de un proceso de mejora continua, pero no cae bajo la definición estricta de “refactoring” que implica no cambiar el comportamiento observable del sistema.
Por lo tanto, la transformación es una mejora del diseño que agrega nuevas capacidades y flexibilidad, pero no puede considerarse un refactoring en sentido estricto.