ОО дизайн книг и тегов - PullRequest
       2

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

10 голосов
/ 21 марта 2012

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

У меня много решений, и я быкак ваш вклад, который лучше с точки зрения принципов 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

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

List<Tag> TagService.getTags(Book book)

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

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

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

Спасибо

Ответы [ 9 ]

7 голосов
/ 27 марта 2012

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

5 голосов
/ 27 марта 2012

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

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

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

4 голосов
/ 26 марта 2012

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

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

И

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

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

2 голосов
/ 30 марта 2012

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

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

Рассмотрим первый вариант.Это может быть объект значения, поскольку он не имеет внутренней структуры, каких-либо операций и его экземпляры могут быть не различимы.Таким образом, 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() {...}
     ...

}

Может показаться сложным, но на самом деле похожий код может быть сгенерирован из описания конструкции всего несколькими щелчками мыши.С другой стороны, если вы используете БД, большой объем кода здесь становится запросами 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
    }
}
2 голосов
/ 28 марта 2012

Вариант 1

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

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

Вариант 3

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

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

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

Вариант 2

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

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

Резюме

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

2 голосов
/ 28 марта 2012

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

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

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

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

... nextприходят определенные интерфейсы.

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

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

Теперь специальный производный класс, который соответствует тегируемому интерфейсу:

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

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

1 голос
/ 02 апреля 2012

Я бы сделал это совершенно по-другому.Подумав немного как ярлыки в 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");
}
1 голос
/ 28 марта 2012

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

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

1 голос
/ 21 марта 2012

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

Книги и теги имеют отношение mang-ко-многим, разделяя их, вы можете упростить выполнение запросов типа «какие книги помечены»«Информатика».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...