#oop
#ооп
Вопрос:
Я ищу элегантный дизайн для проблемы, аналогичной следующей:
История может иметь гибкую иерархическую структуру. Он может состоять из нескольких книг, в каждой из которых есть главы, в каждой из которых есть разделы, каждый из которых содержит текст. Или он может просто содержать разделы (например, короткую историю). Или он может просто состоять из глав, в которых есть разделы. Во всех ситуациях вам не должно быть разрешено смешивать стили (чтобы вы не могли добавить главу к истории, основанной на книгах, вам пришлось бы добавить главу в саму книгу).
Я придумал пару дизайнерских решений для такого типа проблем, но они становятся запутанными. Есть ли чистый способ представить это так, чтобы, учитывая ссылку на класс Story, я мог получить доступ к содержимому четким и систематическим образом?
Комментарии:
1. Подумайте: небольшое количество общих интерфейсов и конкретные классы реализации для каждой отдельной структуры истории, которую вы хотите представить.
2. Хорошо, теперь это становится сложнее, потому что это была аналогия, а не реальная проблема! Но фактически у меня были классы, эквивалентные ChapterBasedStory, SectionBasedStory, BookBasedStory, ChapterBasedBook, SectionBasedBook и так далее. Это стало действительно запутанным с фабричными методами и / или множеством тестов содержащегося типа. Может быть, этот беспорядок неизбежен !? Отсюда мой вопрос 🙂
3. Интересно, я обновил свой ответ на основе ваших комментариев (см. Последний абзац). Я бы рекомендовал взглянуть на Составной шаблон.
4. Составной шаблон почти работает. Проблема в том, что мне мало что мешает (скажем) добавлять книги в главы с помощью метода addChild( Element el ). Composite позволяет мне создавать произвольные иерархии, а не конкретный вид, с которым я имею дело здесь.
5. @Matt Ball, на самом деле все наоборот: «Во всех ситуациях вам нельзя разрешать смешивать стили». Интерфейсы позволяют обрабатывать разные классы, чтобы «смешивать» похожие элементы из разных классов.
Ответ №1:
Это своего рода сложная ситуация, потому что такие понятия, как «Книги», «главы», «разделы», могут иметь некоторые общие элементы, которые предполагают иерархию классов или реализацию общего интерфейса.
И в то же время достаточно разные, чтобы их можно было обрабатывать как разные классы / объекты, как требование «чтобы вы не могли добавить главу в историю, основанную на книгах».
При концептуальной работе с иерархическими объектами существует несколько подходов к их преобразованию в код, каждый из которых лучше подходит для конкретной ситуации.
1. Состав классов
Для каждой концепции существует класс или прототип, они могут быть связаны или нет путем наследования или интерфейсов. Существуют внутренние коллекции элементов, и их операции могут быть ограничены методами.
// this one can be optional, not required,
// replaced by several parent classes,
// or replaced by interfaces
public class LibraryRootClassMaybe {
// members here
}
public class BookText extends LibraryRootClassMaybe {
// members here
} // class BookText
public class BookSection extends LibraryRootClassMaybe {
// element collection should not be public
List BookTexts;
public Book() {
this.BookTexts = new ArrayList();
}
public void addBookTest(BookText Item) {
// validation and casting from object to BookText
}
// members here
} // class BookSection
public class BookChapter extends LibraryRootClassMaybe {
// element collection should not be public
List BookSections;
public Book() {
this.BookSections = new ArrayList();
}
public void addBookTest(BookSection Item) {
// validation and casting from object to BookSection
}
// members here
} // class BookChapter
public class Book extends LibraryRootClassMaybe {
// element collection should not be public
List BookChapters;
public Book() {
this.BookChapters = new ArrayList();
}
public void addBookTest(BookText Item) {
// validation and casting from object to BookText
}
// members here
} // class Book
Этот подход хорош, когда не так много разных классов, может быть, 5.
2. Шаблон проектирования дерева.
Это применяется, когда все элементы будут равны, если не похожи, обычно один и тот же класс или интерфейс, обычно много элементов.
Это не относится к вашему случаю, но, я должен был упомянуть, лучше применять.
Обычно используется класс дерева / иерархической коллекции. Это может быть подкласс общей / шаблонной коллекции дерева или подкласс базовой коллекции tre, которая предназначена для замены дочерними классами с определенными членами.
public class FileSystemRootClass {
public bool AmISystemRoot() {
// more
}
public bool AmIAFolder() {
// more
}
public bool AmIAFile() {
// more
}
public void addSystemRoot(string FileSystemName) {
// more
}
public void addFolder(string FileSystemName) {
// more
}
public void addFile(string FileSystemName) {
// more
}
// members here
}
3. Гибрид.
Это комбинация двух предыдущих, она используется, когда есть много связанных элементов, она более сложная, может использовать или нет шаблоны Factory amp; Abstract Factory, и ее более распространенным примером являются библиотеки визуальных элементов управления и виджетов.
import java.util.*;
public class WidgetsExample {
public static void main(String[] args) {
TreeSet <Widget> FormControls = new TreeSet<Widget>();
TextBoxWidget TextBox = new TextBoxWidget();
FormControls.add(TextBoxWidget);
ListBoxWidget ListBox = new ListBoxWidget();
FormControls.add(TextBoxWidget);
ButtonWidget Button = new ButtonWidget();
FormControls.add(Button);
} // class WidgetsExample
Вы можете заметить, что я не использовал «фабричный шаблон» и «абстрактную фабрику»,
поскольку требуется больше кода.
Удачи.
Ответ №2:
Хороший ОО-дизайн начинается с обдумывания вариантов использования, а не иерархий классов. Это распространенная ошибка, которая приводит к чрезмерно сложным проектам.
Сначала подумайте, что вы создаете, и напишите формулировку проблемы — описание проблемы, которую вы решаете, используя английскую прозу на языке проблемной области.
Затем рассмотрите возможность создания макета, если продукт является пользовательским интерфейсом.
Затем вы можете начать писать варианты использования и начать думать о том, как объекты будут взаимодействовать друг с другом.
Это называется объектно-ориентированным программированием, а не программированием, ориентированным на классы. Классы — это спецификация в коде для управления / создания / запуска всех объектов в системе. Я бы подумал об объектах и о том, что они делают. Классы — это просто деталь реализации.
Если ваша цель — выполнять операции над иерархиями, вы можете рассмотреть возможность использования составного шаблона. Вы могли бы сделать что-то вроде иметь объект истории, который может содержать список объектов истории. Каждый объект истории также будет иметь свой собственный тип (книжная коллекция, книга, глава, подраздел, параграф, эссе), а также собственные атрибуты и методы (в зависимости от ваших вариантов использования).
Комментарии:
1. 1 Пусть иерархия классов развивается из реализации поведения, а не пытается смоделировать проблему.
Ответ №3:
Я бы попробовал что-то вроде этого:
interface StoryElement<SE extends SubElements>{
List<SE> getContents()
}
class Story<T extends StoryElement>
class Book implements StoryElement<Chapter> ...
class Chapter implements StoryElement<Section> ...
class Section implements StoryElement<Text>
class Text implements StoryElement<Text> {... // ugly hack, don't know of a clean way to end the recursion with Java Generics
Теперь у вас может быть история книг или история текста.
Предупреждение: дженерики Java, как правило, падают на лицо при попытке усложнить вещи. В случае сомнений просто забудьте о дженериках и приведении.
Комментарии:
1. Спасибо, Йенс. К сожалению, я хочу избежать выполнения чего-то вроде class Book implements StoryElement<Глава>, поскольку что произойдет, если я найду книгу, в которой нет глав, только разделы?
2. Я думаю, это была бы не книга. Это будет либо глава (когда она содержится в книге или в истории <Глава>, либо это будет История <Раздел>. Все это всегда история. Книга — это только часть истории.
3. Да, я понимаю, что вы имеете в виду. Возможно, стоит поиграть. Моя единственная небольшая проблема заключается в том, что Story<Book> и Story<Section> — это разные типы, и для них потребуется немного повозиться на верхнем уровне. Например, если Story s = getStory(); тогда я должен получить тип истории.
4. Слишком быстро переходит прямо к интерфейсам. Если объекты совместно используют элементы или элементы, то вместо интерфейса следует использовать иерархию классов.
Ответ №4:
Спасибо всем постерам. Вот мое незавершенное решение:
public abstract class Element
{
public ElementSet getContent() { return content; }
// Overrides of setContent() check to see if the content type is appropriate
// using instanceof.
public void setContent( ElementSet content )
{
this.content = content;
}
private ElementSet content;
}
public abstract class ElementSet
{
protected final void addElement( Element e )
{
elements.add(e);
}
private final List<Element> elements = new ArrayList<Element>();
}
public class BookSet extends ElementSet
{
// Typesafe interface.
public void addBook( Book book ) { super.addElement( book ); }
}
public class ChapterSet extends ElementSet { /* similar to BookSet */ }
public class SectionSet extends ElementSet { /* similar to BookSet */ }
public class Book extends Element
{
@Override
public void setContent( ElementSet content )
{
if ( !(content instanceof ChapterSet) amp;amp; !(content instanceof SectionSet) )
{
throw new RuntimeException();
}
super.setContent( content );
}
public boolean addChapter( Chapter chapter )
{
ElementSet content = getContent();
if ( content == null )
{
content = new ChapterSet();
setContent( content );
}
else if ( !(content instanceof ChapterSet) )
{
// Structure is wrong.
return false;
}
ChapterSet chapters = (ChapterSet)content;
chapters.addChapter( chapter);
return true;
}
public boolean addSection( Section section )
{
ElementSet content = getContent();
if ( content == null )
{
content = new SectionSet();
super.setContent( content );
}
else if ( !(content instanceof SectionSet) )
{
// Structure is wrong.
return false;
}
SectionSet sections = (SectionSet)content;
sections.addSection( section );
return true;
}
}
public class Chapter extends Element
{
@Override
public void setContent( ElementSet content )
{
if ( !(content instanceof SectionSet) )
{
throw new RuntimeException();
}
super.setContent( content );
}
public boolean addSection( Section section )
{
ElementSet content = getContent();
if ( content == null )
{
content = new SectionSet();
super.setContent( content );
}
else if ( !(content instanceof SectionSet) )
{
// Structure is wrong.
return false;
}
SectionSet sections = (SectionSet)content;
sections.addSection( section );
return true;
}
}
Я пытался использовать дженерики для достижения той же цели, но это выглядело довольно уродливо из-за необходимости отражать параметризованный тип контейнера.