Análisis orientado a objetos en mis ojos
Análisis orientado a objetos en mis ojos
Orientado a objetos
Parece que no he aprendido otros métodos de pensamiento de programación. La orientación a objetos es lo que a menudo hago. Lo uso al programar, porque creo que está más cerca de nuestras vidas y hace que sea más fácil entender y definir lo que el programa quiere expresar. Por esta razón, cada vez que un proyecto está a punto de comenzar, utilizaré este tipo de pensamiento para analizar y diseñar el programa. Con los años he descubierto que tiene su encanto y me ha ayudado a resolver muchos problemas de diseño. Así que lo resumí y hablé sobre algunas experiencias en el uso orientado a objetos en los siguientes capítulos.
Clasifica tu programa en "clases"
La base de la orientación a objetos es el diseño de "clases", y la calidad de su diseño afecta directamente a la estructura del programa. Entonces, ¿cómo podemos diseñar tipos razonables? Creo que deberíamos partir de las necesidades reales, extraer varios objetos de entidad en los requisitos y finalmente formar los objetos utilizados en el programa. A algunos estudiantes les gusta profundizar en sus propios programas y agregar muchos tipos para futuras expansiones. De hecho, no recomiendo un diseño excesivo. Creo que se pueden reservar algunas extensiones funcionales para las entidades mencionadas en los requisitos. No lo recomiendo. Tipos de diseño para puntos de función que no están en los requisitos, porque nunca se puede estar seguro de cómo será el desarrollo futuro del producto o cuál será su forma final. También es posible que el programa lo necesite. que será reelaborado antes de que los puntos que usted estableció puedan realizarse. De no hacerlo, se desperdiciará su trabajo adicional.
Supongamos que la empresa le pide que cree una aplicación para hacer amigos con extraños cercanos. De los requisitos de esta aplicación, podemos extraer las tres palabras clave cercano, extraños y amigos. Cercano describe una ubicación geográfica relativa, por lo que debe ser un atributo de una clase, y los extraños también son relativos a una determinada persona. personas, por lo que se dividirá en la entidad de una persona y una relación con las entidades de otras personas. La última palabra clave de amistad en realidad implica un comportamiento de establecer una relación, que puede ser chatear u otras formas de hacer amigos. Analicémoslo más a fondo. Si la aplicación proporciona una función de chat, entonces hay una entidad implícita en el requisito, que es el contenido del chat. Por lo tanto, en función de estos requisitos, podemos diseñar dos tipos:
Estos son tipos impulsados por requisitos. Estos tipos se utilizan como clases principales para complementar y expandir los requisitos para satisfacer continuamente las necesidades posteriores.
Los tipos Let también se ajustan al diseño de entidades de bases de datos
La tercera forma normal se menciona a menudo en el diseño de bases de datos relacionales, que se caracteriza por eliminar las dependencias directas en las tablas. Esto significa que no se deben diseñar dos objetos de entidad en la misma tabla. Por ejemplo, si hay registros en la tabla de comentarios que son comentarios publicados por un usuario, el nombre de usuario y otra información se pueden clasificar en la tabla de comentarios. Si no cumple con los requisitos del tercer paradigma, el enfoque correcto es colocar la información del usuario en la tabla de usuarios. Se puede retener una identificación de usuario en la tabla de comentarios para identificar el registro de usuario en la tabla de usuarios, cancelando así el registro. dependencia directa de la información del usuario en la tabla de comentarios, lo que provoca redundancia de información del usuario.
De hecho, personalmente creo que este requisito de paradigma divide muy bien los límites entre entidades y también es aplicable al diseño de clases. Por ejemplo: usted define un ser humano, cuyo tipo tiene atributos de extremidades y comportamiento del habla. Esto es fácil de entender, porque las personas en realidad tienen estas características. Sin embargo, debido a los cambios en la demanda durante el proceso de desarrollo posterior, se demostró que los humanos podían volar gracias a la disponibilidad de accesorios voladores. En cuanto a asignar el comportamiento de volar directamente a los humanos, este enfoque sin esfuerzo en realidad tiene muchas desventajas:
En primer lugar, no es fácil de entender, porque el sentido común cree que las personas no pueden volar, y las personas sí pueden. Si vuela, debe ser un hombre pájaro. Entonces es confuso.
Además, esta definición a menudo limita a otros colegas que colaboran con usted en el desarrollo. Porque en la mayoría de los casos insistiremos en no tocar el código antiguo en la medida de lo posible, para no causar problemas. Entonces su diseño obliga a las personas a expandirse de acuerdo con el pensamiento existente. Hasta que llegue el día en que no quede otro camino, entonces realizar una reconstrucción completa.
Entonces, un mejor enfoque sería definir una clase de utilería.
En cuanto a las funciones implementadas por los accesorios, se implementan mediante sus subclases:
// Clase base de accesorios
clase Artículo
{
// Comportamiento de los accesorios
function action(target: Person);
}
//Accesorios voladores
clase FlyingItem: Elemento p>
{
función acción(objetivo: Persona)
{
//Código de implementación volador
}
}
Una implementación como esta puede desacoplar el acoplamiento entre humanos y tipos de accesorios, y también mejorar la expansión de los tipos de accesorios. Por lo tanto, al diseñar tipos, se deben distinguir claramente los límites entre entidades.
Otra cosa que se utiliza a menudo en el diseño de bases de datos es la descripción de relaciones entre entidades. Las relaciones entre entidades son diferentes y el diseño de tablas para almacenar datos también es diferente. Generalmente existen tres tipos de relaciones entre entidades:
Uno a uno, es decir, la relación entre dos entidades es una correspondencia uno a uno. Por ejemplo: un ciudadano chino está vinculado de forma única a su tarjeta de identificación. Si conoce la tarjeta de identificación, puede encontrar la persona correspondiente, y la persona correspondiente también puede encontrar la tarjeta de identificación única correspondiente. Para una relación como esta, generalmente se establecen dos tablas de datos de entidades y luego la relación se puede colocar en ambas tablas de entidades al mismo tiempo.
Uno a muchos, es decir, entre dos entidades, la primera entidad puede corresponder a múltiples entidades del segundo tipo, pero la segunda entidad solo puede corresponder a la única entidad del primer tipo. Por ejemplo, en el ejemplo de los comentarios de los usuarios, los usuarios pueden publicar varios comentarios, pero un comentario solo puede ser publicado por un usuario específico. Para este tipo de relación, generalmente se establecen dos tablas de datos de entidades y luego la relación se registra en las tablas de datos correspondientes a múltiples entidades. Tome el ejemplo anterior, es la tabla de usuarios y la tabla de comentarios, y luego se generará un ID de usuario. colocados en la tabla de comentarios. Dichos identificadores se utilizan para registrar la relación correspondiente con el usuario.
Muchos a muchos, es decir, entre dos entidades, la primera entidad puede corresponder a múltiples entidades del segundo tipo, y la segunda entidad también puede corresponder a múltiples entidades del primer tipo. Por ejemplo, la relación entre autores y libros es una relación de muchos a muchos. Un autor puede escribir varios libros al mismo tiempo y varios autores pueden escribir un libro al mismo tiempo. Para este tipo de relación, es necesario establecer dos tablas de datos de entidades y una tabla de relaciones para describirla.
Habiendo dicho tanto sobre las relaciones entre entidades, lo más importante es que al realizar análisis y diseño orientado a objetos, el análisis de relaciones también es indispensable. Pero, ¿cómo evaluar la división y atribución de relaciones? Creo que también es un buen método seguir los estándares de diseño de relaciones de la base de datos.
En este tipo de relación, la descripción de la relación se puede colocar en dos tipos al mismo tiempo, por ejemplo, en el ejemplo de los ciudadanos chinos y los documentos de identidad mencionados anteriormente. , la clase se puede describir como:
clase china
{
//Nombre
nombre de propiedad: Cadena
//Certificado de identidad correspondiente
propiedad idCard: IDCard;
}
clase IDCard
{
//Número de tarjeta de identificación
ID de propiedad: Cadena
//Persona correspondiente
Persona de propiedad: chino
}
Uno a muchos, este tipo de relación es ligeramente diferente del diseño de la tabla de la base de datos, porque la tabla de datos puede encontrar múltiples entidades de otro tipo correspondientes a la entidad mediante consultas, lo cual está más allá del alcance de la etapa de diseño de clases.
Por lo tanto, un tipo que corresponde a múltiples instancias de clase necesita definir un conjunto de propiedades para contener las múltiples instancias de clase correspondientes. De esta manera, se puede entender fácilmente al leer el código. Tome el ejemplo de los comentarios de los usuarios:
clase Usuario
{
comentarios de propiedad: Setlt; Commentgt;
}
clase Comentario
{
propiedad usuario: Usuario;
} p>
De manera similar a esto, puede ser una buena descripción de cuántos comentarios ha realizado un usuario y qué comentario fue realizado por qué usuario.
Muchos a muchos es una relación muy compleja. La razón principal es que en muchos casos, ignoramos dicha relación y la convertimos directamente en un diseño de relación de uno a muchos. De hecho, para cada situación, podemos transferir el procesamiento de la relación a otro tipo. Al igual que el diseño de la tabla de la base de datos, hay dos tipos de entidad y un tipo de relación. Tomando como ejemplo la relación entre autores y libros, se puede diseñar de la siguiente manera:
//Tipo de gestión de relaciones entre autores y libros
clase AuthorBookRelationshipManager
{ p>
//Establece la información del autor del libro
función estática setBookAuthor(libro: Libro, autores: Arraylt; Authort;): void;
/ /Obtener la información del libro especificado Autor
función estática getAuthors(book: Book): Arraylt;Authorgt;;
//Obtener los libros del autor especificado
función estática getBooks(autor: Autor): Arraylt;;
}
//Tipo de autor
clase Autor
{
//Nombre del autor
nombre de propiedad: String;
//Obtener los libros publicados por el autor
función getBooks(): Arraylt; Bookgt;
{
return AuthorBookRelationshipManager.getBooks(this);
}
}
//Tipo de libro
clase Libro
{
//Nombre del libro
nombre de propiedad: Cadena;
//Obtener información del autor del libro
function getAuthors(): Arraylt;Authorgt;
{
return AuthorBookRelationshipManager.getAuthors(this);
}
p>
}
Como se puede ver en el código anterior, se utiliza un tipo AuthorBookRelationshipManager para gestionar la relación entre libros y autores.
Entonces el tipo de entidad ya no guarda la relación entre ellas. Para obtener las entidades relacionadas, debe obtenerlas a través del tipo AuthorBookRelationshipManager.
La división de entidades y la división de relaciones mencionadas anteriormente son de gran importancia en la orientación a objetos. Se necesita más práctica para hacer clasificaciones razonables cuando se enfrenta al desarrollo de proyectos reales, de modo que la arquitectura que desarrolle pueda ser más flexible y poderosa.
Lo anterior básicamente ha terminado de hablar sobre el diseño de tipos. Es hora de hablar sobre la herencia, que es una característica muy poderosa de la orientación a objetos. Puede mejorar los problemas de estructura del código y ahorrar la carga de trabajo de escritura de código. A continuación hablaré de mis puntos de vista al respecto.
El antepasado de todas las cosas
De hecho, ya sea que estemos escribiendo lenguajes de alto nivel como C, C# o Java, encontraremos que siempre que creemos clases , heredarán directa o indirectamente de la clase Objeto. Y esta clase de Objeto es el antepasado de todas las cosas de las que voy a hablar. Es un tipo raíz que todas las cosas heredan. El nombre Objeto también es bastante bueno. Se refiere a todos los objetos en general y da una definición abstracta y sin forma. Esto significa que, como creadores del programa, debemos cambiar este objeto informe a varias formas específicas de cosas, lo que requiere herencia.
Bueno, en realidad no hay muchas funciones en Object, porque no involucra funciones específicas, por lo que en muchos casos solo puede distinguir si es el mismo objeto o decirnos que esIgual en su implementación. . Pero esto ya tiene un gran significado guía, porque siempre que heredes el tipo de Objeto, tu tipo tendrá dicha función, lo que ilustra el poder de la herencia, al mismo tiempo, también puedes asignar tu objeto de subtipo a una Instancia de Objeto; Debido a que Objeto es la clase raíz, puede retener cualquier objeto de tipo subclase, que es la encarnación básica del polimorfismo orientado a objetos.
Con un ejemplo como Object, se demuestra además que también debemos diseñar clases base para el código del proyecto durante el desarrollo del proyecto. La clase base aquí es un poco más específica que la función de Objeto. Porque implica la implementación de requisitos funcionales específicos y debería ser más que una simple clase base. Hablemos de cómo diseño la clase base.
Haga un acuerdo con la clase base
La forma de diseñar la clase base en realidad depende de las necesidades reales del proyecto. Suponiendo que el proyecto que desarrolla contiene muchos servicios en segundo plano, debe definir una clase base llamada BaseService y luego usar esta clase base para diferenciar varios tipos de servicios. Si su proyecto implica el procesamiento de múltiples UI, entonces debe considerar que es posible que necesite un tipo básico de administración de UI (generalmente los componentes de UI tienen sus propias clases base, lo que se necesita aquí es el tipo básico para administrar y mantener la UI), como como: UIController. Luego, se diferencian varias pantallas de interfaz de usuario en función de esta clase base.
Básicamente, si el programa tiene múltiples módulos funcionales con características similares o similares, entonces se deben establecer clases base para estos módulos para facilitar una mejor expansión en el futuro. No se recomienda definir una clase base para módulos que puedan tener múltiples funciones similares en el futuro. Como dice el viejo dicho, su suposición puede no ser cierta. Y no es demasiado tarde para definirlo hasta que surja esa necesidad. Después de todo, es sólo un tipo el que necesita ser transformado.
Sabemos cómo abstraer la clase base anterior, por lo que la forma en que la clase base debe encapsular propiedades y métodos puede depender en realidad de sus subclases, porque esto es lo más cercano a las necesidades.
Como se mencionó anteriormente, existen múltiples servicios en segundo plano, asumiendo los siguientes tipos de servicio:
clase ServicioA
{
//Nombre del servicio
nombre de propiedad: Cadena
// Llamar al servicio
función exec(config: Dicacional): void
{
// Ejecutar servicio
}
}
clase ServicioB
{
//Nombre del servicio
nombre de propiedad: String;
// Llamar al servicio
función call(): void
{
//Ejecución Servicio
}
}
Los servicios descritos anteriormente son en realidad bastante diferentes de la estructura del código, especialmente en el comportamiento de ejecutar el servicio y recibir parámetros. . Sería confuso abstraer las clases base de ellos. Sin embargo, como queremos abstraer la clase base, definitivamente transformaremos la subclase. Yo mismo he resumido algunas reglas abstractas:
Los mismos atributos y métodos en las subclases deben encapsularse en la clase base.
Para las propiedades y métodos que existen en la mayoría de las subclases (por ejemplo, si más de 3 tipos de 5 subclases tienen las mismas propiedades y métodos), puede considerar encapsularlos en la clase base. pueden ser propiedades y métodos de la clase base que se ignoran.
Para atributos con significados similares, puede considerar fusionar o reemplazar los atributos (por ejemplo, si la subclase usa los atributos id o name para representar la unicidad del objeto, entonces la clase base puede considerar tomar solo uno de los atributos o Crear una colección de ambas definiciones de propiedad).
Para métodos con el mismo comportamiento en subclases, si el número o tipo de parámetros del método declarados son diferentes, puede considerar el conjunto de métodos de la clase base para todos los parámetros en el método de la subclase (es decir , tome la unión de parámetros del método) o considere definir parámetros ***, y los parámetros especiales se implementan convirtiendo subclases en atributos (generalmente los parámetros que se pueden mantener continuamente se pueden diseñar de esta manera, como la configuración del sistema).
Basándonos en lo dicho anteriormente, podemos cambiar ServiceA y ServiceB a lo siguiente a través de clases base abstractas:
class BaseService
{
//Nombre del servicio
nombre de propiedad: String;
//Llamar al servicio
función exec(): void;
}
clase ServiceA extiende BaseService
{
//Establezca los parámetros como propiedades aquí
property config: Dictionary
función exec(): void
{
//Ejecutar servicio
}
} p>
clase ServiceB extiende BaseService
{
función exec(): void
{
//Ejecutar servicio
}
}
De manera similar, podemos abstraer la clase base.
Dejemos que los tipos evolucionen
En el proceso de desarrollo y mantenimiento del código, es inevitable que haya cambios importantes en los requisitos debido a las iteraciones del producto. Luego puede haber la desaprobación de ciertas características o la fusión o evolución de características. En este momento, los tipos previamente definidos enfrentarán el destino de la reconstrucción o el ajuste.
Cuando nos encontramos con un problema de este tipo, no debemos apresurarnos a negar todo por completo antes y luego reescribirlo por completo. En lugar de ello, considere primero si hay algo disponible en la definición anterior. Para extraer módulos que se puedan reutilizar, entonces la elección de los módulos es algo que debemos evaluar.
Si los nuevos requisitos tienen funciones obsoletas, entonces los tipos involucrados en el módulo pueden quedar obsoletos. Para el procesamiento de estos módulos, personalmente prefiero eliminar directamente los tipos relevantes y luego compilarlos. Después de informar un error, modifico directamente el código que hace referencia al tipo hasta que la compilación sea exitosa. Para los métodos que deben modificarse después de que falta el tipo, no los modificaré por el momento, pero los marcaré (como TODO o macro de advertencia) y esperaré el desarrollo oficial de nuevas funciones para encontrar estas marcas y resolverlos uno por uno.
Si el nuevo requisito contiene este tipo de función, pero también incorpora algunos elementos nuevos, entonces nuestro tipo no se puede utilizar directamente. Evalúe si el nuevo elemento es parte del tipo o si es un tipo nuevo. Por ejemplo: una aplicación de chat solo tiene chats privados (chat uno a uno) al inicio, luego el mensaje enviado es del tipo Usuario:
//Tipo de usuario
clase Usuario
{
//Enviar un mensaje a un usuario
función sendMessage(usuario: Usuario, mensaje: Mensaje): void;
}
En la segunda versión, puede permitir a los usuarios enviar mensajes grupales a amigos. Entonces, es obvio que el código anterior no puede satisfacer las necesidades. En este momento, es necesario transformar el tipo:
//Tipo de usuario
clase Usuario
<. p>{//Enviar un mensaje a un usuario
function sendMessage(usuario: Usuario, mensaje: Mensaje): void;
//Enviar un mensaje a múltiples usuarios
función sendMessage(usuarios: Arrraylt; Usergt;, mensaje: Mensaje): void
{
for (Usuario usuario en usuarios)
{
this.sendMessage(usuario, mensaje);
}
}
}
Se agregó uno. La versión polimórfica del método sendMessage se utiliza para enviar mensajes grupales. Esto no solo garantiza que la exportación de métodos esté unificada, sino que también garantiza que los métodos anteriores no se modifiquen y que el problema del envío masivo se pueda resolver fácilmente.
Luego, en la tercera versión, apareció el concepto de sala de chat. En este momento, había una palabra clave adicional de sala de chat en los requisitos, utilizando el método mencionado anteriormente, se debe agregar un nuevo tipo aquí. , no se puede extender directamente en el tipo de Usuario.
Luego, se puede diseñar como:
//Sala de chat
clase ChatRoom
{
//Nombre de la sala de chat p>
nombre de propiedad: String;
//Usuarios de la sala de chat
usuarios de propiedad: Arraylt;
//Enviar mensaje p>
función sendMessage(usuario: Usuario, mensaje: Mensaje): void;
}
Aquí se agrega un tipo ChatRoom, que se utiliza principalmente para registrar dónde están esas personas. están charlando en la sala. Este tipo tiene un método sendMessage, que se utiliza para indicar qué usuario quiere hablar en la sala de chat y cuál es el contenido del discurso.
Como se puede ver en los ejemplos anteriores, la iteración del producto a veces debe realizarse por tipo y, a veces, es necesario que nazcan nuevos tipos. La elección a tomar debe decidirse en función de los requisitos del producto.
Siempre que este tipo de polimorfismo
En mi opinión, polimorfismo significa múltiples estados. Al igual que cuando sostenemos algo con nuestras manos, si sostenemos una pelota, es posible que queramos jugar a la pelota, si sostenemos una manzana, entonces es posible que queramos comerla. Podemos responder de manera diferente a diferentes cosas. En el polimorfismo orientado a objetos se utiliza para resolver este tipo de problemas. Supongamos lo frustrante que se vería nuestro código sin polimorfismo:
función hacerAlgo(obj: Objeto): void
{
if (obj instanciade ObjetoA) p>
{
//hacer algo por el ObjetoA
}
else if (obj instancia del ObjetoB)
{
//hacer algo por ObjetoB
}
}
Cuantos más tipos se juzguen, más larga será la declaración if. Entonces, con el polimorfismo, podemos diseñarlo de manera muy elegante:
función hacerAlgo(obj: ObjetoA): void
{
//hacer algo por ObjetoA p>
}
función hacerAlgo(obj: ObjetoB): void
{
//hacer algo por ObjetoB
}
El polimorfismo puede garantizar que los nombres de salida del diseño del código estén unificados. Las personas que no necesitan llamadas externas tienen que llamar a métodos con diferentes nombres según los diferentes tipos. Para llamadas externas, solo los tipos entrantes. para ser diferente. Puedes decidir qué método llamar.
Si no resuelve los problemas anteriores, no recomiendo utilizar polimorfismo. Debido a que el polimorfismo complicará el tipo, si este tipo que usa polimorfismo se hereda y luego la subclase heredada se procesa polimórficamente, afectará la calidad del programa y, una vez que ocurra un problema, será más difícil de solucionar.
Epílogo
Lo anterior son todas las cosas que he entendido personalmente en estos años de desarrollo. La orientación a objetos ha estado en mi mente durante muchos días y noches, y siempre pienso en ello. Estaba intentando escribir algo y finalmente lo terminé hoy. Continuaré escribiendo otros artículos sobre pensamiento procedimental en el futuro y espero que me apoyen.
No hice referencia a otros materiales al escribir este artículo y puede haber errores y omisiones. Si eres un Yuan/Yuan de buen corazón, corrígeme.