Расширение уже написанного класса с помощью OOPS

Я пытаюсь изучить практики дизайна и ООП. Я использую проблему с парковкой в ​​качестве образца для начала.

У меня есть интерфейс GeneralParkingLot и интерфейс Vehicle. GeneralParkingLot имеет только одну функцию returnParkingLotSize, интерфейс Vehicle имеет несколько атрибутов Vehicle.

Я создал класс DowntownParkingLot, который расширяет GeneralParkingLot и имеет другие атрибуты, такие как listOfCars, availableSlots и т. д., И класс Car, который расширяет класс Vehicle.

У меня есть HandlerClass, который обрабатывает входящие команды, и внутри этого класса я помечал объект DownTownParkingLot и несколько функций для обработки команд, поэтому в каждой функции я просто передаю объект DowntownParkingLot и работаю с ним.

Я создал различные службы, такие как CreateParkingLotObject, ParkACar, FreeASlot и т. д., Которые вызываются обработчиком команд.

Я также создал модульные тесты для тестирования своего приложения.

Моя проблема в том, что если я хочу расширить свою текущую парковку, чтобы иметь дополнительные функции, например, допустим, несколько атрибутов этажа, или если я хочу теперь обрабатывать несколько парковок вместо одной, так что будет лучший способ расширить мой класс GeneralParkingLot или DowntownParkingLot класс. Я также читал о шаблонах адаптеров и декораторов, но я думаю, что они полезны, когда я уже с самого начала следую определенному шаблону проектирования. В моем случае я не следовал никакому конкретному шаблону, поэтому как лучше всего расширить свой код. Я спрашиваю об этом, потому что иногда мы сталкиваемся с классом, который не создан в соответствии с каким-либо шаблоном проектирования и используется во многих местах (например, во многих API и т. д.), Поэтому каков наилучший способ расширить такой код? Рефакторинг с самого начала - единственный вариант? или создание новых классов, которые наследуются от старых классов? Что было бы лучше всего? Также я хотел бы как можно больше использовать уже созданные модульные тесты и больше не переписывать одни и те же тестовые примеры.

Поскольку у вас есть цепочка extends, вам нужно будет решить, в какую позицию следует поместить новые атрибуты. Если взять multiple floor attribute, имеет смысл добавить его к GeneralParkingLot, потому что (по крайней мере теоретически) каждая потерянная парковка может иметь несколько этажей, а не только DowntownParkingLot. Если вы не можете изменить этот класс, создайте новый «суперкласс», расширяющий GeneralParkingLot. Теперь все ваши классы могут расширять новый «суперкласс» и содержать всю информацию. Надеюсь я правильно понял ваш вопрос

XtremeBaumer 13.06.2018 16:37

Если я добавлю пол к GeneralParkingLot, то для его реализации должен ли я создать новый класс или реализовать в старом классе? Я не знаю, какой из них лучше, учитывая, что в будущем могут быть добавлены дополнительные функции. Также я не хочу нарушать текущую функциональность

Legendary_Hunter 13.06.2018 16:41

Кроме того, речь идет не только о добавлении этажа, я хочу знать общую идею, например, как мы расширяем коды в таких ситуациях?

Legendary_Hunter 13.06.2018 16:42

Не делайте это интерфейсом. Сделайте его abstract class (или даже просто обычным классом. В зависимости от варианта использования). Интерфейсы следует использовать, если у вас есть только методы, которые вы хотите раскрыть, но не какие-либо поля.

XtremeBaumer 13.06.2018 16:43

Итак, если я хочу добавить какой-либо новый атрибут, я просто добавляю этот абстрактный класс, а затем создаю новый класс, который расширяет этот абстрактный класс? или использовать тот же старый DowntownParkingLot?

Legendary_Hunter 13.06.2018 17:22

Вы используете тот же DowntownParkingLot. Но вы должны проверить, действительно ли каждый расширяющийся класс должен иметь этот атрибут, и только в этом случае вы добавляете поле в класс abtract.

XtremeBaumer 14.06.2018 08:01

Где вы хотите обрабатывать несколько парковок вместо одной? В классе HandlerClass или в DowntownParkingLot? Должна ли быть разница в использовании класса DowntownParkingLot после добавления нескольких этажей или нет? В целом сложно ответить, как можно добавить функциональность в ООП. Не могли бы вы подробно описать еще несколько примеров? Затем мы могли бы объяснить, как мы их реализуем и почему. И всегда будьте очень точны, хотите ли вы изменить интерфейс, как следует использовать класс, или только внутреннюю реализацию!

Tobias 25.06.2018 03:03

@Tobias Я хочу добавить функциональность нескольких этажей либо на парковку в центре города, либо я могу создать новый объект, который использует парковку в центре города (потому что он уже реализует функциональность одного этажа). Любой из этих двух вариантов, но я хотел бы знать, какой из них лучше, потому что в будущем у меня могут быть другие требования, такие как размер слотов или несколько входов, поэтому я хотел бы использовать решение, которое лучше подходит с учетом будущего расширения функциональности

Legendary_Hunter 25.06.2018 09:52
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
7
8
319
6

Ответы 6

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

interface ParkingLot {
    void park(Vehicle v); 
}

interface Vehicle {
     int size();
     boolean isDIsabled();
}

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

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

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

вы можете использовать «команду», чтобы передавать команду парковки между вашими парковками, пока первая не найдет подходящую парковку для вашей машины. В этом случае вы связываете все свои автостоянки и передаете запрос между ними. Его также можно использовать внутри помещений между этажами, так как каждый этаж будет пытаться найти соответствие от первого этажа до крыши.

Вы можете «украсить» свой автомобиль функциями, связанными с поиском места для парковки. Итак, снова у вас есть универсальный автомобиль с добавленными различными функциями. То же самое и с дизайном парковки.

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

Подобным образом безопасность работает для ролей пользователей (авторизация), например, для веб-сайтов.

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

Один из способов решить вашу проблему - создать самый верхний класс реализации, реализующий интерфейс GeneralParkingLot:

public abstract CommonParkingLot implements GeneralParkingLot {
     // add your missing attributes here
     protected int floorNo;
}

(Если вы просто хотите добавить атрибуты без предоставления некоторого реализация по умолчанию для GeneralParkingLot, вы можете опустить часть implements)

А затем пусть ваш класс реализации расширяет этот CommonParkingLot:

public class DowntownParkingLot extends CommonParkingLot implement GeneralParkingLot {
     // Now all missing attributes are accessible here
}

Если функциональные возможности отсутствуют, вам может пригодиться функциональный интерфейс default. (ключевое слово - это язык), вы можете добавить столько функций, сколько вам нужно, в интерфейс GeneralParkingLot с телом по умолчанию для него.

Еще один момент: ваша текущая реализация как бы ограничена DowntownParkingLot, потому что вы не добавляете общие функции в интерфейс. Я бы посоветовал вам немного реорганизовать класс HandlerClass:

public interface GeneralParkingLot {
    int returnParkingLotSize();
    ParkingLotObject createParkingLotObject();
    boolean parkACar();
    int freeASlot();
    default public void parkMultipleCar() {
         throws new UnsupportedOperationException();
    }
}

Теперь в вашем HandlerClass должен работать GeneralParkingLot вместо DowntownParkingLot.

Сделав это, вы можете добавить больше GeneralParkingLot без изменения кода.

Думаю, здесь нужно использовать паттерн Visitor. Определение паттерна visitor, как указано в Gang of Four:

Представляют операцию, выполняемую над элементами структуры объекта. Visitor позволяет вам определить новую операцию, не изменяя классы элементов, с которыми он работает.

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

Хорошо, вот мой ответ после того, как я прочитал комментарии к вашему вопросу:

Парковка в центре города с несколькими этажами, несомненно, также является автостоянкой в ​​центре города. Итак, ясно, что нам понадобится класс, представляющий MultiFloorDowntownParkingLot, производный от DowntownParkingLot, если это не тот же класс.

Как вы хорошо определили, у вас уже есть реализация для SingleFloorDowntownParkingLot, которая также является DowntownParkingLot.

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

MultiFloorDowntownParkingLot состоит из нескольких SingleFloorParkingLot и работает как оболочка или мультиплексор для каждого метода. listOfCars, например, будет объединять только списки, возвращаемые всеми его этажами.

Эта модель может быть расширена многими способами:

  • Несколько входов:DowntownParkingLot может иметь несколько входов. На одном этаже это реализуется, а на многоэтажной автостоянке организовано, какие входы на отдельные этажи выходят наружу, а какие соединяют этажи между собой.
  • Разные размеры слотов: На "Парковочном участке в центре города" can have multiple slot sizes. AОдноэтажный участок для парковки в центре городаhas one slot size. TheМногоэтажный участок для парковки в центре города "есть места всех размеров, соответствующих его этажам.

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

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

Надеюсь, смогу помочь!

Обновлено: Визуальная иллюстрация! Parking Lot Model as Class Diagram

Is refactoring from start the only option? or creating new classes which inherit from old classes? What would be the best way?

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

Например, возьмем пример MultiFloorDowntownParkingLot, предоставленный @Tobias. Если вы ожидаете, что в будущем у вас будут TollParkingLot и TollMultiFloorParkingLot и всевозможные варианты, то предпочтительнее использовать декоратор, потому что стоимость изменения вашего кода для поддержки декоратора будет возмещена, когда вы приступите к реализации этих различных парковок. Но если вы подозреваете, что ваши парковочные места будут простыми вариациями GeneralParkingLot, то просто расширения GeneralParkingLot будет достаточно.

Еще одна вещь, которую следует учитывать, - это композиция vs наследование. Возможно, вы сможете использовать композицию для решения некоторых требований. Например, я мог бы создать функцию взимания дорожных сборов как новый класс, который обертывает (или состоит из) объект GeneralParkingLost (из которого MultiFloorParkingLot можно легко обернуть этим объектом платной парковки).

Очень легко над этим подумать. Возможно, вам придется ограничить время фазы проектирования и выбрать самое дешевое решение, которое решит проблему и позаботится о любых ожидаемых изменениях. Книга «Полный код» прекрасно освещает эти темы. В этой книге он пытается перечислить, какие части программы могут измениться со временем, а затем могут быть разработаны с учетом того, что эти части могут измениться.

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

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

  1. Не смешивайте класс и интерфейс. класс определяет объект, а интерфейс определяет поведение. Класс не может расширять интерфейс, а только реализует его. Однако вы можете использовать интерфейс для расширения другого интерфейса, добавляя дополнительные варианты поведения.

  2. Не смешивайте атрибут и поле. Для класса атрибуты обычно означают частный доступ, тогда как поля означают публичный доступ через геттер / сеттер.

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

Теперь поработаем над вашим примером. Основная концепция в вашем случае только одна: парковка. Однако в разных областях это означает разные абстракции.

В платежном приложении он заботится о двух вещах: пространство-время. Сколько открытых пространств осталось и где они? Для каждого объекта на парковке, как долго он стоит на стоянке и сколько будет изменено при выезде.

В приложении для управления трафиком у него совершенно другая точка зрения: сколько трафика может проходить и какой маршрут лучше всего направлять.

Видите ли, разные домены приводят к разному дизайну даже при очень простой концепции. Если вы наткнетесь на дизайн. Постарайтесь забыть о классе / интерфейсе или ООП и просто сосредоточьтесь на понимании своего домена, что в большинстве случаев решает проблему.

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

GeneralParkingLot:, поскольку это интерфейс, он определяет поведение. и какое поведение должна иметь парковка? Что ж, суть: он должен сообщить availableParkingSpaces, что он должен иметь возможность park транспортного средства и charge, когда он уезжает. Теперь у нас есть 3 поведения:

int getAvailableParkingSpaces();
int park(Vehicle v);
int charge(Vehicle v);

DowntownParkingLot: это должен быть ваш объект, который имеет атрибуты и реализует поведение.

class DowntownParkingLot implements GeneralParkingLot{
   private int totalSpaces;
   private int[] parking_slots;
   private Map<Integer, Vehicle> parkedVehicle;  //<park_space_id, vehicle>

   //implements of interface methods
}

MultiFloorParkingLot: Я не уверен, как этот многоэтажный дом повлияет на ваш дизайн, поскольку он спрятан внутри вашего класса. Предоставляет ли ваша многоэтажная парковка что-нибудь дополнительное с точки зрения поведения? Если да, то это должен быть интерфейс, расширяющий GeneralParkingLot как

interface MultiFloorParkingLot extends GeneralParkingLot{
   //provide extra behaviours here
}

В противном случае это должен быть класс, парковочные места которого хранятся иначе, как

class MultiFloorParkingLot implements GeneralParkingLot{
    private int[][] parking_slots;  // locate parking_id by floor + park_num

    //everything else should not changed much.
}

Лично я не считаю, что многоэтажный дом отличается от одноэтажного. Многоэтажный или одноэтажный этаж - это концепция структуры здания, и она полезна только тогда, когда вашему приложению необходимо найти / направить транспортное средство к доступным пространствам. Либо он находится на «этаже 4, столбец A, № 24», либо в «Области 1, столбец B, № 25» не имеет значения в концепции, разница заключается в реализации, которая должна быть инкапсулированный внутри самого вашего класса. Но опять же, это зависит от вашего домена.

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

interface Vehicle {
    String getLicense();
    void park(GeneralParkingLot p);
}

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

class Car implements Vehicle{
    String licensePlate;
    //if your domain needs to track how long it parks
    long enterTimeStamp, exitTimeStamp; 

    @Override
    public void park(GeneralParkingLot p){
        //do something that car needs to do, then register car on parking-lot
        p.park(this);
    }
}

Глянь сюда? Процесс парковки автомобиля на стоянке - это процесс регистрации автомобиля на стоянке. Им обоим, вероятно, потребуется что-то сделать, чтобы завершить процесс. И то, что они будут делать соответственно НЕ СЛЕДУЕТ, будет делать внешний класс. Принцип открытия-закрытия: вы можете сказать мне, что делать, но не можете сказать мне, как это сделать. Это оставляет возможность изменять детали разных автомобилей на разных стоянках.

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

class HandlerService {
    //let's use park a car as example. 
    //notice that your using interface instead of class.
    //programming on interface, not class
    public void parkACar(Vehicle v, GeneralParkingLot p){
       v.park(p);
    }
}

В любом случае, программирование - это просто инструмент для решения проблемы. А ООП - это просто способ абстракции. Хороший гибкий дизайн зависит от ваших знаний в предметной области больше, чем от навыков программирования. Так что относитесь к своему бизнес-анализу хорошо и дружите с ними. ^ - ^

Надеюсь, это поможет.

Другие вопросы по теме