Иерархический объектный дизайн (Java) - PullRequest
2 голосов
/ 24 октября 2011

Я ищу элегантный дизайн для задачи, аналогичной следующей:

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

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

Ответы [ 4 ]

2 голосов
/ 24 октября 2011

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

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

Тогда рассмотрите возможность создания макета, если продукт представляет собой пользовательский интерфейс.

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

Это называется объектно-ориентированным программированием, а не класс-ориентированным программированием. Классы - это спецификация в коде для управления / создания / запуска всех объектов в системе. Я буду думать об объектах и ​​о том, что они делают. Классы - это просто деталь реализации.

Если ваша цель - выполнять операции с иерархиями, вы можете рассмотреть возможность использования шаблона Composite. Вы можете сделать что-то вроде объекта Story, который может содержать список объектов Story. Каждый объект Story также имеет свой собственный тип (коллекция книг, книга, глава, подраздел, абзац, эссе) и свои собственные атрибуты и методы (в зависимости от ваших вариантов использования).

1 голос
/ 24 октября 2011

Это довольно сложная ситуация, потому что такие понятия, как "Книги", "Главы", "Разделы", могут иметь некоторые общие элементы, которые предлагают иерархию классов или реализацию общего интерфейса.

И в то же время достаточно различно, чтобы обращаться с ним как с разными классами / объектами вообще, как с требованием «чтобы нельзя было добавить главу в историю, основанную на книгах».

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

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. Гибрид.

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

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

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

Удачи.

0 голосов
/ 27 октября 2011

Спасибо всем авторам. Вот мое незавершенное решение:

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) && !(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;                
    }
}

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

0 голосов
/ 24 октября 2011

Я бы попробовал что-то вроде этого:

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 имеют тенденцию к падениюлицо при попытке сложных вещей.В случае сомнений, просто забудь дженерики и бросьте.

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