Распространение информации через границы объекта

10

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

В качестве примера предположим, что у нас есть Order, который имеет несколько OrderItems, который ссылается на InventoryItem, у которого есть цена. Я вызываю Order.GetTotal (), который суммирует результат OrderItem.GetPrice (), который умножает количество на InventoryItem.GetPrice (). Все идет нормально.

Но затем мы узнаем, что некоторые вещи продаются с двумя за одну сделку. Мы можем справиться с этим, если бы OrderItem.GetPrice () сделал что-то вроде InventoryItem.GetPrice (количество) и позволил InventoryItem справиться с этим.

Однако затем мы обнаруживаем, что сделка «два к одному» длится только в течение определенного периода времени. Этот период времени должен быть основан на дате заказа. Теперь мы изменим OrderItem.GetPrice () на InventoryItem.GetPrice (quatity, order.GetDate ())

Но тогда нам нужно поддерживать разные цены в зависимости от того, как долго клиент находился в системе: InventoryItem.GetPrice (amount, order.GetDate (), order.GetCustomer ())

Но затем выясняется, что сделки «два к одному» применяются не только к покупке нескольких товаров одного и того же предмета, но и нескольких для любого предмета в InventoryCategory. На этом этапе мы возбуждаем руки и просто даем InventoryItem элемент заказа и позволяем ему перемещаться по графу ссылок объекта через средства доступа, чтобы получить информацию, которая ему нужна: InventoryItem.GetPrice (this)

TL; DR Я хочу иметь низкую связь между объектами, но бизнес-правила часто вынуждают меня получать доступ к информации повсюду, чтобы принимать конкретные решения.

Есть ли хорошие методы для борьбы с этим? Другие находят ту же проблему?

Уинстон Эверт
источник
4
На первый взгляд кажется, что класс InventoryItem пытается сделать слишком много, вычисляя цену, возвращая заданную цену - это одно, а цены продажи и специальные бизнес-правила не должны быть адресами в InventoryItem. Разверните класс для расчета ваших цен и позвольте ему обрабатывать потребность в данных из заказа, инвентаря и клиента и так далее.
Крис
@ Крис, да, разделение ценовой логики, вероятно, хорошая идея. Моя проблема в том, что такой объект будет тесно связан со всеми объектами, связанными с порядком. Это та часть, которая беспокоит меня, и та часть, которая меня интересует, могу ли я избежать.
Уинстон Эверт
1
Если что-то, я не хочу называть это чем-либо, нуждается во всей этой информации, то каким-то образом оно должно использовать эту информацию для получения желаемого результата. Если у вас уже есть инвентарь, товар, клиент и т. Д., То реализация бизнес-логики в своей области имеет смысл. У меня нет лучшего решения.
Крис

Ответы:

6

У нас был практически такой же опыт, когда я работаю, и мы решили его с помощью класса OrderBusinessLogic. По большей части макет, который вы описали, работает для большей части нашего бизнеса. Это красиво, чисто и просто. Но в тех случаях, когда вам приходится покупать любые 2 из этой категории, мы рассматриваем это как «исключение бизнеса», и класс OrderBL пересчитывает итоговые значения путем обхода необходимых объектов.

Это идеальное решение, нет. У нас все еще есть один класс, который слишком много знает о других классах, но по крайней мере мы переместили эту потребность из бизнес-объектов в класс бизнес-логики.

Вальтер
источник
2
Если есть сомнения, добавьте еще один класс.
Крис
1

Похоже, вам нужен отдельный объект Discount (или их список), который отслеживает все эти вещи, а затем применяет Discount (s) к Ордену, что-то вроде Order.getTotal(Discount)или Discount.applyTo(Order)или подобное.

Джон Боде
источник
Я вижу, куда лучше поместить код в Discount, а не в объект Inventory. Но объекту скидки, по-видимому, все равно придется получить доступ к информации из всех различных объектов, чтобы выполнить свою работу. Так что это, по крайней мере, насколько я вижу, не решает проблему.
Уинстон Эверт
2
Я думаю, что объект Discount - хорошая идея, но я бы заставил его реализовать шаблон наблюдателя ( en.wikipedia.org/wiki/Observer_pattern ), где в качестве субъекта (-ов) шаблона используются Discounts
BlackICE
1

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

Изменить: Ответ на комментарий Уинстона

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

Я бы сослался на предметы инвентаря по идентификатору. Каждый заказ будет содержать список идентификаторов инвентаря и соответствующее количество.

Тогда я бы посчитал сумму заказа примерно так.
Inventory.GetCost (товары, CUSTOMERNAME, дата)

Тогда у вас может быть другая вспомогательная функция, например:

Inventory.ItemsLefts (int itemID)
Inventory.AddItem (int itemID, int количество)
Inventory.RemoveItem (int itemID, int количество)
Inventory.AddBusinessRule (...)
Inventory.DeleteBusinessRule (...)

Pemdas
источник
Прекрасно просить данные у тесно связанных классов. Проблема возникает, когда я обращаюсь к данным из косвенно связанных классов. Для реализации этой логики в классе Order потребуется, чтобы класс Order имел дело непосредственно с объектом Inventory (который содержит необходимые данные о ценах). Это та часть, которая беспокоит меня, потому что она связывает Order с классами, о которых (в идеале) он ничего не знает.
Уинстон Эверт
По сути, ваша идея состоит в том, чтобы исключить OrderItem, а Order следит за всей этой информацией. Затем легко передать эту информацию другому объекту для получения информации о ценах. Это может сработать. (В конкретном сценарии этот пример основан на том, что OrderItem был слишком сложным и действительно должен был быть своим собственным объектом)
Уинстон Эверт
Что не имеет смысла для меня, так это то, почему вы хотите, чтобы ссылки на Инвентарь использовались с использованием номеров идентификаторов, а не ссылок на объекты. Мне кажется, лучше отслеживать ссылки на объекты и их количество, чем идентификаторы и ссылки на элементы.
Уинстон Эверт
Я действительно не предлагаю это вообще. Я думаю, что у вас все еще должен быть класс или структура для элементов заказа. Я просто не думаю, что отдельные объекты инвентаря должны иметь цену внутри, главным образом потому, что я не уверен, как вы могли бы получить это без запроса какой-либо базы данных. Заказ или товарная единица будут строго классом данных, содержащим идентификатор товара и количество. Сам заказ будет иметь список этих объектов.
Пемдас
Я не совсем уверен, что вы спрашиваете во втором комментарии. Не все должно быть объектом. Я действительно не уверен, как бы вы нашли конкретный элемент без какого-либо идентификатора.
Пемдас
0

Подумав об этом больше, я придумал собственную альтернативную стратегию.

Определите ценовой класс. Inventory.GetPrice()возвращает объект цены

Price OrderItem::GetPrice()
{
    return inventory.GetPrice().quantity(quantity);
}

Currency OrderItem::GetTotal()
{
    Price price = empty_price();
    for(item in order_items)
    {
         price = price.combine( item.GetPrice() )
    }
    price = price.date(date).customer(customer);
    return price.GetActualPrice();
}

Теперь класс Price (и, возможно, некоторые родственные классы) инкапсулирует логику ценообразования, и заказ не должен беспокоиться об этом. Прайс ничего не знает о Order / OrderItem, а просто вводит в него информацию.

Уинстон Эверт
источник