Mi experiencia de errores

Mi experiencia de errores


Lista de errores


  1. Clase omnipotente MCManager
  2. Inventando nuestra navegación entre pantallas
  3. Nunca hay mucha herencia
  4. Arquitectura de nuestra propia producción o continuamos creando bicicletas.
  5. MVVM con alma MVP
  6. El segundo intento con navegación o enrutador y la curvatura de la navegación.
  7. Gerente persistente

Muchas personas, incluyéndome a mí, escriben cómo hacer lo correcto en una situación dada, cómo escribir código correctamente, cómo aplicar soluciones arquitectónicas, etc. Pero me gustaría compartir mi experiencia de cómo se hizo incorrectamente y las conclusiones que yo hecho en base a sus errores. Lo más probable es que estos sean errores comunes de todos los que siguen el camino del desarrollador, o tal vez algo sea nuevo. Solo quiero compartir mi experiencia y leer los comentarios de otros chicos.

Clase omnipotente MCManager


Después del primer año de trabajo en TI, y más específicamente en el desarrollo de iOS, decidí que ya era lo suficientemente arquitecto y estaba bastante listo para crear. Incluso entonces, entendí intuitivamente que era necesario separar la lógica empresarial de la capa de presentación. Pero la calidad de mi idea de cómo hacer esto estaba lejos de la realidad.

Me mudé a un nuevo lugar de trabajo, donde me asignaron a desarrollar independientemente una nueva característica para un proyecto existente. Fue un análogo a la grabación de videos en Instagram, donde la grabación se realiza mientras el usuario mantiene el dedo sobre el botón, y luego varios fragmentos del video se conectan entre sí. Inicialmente, se decidió hacer esta característica como un proyecto separado, o más bien en forma de muestra. Esto, según tengo entendido, comienza la fuente de mis problemas arquitectónicos, que duraron más de un año.

En el futuro, esta muestra se convirtió en una aplicación completa para grabar y editar videos. Es curioso que la muestra originalmente tuviera un nombre, de la abreviatura de la cual se recolectó el prefijo MC. Aunque el proyecto pronto fue renombrado, pero el prefijo requerido por la convención de nombres en Objective-C, MC permaneció. Así nació la clase todopoderosa MCManager.



Como era una muestra y al principio la funcionalidad era simple, decidí que una clase de administrador sería suficiente. La funcionalidad, como mencioné anteriormente, incluyó la grabación de un fragmento de video con opciones de inicio / parada y la combinación de estos fragmentos en un video completo. Y en ese mismo momento puedo nombrar mi primer error: el nombre de la clase MCManager. MCManager, Karl! ¿Qué debe decir un nombre de clase a otros desarrolladores sobre su propósito, sus capacidades y cómo usarlo? Correcto, absolutamente nada! Y esto está en el apéndice, cuyo nombre ni siquiera contiene las letras M y, su madre, C. Aunque, este no es mi error principal, ya que la clase con el nombre partidista hizo todo, todo desde la palabra es absolutamente todo, que fue el error clave.

La grabación de video es un servicio pequeño, la administración del almacenamiento de archivos de video en el sistema de archivos es el segundo servicio adicional para combinar varios videos en uno. El trabajo de estos tres servicios independientes, se decidió combinar en un solo gerente. La idea era noble, utilizando el patrón de fachada, para crear una interfaz simple para la lógica empresarial y ocultar todos los detalles innecesarios sobre la interacción de varios componentes. En las etapas iniciales, incluso el nombre de tal clase de fachada no levantó ninguna sospecha, especialmente en la muestra.
Pero al cliente le gustó la demostración y pronto la muestra se convirtió en una aplicación completa. Puede justificar que no hubo tiempo suficiente para refactorizar, que el cliente no quería rehacer el código de trabajo, pero, francamente, en ese momento yo mismo pensé que había establecido una excelente arquitectura. En verdad, la idea de separar la lógica comercial y la presentación fue un éxito. La arquitectura era una clase de MCManager singleton, que era la fachada de un par de docenas de servicios y otros gerentes. Sí, también era un singleton, que estaba disponible desde todos los rincones de la aplicación.

Ya se puede entender la escala de todo el desastre. Una clase con varios miles de líneas de código que es difícil de leer y difícil de mantener. Ya estoy en silencio sobre la posibilidad de resaltar características individuales para transferirlas a otra aplicación, lo cual es bastante común en el desarrollo móvil.
Las conclusiones que hice después de un tiempo no son crear clases universales con nombres oscuros. Me di cuenta de que la lógica debe romperse en pedazos y no crear una interfaz universal para todo. De hecho, este fue un ejemplo de lo que sucedería si no se adhiere a uno de los principios de SOLID, el Principio de segregación de interfaz.

Inventando nuestra navegación entre pantallas


La separación de la lógica y la interfaz no es el único problema que me preocupa sobre el proyecto anterior. No diré que en ese momento iba a separar el código de pantalla y el código de navegación, pero resultó que se me ocurrió mi bicicleta para la navegación.



La muestra tenía solo tres pantallas: un menú con una tabla de videos grabados, una pantalla de grabación y una pantalla de procesamiento posterior. Para no preocuparme de que la pila de navegación contenga ViewControllers duplicados, decidí no usar UINavigationController. Agregué el RootViewcontroller, los lectores atentos ya han adivinado que era el MCRootViewController, que se configuró como el principal en la configuración del proyecto. Al mismo tiempo, el controlador raíz no era una de las pantallas de la aplicación, solo presentaba el UIViewController deseado. Como si esto no fuera suficiente, el controlador raíz también era un delegado de todos los controladores representados. Como resultado, en cada momento en el tiempo solo había dos vc en la jerarquía, y toda la navegación se implementó utilizando el patrón de delegado.

Cómo se veía: cada pantalla tenía su propio protocolo delegado, donde se indicaban los métodos de navegación, y el controlador raíz implementó estos métodos y cambió las pantallas. RootViewController desestimó el controlador actual, creó uno nuevo y lo presentó, mientras que era posible transferir información de una pantalla a otra. Afortunadamente, la lógica de negocios estaba en la mejor clase de singleton, por lo que ninguna de las pantallas almacenaba nada y podía destruirse sin dolor. Una vez más, se realizó una buena intención, aunque la realización cojeó en ambas piernas, y a veces tropezó.

Como puede suponer, si necesita pasar de la pantalla de grabación de video al menú principal, el método se denominó:

- (void)cancel; 

o algo así, y el controlador raíz ya está haciendo todo el trabajo sucio.
Al final, MCRootViewController, se convirtió en un análogo de MCManager, pero en la navegación entre pantallas, al igual que con el crecimiento de la aplicación y la adición de nuevas funcionalidades, se agregaron nuevas pantallas.

La fábrica de bicicletas funcionaba inexorablemente y seguí ignorando los artículos sobre arquitecturas de aplicaciones móviles. Pero nunca renuncié a la idea de separar la navegación de las pantallas.

La ventaja era que las pantallas eran independientes y podían reutilizarse, pero esto no es exacto. Pero las desventajas incluyen la dificultad para mantener tales clases. El problema con la falta de una pila de pantallas cuando necesita regresar desplazándose por las pantallas previamente seleccionadas. La compleja lógica de transición entre pantallas, el controlador raíz afectó parte de la lógica de negocios para mostrar correctamente una nueva pantalla.

En general, no debe implementar toda la navegación en la aplicación de esta manera, ya que mi MCRootViewController violó el principio de principio abierto-cerrado. Es casi imposible expandirse, y todos los cambios deben hacerse constantemente a la clase misma.
Comencé a leer más sobre la navegación entre pantallas en una aplicación móvil, me familiaricé con enfoques como Router y Coordinator. Escribiré sobre Router un poco más tarde, ya que hay algo para compartir.

Nunca hay mucha herencia


También quiero compartir no solo mis perlas, sino también los enfoques y soluciones divertidas de otras personas con las que tuve que lidiar. En el mismo lugar donde creé mis obras maestras, me confiaron una tarea simple. La tarea consistía en agregar una pantalla de otro proyecto a mi proyecto. Como determinamos con PM, después de un análisis superficial y un poco de reflexión, esto debería haber tomado dos o tres horas y no más, porque lo que está mal con esto, solo necesita agregar una clase de pantalla preparada a su aplicación. De hecho, todo ya se ha hecho por nosotros, necesitamos hacer ctrl + c y ctrl + v. Aquí hay solo un pequeño matiz, el desarrollador que escribió esta aplicación realmente amaba la herencia.



Rápidamente encontré el ViewController que necesitaba, tuve la suerte de que no había separación de lógica y presentación. Era un buen enfoque antiguo, cuando el controlador contenía todo el código necesario. Lo copié en mi proyecto y comencé a descubrir cómo hacerlo funcionar. Y lo primero que descubrí es que el controlador que necesito hereda de otro controlador. Una cosa común, un evento bastante esperado. Como no tenía mucho tiempo, encontré la clase que necesitaba y la arrastré a mi proyecto. Bueno, ahora debería funcionar, pensé, ¡y nunca me había equivocado tanto!

La clase que necesitaba no solo tenía muchas variables de clases personalizadas que también debían copiarse en mi proyecto, por lo que cada una de ellas heredó algo. A su vez, las clases base eran campos heredados o contenidos con tipos personalizados que, como muchos habrían adivinado, heredaron algo, y esto, desafortunadamente, no era NSObject, UIViewController o UIView. Por lo tanto, un buen tercio del proyecto innecesario migró a mí en el proyecto.

Como no se esperaba mucho tiempo para completar esta tarea, no vi otra manera de simplemente agregar las clases necesarias que xCode requería simplemente para iniciar mi proyecto sin problemas. Como resultado, dos o tres horas se prolongaron un poco, ya que al final tuve que profundizar en toda la red de la jerarquía de la herencia, como un verdadero escultor, cortando el exceso.

Como resultado, llegué a la conclusión de que todas las cosas buenas deberían ser con moderación, incluso algo tan "maravilloso" como la herencia. Entonces comencé a comprender las desventajas de la herencia. Concluí por mí mismo, si quiero hacer módulos reutilizables, debería hacerlos más independientes.

Arquitectura de nuestra propia producción o continuamos creando bicicletas.


Al mudarme a un nuevo lugar de trabajo y comenzar un nuevo proyecto, tomé en cuenta toda la experiencia disponible en el diseño de arquitectura y continué creando. Naturalmente, seguí ignorando las arquitecturas ya inventadas, pero al mismo tiempo me adherí persistentemente al principio de "divide y vencerás".

No pasó mucho tiempo antes del advenimiento de Swift, así que profundicé en las posibilidades de Objective-c. Decidí hacer una inyección de dependencia usando las características del lenguaje. Me inspiró la herramienta de expansión lib; ni siquiera recuerdo su nombre.

La conclusión es: en la clase base BaseViewController, agregué un campo de clase BaseViewModel. En consecuencia, para cada pantalla creé mi propio controlador, que heredó los básicos, y agregué un protocolo para que el controlador interactúe con viewModel. Luego vino la magia. Redefiní las propiedades de viewModel y agregué soporte para el protocolo deseado. A su vez, creé una nueva clase ViewModel para una pantalla particular que implementó este protocolo. Como resultado, en BaseViewController en el método viewDidLoad, verifiqué el tipo de protocolo del modelo, verifiqué la lista de todos los descendientes de BaseViewModel, encontré la clase que necesitaba y creé el viewModel del tipo que necesitaba.

Ejemplo de ViewController básico
 #import <UIKit/UIKit.h> // MVC model #import "BaseMVCModel.h" @class BaseViewController; @protocol BaseViewControllerDelegate <NSObject> @required - (void)backFromNextViewController:(BaseViewController *)aNextViewController withOptions:(NSDictionary *)anOptionsDictionary; @end @interface BaseViewController : UIViewController <BaseViewControllerDelegate> @property (nonatomic, weak) BaseMVCModel *model; @property (nonatomic, assign) id<BaseViewControllerDelegate> prevViewController; - (void)backWithOptions:(NSDictionary *)anOptionsDictionary; + (void)setupUIStyle; @end import "BaseViewController.h" // Helpers #import "RuntimeHelper.h" @interface BaseViewController () @end @implementation BaseViewController + (void)setupUIStyle { } #pragma mark - #pragma mark Life cycle - (void)viewDidLoad { [super viewDidLoad]; self.model = [BaseMVCModel getModel:FindPropertyProtocol(@"model", [self class])]; } #pragma mark - #pragma mark Navigation - (void)backWithOptions:(NSDictionary *)anOptionsDictionary { if (self.prevViewController) { [self.prevViewController performSelector:@selector(backFromNextViewController:withOptions:) withObject:self withObject:anOptionsDictionary]; } } #pragma mark - #pragma mark Seque - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.destinationViewController isKindOfClass:[BaseViewController class]] { ((BaseViewController *)segue.destinationViewController).prevViewController = self; } } #pragma mark - #pragma mark BaseViewControllerDelegate - (void)backFromNextViewController:(BaseViewController *)aNextViewController withOptions:(NSDictionary *)anOptionsDictionary { [self doesNotRecognizeSelector:_cmd]; } @end 


Ejemplo de ViewModel básico
 #import <Foundation/Foundation.h> @interface BaseMVCModel : NSObject @property (nonatomic, assign) id delegate; + (id)getModel:(NSString *)someProtocol; @end #import "BaseMVCModel.h" // IoC #import "IoCContainer.h" @implementation BaseMVCModel + (id)getModel:(NSString *)someProtocol { return [[IoCContainer sharedIoCContainer] getModel:NSProtocolFromString(someProtocol)]; } @end 


Clases de ayuda
 #import <Foundation/Foundation.h> @interface IoCContainer : NSObject + (instancetype)sharedIoCContainer; - (id)getModel:(Protocol *)someProtocol; @end #import "IoCContainer.h" // Helpers #import "RuntimeHelper.h" // Models #import "BaseMVCModel.h" @interface IoCContainer () @property (nonatomic, strong) NSMutableSet *models; @end @implementation IoCContainer #pragma mark - #pragma mark Singleton + (instancetype)sharedIoCContainer { static IoCContainer *_sharedIoCContainer = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedIoCContainer = [IoCContainer new]; }); return _sharedIoCContainer; } - (id)getModel:(Protocol *)someProtocol { if (!someProtocol) { return [BaseMVCModel new]; } NSArray *modelClasses = ClassGetSubclasses([BaseMVCModel class]); __block Class currentClass = NULL; [modelClasses enumerateObjectsUsingBlock:^(Class class, NSUInteger idx, BOOL *stop) { if ([class conformsToProtocol:someProtocol]) { currentClass = class; } }]; if (currentClass == nil) { return [BaseMVCModel new]; } __block BaseMVCModel *currentModel = nil; [self.models enumerateObjectsUsingBlock:^(id model, BOOL *stop) { if ([model isKindOfClass:currentClass]) { currentModel = model; } }]; if (!currentModel) { currentModel = [currentClass new]; [self.models addObject:currentModel]; } return currentModel; } - (NSMutableSet *)models { if (!_models) { _models = [NSMutableSet set]; } return _models; } @end #import <Foundation/Foundation.h> NSString * FindPropertyProtocol(NSString *propertyName, Class class); NSArray * ClassGetSubclasses(Class parentClass); #import "RuntimeHelper.h" #import <objc/runtime.h> #pragma mark - #pragma mark Functions NSString * FindPropertyProtocol(NSString *aPropertyName, Class class) { unsigned int propertyCount; objc_property_t *properties = class_copyPropertyList(class, &propertyCount); for (unsigned int i = 0; i < propertyCount; i++) { objc_property_t property = properties[i]; const char *propertyName = property_getName(property); if ([@(propertyName) isEqualToString:aPropertyName]) { const char *attrs = property_getAttributes(property); NSString* propertyAttributes = @(attrs); NSScanner *scanner = [NSScanner scannerWithString: propertyAttributes]; [scanner scanUpToString:@"<" intoString:NULL]; [scanner scanString:@"<" intoString:NULL]; NSString* protocolName = nil; [scanner scanUpToString:@">" intoString: &protocolName]; return protocolName; } } return nil; } NSArray * ClassGetSubclasses(Class parentClass) { int numClasses = objc_getClassList(NULL, 0); Class *classes = NULL; classes = (Class *)malloc(sizeof(Class) * numClasses); numClasses = objc_getClassList(classes, numClasses); NSMutableArray *result = [NSMutableArray array]; for (NSInteger i = 0; i < numClasses; i++) { Class superClass = classes[i]; do { superClass = class_getSuperclass(superClass); } while(superClass && superClass != parentClass); if (superClass == nil) { continue; } [result addObject:classes[i]]; } free(classes); return result; } 


Ejemplo de pantalla de inicio de sesión
 #import "BaseViewController.h" @protocol LoginProtocol <NSObject> @required - (void)login:(NSString *)aLoginString password:(NSString *)aPasswordString completionBlock:(DefaultCompletionBlock)aCompletionBlock; @end @interface LoginVC : BaseViewController @end #import "LoginVC.h" #import "UIViewController+Alert.h" #import "UIViewController+HUD.h" @interface LoginVC () @property id<LoginProtocol> model; @property (weak, nonatomic) IBOutlet UITextField *emailTF; @property (weak, nonatomic) IBOutlet UITextField *passTF; @end @implementation LoginVC @synthesize model = _model; #pragma mark - #pragma mark IBActions - (IBAction)loginAction:(id)sender { [self login]; } #pragma mark - #pragma mark UITextFieldDelegate - (BOOL)textFieldShouldReturn:(UITextField *)textField { if (textField == self.emailTF) { [self.passTF becomeFirstResponder]; } else { [self login]; } return YES; } #pragma mark - #pragma mark Login - (void)login { NSString *email = self.emailTF.text; NSString *pass = self.passTF.text; if (email.length == 0 || pass.length == 0) { [self showAlertOkWithMessage:@"Please, input info!"]; return; } __weak __typeof(self)weakSelf = self; [self showHUD]; [self.model login:self.emailTF.text password:self.passTF.text completionBlock:^(BOOL isDone, NSError *anError) { [weakSelf hideHUD]; if (isDone) { [weakSelf backWithOptions:nil]; } }]; } @end 


De una manera tan directa, hice una inicialización perezosa de viewModel y conecté mal la vista con los modelos a través de protocolos. Con todo esto, en ese momento todavía no sabía nada sobre la arquitectura MVP, aunque algo similar se cernía sobre mí.
La navegación entre las pantallas se dejó a discreción de "viewModel", ya que agregué un enlace débil al controlador.

Recordando esta implementación ahora, no puedo decir con certeza si todo estuvo mal. La idea de separar capas fue un éxito, se simplificó el momento de crear y asignar modelos al controlador.
Pero por mí mismo, decidí aprender más sobre enfoques y arquitecturas ya hechas, ya que durante el desarrollo de una aplicación con mi propia arquitectura, tuve que lidiar con muchos matices. Por ejemplo, reutilización de pantallas y modelos, herencia, transiciones complejas entre pantallas. En ese momento, me pareció que viewModel era parte de la lógica empresarial, aunque ahora entiendo que todavía es una capa de presentación. Obtuve una gran experiencia durante este experimento.

MVVM con alma MVP


Habiendo adquirido experiencia, decidí elegir una arquitectura específica para mí y seguirla, en lugar de inventar bicicletas. Comencé a leer más sobre arquitecturas, a estudiar en detalle lo popular en ese momento y me decidí por MVVM. Francamente, no entendí de inmediato su esencia, pero la elegí porque me gustó el nombre.

No entendí de inmediato la esencia de la arquitectura y la relación entre ViewModel y View (ViewController), pero comencé a hacer lo que entendía. Los ojos tienen miedo y las manos escriben frenéticamente el código.

En mi defensa, agregaré que en ese momento el tiempo y el tiempo para pensar y analizar la creación que creé eran muy ajustados. Por lo tanto, en lugar de carpetas, hice enlaces directos en el ViewModel a la Vista correspondiente. Y ya en ViewModel, hice la configuración de la presentación.

Sobre MVP, tuve la misma idea que sobre otras arquitecturas, así que creí firmemente que era MVVM, en el que ViewModel resultó ser la presentación más real.

Un ejemplo de mi arquitectura "MVVM" y sí, me gustó la idea con RootViewController, que es responsable del más alto nivel de navegación en la aplicación. Sobre el enrutador se escribe a continuación.
 import UIKit class RootViewController: UIViewController { var viewModel: RootViewModel? override func viewDidLoad() { super.viewDidLoad() let router = (UIApplication.shared.delegate as? AppDelegate)!.router viewModel = RootViewModel(with: self, router: router) viewModel?.setup() } } import UIKit protocol ViewModelProtocol: class { func setup() func backAction() } class RootViewModel: NSObject, ViewModelProtocol { unowned var router : RootRouter unowned var view: RootViewController init(with view: RootViewController, router: RootRouter) { self.view = view self.router = router } // MARK: - ViewModelProtocol func setup() { if AccountManager.shared.isLoggedIn() { router.route(to: RootRoutes.launch.rawValue, from: view, parameters: nil) } else { router.route(to: RootRoutes.loginregistartion.rawValue, from: view, parameters: nil) } } func backAction() { } } 


Esto no afectó particularmente la calidad del proyecto, ya que se respetó el orden y un enfoque único. Pero la experiencia fue invaluable. Después de las bicicletas que creé, finalmente comencé a hacerlo según la arquitectura generalmente aceptada. A menos que los presentadores se llamaran presentadores, lo que podría confundir a un desarrollador externo.

Decidí que en el futuro vale la pena hacer pequeños proyectos de prueba, para profundizar en la esencia de un enfoque particular en el diseño con más detalle. Por así decirlo, primero siente en la práctica y luego entra en la batalla. Tal es la conclusión que hice por mí mismo.

El segundo intento con navegación o enrutador y la curvatura de la navegación.


En el mismo proyecto, donde implementé MVVM valiente e ingenuamente, decidí probar un nuevo enfoque en la navegación entre pantallas. Como mencioné anteriormente, todavía me adhiero a la idea de separar las pantallas y la lógica de transición entre ellas.

Leyendo sobre MVVM, estaba interesado en un patrón como Router. Después de revisar la descripción nuevamente, comencé a implementar la solución en mi proyecto.

Ejemplo de enrutador
 import UIKit protocol Router: class { func route(to routeID: String, from view: UIViewController, parameters: Any?) func back(from view: UIViewController, parameters: Any?) } extension Router { func back(from view: UIViewController, parameters: Any?) { let navigationController: UINavigationController = checkNavigationController(for: view) navigationController.popViewController(animated: false) } } enum RootRoutes: String { case launch = "Launch" case loginregistartion = "LoginRegistartionRout" case mainmenu = "MainMenu" } class RootRouter: Router { var loginRegistartionRouter: LoginRegistartionRouter? var mainMenuRouter: MainMenuRouter? // MARK: Router func route(to routeID: String, from view: UIViewController, parameters: Any?) { var rootView = view if view is EPLaunchViewController { rootView = (view.navigationController?.viewControllers.first)! view.navigationController?.popViewController(animated: false) } if routeID == RootRoutes.loginregistartion.rawValue { loginRegistartionRouter = LoginRegistartionRouter(with: self) loginRegistartionRouter?.route(to: LRRouteID.phoneNumber.rawValue, from: rootView, parameters: nil) } else if routeID == RootRoutes.mainmenu.rawValue { if mainMenuRouter == nil { mainMenuRouter = MainMenuRouter(with: self) } mainMenuRouter?.route(to: MMRouteID.start.rawValue, from: rootView, parameters: nil) } else if routeID == RootRoutes.launch.rawValue { let storyboard = UIStoryboard(name: "RootStoryboard", bundle: nil) let launchView = storyboard.instantiateViewController(withIdentifier: "LaunchViewController") as! LaunchViewController let navigationController: UINavigationController = checkNavigationController(for: view) launchView.viewModel = LaunchViewModel(with: launchView, router: self) navigationController.pushViewController(launchView, animated: false) } } } 


La falta de experiencia en la implementación de este patrón se ha hecho sentir. Todo parecía estar ordenado y claro, el enrutador creó una nueva clase UIViewController, creó un ViewModel para él y ejecutó la lógica para cambiar a esta pantalla. Pero aún así, muchas deficiencias se hicieron sentir.
Comenzaron a surgir dificultades cuando era necesario abrir una aplicación con una pantalla determinada después de una notificación push. Como resultado, en algunos lugares obtuvimos una lógica confusa para elegir la pantalla correcta y más dificultades para respaldar dicho enfoque.

No abandoné la idea de implementar Router, pero continué en esta dirección, ganando más y más experiencia. No renuncies a algo después del primer intento fallido.

Gerente persistente


Otra clase de gerente interesante en mi práctica. Pero este es relativamente joven. De todos modos, el proceso de desarrollo consiste en prueba y error, y dado que todos, bueno, o la mayoría de nosotros, estamos constantemente en el proceso de desarrollo, siempre aparecen errores.

La esencia del problema es esta, la aplicación tiene servicios que deben colgarse constantemente y al mismo tiempo deben estar disponibles en muchos lugares.

Ejemplo: determinar el estado de Bluetooth. En mi aplicación, en varios servicios necesito comprender si bluetooth está activado o desactivado y suscribirme a las actualizaciones de estado. Como hay varios lugares de este tipo: un par de pantallas, varios administradores de lógica de negocios adicionales, etc., es necesario que cada uno de ellos se suscriba al delegado CBPeripheralManager (o CBCentralManager).

La solución parece ser obvia, creamos una clase separada que monitorea el estado del bluetooth y notifica a todos los que lo necesitan a través del patrón Observador. Pero entonces surge la pregunta, ¿quién almacenará permanentemente este servicio? ¡Lo primero que viene a la mente en este momento es hacerlo un singleton! ¡Todo parece estar bien!

Pero aquí llega el momento en que más de uno de estos servicios se ha acumulado en mi aplicación. Tampoco quiero hacer 100500 singletones en el proyecto.

Y luego se encendió otra luz sobre mi pequeña cabeza ya brillante. Cree un singleton que almacene todos esos servicios y brinde acceso a ellos en toda la aplicación. Y así nació el "gerente permanente". Con el nombre, no pensé por mucho tiempo y lo llamé, como todos ya podían adivinar, PersistentManager.

Como puede ver, también tengo un enfoque muy original para nombrar clases. Creo que necesito agregar una moda sobre el nombre de las clases en mi plan de desarrollo.

El tema clave en esta implementación es el singleton, que está disponible en cualquier parte del proyecto. Y esto lleva al hecho de que los gerentes que usan uno de los servicios permanentes acceden a él dentro de sus métodos, lo cual no es obvio. Por primera vez, me encontré con esto cuando estaba haciendo una gran característica compleja en un proyecto de demostración por separado y transfiriendo parte de la lógica empresarial del proyecto principal. Luego comencé a recibir mensajes con errores sobre servicios faltantes.

La conclusión que hice después de esto es que necesita diseñar sus clases de tal manera que no haya dependencias ocultas. Los servicios necesarios se deben pasar como parámetros al inicializar la clase, pero no utilizando un singleton, al que se puede acceder desde cualquier lugar. Y aún más bonito, vale la pena hacerlo usando protocolos.
Esto resultó ser otra confirmación de la falta de un patrón singleton.

Resumen


Y eso, no me quedo quieto, sino que sigo adelante, dominando nuevos enfoques en la programación. Lo principal es moverse, buscar y experimentar. Los errores siempre serán, no hay escapatoria de esto. Pero solo por el reconocimiento de sus errores, uno puede desarrollarse cualitativamente.



En la mayoría de los casos, los problemas son las superclases que hacen mucho o las dependencias incorrectas entre las clases. Lo que sugiere que es necesario descomponer la lógica de manera más competente.

Source: https://habr.com/ru/post/451996/


All Articles