2012-03-21 180 views
10

我正在做一个简单用例的面向对象设计的基本练习:书籍可以用很多标签进行标记。书籍和标签的OO设计

我有很多的解决方案,我希望你的意见能够在OOD原则和可靠性方面更好。

选项1

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

    private List<Tag> tags; 
} 

困扰我的是,我们混合一书的核心属性与附加的分类或搜索数据的事情。我将来可能会有某些书无法加标签的要求。在未来,当我添加更多responsabilities书类可以变得臃肿:类别,即读取它的用户,评级列表...

选项2

public class TaggedBook extends Book { 
    private Book book; 

    private List<Tag> tags; 
} 

我认为这是类似于装饰者模式,但我不认为它适合在这里,因为我不扩展行为。

选项3

解耦书籍和标签comnpletely,并使用一个服务从一本书中检索标签(给每本书都有一个唯一的标识符)

List<Tag> TagService.getTags(Book book) 

不过,我不知道找到这个解决方案非常优雅(是吗?),我可能不得不发送两个查询:一个检索该书,另一个检索标签。

我计划申请其他要求的最佳选项:一本书有一个等级,一本书可以被分类...

我还计划使用DMS存储书籍和标签的对象。由于它不是一个关系数据库,它的模式可能会对应于类设计。

谢谢

+0

“标签”不是自己的实体吗?如在,不仅仅是一个字符串 - 它可能有一个描述等... – cHao 2012-03-21 06:21:37

+0

是的,谢谢,这是我的原创设计,但我忘了将它包括在问题 – 2012-03-21 06:24:59

回答

1

我更喜欢第三个选项,将它们完全分开。

书籍和标签之间存在多对多的关系,通过将它们分开,您可以更容易地进行查询,如“哪些书籍被'计算机科学'标记了”。

+0

顺便说一句,“标签”也是一个实体,对吗? – 2012-03-21 06:24:07

+0

是的,是的。我纠正了我的问题 – 2012-03-22 06:08:49

4

如果书籍不是您模型中可以标记的唯一实体。我会用这个接口去:

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

而且

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

该种图书/可标记实体只需要实现此接口。这样,您可以让Book对象允许标记,而其他对象则不允许。此外,标记机制可以与您的模型的其他对象一起使用。

5

所有三个选项都可以为您的课堂设计提供有效且良好的选择。这一切都取决于完整的上下文/要求。您列出的要求很可能不足以做出“正确”的决定。例如,如果您的应用程序相当以书为中心,且标记无需独立于书籍进行演变或编写,则选项3可能会带来不必要的复杂性。如果你设计一个公共API作为实际逻辑的外观,你仍然可以选择1或2,即使内部BookTag都是完全去耦合的聚合根。可扩展性,性能和可扩展性......这些都是您需要平衡并且会影响您的设计的所有可能的要求。

如果您正在寻找一个良好的正式方法和企业应用程序的类设计指南,我建议你看看Domain Driven Design

此外,不要为未知的未来需求设计类。这也会再次增加无用的复杂性(想想:成本)。只需确保在需要重新构建新需求时您有足够的单元测试来覆盖背部。

+0

“另外,不要为未知的未来需求设计类。”这,这个!当那一天到来时,重构。在此之前,不要让它比以前更复杂。 – GalacticCowboy 2012-03-28 16:47:45

7

Decorator模式的概念在你的case.But非常适合我觉得strategy pattern 是你更多的有用和有效case.If你不知道的战略格局再拿上This。它一看就会给你一个如果您需要更多建议或有任何疑问,请发表评论。 谢谢 一切顺利..

+0

嗨,感谢策略模式的链接,今天我也学到了一些新的东西。但为什么你认为在这种情况下,这将是最好的选择? – MrTJ 2012-03-28 09:09:20

+0

@MrTJ:我不认为这是最好的选择。上面给出的代码的上下文似乎认为,战略模式可以通过额外的分类,类别,搜索数据,用户列表等满足他对未来扩展的需求。 。 – 2012-04-02 12:13:46

2

我认为这将是更好的混合模式更好的解决方案。记住一个特定的模式只能解决一个特定的问题。

我的建议是隔离不同的接口并相应地加入它们。基类应该能够查询支持的接口,以便它可以调用适当的接口函数。

第一界面是查询支持的接口:

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

...其次是特定的接口。

假设第一特定接口是可加标签:

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

希望这有助于。 :)

+0

谢谢阿洛伊斯:) – fadedreamz 2012-03-28 19:35:10

+0

那本书可以在同一时间进行标记和评分?标签应该延长额定值,反之亦然?或者应该有一个实现标签和评级的新类? – Ivan 2012-04-03 12:38:42

+0

应该有一个实现标签和评分的新类。 – fadedreamz 2012-04-04 16:42:41

2

选项1

第一解决方案支持具有-的关系的概念。我没有看到这个设计有什么缺点。你说在向类中添加职责时,代码可能会膨胀,但这是一个完全独立的问题(在SOLID中打破S)。一个有许多成员的班级本质上并不是一件坏事(有时候可能表明出了问题,但并非总是如此)。

您提出的另一个问题是,将来您可能会有一个Book而没有Tag s。由于我不知道整个情况,所以我只能猜测,但我会强烈认为,这个Book会/可能只是一个Book,0 Tag s。

选项3

我认为这是做事的非面向对象的方式。通过某个ID的关联实现一个has-a关系。我根本不喜欢它。

对于您想要添加到Book的每个附加属性,您还需要创建一个合适的Service类型对象,并进行大量额外的和不必要的调用,而不会因为我可以看到的选项1而获益。

我不喜欢这个的另一个原因是,这意味着Tag与书有关系。我不认为他们这样做。

选项2

这不是在我看来不错,但主要是因为我觉得Decorator模式是不适合在这种情况下使用,因为你可能会需要利用rtti能够使用你的结果对象,或者在你的基类中实现很多空方法。

我认为你的第一个解决方案绝对是最好的。如果您担心代码膨胀,您可能会考虑将Tags对象作为Book的成员,该对象负责搜索自身(这对SOLID中的S也有帮助),对于Book的任何其他属性也是如此。如果Book没有标签,那么Tags只会在查询时返回false,而Book会回显。

摘要

对于这样一个简单的问题,不好好考虑一下。面向对象设计的基本原则(has-a,is-a)是最重要的。

+0

1为_DO不overthink_。 – 2012-04-02 12:27:37

1

除非书上的标签顺序很重要,否则书本可以有相同的标签两次,否则应该将标签存储在一个集合中,而不是一个列表中。

一旦你这样做了,我会像第三种选择一样。在我看来,这些书不拥有这些标签,并且这些标签不拥有这些书(实际上,您可能希望以任何方式查看这些书)。之后,如果您希望将其他内容与您的图书相关联(,例如评论,评分,图书馆),则可以在不修改图书类的情况下创建另一个关联。

2

我会建议先考虑它作为设计问题,然后尝试在代码中表达设计。

所以,我们必须决定我们有什么类(实体)。 Book是一个类,因为它是问题的中心,具有不同的实例,可能还有几个属性和操作。 Tag可能既是一个值对象又是一个类。

让我们考虑第一个选项。它可以是一个值对象,因为它没有内部结构,没有任何操作,它的实例可能不是独特的。因此Tag可以被认为是String标记。这样,Book就有一个属性,比如tags,它包含一组值tag。可以添加和删除值,不受任何限制。书籍可以通过标签按价值提供的标签进行搜索。很难得到完整的标签列表或获取特定标签的所有书籍。

现在,第二个选项。 Tag也可以是一个类,因为它与另一个类(Book)相关,其实例可能不同。然后我们有两个类:BookTag,以及它们之间的'多对多'关联 - TaggedWith。正如你可能知道的那样,除了是一种关系之外,关联本身就是一种类。 TaggedWith关联实例(链接)连接BookTag的实例。接下来,我们必须决定在BookTag之间将负责管理对应关系的哪个类(创建,读取,查找,销毁,更新...)。这里最自然的选择是将这个责任分配给协会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 
    } 
} 
1

我会做完全不同的。如果想像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"); 
}