diseño de software orientado a objetos

Recientemente, encontré algunos problemas en el proceso de desarrollo del proyecto y descubrí que en cada ola del proceso de desarrollo iterativo, a menudo es necesario modificar el código anterior. Aunque tal situación es normal, los nuevos requisitos inevitablemente traerán nuevas funciones y nuevos diseños. .Provoca que el código anterior se vea afectado. Recuerdo haber visto un chiste:

"No necesitas usar un arma para matar a un programador, solo cambia los requisitos tres veces"

De hecho, el diseño de requisitos es un aspecto Además, como diseñadores y desarrolladores, a veces necesitamos reflexionar sobre si el diseño del código es razonable, por qué es tan difícil expandir nuevas funciones en el código original y por qué nuestro código es tan inestable Mover todo el cuerpo a la vez?
  Creo que para ser programador, al menos no una persona estúpida. Si quieres completar una función, siempre puedes encontrar la manera de lograrlo (de lo contrario, te habrían despedido~), pero la forma de lograrlo es buena o mal, pero creo que las ideas se pueden guiar, el desarrollo de software no acaba de comenzar, existe desde hace un tiempo, podemos absorber algo de experiencia y lecciones de nuestros predecesores para mejorar, como "Patrones de diseño: la base de reutilizable" de GOF. Software orientado a objetos", ayúdanos a resumir las soluciones a muchos problemas. Durante este tiempo, también pasé un tiempo aprendiendo algunas ideas de diseño orientado a objetos y también hablé sobre algunos de mis conocimientos.
  Cuando se trata de patrones de diseño, creo que muchas personas han leído algunos libros en esta área, pero no sé si tendrán la misma confusión que yo: lo entiendo cuando lo leo, pero no puedo integrarlo. en el desarrollo real, y poco a poco lo aprenderé más tarde. Vergonzoso, tal vez solo vemos la superficie de cierto patrón, pero algunas "verdades" ocultas detrás del patrón no han sido descubiertas. ¿Qué problema resuelve este patrón? De hecho, detrás del patrón de diseño hay que seguir un determinado principio de diseño.

"Más importante que los patrones de diseño son los principios de diseño"

Todo el mundo conoce el concepto de diseño orientado a objetos. Su objetivo de diseño es esperar que el sistema de software pueda hacer lo siguiente:

  • Extensible: las nuevas funciones se pueden agregar fácilmente a los sistemas existentes sin afectar el original
  • Modificable: Al modificar una determinada parte del código, no afectará a otras partes no relacionadas
  • Sustituible: cuando una determinada parte del código en el sistema se reemplaza con otras clases con la misma interfaz, no afectará el sistema existente

Estos pocos se pueden utilizar para detectar si nuestro sistema de software está diseñado razonablemente, y cómo diseñar un sistema de software que sea fácil de mantener y ampliar es un principio de diseño que se puede seguir.Robert C. Martin propuso cinco principios básicos de diseño orientado a objetos (SÓLIDO):

  • S - Principio de Responsabilidad Única
  • O - Principio abierto cerrado
  • L - Principio de sustitución de Liskov
  • Principio de segregación de interfaz I
  • D - Principio de inversión de dependencia

Debemos tener en cuenta estos principios al hacer un diseño orientado a objetos, lo que puede convertirlo en un mejor desarrollador de diseño --- al menos su código no será tan malo, entendamos brevemente estos principios.

Principio de Responsabilidad Única: Principio de Responsabilidad Única

Una clase tiene una y solo una responsabilidad, y solo una razón para su cambio.

En pocas palabras, una clase solo puede hacer una cosa bien y no le importan las cosas que no tienen nada que ver con ella misma. Los perros y los ratones se entrometen en sus propios asuntos. El núcleo es el desacoplamiento y la alta cohesión. Este principio parece muy simple. Incluso si no conocemos este principio al escribir código, nos moveremos más cerca de esta dirección y escribiremos clases con funciones relativamente únicas. Sin embargo, este principio es fácil de violar, porque por alguna razón, el original la función es única La clase debe refinarse en responsabilidad 1 y responsabilidad 2 de grano más pequeño, por lo que en cada proceso de iteración, puede ser necesario reorganizar el código escrito antes de la refactorización y encapsular diferentes responsabilidades en diferentes clases o módulos.
Toma una castaña:

 

@interface DataTransfer : NSObject
-(void)upload:(NSData *)data; //上传数据
-(void)download(NSString*)url;  //根据URL下载东西
@end

DataTransfer incluye funciones de carga y descarga. Después de una cuidadosa consideración, se puede encontrar que esto es equivalente a implementar dos funciones, una es responsable de la lógica relacionada de carga y la otra es responsable de la lógica de descarga. Estas dos funciones son relativamente opuesto Cuando una función cambia, por ejemplo, antes usamos AFNetworking, pero ahora queremos cambiar a otros terceros o nsurlconnection para cargar y descargar:

  • Los cambios en el método de carga conducen a cambios en DataTransfer
  • Los cambios en el método de descarga conducen a cambios en DataTransfer

Esto viola el principio de responsabilidad única, por lo que las diferentes funciones deben desmontarse en dos clases diferentes para ser responsables de sus respectivas responsabilidades. Sin embargo, la granularidad de este desmontaje puede variar según la persona. A veces, no es necesario desmontarlo también. Cuidadosamente No se convirtió en diseño para diseño.

 

única responsabilidad

 

  En nuestros proyectos, a menudo vemos mucho código que viola este principio, y la violación es más obvia. Muchas clases son una súper colección de funciones ricas, y toda la clase se vuelve inflada y difícil de entender. En este momento, necesitamos para refactorizar conscientemente.

Principio abierto cerrado: Principio abierto cerrado

La definición del principio abierto-cerrado es que una entidad de software, como una clase, un módulo y una función, debe estar abierta a la extensión, pero cerrada a la modificación. Específicamente, debe implementar cambios a través de extensiones, no modificando el código original. Esto El principio es el principio más básico del diseño orientado a objetos.
  Dije antes que cada vez que se necesita cambiar los requisitos en el proyecto, a menudo es necesario hacer grandes cambios en el código, en gran parte porque nuestra comprensión de este principio no es lo suficientemente completa.
  La clave del principio de apertura y cierre radica en la abstracción. Necesitamos abstraer aquellas cosas que no cambiarán o básicamente permanecerán sin cambios. Estas cosas son relativamente estables, que es donde se cierra la modificación (esto no significa que no pueda ser modificado), y para aquellas partes que son fáciles de cambiar, también las encapsulamos, pero esta parte se puede modificar dinámicamente, que es donde se desarrolla la extensión. Por ejemplo, el modo de estrategia y el modo de plantilla en el modo de diseño están implementando este principio (ahora el modo debería ser Tener una comprensión más perceptiva~).

Por ejemplo: necesitamos guardar objetos en la base de datos, hay un método de guardado similar a save(), esta parte debe permanecer sin cambios, la interfaz es relativamente estable, pero la implementación de un guardado específico puede ser diferente, ahora podemos guardar en la base de datos Sqlite, si queremos guardarla en una base de datos autoimplementada en el futuro, solo necesitamos implementar una clase de extensión con la misma interfaz y agregarla. Esto está abierto a extensiones y no tendrá ningún impacto en el código anterior. , se puede realizar la función de guardar en la nueva base de datos, lo que garantiza la estabilidad del sistema.

 

Principio abierto y cerrado

 

La ideología rectora para realizar el principio de apertura y cierre es:

  • Resumen una interfaz relativamente estable, esta parte no debe cambiarse o cambiarse rara vez
  • cambio de paquete

Sin embargo, en el proceso de desarrollo de software, puede ser difícil seguir completamente el principio abierto-cerrado desde el principio, más a menudo, es mejorar en el proceso de refactorización iterativa continua y diseñar dentro del rango previsible de cambios.

Principio de sustitución de Liskov: Principio de sustitución de Liskov

Definición de este principio: Todas las referencias a la clase base deben poder utilizar objetos de sus subclases de forma transparente. En pocas palabras, si todos los lugares donde se usa el código de la clase base aún pueden ejecutarse normalmente cuando se reemplazan con un objeto de subclase, entonces se cumple este principio; de lo contrario, hay un problema con la relación de herencia, y la relación de herencia entre los dos debería Este principio se puede utilizar para determinar si nuestra relación de herencia de objetos es razonable.
Por ejemplo, hay una clase de ballena, dejamos que la ballena herede del pez y luego el pez tiene una función de respiración:

 

las ballenas heredan de los peces

Luego, mientras está en el agua, el pez puede respirar:

 

if(isInwater){
    //在水中了,开始呼吸
    fish.breath();
}

Cuando reemplazamos el subobjeto de la ballena con el objeto original de la clase base pez, la ballena comienza a respirar en el agua y luego surge el problema. La ballena es un mamífero y no puede respirar en el agua. Será GG si siempre está en el agua Smecta, entonces esto viola el principio, podemos juzgar que no es razonable que las ballenas hereden de los peces, y necesita ser rediseñado.
  Por lo general, al diseñar, le daremos prioridad a la composición en lugar de la herencia, porque aunque la herencia reduce el código y mejora la reutilización del código, la clase principal y la subclase tendrán un fuerte acoplamiento, rompiendo la encapsulación.

Principio de segregación de interfaz: Principio de segregación de interfaz

Definición de este principio: No se puede obligar a los usuarios a confiar en interfaces que no utilizan. En pocas palabras, qué tipo de interfaz se proporciona al cliente, y no se deben proporcionar otras interfaces redundantes, para no inflar la interfaz; de lo contrario, cuando se cambia un método no utilizado del objeto, el objeto también se verá afectado. El diseño de la interfaz debe seguir el principio de la interfaz más pequeña. De hecho, esto también es una manifestación de alta cohesión. En otras palabras, es mejor usar múltiples interfaces con una sola función y alta cohesión que usar una interfaz enorme. .
  Para dar un ejemplo simple: por ejemplo, tenemos una interfaz de bicicleta, esta interfaz contiene muchos métodos, incluido el posicionamiento GPS y el método de cambio de marchas.

 

No cumple con los principios del ISP

 

 Luego descubrimos que incluso las bicicletas ordinarias necesitan implementar funciones de cambio y posicionamiento GPS, lo que obviamente viola el principio de aislamiento de la interfaz. Siguiendo el principio de minimización de interfaces, rediseñamos:

 

Cumplir con los principios del ISP

 

  De esta manera, la función de cada interfaz es relativamente única, y es mejor usar múltiples interfaces especializadas que usar una interfaz general.Si nuestra bicicleta de montaña no tiene la función de posicionamiento GPS, no necesitamos heredar e implementar la interfaz correspondiente. En iOS, hay muchos ejemplos de este tipo en desarrollo. Por ejemplo, el proxy de UITalbleView tiene dos interfaces diferentes. UITableViewDataSource es responsable del contenido que debe mostrarse, y UITableViewDelegate es responsable de la visualización personalizada de algunas vistas. Entonces heredaremos múltiples interfaces, lo que satisface al ISP en principio.

 

@interface ViewController () <UITableViewDataSource,UITableViewDelegate,OtherProtocol>

Principio de inversión de dependencia: Principio de inversión de dependencia

Definición de este principio: Los módulos de alto nivel no deben depender de los módulos de bajo nivel, ambos deben depender de sus abstracciones; las abstracciones no deben depender de los detalles; los detalles deben depender de las abstracciones. De hecho, esto es lo que a menudo decimos "programar para la interfaz". La interfaz aquí es abstracción, y debemos confiar en la interfaz en lugar de confiar en la implementación específica para programar.
  Si desarrolla un nuevo sistema de base de datos AWEDatabase sobre la base de la base de datos Sqlite, entonces Sqlite es equivalente al módulo inferior, y su AWEDatabase pertenece al módulo de alto nivel; y desde la perspectiva de los usuarios de desarrollo de AWEDatabase, su capa comercial es equivalente a Un módulo de alto nivel y AWEDatabase se convierte en un módulo de bajo nivel, por lo que el nivel del módulo debe verse desde la perspectiva actual del desarrollador, pero el principio DIP es adecuado y debe observarse desde diferentes perspectivas. Si nuestros módulos de alto nivel dependen directamente de los módulos de bajo nivel, la consecuencia es que cada vez que se cambien los módulos de bajo nivel, los módulos de alto nivel se verán afectados y todo el sistema se volverá inestable, lo que también viola la principio abierto-cerrado.
  Por lo general, resolvemos este problema introduciendo una capa intermedia. Esta capa intermedia es equivalente a una capa de interfaz abstracta. Tanto los módulos de alto nivel como los módulos de bajo nivel dependen de esta capa intermedia para interactuar. De esta manera, siempre que la abstracción intermedia capa permanece sin cambios, los módulos subyacentes cambian. No afectará a los módulos de alto nivel, lo que satisface el principio de apertura y cierre; y si los módulos de alto nivel y los módulos de nivel inferior están en la etapa de desarrollo al mismo tiempo , con la capa de abstracción intermedia, cada módulo se puede desarrollar para la interfaz de esta capa de abstracción al mismo tiempo, y el módulo de alto nivel no necesita esperar hasta que se desarrolle el módulo subyacente antes de continuar.
  Por ejemplo, en nuestro proyecto, hay funciones relacionadas con IM. Ahora este módulo IM está implementado por el protocolo XMPP. El cliente usa este módulo para realizar el envío y recepción de mensajes. Sin embargo, si queremos cambiar a otros protocolos más adelante , como MQTT, etc., para la programación de la interfaz nos permite implementar fácilmente el reemplazo de módulos:

 

Programación contra la interfaz

 

@protocol MessageDelegate <NSObject>
@required
-(void)goOnline;
-(void)sendMessage:(NSString*)content;
@end

//xmpp实现
@interface XMPPMessageCenter <MessageDelegate>
@end

//MQTT实现
@interface MQTTMessageCenter <MessageDelegate>
@end

//业务层
@interface BussinessLayer
//使用遵循MessageDelegate协议的对象,针对接口编程,以后替换也很方便
@property(nonatomic,strong)id<MessageDelegate> messageCenter;
@end

Cuando estamos haciendo diseño orientado a objetos, debemos considerar completamente los principios anteriores.El diseño puede no ser perfecto al principio, pero puede mejorarse continuamente en el proceso de refactorización. Pero, de hecho, muchas personas se saltan el proceso de diseño, obtienen un módulo y escriben el código directamente, y mucho menos piensan en el diseño. Hay muchos ejemplos de este tipo en el proyecto. Por supuesto, puede que no haya mucho diseño para módulos simples, pero si los módulos son relativamente complejos, puede diseñarlos y pensar en ellos antes de escribir el código a mano. Desarrollar este hábito definitivamente mejorará la legibilidad, la estabilidad y la escalabilidad del código. El código es útil.

La herramienta de desarrollo de software más crítica es una mente entrenada en los principios del buen diseño.

Supongo que te gusta

Origin blog.csdn.net/gp18391818575/article/details/112696680
Recomendado
Clasificación