我正在为一个简单的用例进行面向对象设计的基本练习:一本书可以用很多标签标记。
我有很多解决方案,我希望您的投入在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对象。由于它不是关系数据库,因此其架构可能与类设计相对应。
谢谢
答案 0 :(得分:7)
装饰模式的概念很适合你的情况。但我认为strategy pattern 在你的情况下更有用和有效。如果你不了解战略模式,那么看看This。它会给你一个关于战略模式的好主意。如果你需要更多的建议或有任何疑问,那么在评论中提问。 谢谢 一切顺利..
答案 1 :(得分:5)
这三个选项都是有效的,也是您班级设计的不错选择。这完全取决于完整的背景/要求。您列出的要求很可能不足以做出“正确”的决定。例如,如果您的应用程序是以书籍为中心而且标签不需要独立于书籍进行演变或编写,则选项3可能会引入不必要的复杂性。如果您设计一个公共API作为围绕实际逻辑的外观,您仍然可以使用选项1或2,即使Book
和Tag
内部都是完全解耦的聚合根。可伸缩性,性能,可扩展性......这些都是您需要平衡并且会影响您的设计的所有可能要求。
如果您正在为企业应用程序的类设计寻找一个良好的形式化方法和指南,我建议您研究Domain Driven Design。
另外,不要为未来未知的要求设计类。这也将再次增加无用的复杂性(想想:成本)。当您需要重构新的要求时,请确保您有足够的单元测试覆盖您的背部。
答案 2 :(得分:4)
如果图书不是模型中唯一可以标记的实体。我会选择这个界面:
public interface Taggeable {
public List<Tag> getTags();
public void setTags (List<Tag> tags)
}
和
public class Book implements Taggeable {
//Book attributes and methods
可以标记的图书/实体种类只需要实现此界面。这样,您可以拥有允许标记的Book对象和不允许标记的其他对象。此外,标记机制可以与模型的其他对象一起使用。
答案 3 :(得分: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;
}
}
希望这会有所帮助。 :)
答案 4 :(得分:2)
选项1
第一个解决方案支持has-a关系的概念。我没有看到这种设计有任何缺点。您说在向类中添加职责时可能存在代码膨胀,但这是一个完全独立的问题(在SOLID中破坏了S)。一个有很多成员的班级本身并不是一件坏事(有时可能表明事情出了问题,但并非总是如此)。
您提出的另一个问题是,将来您可能会Book
没有Tag
。由于我不知道整个画面,我只是猜测,但我强烈反对这个Book
会/可能只是Book
0 Tag
s。
选项3
我认为这是非OO的做事方式。通过关联某个ID来实现has-a关系。我完全不喜欢它。
对于要添加到Book
的每个附加属性,您还需要创建一个适当的Service
类型对象,并进行大量额外的和不必要的调用,而不会对选项1执行任何操作我能看到。
我不喜欢这个的另一个原因是,这意味着Tag
与书有关系。我不认为他们这样做。
选项2
这在我看来并不好,但这主要是因为我认为装饰器模式不适合在这种情况下使用,并且因为你可能需要使用rtti才能使用你的结果对象,或在基类中实现许多空方法。
我认为您的第一个解决方案绝对是最好的解决方案。如果您担心代码膨胀,您可能会考虑将Tags
对象作为Book
的成员,它负责搜索自身(这也有助于SOLID中的S)以及任何其他对象Book
的属性。如果Book
没有标记,则Tags
只会在查询时返回false,而Book
会回显该标记。
<强>摘要强>
对于这样一个简单的问题,不要过度思考。 OO设计的基本原则(has-a,is-a)是最重要的。
答案 5 :(得分:2)
我建议首先将其视为设计问题,然后尝试用代码表达设计。
因此,我们必须决定我们拥有哪些类(实体)。 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() {...}
...
}
它可能看起来很复杂,但实际上可以通过几次鼠标点击从设计描述中生成类似的代码。另一方面,如果你使用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
}
}
答案 6 :(得分:1)
我更喜欢第三种选择,将它们完全分开。
书籍和标签之间存在多对多的关系,通过将它们分开,您可以更轻松地进行查询,例如“哪些书籍被'计算机科学'标记”。
答案 7 :(得分:1)
除非图书上的标签顺序很重要或图书的标签两次都相同,否则您应该将标签存储在一个集合而不是列表中。
一旦你完成了,我会选择类似第三种选择的东西。在我看来,这些书并不拥有这些标签,而且这些标签并不拥有书籍(事实上,你可能想要以任何一种方式查看这些书籍)。之后,当您想要将其他内容与您的图书(例如评论,评级,图书馆)相关联时,您可以创建另一个关联,而无需修改图书类。
答案 8 :(得分: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");
}