Lottando con il principio della singola responsabilità

10

Considera questo esempio:

Ho un sito web. Permette agli utenti di creare post (può essere qualsiasi cosa) e aggiungere tag che descrivano il post. Nel codice, ho due classi che rappresentano il post e i tag. Consente di chiamare queste classi Post e Tag .

Post si occupa di creare post, eliminare post, aggiornare post, ecc. Tag si occupa di creare tag, eliminare tag, aggiornare tag, ecc.

C'è una operazione che manca. Il collegamento dei tag ai post. Sto lottando con chi dovrebbe fare questa operazione. Potrebbe adattarsi ugualmente bene in entrambe le classi.

Da una parte, la classe Post potrebbe avere una funzione che accetta un Tag come parametro e quindi la memorizza in un elenco di tag. D'altra parte, la classe Tag potrebbe avere una funzione che accetta un Post come parametro e collega il Tag a Post .

Quanto sopra è solo un esempio del mio problema. In realtà mi sto imbattendo in questo con più classi che sono tutte simili. Potrebbe adattarsi ugualmente bene in entrambi. A parte mettere le funzionalità in entrambe le classi, quali convenzioni o stili di progettazione esistono per aiutarmi a risolvere questo problema. Suppongo che debba esserci qualcosa a parte solo sceglierne uno?

Forse metterlo in entrambe le classi è la risposta corretta?

    
posta AngryBird 29.11.2011 - 22:58
fonte

6 risposte

10

Come il Codice dei pirati, l'SRP è più di una linea guida che di una regola, e non è nemmeno una parola particolarmente ben formulata. La maggior parte degli sviluppatori ha accettato le ridefinizioni di Martin Fowler (in Refactoring ) e Robert Martin (in Pulisci codice ), suggerendo che una classe dovrebbe avere solo un motivo per cambiare (al contrario di una sola responsabilità).

È una linea guida valida, solida (scusa per il gioco di parole), ma è quasi altrettanto pericoloso rimanere impigliata come ignorarla.

Se puoi aggiungere un post a un tag e vice versa, non hai violato il principio di responsabilità singola. Entrambi hanno ancora una sola ragione per cambiare - se la struttura di quell'oggetto cambia. Cambiare la struttura di uno dei due non cambia il modo in cui viene aggiunto all'altro, quindi non aggiungerai una nuova "responsabilità" ad esso.

La tua decisione finale dovrebbe essere dettata dalle funzionalità richieste sul front-end. Probabilmente a un certo punto sarà necessario aggiungere un tag a un post, quindi fare qualcosa di simile al seguente:

// 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
}

Se, successivamente, trovi la necessità di aggiungere Post ai tag, aggiungi semplicemente un metodo addPost alla classe Tag e un metodo referenceTag alla classe Post. Ovviamente, li ho denominati in modo diverso in modo da non causare accidentalmente uno stack overflow chiamando addTag da addPost e addPost da addTag.

    
risposta data 29.11.2011 - 23:16
fonte
5

No, non in entrambi! Dovrebbe essere in un posto.

Ciò che trovo inquietante nella tua domanda è il fatto che tu dica " Post si occupa di creare post, eliminare post, aggiornare post" e lo stesso per Tag . Bene, non è giusto. Post può solo curare l'aggiornamento, lo stesso per Tag . Creare e cancellare è il lavoro di qualcun altro, esterno a Post e Tag (chiamiamolo Store ).

La buona responsabilità per Post è "conosce il suo autore, i contenuti e la data dell'ultimo aggiornamento". La buona responsabilità per Tag è "conosce il nome e lo scopo (leggi: descrizione)". La buona responsabilità per Store è "conosce tutti i messaggi e tutti i tag e può aggiungere, rimuovere e cercare".

Se guardi questi tre partecipanti, chi è più naturalmente colui che dovrebbe avere la conoscenza della relazione Post-Tag?

(per me, è il Post, sembra naturale che "conosca i suoi tag"; la ricerca inversa (tutti i post di un tag) sembra essere il lavoro dello Store, anche se potrei sbagliarmi)

    
risposta data 29.11.2011 - 23:20
fonte
3

C'è un dettaglio importante che manca all'equazione. Perché i tag contengono post e viceversa? La risposta a questa domanda determina la soluzione per ogni set dato.

In generale, posso pensare a una situazione simile. Una scatola e contenuti. Una scatola ha contenuti quindi una relazione ha-una è appropriata. I contenuti possono avere una scatola? Certo, una scatola con una scatola. Una scatola è il contenuto. Ma IS-A non è un buon progetto per tutte le scatole. In tal caso, considererei il modello di decoratore. In questo modo una scatola viene decorata con contenuti in fase di esecuzione, se necessario.

Anche i tag possono avere post, ma per me questa non è una relazione statica. Piuttosto potrebbe essere un rapporto di tutti i post che hanno detto tag. In questo caso è una nuova entità, non ha-a.

    
risposta data 30.11.2011 - 00:22
fonte
2

Mentre in teoria cose del genere possono andare in entrambi i modi, in pratica quando si arriva all'implementazione in un modo è quasi sempre una soluzione migliore rispetto all'altra. La mia impressione è che si adatterà meglio alla classe Post perché l'associazione verrà creata durante la creazione o la modifica di un post, mentre altre cose sul post cambiano allo stesso tempo.

Inoltre, se si associano più tag e si desidera eseguirlo in un aggiornamento del database, è necessario creare una sorta di elenco di tutti i tag associati allo stesso post prima di eseguire l'aggiornamento. Questa lista si adatta molto meglio alla classe Post .

    
risposta data 30.11.2011 - 00:08
fonte
1

Personalmente, non aggiungerei questa funzionalità a nessuno di essi.

Per me, sia Post che Tag sono oggetti dati, quindi non dovrebbe gestire la funzionalità del database. Dovrebbero semplicemente esistere. Sono pensati per contenere i dati e possono essere utilizzati da altre parti della tua applicazione.

Invece, avrei un'altra classe che è responsabile della tua logica aziendale e dei dati relativi alla tua pagina web. Se la tua pagina sta visualizzando un post e consente agli utenti di aggiungere tag, la classe dovrebbe avere un oggetto Post e contenere la funzionalità per aggiungere Tags a quel Post . Se la tua pagina visualizza tag e consente agli utenti di aggiungere post a questi tag, conterrebbe un oggetto Tag e avrà funzionalità per aggiungere Posts a quel Tag .

Però sono solo io. Se ritieni di dover gestire la funzionalità del database nei tuoi oggetti dati, allora ti consigliamo di risposta del pdr

    
risposta data 30.11.2011 - 21:39
fonte
0

Mentre leggevo questa domanda, la prima cosa che mi è venuta in mente era una relazione tra database Many To Many . I post possono avere molti tag ... I tag possono avere molti post .... Mi sembra che entrambe le classi abbiano la capacità di gestire questa relazione in una certa misura.

Dal punto di vista Post ... Se la modifica o la creazione di un post un'attività secondaria diventa la gestione delle relazioni tag .

  1. Aggiungi tag esistente al post
  2. Revove Tag from Post

IMO, la creazione di un TAG completamente nuovo non appartiene qui.

Dal punto di vista Tag ... Puoi creare un tag senza doverlo assegnare a un post. L'unica attività che vedo riguarda l'interazione con un post è una funzione Elimina tag. Tuttavia, questa funzione dovrebbe essere una funzione indipendente autonoma.

Funzionerà solo se esiste una tabella di collegamento al database che risolve la relazione Molti a Molti

    
risposta data 30.11.2011 - 14:43
fonte