2012-03-21 12 views
10

Sto facendo un esercizio di base di progettazione orientata agli oggetti per un caso di utilizzo semplice: Un libro può essere taggato con molti tag.Progettazione OO di libri e tag

Ho molte soluzioni e vorrei il vostro input su cui è meglio in termini di principi OOD e maintanability.

Opzione 1

public class Book { 
    private String title; 
    //... other attributes 

    private List<Tag> tags; 
} 

La cosa che mi preoccupa è che abbiamo mescolato attributi principali di un libro con la categorizzazione supplementare o dati di ricerca. Potrei avere in futuro un requisito in cui determinati Libri non possono essere taggati. In futuro, la classe book può diventare gonfio quando aggiungo più responsabilità: categoria, lista degli utenti che lo leggono, valutazioni ...

Opzione 2

public class TaggedBook extends Book { 
    private Book book; 

    private List<Tag> tags; 
} 

Penso che questo è simile a il pattern Decorator, ma non lo vedo adatto perché non sto estendendo il comportamento.

Opzione 3

disaccoppiare Libri e Tag comnpletely, e utilizzare un servizio per recuperare Tag da un libro (assegnato ogni libro ha un identificativo univoco)

List<Tag> TagService.getTags(Book book) 

Tuttavia, non fare trova questa soluzione molto elegante (vero?), e potrei dover inviare due query: una per recuperare il libro, l'altra per i tag.

Sto pensando di applicare le migliori opzioni per altri requisiti: un libro ha un rating, un libro può essere classificati ...

Sto anche pensando di usare un DMS per archiviare libri e oggetti Tag. Poiché non è un database delle relazioni, il suo schema probabilmente corrisponderà al design della classe.

Grazie

+0

Un "tag" non dovrebbe essere un'entità propria? Come in, più di una stringa - potrebbe avere una descrizione e così ... – cHao

+0

Sì, grazie, questo era il mio design originale, ma ho dimenticato di includerlo nella domanda –

risposta

1

io preferisco la terza opzione, per separare completamente.

I libri e i tag hanno un rapporto mang-to-molti, separandoli, è possibile rendere più facile fare query come "quali libri sono stati taggati da 'Computer Science'".

+0

a proposito, un "tag" è anche un'entità, giusto? –

+0

sì, lo è. Ho corretto la mia domanda –

4

Se i libri non sono l'unica entità nel modello che può essere taggata. Vengo con questa interfaccia:

public interface Taggeable { 
    public List<Tag> getTags();   
    public void setTags (List<Tag> tags) 
} 

E

public class Book implements Taggeable { 
//Book attributes and methods 

I tipi di libri/entità che possono essere contrassegnati solo bisogno di implementare questa interfaccia. In questo modo, puoi avere oggetti Libro che consentono la codifica e altri che non lo fanno. Inoltre, il meccanismo di codifica può essere utilizzato con altri oggetti del modello.

5

Tutte e tre le opzioni possono essere valide e buone scelte per il design della classe. Tutto dipende dal contesto/requisiti completi. Il requisito che hai elencato è molto probabilmente non sufficiente per prendere la decisione "giusta".Ad esempio, se la tua applicazione è piuttosto incentrata sui libri e i tag non devono necessariamente evolversi o essere creati indipendentemente dai libri, l'opzione 3 probabilmente introdurrebbe una complessità non necessaria. Se si progetta un'API pubblica che funge da facciata attorno alla propria logica effettiva, è possibile continuare a utilizzare l'opzione 1 o 2 anche se internamente sia Book sia Tag sono radici aggregate totalmente disaccoppiate. Scalabilità, prestazioni, estensibilità ... questi sono tutti i requisiti che è necessario bilanciare e che influenzeranno il tuo design.

Se si sta cercando una buona metodologia formalizzata e una guida per la progettazione della classe per le applicazioni aziendali, suggerirei di esaminare Domain Driven Design.

Inoltre, non progettare classi per futuri requisiti sconosciuti. Ciò aggiungerà di nuovo una complessità inutile (pensa: costo). Assicurati di avere abbastanza test unitari che ti coprano la schiena quando hai bisogno di refactoring per nuovi requisiti.

+0

"Inoltre, non progettare classi per esigenze future sconosciute." Questo, questo, questo! Quando quel giorno arriverà, refactoring. Fino ad allora, non renderlo più complesso di quanto non debba già essere. – GalacticCowboy

7

Il concetto di modello decorator si adatta bene nella vostra case.But penso strategy pattern è più utile ed efficace in voi case.If non si sa su modello di strategia Dai un'occhiata sul This .E vi darà un buona idea sul modello della strategia. Se hai bisogno di più suggerimenti o hai qualche domanda, chiedi nel commento. Grazie Tutto il meglio ..

+0

Ciao grazie per il collegamento del modello di strategia, ho imparato anche oggi qualcosa di nuovo. Ma perché pensi che in questo caso sarebbe la scelta migliore? – MrTJ

+0

@MrTJ: Non dico che sarebbe la scelta migliore .. Il contesto del codice sopra riportato mi sembra che il modello di strategia possa soddisfare il suo requisito per l'estensione futura con categorizzazione aggiuntiva, categorie, dati di ricerca, elenco di utenti, ecc. –

2

Penso che sarebbe meglio mescolare il modello per una soluzione migliore. Ricorda che un particolare schema risolve solo un particolare problema.

Il mio suggerimento è di isolare interfacce diverse e unirle di conseguenza. La classe base dovrebbe avere la capacità di interrogare le interfacce supportate, in modo che possa chiamare le funzioni di interfaccia appropriate.

prima interfaccia è l'interfaccia supportata query:

public interface QueryInterface { 
    public boolean isTaggable(); 
    public boolean isRatable(); 
} 

... successivo viene interfacce particolari.

Supponiamo la prima interfaccia particolare è oggetto di tag:

public interface Taggable { 
    public Vector<Tag> getTags(); 
    public boolean addTag(Tag newTag); 
} 

... e la seconda è rateable ...

public interface Rateable { 
    public Rating getRating(); 
    public void setRating(Rating newRating); 
} 

La piana classe base vecchia stessa: :)

public class Book implements QueryInterface { 
    private String title; 
    public boolean isTaggable() { 
     return false; 
    } 

    public boolean isRateable() { 
     return false; 
    } 
} 

Ora la classe derivata speciale conforme all'interfaccia Taggable:

public class TaggedBook extends Book implements Taggable { 
    private Vector<Tag> tags; 
    public Vector<Tag> getTags() { 
     return tags; 
    } 

    @override 
    public boolean isTaggable() { 
     return true; 
    } 

    public boolean addTag(Tag newTag) { 
     return tags.insert(newTag); 
    } 
} 

... e la diversa libro che è imponibile solo:

public class RatedBook extends Book implements Rateable { 
    private Rating rating; 
    public Rating getRating() { 
     return rating; 
    } 
    public void setRating(Rating newRating) { 
     this.rating = newRating; 
    } 

    @override 
    public boolean isRateable() { 
     return true; 
    } 
} 

Spero che questo aiuti. :)

+0

Grazie Alois :) – fadedreamz

+0

E il libro che può essere taggato e valutato nello stesso tempo? etichettato dovrebbe estendere valutato o viceversa? O dovrebbe esserci una nuova classe che implementa taggable e rateable? – Ivan

+0

Ci dovrebbe essere una nuova classe che implementa taggable e rateable. – fadedreamz

2

Opzione 1

La prima soluzione supporta il concetto di presenta-una relazione. Non vedo che ci sia qualche svantaggio in questo progetto. Dici che c'è una possibilità di gonfiare il codice quando aggiungi responsabilità alla classe, tuttavia questo è un problema completamente separato (rompendo la S in SOLID). Una classe con molti membri non è intrinsecamente una cosa negativa (a volte può essere un'indicazione che qualcosa è andato storto, ma non sempre).

L'altro problema che si dà è che in futuro si potrebbe avere un Book senza Tag s. Dato che non conosco l'intera immagine, sto solo supponendo, ma direi con forza che questo Book sarebbe/potrebbe semplicemente essere un Book con 0 Tag s.

Opzione 3

Penso che questo sia il modo non OO di fare le cose. Implementazione di una relazione has-a mediante associazione di alcuni ID. Non mi piace affatto.

Per ogni proprietà aggiuntiva che si desidera aggiungere a Book, è necessario creare anche un oggetto di tipo Service appropriato e effettuare molte chiamate aggiuntive e non necessarie senza alcun vantaggio per farlo tramite l'opzione 1 che è possibile vedere.

Un'altra ragione per cui non mi piace è che questo implica che uno Tag ha una relazione has-a con i libri. Non penso che lo facciano.

Opzione 2

Questo non è buono a mio parere, ma che è soprattutto perché penso che il motivo decoratore non è stato progettato per l'uso in questo tipo di situazione, sia perché si sarebbe probabilmente necessario fare uso di rtti per poter usare gli oggetti risultanti, o implementare molti metodi vuoti nella tua classe base.

Penso che la tua prima soluzione sia in assoluto la migliore. Se sei preoccupato per il code bloat potresti considerare di avere un oggetto Tags come membro di Book, che è responsabile della ricerca stessa (Questo aiuta anche con la S in SOLID) e lo stesso per qualsiasi proprietà aggiuntiva di Book. Se uno Book non ha tag allora Tags restituirebbe semplicemente false quando richiesto, e Book lo farebbe eco.

Sommario

Per un semplice problema così, non più di pensarlo. I principi di base del design OO (has-a, is-a) sono i più importanti.

+0

+1 per _non overthink_. –

1

A meno che l'ordine dei tag su un libro sia importante o un libro possa avere lo stesso tag due volte, è necessario memorizzare i tag in un set anziché in un elenco.

Una volta fatto, vorrei andare con qualcosa come la terza opzione. Mi sembra che i libri non possiedano i tag e che i tag non possiedano i libri (in effetti, si vorrebbe cercare in entrambi i casi, probabilmente). Successivamente, quando desideri associare altre cose ai tuoi libri (ad esempio recensioni, valutazioni, librerie) puoi creare un'altra associazione senza modificare la classe del libro.

2

Vorrei suggerire di pensarci prima come un problema di progettazione, quindi provare ad esprimere il disegno nel codice.

Quindi, dobbiamo decidere quali classi (entità) abbiamo. Il Book è una classe perché è centrale per il problema, ha istanze distinte e, probabilmente, diversi attributi e operazioni. Lo Tag può essere sia un oggetto valore sia una classe.

Consideriamo la prima opzione. Può essere un oggetto valore perché non ha una struttura interna, nessuna operazione e le sue istanze potrebbero non essere distinte.Pertanto un Tag può essere considerato come un indicatore String. In questo modo, Book ha un attributo, ad esempio tags che contiene una raccolta di valori tag. I valori possono essere aggiunti e rimossi senza alcuna restrizione. I libri possono essere cercati per tag in cui i tag sono forniti in base al valore. È difficile ottenere un elenco completo di tag o ottenere tutti i libri per un tag specifico.

Ora, la seconda opzione. Lo Tag può anche essere una classe perché è correlato a un'altra classe (Book) e le sue istanze potrebbero essere distinte. Quindi abbiamo due classi: Book e Tag e un'associazione 'many-to-many' tra di loro - TaggedWith. Come forse sapete, l'associazione è una sorta di classe stessa oltre a essere una relazione. Le istanze dell'associazione TaggedWith collegano le istanze di Book e Tag. Quindi dobbiamo decidere quale classe sarà responsabile della gestione della corrispondenza (creare, leggere, cercare, distruggere, aggiornare ...) tra Book e Tag. La scelta più naturale qui è quella di assegnare questa responsabilità all'associazione TaggedWith.

Consente di scrivere un codice.

Opzione 1

public class Book { 
    private Collection<String> tags; 

    /* methods to work with tags, e.g. */ 
    public void addTag(String tag) {...} 
    public String[] getAllTags() {...} 
    ... 

} 

Può sembrare complesso, ma in realtà codice simile può semplicemente essere generato dalla descrizione di progettazione in un paio di clic del mouse. D'altra parte, se si utilizza DB un sacco di codice qui diventa query SQL.

Opzione 2

public class Tag { 
    /* we may wish to define a readable unique id for Tag instances */ 
    @Id 
    private String name; 

    /* if you need navigation from tags to books */ 
    private Collection<Book> taggedBooks; 
    public Collection<Book> getTaggedBooks() {...} 
    public void addBook(Book book) {...} // calls TaggedWith.create(this, book) 
    public void _addBook(Book book) {...} // adds book to taggedBooks 
    .... 
    /* I think you get the idea */ 


    /* methods to work with tags */ 
    public String getName() {...} 
    ... 

    /* Tags cannot be created without id (i.e. primary key...) */ 
    public Tag(String name) {...} 


    /* if you'd like to know all tags in the system, 
     you have to implement 'lookup' methods. 
     For this simple case, they may be put here. 
     We implement Factory Method and Singleton patterns here. 
     Also, change constructor visibility to private/protected. 
    */ 
    protected static HashMap<String, Tag> tags = ...; // you may wish to use a DB table instead 
    public static Tag getInstance(String name) {...} // this would transform to DAO for DB 
} 

public class Book { 
    /* if we need an id */ 
    @Id // made up 
    private String bookId; 

    /* constructors and lookup the same as for Tag 
     If you wish to use a database, consider introducing data access layer or use ORM 
    */ 

    /* if you need navigation from Book to Tag */ 
    private Collection<Tag> tags; 
    public Collection<Tag> getTags() {...} 
    ... 

} 

public TaggedWith { 
    /* constructor and lookup the same as for Tag and Book (!) */ 

    /* manage ends of the association */ 
    private Book book; 
    private Tag tag; 
    public Book getBook() {...} 
    public Tag getTag() {...} 

    protected TaggedWith(Book book, Tag tag) {  
      this.book = book; 
      this.tag = tag; 
      book._addTag(tag); // if you need navigation from books to tags 
      tag._addBook(book); // if you need navigation from tags to books 
    } 


    /* if you need to search tags by books and books by tags */ 
    private static Collection<TaggedWith> tagsBooks = ...; 
    public static TaggedWith create(Tag tag, Book book) { 
      // create new TaggedWith and add it to tagsBooks 
    } 
} 
1

lo farei completamente diverso. Pensando un po 'alle etichette di Gmail, farei in modo che sarebbe più facile cercare libri con determinati tag piuttosto che trovare i tag sul libro. Vale a dire, i tag funzionano come un filtro per trovare libri, non il contrario.

public interface ITaggable 
{ 
    string Name { get; } 
} 

public class Book : ITaggable 
{ 
} 

public class Tag 
{ 
    private List<ITaggable> objects; 
    private String name; 

    public void AddObject() {} 
    public void RemoveObject() {} 
    public void HasObject() {} 
} 

public class TagManager 
{ 
    private List<Tag> tags; 

    private void InitFromDatabase() {} 
    public void GetTagsForObject(o: ITaggable) {} 
    public void GetObjectsForTag(objectName: String) {} // 
    public void GetObjectsForTag(t: Tag) {} // 
    public void GetObjectsForTag(tagName: String) {} // 
    public void GetAllTags(); 
} 

... somewhere else ... 

public void SearchForTag(tag: Tag) 
{ 
    TagManager tagManager = new TagManager(); 
    // Give me all tags with 
    List<ITaggable> books = tagManager.GetObjectsForTag("History"); 
} 
Problemi correlati