Luchando con el principio de responsabilidad única

11

Considere este ejemplo:

Tengo un sitio web Permite a los usuarios hacer publicaciones (puede ser cualquier cosa) y agregar etiquetas que describen la publicación. En el código, tengo dos clases que representan la publicación y las etiquetas. Vamos a llamar a estas clases Posty Tag.

Postse encarga de crear publicaciones, eliminar publicaciones, actualizar publicaciones, etc. Tagse encarga de crear etiquetas, eliminar etiquetas, actualizar etiquetas, etc.

Hay una operación que falta. La vinculación de etiquetas a publicaciones. Estoy luchando con quién debería hacer esta operación. Podría encajar igualmente bien en cualquier clase.

Por un lado, la Postclase podría tener una función que tome a Tagcomo parámetro y luego la almacene en una lista de etiquetas. Por otro lado, la Tagclase podría tener una función que tome a Postcomo parámetro y vincule Taga Post.

Lo anterior es solo un ejemplo de mi problema. De hecho, me encuentro con esto con varias clases que son todas similares. Podría encajar igualmente bien en ambos. Además de poner realmente la funcionalidad en ambas clases, qué convenciones o estilos de diseño existen para ayudarme a resolver este problema. ¿Asumo que tiene que haber algo menos que elegir uno?

¿Quizás ponerlo en ambas clases es la respuesta correcta?

Pájaro enojado
fuente

Respuestas:

11

Al igual que el Código Pirata, el SRP es más una guía que una regla, y ni siquiera es particularmente bien redactado. La mayoría de los desarrolladores han aceptado las redefiniciones de Martin Fowler (en Refactoring ) y Robert Martin (en Clean Code ), lo que sugiere que una clase solo debe tener una razón para cambiar (en lugar de una responsabilidad).

Es una directriz buena y sólida (disculpe el juego de palabras), pero es casi tan peligroso quedar atrapado en él como ignorarlo.

Si puede agregar una publicación a una etiqueta y viceversa, no ha roto el principio de responsabilidad única. Ambos todavía tienen una sola razón para cambiar, si la estructura de ese objeto cambia. Cambiar la estructura de uno de los dos no cambia la forma en que se agrega al otro, por lo que no está agregando una nueva "responsabilidad".

Su decisión final realmente debería estar dictada por la funcionalidad requerida en el front-end. Es probable que sea necesario agregar una etiqueta a una publicación en algún momento, así que haga algo como lo siguiente:

// C-style-language pseudo-code
class Post {
    string _title;
    string _content;
    Date _date;
    List<Tag> _tags;

    Post(string title, string content) {
        _title = title;
        _content = content;
        _date = Now;
        _tags = new List<Tag>();
    }

    Tag[] getTags() {
        return _tags.toArray();
    }

    void addTag(Tag tag) {
        if (_tags.contains(tag)) {
            throw "Cannot add tag twice";
        }

        _tags.Add(tag);
        tag.referencePost(this);
    }

    // more stuff here, obviously
}

class Tag {
    string _name;
    List<Post> _posts;

    Tag(string name) {
        _name = name;
    }

    Post[] getPosts() {
        return _posts.toArray();
    }

    void referencePost(Post post) {
        if (!post.getTags().contains(this) || _posts.contains(post)) {
            throw "Only reference a post by calling Post.addTag()";
        }

        _posts.Add(post);
    }

    // more stuff here too
}

Si, más tarde, también necesita agregar publicaciones a las etiquetas, simplemente agregue un método addPost a la clase Tag y un método referenceTag a la clase Post. Obviamente, los he nombrado de manera diferente para que no provoque un desbordamiento de la pila al llamar a addTag desde addPost y addPost desde addTag.

pdr
fuente
Creo que la relación entre Etiqueta y Publicación es de muchos a muchos, en cuyo caso tiene sentido que una Etiqueta mantenga referencias a múltiples Publicaciones. ¿Cómo manejarías esto si mantienes una sola referencia?
Andres F.
@AndresF .: Estoy de acuerdo con usted, así que claramente no escribí mi respuesta muy bien. He editado significativamente. (Disculpas a la upvoter anterior si esto cambia el significado que lo viste.)
PDR
6

No, no en ambos! Debería estar en un solo lugar.

Lo que encuentro incómodo en su pregunta es el hecho de que usted dice " Postse encarga de crear publicaciones, eliminar publicaciones, actualizar publicaciones" y lo mismo para Tag. Bueno, eso no está bien. Postsolo puede encargarse de actualizar, lo mismo para Tag. Crear y eliminar es el trabajo de otra persona, externo a Posty Tag(llamémoslo Store).

La buena responsabilidad Postes "conoce a su autor, contenido y fecha de la última actualización". La buena responsabilidad Tages "conoce su nombre y propósito (léase: descripción)". La buena responsabilidad Storees "conoce todas las publicaciones y todas las etiquetas y puede agregarlas, eliminarlas y buscarlas".

Si nos fijamos en estos tres participantes, ¿quién es, naturalmente, el que debería tener el conocimiento de la relación Post-Tag?

(para mí, es la publicación, parece natural que "conozca sus etiquetas"; la búsqueda inversa (todas las publicaciones para una etiqueta) parece ser el trabajo de la tienda; aunque puedo estar equivocado)

herby
fuente
Si cada publicación tiene una lista de etiquetas, y / o cada etiqueta tiene una lista de publicaciones en las que se incluye, se puede responder fácilmente a la pregunta "si la publicación x incluye la etiqueta y". ¿Hay alguna manera de responder eficientemente a tal pregunta sin que ninguna de las clases se responsabilice, aparte de usar algo como un ConditionalWeakTable(suponiendo que uno tenga la suerte de tener un marco donde exista)?
supercat
3

Hay un detalle importante que falta en la ecuación. ¿Por qué la etiqueta contiene publicación y viceversa? La respuesta a esta pregunta determina la solución para cada conjunto dado.

En general, puedo pensar en una situación similar. Una caja y contenido. Una caja tiene contenido, por lo que una relación has-a es apropiada. ¿Los contenidos pueden tener una caja? Claro, una caja con una caja. Una caja es contenido. Pero IS-A no es un buen diseño para todas las cajas. En tal caso, consideraría el patrón decorador. De esta manera, una caja se decora con contenido en tiempo de ejecución según sea necesario.

Las etiquetas también pueden tener publicaciones, pero para mí esto no es una relación estática. Más bien podría ser un informe de todas las publicaciones que tienen dicha etiqueta. En este caso es una nueva entidad, no tiene-a.

P.Brian.Mackey
fuente
2

Si bien, en teoría, cosas como esa pueden ir en cualquier dirección, en la práctica, cuando se llega a la implementación, una de ellas es casi siempre mejor que la otra. Mi presentimiento es que encajará mejor en la Postclase porque la asociación se creará durante la creación o edición de la publicación, cuando otras cosas sobre la publicación están cambiando al mismo tiempo.

Además, si está asociando varias etiquetas y desea hacerlo en una actualización de la base de datos, deberá crear algún tipo de lista de todas las etiquetas asociadas con la misma publicación antes de realizar la actualización. Esa lista encaja mucho mejor en la Postclase.

Karl Bielefeldt
fuente
1

Personalmente, no agregaría esa funcionalidad a ninguno de ellos.

Para mí, tanto Posty Tagson objetos de datos, por lo que no se debe manejar la funcionalidad de base de datos. Simplemente deberían existir. Están destinados a almacenar datos y ser utilizados por otras partes de su aplicación.

En cambio, tendría otra clase que es responsable de la lógica de su negocio y los datos relacionados con su página web. Si su página muestra una publicación y permite a los usuarios agregar etiquetas, entonces la clase tendría un Postobjeto y contendría funcionalidad para agregar Tagsa eso Post. Si su página muestra etiquetas y permite a los usuarios agregar publicaciones a esas etiquetas, contendría un Tagobjeto y tendría funcionalidad para agregar Postsa eso Tag.

Aunque solo soy yo. Si cree que debe manejar la funcionalidad de la base de datos en sus objetos de datos, entonces recomendaría la respuesta de pdr

Rachel
fuente
0

Cuando leí esta pregunta, lo primero que me vino a la mente fue una relación de base de datos Many To Many . Las publicaciones pueden tener muchas etiquetas ... Las etiquetas pueden tener muchas publicaciones ... Me parece que ambas clases necesitan la capacidad de gestionar esta relación hasta cierto punto.

Desde el punto de vista de la publicación ...
Si edita o crea una publicación, una actividad secundaria se convierte en la gestión de las relaciones de etiquetas .

  1. Agregar etiqueta existente a la publicación
  2. Revocar etiqueta de publicación

En mi opinión, la creación de un TAG completamente nuevo no pertenece aquí.

Desde el punto de vista de la etiqueta ...
Puede crear una etiqueta sin tener que asignarla a una publicación. La única actividad que veo que implica interactuar con una publicación es una función Eliminar etiqueta. Sin embargo, esta función debería ser una función independiente independiente.

Esto solo funcionará si hay una tabla de enlace de base de datos que resuelva la relación Muchos a Muchos

Michael Riley - también conocido como Gunny
fuente