Lottando con il principio della responsabilità unica


11

Considera questo esempio:

Ho un sito web. Consente agli utenti di pubblicare post (può essere qualsiasi cosa) e aggiungere tag che descrivono il post. Nel codice, ho due classi che rappresentano il post e i tag. Chiamiamo queste classi Poste Tag.

Postsi occupa di creare post, eliminare post, aggiornare post, ecc. Tagsi occupa di creare tag, eliminare tag, aggiornare tag, ecc.

Manca un'operazione. Il collegamento di tag ai post. Sto lottando con chi dovrebbe fare questa operazione. Potrebbe adattarsi ugualmente bene in entrambe le classi.

Da un lato, la Postclasse potrebbe avere una funzione che accetta un Tagcome parametro e quindi lo memorizza in un elenco di tag. D'altra parte, la Tagclasse potrebbe avere una funzione che accetta Postun parametro come e lo collega Tagal Post.

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

Forse metterlo in entrambe le classi è la risposta corretta?

Risposte:


11

Come il codice pirata, l'SRP è più una linea guida che una regola, e non è nemmeno particolarmente formulato. La maggior parte degli sviluppatori ha accettato le ridefinizioni di Martin Fowler (in Refactoring ) e Robert Martin (in Clean Code ), suggerendo che una classe dovrebbe avere solo una ragione per cambiare (al contrario di una responsabilità).

È una buona e solida linea guida (scusate il gioco di parole), ma è quasi pericoloso rimanere impiccati come lo è ignorarlo.

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

La tua decisione finale dovrebbe davvero essere dettata dalla funzionalità richiesta sul front-end. Probabilmente a un certo punto sarà necessario aggiungere un tag a un post, quindi fai qualcosa del tipo:

// 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, in seguito, trovi la necessità di aggiungere anche Post ai tag, aggiungi semplicemente un metodo addPost alla classe Tag e un metodo referenceTag alla classe Post. Ovviamente, li ho nominati in modo diverso in modo da non causare accidentalmente un overflow dello stack chiamando addTag da addPost e addPost da addTag.


Penso che il rapporto tra Tag e Post sia molti-a-molti, nel qual caso ha senso che un Tag mantenga riferimenti a più Post. Come gestiresti questo se mantieni un unico riferimento?
Andres F.

@AndresF .: Sono d'accordo con te, quindi chiaramente non ho scritto molto bene la mia risposta. Ho modificato in modo significativo. (Mi scuso con il precedente promotore se questo cambia il significato come l'hai visto.)
pdr

6

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

Quello che trovo incessante nella tua domanda è il fatto che dici " Postsi occupa di creare post, eliminare post, aggiornare post" e lo stesso per Tag. Bene, non è giusto. Postposso solo occuparmi dell'aggiornamento, lo stesso per Tag. Creare ed eliminare è opera di qualcun altro, esterno a Poste Tag(chiamiamolo Store).

Una buona responsabilità Postè "conosce l'autore, i contenuti e la data dell'ultimo aggiornamento". Una buona responsabilità Tagè "conosce il nome e lo scopo (leggi: descrizione)". Una buona responsabilità Storeè "conosce tutti i post e tutti i tag e può aggiungerli, rimuoverli e cercarli".

Se guardi questi tre partecipanti, chi è più naturalmente quello 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 per un tag) sembra essere il lavoro dello Store; anche se potrei sbagliarmi)


Se ogni post ha un elenco di tag e / o ogni tag ha un elenco di post a cui includerlo, si può facilmente rispondere alla domanda "post x include tag y". Esiste un modo per rispondere efficacemente a una domanda del genere senza che nessuna classe si ConditionalWeakTableassuma la responsabilità, se non usando qualcosa come un (supponendo che uno abbia la fortuna di avere una struttura in cui esiste)?
supercat il

3

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

In generale, riesco a pensare a una situazione simile. Una scatola e contenuti. Una casella ha dei contenuti, quindi una relazione has-a è appropriata. Il contenuto può avere una scatola? Certo, una scatola con una scatola. Una scatola è il contenuto. Ma IS-A non è un buon design per tutte le scatole. In tal caso, prenderei in considerazione il motivo del decoratore. In questo modo una scatola viene decorata con i contenuti in fase di esecuzione, se necessario.

Anche i tag possono contenere 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.


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 dell'altra. La mia impressione è che si adatterà meglio alla Postclasse perché l'associazione verrà creata durante la creazione o la modifica dei post, quando altre cose sul post cambiano allo stesso tempo.

Inoltre, se si stanno associando più tag e si desidera farlo in un aggiornamento del database, è necessario creare una sorta di elenco di tutti i tag associati allo stesso post prima di eseguire l'aggiornamento. Tale elenco si adatta molto meglio alla Postclasse.


1

Personalmente, non aggiungerei questa funzionalità a nessuno dei due.

Per me, entrambi Poste Tagsono oggetti dati, quindi non dovrei gestire la funzionalità del database. Dovrebbero semplicemente esistere. Sono pensati per contenere i dati ed essere utilizzati da altre parti dell'applicazione.

Invece, avrei un'altra classe che è responsabile della tua logica aziendale e dei dati relativi alla tua pagina web. Se la tua pagina mostra un post e consente agli utenti di aggiungere tag, la classe avrebbe un Postoggetto e conterrebbe funzionalità da aggiungere Tagsa quello Post. Se la tua pagina mostra tag e consente agli utenti di aggiungere post a tali tag, conterrebbe un Tagoggetto e avrebbe funzionalità da aggiungere Postsa quello Tag.

Sono solo io però. Se ritieni di dover gestire la funzionalità del database nei tuoi oggetti dati, allora consiglierei la risposta di pdr


0

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

Dal punto di vista Posta ...
Se la modifica o la creazione di una Posta un'attività secondaria diventa la gestione delle relazioni Tag .

  1. Aggiungi tag esistente al post
  2. Rimuovi tag dalla posta

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 che riguarda l'interazione con un post è la funzione Elimina tag. Tuttavia, questa funzione dovrebbe essere una funzione autonoma indipendente.

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.