иметь проблемы с проектированием в классе надлежащим образом - PullRequest
0 голосов
/ 18 января 2012

В чем проблема со следующим, и как бы я лучше реализовал его, используя принципы ОО?

Мое приложение содержит набор классов фигур, которые все наследуются от Shape - Circle, Rectangle, Triangle и т. Д. Некоторые из них должны отображаться на экране, в этом случае они должны использовать общую экранную логику, поэтому существует суперкласс ScreenShape, который содержит общую логику, и ScreenCircle, ScreenTriangleдетские классы.

Ответы [ 6 ]

3 голосов
/ 18 января 2012

Я бы предложил создать интерфейс Shape, который обеспечивает базовый синий шрифт о форме геометрической формы, и все ваши классы реализуют интерфейс формы и создают отдельный класс ScreenShape (или абстрактный класс), который будет расширяться всеми вашими классами и предоставьте метод для отображения на экране в классе ScreenShape. например, ваш класс прямоугольника будет выглядеть примерно так

class rectangle extends ScreenShape implements Shape
{

// provide implementation of Shape interace methods.


// over-ride ScreenShape methods

public void draw()
{
// actual logic of drawing the objects on screen

}

}
2 голосов
/ 18 января 2012

Проблема, с которой ваш менеджер сталкивается с дизайном, вероятно, заключается в том, что вы вносите свои проблемы в вашу объектную модель. Вы смешали понятия, что такое форма с тем, как она отображается. Так что теперь, если вам нужно использовать фигуры в графической программе? С вашим текущим дизайном вам необходимо определить PlottedShape, PlottedCircle, PlottedSquare и т. Д. И т. Д.

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

1 голос
/ 19 января 2012

О том, что вы отправили в ответ на @DOC:

@ DOK менеджер не удовлетворен ... хотя он дал мне разрешение на разработку, но все же он сказал, что вы должны изучить это.

Полагаю, причина, по которой ваш менеджер не удовлетворен текущим дизайном, заключается в том, что он страдает от запаха кода , называемого Иерархии параллельного наследования . В основном, это описывает то, что вы испытываете: каждый раз, когда создается новый подкласс, вы должны создать его подкласс в параллельной иерархии. Это усложнит изменение вашего дизайна и, следовательно, его поддержку.

Обзор текущих ответов

Я хотел бы прокомментировать некоторые вещи, с которыми я не согласен в текущих ответах. В основном вам предлагается либо:

  1. Наследование от ScreenShape класса
  2. Реализовать логику рисования в каждом подклассе Shape

Я вижу две проблемы с опцией n ° 1:

  • Он определяет Shape как интерфейс, поэтому вам придется заново его реализовывать в каждом подклассе (Rectangle и т. Д.)
  • Чтобы преодолеть предыдущее, вы можете ScreenShape реализовать его, потому что все виды форм будут наследовать его. Это плохо, и ответ @Perception объясняет почему.
  • Предположим, вас сейчас просят экспортировать фигуры в XML-файл (это еще одна операция с фигурами, такая же, как их отображение). Следуя этому подходу, вам нужно реализовать это поведение для класса XMLShape и наследовать его, но вы уже наследуете от ScreenShape. Видите проблему?

Опция n ° 2 заставляет вас раздуть модель вашего домена из-за проблем с презентацией, опять же, как сказал @Perception:)

Некоторые цели для достижения

Из предыдущего видно, что:

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

Предлагаемое решение

Прежде всего, очистите иерархию Shape и оставьте только то, что принадлежит вашему проблемному домену.

Затем вам нужны разные способы отображения фигур без реализации этого поведения в иерархии Shape. Если вы не будете иметь дело с вложенными фигурами (фигурами, которые содержат другие фигуры), вы можете использовать технику под названием Double Dispatch . Он позволяет вам отправлять вызов метода в зависимости от типа получателя, кодируя эту информацию в имени метода, передаваемого в аргумент, который он получает, следующим образом:

public class Circle extends Shape {
    public void DisplayOn(IShapeDisplay aDisplay) {
        aDisplay.DisplayCircle(this);
    }
}

public class Rectangle extends Shape {
    public void DisplayOn(IShapeDisplay aDisplay) {
        aDisplay.DisplayRectangle(this);
    }
}

interface IShapeDisplay {
    void DisplayCircle(Circle aCircle);
    void DisplayRectangle(Rectangle aRectangle);
}

Классы, реализующие IShapeDisplay, будут ответственны за отображение каждого вида фигуры. Приятно то, что вам удалось вычистить эти неприятные детали из иерархии Shape, инкапсулировав их в своем собственном классе. Благодаря этому теперь вы можете изменять этот класс без изменения подклассов Shape.

Заключительные комментарии

Подробнее о запахах кода и параллельных иерархиях наследования можно прочитать в книге Мартина Фаулера: Рефакторинг: улучшение дизайна существующего кода .

Кроме того, вы можете обратиться к книге Design Patterns: Элементы многократно используемого объектно-ориентированного программного обеспечения , если вам нужно разобраться с вложением фигур: шаблонами Composite и Visitor.

Надеюсь, это было полезно для вас!

1 голос
/ 18 января 2012

Проблема, которую я вижу, заключается в том, что вы не можете наследовать от более чем одного класса в Java (хотя вы можете реализовать несколько интерфейсов).Так что вам лучше включить функциональность ScreenShape в Shape, предполагая, что ScreenShape имеет некоторый конкретный код в своих методах.

0 голосов
/ 18 января 2012

Вы можете реализовать метод draw (), который будет реализовывать общую логику.

public abstract class Shape{

    public void draw(){
        //common logic here

        drawImpl();

        //more logic here if needed
    }

    public abstract void drawImpl();        

}

Тогда каждая ваша реализация будет реализовывать класс drawImpl.

В качестве альтернативы вы можете реализовать класс ScreenBehaviour и использовать композицию. Тогда каждая из ваших реализаций может использовать разные реализации ScreenBehaviour, например:

public abstract class Shape{
    private ScreenBehaviour screenBehaviour;

    public final void draw(){
        screenBehaviour.execute();
    }
}
0 голосов
/ 18 января 2012

Работа от самого общего до самого конкретного.

Так и должно быть:

. Shape (содержащий код, который существует для любого вида Shape - может быть, абстрактного класса или интерфейса)

.. ScreenShape (содержит логику для рисования на экране)

... Круг (содержащий логику для рисования круга на экране)

... Квадрат (содержащий логику для рисования квадрата на экране)

Итак:

public abstract class Shape {
  // ... generic Shape stuff here, possibly an interface
  public abstract void getCoordinates();
}

public abstract class ScreenShape extends Shape {
  public void drawOnScreen() {
    // logic for drawing on a screen here
    // likely invoking 'getCoordinates()' 
  }
}

public class Circle extends ScreenShape {
  public void getCoordinates() {
    // circle specific stuff here, implementation of stuff inherited from Shape
  }
  // also inherits whatever 'drawOnScreen()' implementation 
  // is provided in ScreenShape
}
...