OO дизайн книг и тегов

Я делаю базовое упражнение объектно-ориентированного дизайна для простого использования: книга может быть помечена многими тегами.

У меня много решений, и я хотел бы, чтобы ваш вклад был лучше с точки зрения принципов OOD и универсальности.

Вариант 1

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

    private List<Tag> tags;
}

Меня беспокоит то, что мы смешали основные атрибуты Книги с дополнительной категоризацией или поисковыми данными. Возможно, в будущем я буду требовать, чтобы определенные Книги не могли быть помечены. В будущем класс Book может стать раздутым, когда я добавлю больше ответственности: категорию, список пользователей, которые ее читают, рейтинги...

Вариант 2

public class TaggedBook extends Book {
    private Book book;

    private List<Tag> tags;
}

Я думаю, что это похоже на шаблон Decorator, но я не вижу его здесь подходящим, потому что я не расширяю поведение.

Вариант 3

Разделить книги и теги полностью и использовать службу для извлечения тегов из книги (если каждая книга имеет уникальный идентификатор)

List<Tag> TagService.getTags(Book book)

Однако я не считаю это решение очень элегантным (это?), и мне, возможно, придется отправить два запроса: один для извлечения книги, другой для тегов.

Я планирую применить наилучшие варианты для других требований: Книга имеет рейтинг, книгу можно классифицировать...

Я также планирую использовать DMS для хранения объектов Books и Tags. Поскольку это не база данных отношений, его схема, скорее всего, будет соответствовать дизайну класса.

Спасибо

Ответ 1

Концепция декоратора хорошо вписывается в ваш случай. Но я думаю, шаблон стратегии более полезен и эффективен в вашем случае. Если вы не знаете о шаблоне стратегии, посмотрите на Это. Это даст вам хорошее представление о шаблоне стратегии. Если вам нужно больше предложений или у вас есть какой-либо запрос, попросите в комментарии. спасибо Все лучшее..

Ответ 2

Все три варианта могут быть действительными и хорошими вариантами дизайна вашего класса. Все зависит от полного контекста/требований. Указанное вами требование, скорее всего, недостаточно для принятия "правильного" решения. Например, если ваше приложение является ориентированным на книги, и теги не должны развиваться или быть независимыми от книг, вариант 3, вероятно, приведет к ненужной сложности. Если вы создаете публичный API, который действует как фасад вокруг вашей фактической логики, вы все равно можете перейти к варианту 1 или 2, хотя внутри и Book и Tag являются полностью развязанными корнями совокупности. Масштабируемость, производительность, extensibilty... это все возможные требования, которые вам необходимо сбалансировать, и это повлияет на ваш дизайн.

Если вы ищете хорошую формализованную методологию и руководство для дизайна классов для корпоративных приложений, я бы предложил вам изучить Проект, управляемый доменом.

Кроме того, не разрабатывайте классы для неизвестных будущих требований. Это также снова добавит бесполезную сложность (подумайте: стоимость). Просто убедитесь, что у вас достаточно unit test, которые закрывают спину, когда вам нужно реорганизовать новые требования.

Ответ 3

Если книги не являются единственными объектами в вашей модели, которые могут быть помечены. Я пойду с этим интерфейсом:

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

и

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

Типы книг/сущностей, которые могут быть помечены, должны только реализовать этот интерфейс. Таким образом, вы можете иметь объекты Book, которые позволяют помечать теги, а другие - нет. Кроме того, механизм тегов можно использовать с другими объектами вашей модели.

Ответ 4

Я думаю, было бы лучше смешать шаблон для лучшего решения. Помните, что определенный шаблон решает только одну проблему.

Мое предложение состоит в том, чтобы изолировать различные интерфейсы и присоединиться к ним соответствующим образом. Базовый класс должен иметь возможность запрашивать поддерживаемые интерфейсы, чтобы он мог вызывать соответствующие функции интерфейса.

Первый интерфейс - это поддерживаемый запросом интерфейс:

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

... далее идут конкретные интерфейсы.

Предположим, что первый конкретный интерфейс является taggable:

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

..., а второй - с переменной скоростью...

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

Обычный простой базовый класс::)

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

   public boolean isRateable() {
      return false;
   }
}

Теперь специальный производный класс, который соответствует интерфейсу 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);
   }
}

... и другая книга, которая может быть изменена только:

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

Надеюсь, это поможет.:)

Ответ 5

Вариант 1

Первое решение поддерживает концепцию отношения has-a. Я не вижу недостатка в этом дизайне. Вы говорите, что существует возможность раздувания кода, когда вы добавляете обязанности к классу, однако это совершенно отдельная проблема (нарушение S в SOLID). Класс со многими членами не является по своей сути плохим (иногда это может быть признаком того, что что-то пошло не так, но не всегда).

Другая проблема, которую вы даете, заключается в том, что в будущем у вас может быть Book без Tag s. Поскольку я не знаю всю картину, я только догадываюсь, но я бы сказал, что этот Book мог бы/просто быть Book с 0 Tag s.

Вариант 3

Я думаю, что это не OO-способ делать что-то. Реализация отношения has-a путем объединения некоторого ID. Мне это совсем не нравится.

Для каждого дополнительного свойства, которое вы хотели добавить в Book, вам также необходимо создать соответствующий объект типа Service и сделать много дополнительных и ненужных вызовов без каких-либо преимуществ для этого в отношении варианта 1, который я могу видеть.

Еще одна причина, по которой мне это не нравится, заключается в том, что это означает, что Tag имеет отношения с книгами. Я не думаю, что они это делают.

Вариант 2

Это нехорошо, на мой взгляд, но это в основном потому, что я считаю, что шаблон декоратора не был предназначен для использования в подобной ситуации, и потому, что вам, вероятно, понадобится использовать rtti, чтобы иметь возможность использовать объектов или реализовать множество пустых методов в базовом классе.

Я думаю, что ваше первое решение в подавляющем большинстве - лучшее. Если вы беспокоитесь о раздувании кода, вы можете подумать о том, что объект Tags является членом Book, который отвечает за сам поиск (это также помогает с S в SOLID) и то же самое для любых дополнительных свойств Book. Если a Book не имеет тегов, тогда Tags просто возвращает false при запросе, а Book будет эхо-то.

Резюме

Для такой простой проблемы, не думайте об этом. Основные принципы проектирования OO (имеет-a, is-a) являются наиболее важными.

Ответ 6

Я бы предложил сначала подумать об этом как о проблеме проектирования, а затем попытаться выразить дизайн в коде.

Итак, мы должны решить, какие у нас классы (сущности). Book - это класс, который является центральным для этой проблемы, имеет разные экземпляры и, возможно, несколько атрибутов и операций. Tag может быть как объектом ценности, так и классом.

Рассмотрим первый вариант. Это может быть объект ценности, потому что он не имеет внутренней структуры, никаких операций и его экземпляры могут не различаться. Таким образом, a Tag можно рассматривать как маркер String. Таким образом, Book имеет атрибут, скажем, tags, который содержит коллекцию значений Tag. Значения могут быть добавлены и удалены без каких-либо ограничений. Книги можно искать по тегам, где теги предоставляются по значению. Трудно получить полный список тегов или получить все книги для определенного тега.

Теперь второй вариант. Tag также может быть классом, потому что он связан с другим классом (Book), и его экземпляры могут быть разными. Тогда мы имеем два класса: Book и Tag, а между ними - связь "многие-ко-многим" - TaggedWith. Как вы знаете, ассоциация - это своего рода класс, помимо отношения. Экземпляры связи TaggedWith (ссылки) соединяют экземпляры Book и Tag. Затем мы должны решить, какой класс будет отвечать за управление перепиской (создание, чтение, поиск, уничтожение, обновление...) между Book и Tag. Наиболее естественным выбором здесь является назначение этой ответственности ассоциации TaggedWith.

Позволяет написать код.

Вариант 1

public class Book {
     private Collection<String> tags;

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

}

Он может выглядеть сложным, но на самом деле аналогичный код можно просто создать из описания дизайна за пару щелчков мыши. С другой стороны, если вы используете DB, здесь много кода становится SQL-запросами.

Вариант 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
    }
}

Ответ 7

Я предпочитаю третий вариант, чтобы полностью их отделить.

В книгах и тегах есть отношения mang-to-many, разделяя их, вы можете упростить создание таких запросов, как "книги, помеченные тегом" Computer Science ".

Ответ 8

Если ни один из тегов в книге не имеет значения, или книга может иметь один и тот же тег дважды, вы должны хранить свои теги в наборе, а не в списке.

Как только вы это сделаете, я пойду с чем-то вроде третьего варианта. Мне кажется, что книги не имеют тегов, а теги не владеют книгами (действительно, вы бы хотели посмотреть это в любом случае, возможно). Позже, когда вы хотите связать другие вещи с вашими книгами (например, обзоры, рейтинги, библиотеки), вы можете создать другую ассоциацию, не изменяя класс книги.

Ответ 9

Я бы сделал это совершенно иначе. Подумав немного, как ярлыки в Gmail, я бы сделал так, чтобы было легче искать книги с определенными тегами, а не находить те метки в книге. А именно, теги работают как фильтр для поиска книг, а не наоборот.

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");
}