Роман Б.
42 сообщения
#15 лет назад
Есть два класса:

- класс Товар
- класс Корзина..

Вопрос: где описать метод "положить товар в корзину" ?
- в классе Товар
- в классе Корзина
- ______________
Леонид З.
97 сообщений
#15 лет назад
Скорей всего в классе МенеджерТоваров
Роман Беляев
16382 сообщения
#15 лет назад
В корзине, имхо. Самим товарам о корзине знать нечего.
Вадим Т.
3240 сообщений
#15 лет назад
Если есть выбор только из двух классов — Товар и Корзина, то однозначно метод "положить товар в корзину" должно быть реализовано в Корзине.

Но если делать по-хорошему, есть смысл вынести бизнес-логику в отдельный класс.
В данном случае это может быть, например, класс МенеджерКорзины
Именно там будут методы "положить товар в корзину", "удалить из корзины", и т.д.

Ибо МенеджерТоваров будет и так завален методами по добавлению, изменению, удалению, поиску и т.д. товаров.

А вообще желательно сначала расписать все-все сущности и все-все операции, которые с ними будут производиться.
И уже лишь потом начинать проектировать архитектуру — выделять классы, определять взаимосвязи между ними, при необходимости применять те или иные паттерны.
Олег Казакевич
702 сообщения
#15 лет назад
Можно и так и эдак, смотря какая логика.

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

С другой стороны, если корзина работает по принципу контейнера товаров, тогда однозначно - метод поместить в корзину.
Только упаси боже - делать товар потомком корзины !

А можно и забацать менеджера покупок, как тут предлагали.

Вообще, сколько программистов, столько и мнений.
Лучшего мнения не существует, а единственно разумное часто отвергается из-за каких-нибудь специфических требований к данному ПО.
Вадим Т.
3240 сообщений
#15 лет назад
Цитата ("okman"):
Если "товар" - абстрактный класс (а это именно так, судя по названию) и предполагается некоторое
адаптивное поведение его экземпляров (таких как отправка в корзину), то полиморфизм налицо, а значит,
допустимо определить указанный метод в классе "товар". Тогда в качестве аргумента ему можно будет передавать ссылку на
корзину, а поведение конкретного экземпляра "покупки" будет варьироваться в зависимости от родственных отношений с товаром (классика !).

Погодите. Допустим, метод класса Товар принял ссылку на корзину. Что он будет с ней делать? Как именно положит себя в корзину?
IMHO товару все равно придется вызывать какой-то метод "добавить" в Корзине, или же получить колллекцию товаров в Корзине (ссылку на нее) и работать с ней напрямую.
Будет ли это хорошим дизайном?
Ведь в таком случае сущности Товар придется знать о методах класса Корзина... и моментально получаем проблемы с Law of Demeter, что говорит о плохом проектировании.

Далее, как тогда будет выглядить метод "удалить товар из корзины"? Тоже вызвать метод в Товаре, передав в качестве аргумента ссылку на корзину?

=======
UPD. Более позднее примечание.
Проблемы с Law of Demeter в данном случае возникают если избавиться от избыточного параметра-корзины, так как Корзина все равно в одном экземпляре на сеанс.
Если же от избыточного параметра не избавляться, то формально правила LoD нарушаться не будут, хотя уровень связности все равно увеличивается, что плохо.
Николай М.
1895 сообщений
#15 лет назад
В класс "Корзина"
вы уже сами написали "положить товар в корзину"
тоесть это уже метод класса "Корзина", хотя это всеголиш логика, вам виднее
Александр В.
771 сообщение
#15 лет назад
Я конечно извиняюсь за оффтопик и, видимо, неграмотность - но какой смысл так заморачиваться? Большая разница где будет этот метод?
Андрей Халецкий
3562 сообщения
#15 лет назад
Если знаете английский язык - можете воспользоваться этим знанием для получения ответа. Добавить товар в корзину звучит так "add item to shopping cart". Очевидно что действие производится _над_ объектом item с помощью объекта _shoppingCart_ и получается что выглядеть должно так: shoppingCart.addItem(Item).
Т.е. выбор места реализации функции между стороной корзины и стороной товара очевиден.
Олег Казакевич
702 сообщения
#15 лет назад
Да я не претендую на абсолютную истину (ее все равно не найти).
Просто хотел показать, что и совершенно некорректные, на первый взгляд, решения, тоже имеют право на рассмотрение.
Олег Казакевич
702 сообщения
#15 лет назад
Цитата ("tvv"):
Погодите. Допустим, метод класса Товар принял ссылку на корзину. Что он будет с ней делать? Как именно положит себя в корзину?
IMHO товару все равно придется вызывать какой-то метод "добавить" в Корзине, или же получить колллекцию товаров в Корзине (ссылку на нее) и работать с ней напрямую.
Будет ли это хорошим дизайном?
Ведь в таком случае сущности Товар придется знать о методах класса Корзина... и моментально получаем проблемы с Law of Demeter, что говорит о плохом проектировании.

Далее, как тогда будет выглядить метод "удалить товар из корзины"? Тоже вызвать метод в Товаре, передав в качестве аргумента ссылку на корзину?


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

Цитата ("MMM_Corp"):
В класс "Корзина"
вы уже сами написали "положить товар в корзину"
тоесть это уже метод класса "Корзина", хотя это всеголиш логика, вам виднее


Цитата ("SmartDesign"):
Если знаете английский язык - можете воспользоваться этим знанием для получения ответа. Добавить товар в корзину звучит так "add item to shopping cart". Очевидно что действие производится _над_ объектом item с помощью объекта _shoppingCart_ и получается что выглядеть должно так: shoppingCart.addItem(Item).
Т.е. выбор места реализации функции между стороной корзины и стороной товара очевиден.


Сомнительные рекомендации.
В любом случае, все должно решаться конкретно, с учетом специфики разрабатываемого приложения.
Тем более, что русский язык-то побогаче английского будет.
Андрей Халецкий
3562 сообщения
#15 лет назад
Цитата ("okman"):
В любом случае, все должно решаться конкретно, с учетом специфики разрабатываемого приложения.
Тем более, что русский язык-то побогаче английского будет.
Не очень понятно как специфика проекта может заставить применять плохие приемы в проектировании. Хотелось бы увидеть хотя бы один живой пример.
А то что русский язык "побогаче" как раз и играет злую шутку с программистами, который вынуждены пользоваться достаточно бедными языками программирвоания (по сравнению с живими языками). Часто предложение на английском прямо подсказывает как должен выглядеть код.
Виктор Т.
1036 сообщений
#15 лет назад
В корзине. Зачем нужен менеджер корзины, честно говоря, слабо представляю. В данном случае корзина всего одна и по сути сама себе менеджер.
Вадим Т.
3240 сообщений
#15 лет назад
Цитата ("okman"):
tvv, я встречал и такие программные интерфейсы, кстати, в весьма солидных промышленных проектах.
Конечно, это не есть пример хорошего проектирования.
Но бывает, что именно такая логика как нельзя лучше укладывается в абстракцию, задуманную автором проекта, и
никто и ничто не сможет убедить его, что он не прав (тем более, что он по-своему прав).

Пожалуй да, я был излишне категоричен. Спорить нет смысла, не зная нюансов текущего проекта.
Можно придумать условия, когда и Ваш вариант также будет приемлем, и не будет нарушаться ни логика, ни принципы LoD.
Например, если объектов Корзина в сессии пользователя может быть несколько, а объект Товар является помимо всего прочего еще и хранилищем товаров данного типа (например, содержит информацию о наличии товара и количестве товара на складе), то тогда вполне логично принимать объект-корзину в качестве параметра, и производить с ней действия.

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

То есть, все очень зависит от задачи. Но в любом случае, я вряд ли могу понять проектировщика, который засовывает хоть какую-то логику в классы сущностей. IMHO времена классического ООП прошли еще в середине 90х.
Виктор Т.
1036 сообщений
#15 лет назад
tvv, просвятите, плиз, меня, дремучего, что такое "Law of Demeter"?
Олег Казакевич
702 сообщения
#15 лет назад
Цитата ("SmartDesign"):
Не очень понятно как специфика проекта может заставить применять плохие приемы в проектировании. Хотелось бы увидеть хотя бы один живой пример.


Хорошо. Пример - Adobe Photoshop.
Ничего не скажу в минус о самой программе - это если не безусловный лидер, то по меньшей мере один из трех первых.
Но вот программный интерфейс - наиотвратительнейший, кто хоть раз писал плагины для Photoshop-а, тот со мной согласится.
Структуры в 100 и более полей, селекторы, последовательности... И все это ради банального Load/Save.
Добавьте сюда всякие альфа-каналы, CMYK, LAB - ООП тут и не пахло.
У меня был неудачный опыт разработки такого плагина (Adobe Photoshop Format Plugin), потом я зарекся иметь дело с продуктами Adobe.

Могу привести еще примеры, если хотите - уж их авторы, казалось бы, должны на программировании "собаку съесть".
Убежден, что если бы это разрабатывали люди с таким, как у tvv, пониманием, наша жизнь была бы гораздо приятнее...
Вадим Т.
3240 сообщений
#15 лет назад
Цитата ("Sivis"):
В корзине. Зачем нужен менеджер корзины, честно говоря, слабо представляю. В данном случае корзина всего одна и по сути сама себе менеджер.

Попробую пояснить.
Корзина, кончено, может быть сама себе менеджером. Так же, как и Товар в некоторых случаях может быть сам себе менеджером (вариант okman-а).
То есть тут — классика ООП — инкапсуляция, имеем поля, и методы, которые работают с этими полями. Также Корзина-менеджер работает с Товарами, которые передаются методам Корзины в качестве параметров.
Все OK, все оптимально, никаких лишних классов, и при этом нарушения LoD нет.
О чем еще можно мечтать?

Проблемы начинаются тогда, когда классы разрастаются до огромных размеров, и их становится очень сложно поддерживать.
Так, некоторые в класс Корзина еще умудряются засунуть логику чекаута, бонусную систему, реферальную систему...
Некоторые — еще и код представления корзины запихивают туда же (методы генерации HTML кода для отображения корзины).
Стоимость разработки и поддержки такого кода значительно возрастает.
Особенно большие трудности возникают, если нужно вносить изменения в логику работы таких классов, заимствовать часть логики в других подсистемах, и т.д.

========================
Решение проблемы.

Пожалуйста вспомните про паттерн MVC:
Вспомните, зачем нужен этот паттерн, зачем вообще нужно разделять данные (model), представление (view) и бизнес-логику (controller)?
Вот и ответ на Ваш вопрос.

В случае с нашими сущностями имеем тот же принцип разделения, отделяем данные от бизнес-логики.
Корзина представляет собой хранилище Товаров, то есть представляет собой набор методов для доступа к товарам, и по сохранению коллекции товаров в сессии или базе данных. Это — часть модели.
Бизнес логика же ("поместить товар в корзину" — это в данном случае именно бизнес-логика) помещается в отдельный класс-менеджер.

Паттерн MVC — далеко не единственное решение в данном случае, но подход, надеюсь, должен быть понятен.
Вадим Т.
3240 сообщений
#15 лет назад
Цитата ("Sivis"):
tvv, просвятите, плиз, меня, дремучего, что такое "Law of Demeter"?

Law of Demeter, оно же LoD, оно же, в русских изданиях, "Закон Деметры".
Это набор правил проектирования, цель которых — сведение связности к минимуму, что сильно упрощает проект и облегчает жизнь разработчикам.
Кирилл Е.
2817 сообщений
#15 лет назад
Как один с вариантов:

class CShopBox {

function add_to_box($item)
{
// описание метода
}

}

class CShopItems extends CShopBox{

// методы товаров

}

$ShopItem = new CShopItems();

// какое-то событие вызывает добавление в корзину:
$ShopItem->add_to_box($item);

думаю логичнее было бы все методы, в которых последним звеном есть корзина - писать в класс корзина: добавить товар в корзину, получить товар с корзины, удалить с корзины, пересчитать стоимость товаров в корзине и т.д. и т.п.

Далее если корзина не только в товарах нужна, можно сделать класс CGlobals и в нём делать свойства, которым давать ссылку на класс корзины, ранее инициализированный, и этот глобальный класс делать подклассом класса "Товары", правда если нужно несколько корзин, лучше прямо в классе товаров в конструкторе создавать новый экземпляр корзины..

...в общем решений тьма, которое будет оптимально именно вашей задаче - зависит от самой задачи.. с первого поста что-то более конкретно сказать не получиться).

Нарисуйте подробную модель или блок-схему для магазина - проще будет решить как строить логику.
Дмитрий П.
441 сообщение
#15 лет назад
tvv все очень четко расставил по местам.

Я уже года 2 использую в большинстве веб-проектов корзину с примерно таким набором методов:
getItems();
countItems();
getSum();
addItem(&$item, $price, $quantity=1)
setItem(&$item, $price, $quantity)
removeItem(&$item)
clear()

Оформление (HTML) - отдельно
Расчет стоимости как товаров, так и всей покупки (бывают скидки, зависящие от суммы покупки) - отдельно.
Чекаут, реферальная система - все отдельно.


У товара метод "положить себя в корзину" могу представить только в одном случае - когда при этом нужно, например, уменьшить количество товара на складе или что-то подобное сделать. Но это все-равно криво. В таких случаях надо использовать "менеджер магазина".