De un traductor: Severin Peres publicó un artículo sobre el uso de principios SOLID en la programación para usted. La información del artículo será útil tanto para principiantes como para programadores con experiencia.Si es desarrollador, probablemente escuchó sobre los principios SOLID. Permiten al programador escribir código limpio, bien estructurado y fácil de mantener. Vale la pena señalar que en la programación hay varios enfoques sobre cómo realizar correctamente este o aquel trabajo. Diferentes especialistas tienen diferentes ideas y comprensión de la "forma correcta", todo depende de la experiencia de cada persona. Sin embargo, las ideas proclamadas en SOLID son aceptadas por casi todos los representantes de la comunidad de TI. Se convirtieron en el punto de partida para la aparición y el desarrollo de muchas buenas prácticas de gestión del desarrollo.
Veamos qué son los principios SÓLIDOS y cómo nos ayudan.
Skillbox recomienda: Curso práctico "Mobile Developer PRO" .
Le recordamos: para todos los lectores de "Habr": un descuento de 10.000 rublos al registrarse en cualquier curso de Skillbox con el código de promoción "Habr".
¿Qué es sólido?
Este término es una abreviatura, cada letra del término es el comienzo del nombre de un cierto principio:
Principio de responsabilidad exclusiva
El principio de responsabilidad única (SRP) establece que cada clase o módulo en un programa debe ser responsable de solo una parte de la funcionalidad de este programa. Además, los elementos de esta responsabilidad deben asignarse a su clase y no distribuirse entre clases no relacionadas. El desarrollador y principal evangelista de SRP, Robert S. Martin, describe la responsabilidad como la causa del cambio. Inicialmente, propuso este término como uno de los elementos de su trabajo, "Principios del diseño orientado a objetos". El concepto incluía gran parte del patrón de conectividad previamente definido por Tom Demarco.
El concepto también incluyó varios conceptos formulados por David Parnassus. Los dos principales son la encapsulación y la ocultación de información. Parnassus argumentó que dividir un sistema en módulos separados no debería basarse en un análisis de diagramas de flujo o flujos de ejecución. Cualquiera de los módulos debe contener una solución específica que proporcione un mínimo de información a los clientes.
Por cierto, Martin dio un ejemplo interesante con los gerentes senior de la empresa (COO, CTO, CFO), cada uno de los cuales utiliza software específico para negocios con un propósito diferente. Como resultado, cualquiera de ellos puede implementar cambios en el software sin afectar los intereses de otros gerentes.
Objeto divino
Como de costumbre, la mejor manera de aprender SRP es ver todo en acción. Veamos una sección de un programa que NO cumple con el principio de responsabilidad compartida. Este es el código Ruby que describe el comportamiento y los atributos de una estación espacial.
Mira el ejemplo e intenta determinar lo siguiente:
Las responsabilidades de aquellos objetos que se declaran en la clase SpaceStation.
Aquellos que puedan estar interesados en el trabajo de la estación espacial.
class SpaceStation def initialize @supplies = {} @fuel = 0 end def run_sensors puts "----- Sensor Action -----" puts "Running sensors!" end def load_supplies(type, quantity) puts "----- Supply Action -----" puts "Loading
En realidad, nuestra estación espacial no funciona (creo que no recibiré una llamada de la NASA en un futuro próximo), pero hay algo que analizar.
Entonces, la clase SpaceStation tiene varias responsabilidades (o tareas) diferentes. Todos ellos se pueden dividir en tipos:
- Sensores
- suministro (consumibles);
- combustible
- aceleradores
A pesar de que ninguno de los empleados de la estación está definido en la clase, podemos imaginar fácilmente quién es responsable de qué. Lo más probable es que el científico controle los sensores, el logístico es responsable del suministro de recursos, el ingeniero es responsable del suministro de combustible y el piloto controla los aceleradores.
¿Podemos decir que este programa no es compatible con SRP? Si por supuesto. Pero la clase SpaceStation es un típico "objeto divino" que lo sabe todo y lo hace todo. Este es el principal antipatrón en la programación orientada a objetos. Para un principiante, estos objetos son extremadamente difíciles de mantener. Hasta ahora, el programa es muy simple, sí, pero imagine lo que sucederá si agregamos nuevas funciones. Quizás nuestra estación espacial necesite un centro médico o una sala de reuniones. Y cuantas más funciones haya, más SpaceStation crecerá. Bueno, dado que este objeto estará conectado con otros, el mantenimiento de todo el complejo será aún más complicado. Como resultado, podemos interrumpir el trabajo de, por ejemplo, los aceleradores. Si un investigador solicita cambios en el trabajo con sensores, esto puede afectar los sistemas de comunicación de la estación.
La violación del principio SRP puede dar una victoria táctica a corto plazo, pero al final "perderemos la guerra", atender a un monstruo así en el futuro será muy difícil. Es mejor dividir el programa en secciones separadas de código, cada una de las cuales es responsable de realizar una operación específica. Con esto en mente, cambiemos la clase SpaceStation.
Compartir responsabilidadArriba, identificamos cuatro tipos de operaciones controladas por la clase SpaceStation. Al refactorizar, los tendremos en cuenta. El código actualizado coincide mejor con SRP.
class SpaceStation attr_reader :sensors, :supply_hold, :fuel_tank, :thrusters def initialize @supply_hold = SupplyHold.new @sensors = Sensors.new @fuel_tank = FuelTank.new @thrusters = Thrusters.new(@fuel_tank) end end class Sensors def run_sensors puts "----- Sensor Action -----" puts "Running sensors!" end end class SupplyHold attr_accessor :supplies def initialize @supplies = {} end def load_supplies(type, quantity) puts "----- Supply Action -----" puts "Loading
Hay muchos cambios, el programa ahora se ve definitivamente mejor. Ahora nuestra clase SpaceStation se ha convertido, más bien, en un contenedor en el que se inician las operaciones para las partes dependientes, incluido un conjunto de sensores, un sistema de suministro de consumibles, un tanque de combustible y refuerzos.
Para cualquiera de las variables ahora hay una clase correspondiente: sensores; SupplyHold; FuelTank Propulsores
Hay varios cambios importantes en esta versión del código. El hecho es que las funciones individuales no solo están encapsuladas en sus propias clases, sino que están organizadas de tal manera que se vuelven predecibles y consistentes. Agrupamos elementos similares en funcionalidad para seguir el principio de conectividad. Ahora, si necesitamos cambiar el principio del sistema cambiando de una estructura hash a una matriz, solo use la clase SupplyHold, no tendremos que tocar otros módulos. Por lo tanto, si el oficial a cargo de la logística cambia algo en su sección, los elementos restantes de la estación permanecerán intactos. Al mismo tiempo, la clase SpaceStation ni siquiera se dará cuenta de los cambios.
Es probable que los oficiales de nuestra estación espacial estén contentos con los cambios, porque pueden solicitar los que necesitan. Tenga en cuenta que el código tiene métodos como report_supplies y report_fuel contenidos en las clases SupplyHold y FuelTank. ¿Qué sucede si la Tierra pide cambiar la forma en que se generan los informes? Deberá cambiar ambas clases, SupplyHold y FuelTank. Pero, ¿qué sucede si necesita cambiar la forma en que entrega combustible y consumibles? Puede que tenga que cambiar las mismas clases nuevamente. Y esto es una violación del principio SRP. Vamos a arreglarlo
class SpaceStation attr_reader :sensors, :supply_hold, :supply_reporter, :fuel_tank, :fuel_reporter, :thrusters def initialize @sensors = Sensors.new @supply_hold = SupplyHold.new @supply_reporter = SupplyReporter.new(@supply_hold) @fuel_tank = FuelTank.new @fuel_reporter = FuelReporter.new(@fuel_tank) @thrusters = Thrusters.new(@fuel_tank) end end class Sensors def run_sensors puts "----- Sensor Action -----" puts "Running sensors!" end end class SupplyHold attr_accessor :supplies attr_reader :reporter def initialize @supplies = {} end def get_supplies @supplies end def load_supplies(type, quantity) puts "----- Supply Action -----" puts "Loading
En esta última versión del programa, las responsabilidades se dividieron en dos nuevas clases, FuelReporter y SupplyReporter. Ambos son hijos de la clase Reportero. Además, agregamos variables de instancia a la clase SpaceStation para inicializar la subclase necesaria si es necesario. Ahora, si la Tierra decide cambiar algo más, haremos cambios en las subclases, y no en la clase principal.
Por supuesto, algunas clases aquí todavía dependen unas de otras. Por lo tanto, el objeto SupplyReporter depende de SupplyHold y FuelReporter depende de FuelTank. Por supuesto, los refuerzos deben estar conectados al tanque de combustible. Pero aquí todo parece lógico, y hacer cambios no será especialmente difícil: editar el código de un objeto no afectará demasiado al otro.
Por lo tanto, creamos un código modular donde las responsabilidades de cada uno de los objetos / clases se definen con precisión. Trabajar con dicho código no es un problema; su mantenimiento será una tarea simple. Todo el "objeto divino" lo hemos convertido a SRP.
Skillbox recomienda: