Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > PHP: Общие вопросы > Какой подход использовать?


Автор: BuShaRt 20.9.2013, 11:35
На работе пробуем начать программировать на правильном ООП (использовать SOLID и т.п.). Возникают вопросы. Вот один из них.

У нас есть класс User (мы называем такие классы Entity - он описывает все элементарные свойства объекта + имеет набор сеттеров и геттеров + какие-то методы, производяшие подсчеты, на которые влияют только данные уже имеющиеся в объекте). Вообще мы используем Doctrine 2, если кто работал с ней, тем будет сразу ясно о чем речь.

Примерно так
User
- id
- name
+ getId
+ setName
+ getName
+ getStamp { retuen name + id }

А теперь у нас есть задача, получить набор статусов этого пользователя, но для подсчетов используется сложная логика, затрагивающая несолько таблиц в БД и соотвественно несолько объектов (для нагялдности представим, что эта логика состоит из лапши из циклов и SQL-запросов).  Если мы поместим эту логику в наш класс User, то мы автоматически нарушим принцип SOLID, как минимум тем, что этот объект сам по себе не может обращаться к базе, что говорить о том, что он станет в курсе того какие еще объекты есть в системе.

Конечно в голову приходят варианты решения задачи, на пример следующие:
- Сделать отдельные объекты - процессоры, которые будет на входе получать объект user,  а на выходе отдавать значение одного из статусов.
- Сделать объект SuperUser, который принимает на входе объект User и при инициализации производит все необходимые подсчеты (хотя, мне кажется тут тоже нарушается принцип SOLID)
- Еще такая идея пришла, пока писал пост. Каким нибудь образом добавить необходимые статусы в класс User, но поместить в них не значения, а объекты типа процессоров описанных в первом пункте (наверно в данном контексте это будет похоже на паттерн прокси).

Вот... Расскажите, как вы поступаете в таких ситуациях?

Автор: baldina 20.9.2013, 12:09
Цитата(BuShaRt @  20.9.2013,  11:35 Найти цитируемый пост)
логика, затрагивающая несолько таблиц

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

Нужно ответить на вопрос: чем является информация (в БД), на основе которой считаются статусы, по отношению к пользователю. Тогда станет понятно
а) это свойство пользователя или операция над ним
б) как лучше абстрагировать слой БД, что бы User или функция расчета оперировали терминами задачи, а не таблицами

ЗЫ. Отвлеченный вопрос. Предполагается, что юзер может быть переименован (я про setName)?

Добавлено через 1 минуту и 36 секунд
Цитата(BuShaRt @  20.9.2013,  11:35 Найти цитируемый пост)
в голову приходят варианты решения задачи

Прежде поиска технических решений надо с концепцией определиться

Автор: BuShaRt 20.9.2013, 12:18
Цитата

Нужно ответить на вопрос: чем является информация (в БД), на основе которой считаются статусы, по отношению к пользователю. Тогда станет понятно
а) это свойство пользователя или операция над ним
б) как лучше абстрагировать слой БД, что бы User или функция расчета оперировали терминами задачи, а не таблицами


А можно я приведу более подробное описание, а вы ответите сами на эти вопросы? Просто я не уверен, что точно их понял, а на вашем примере мне будет проще сооринтироваться.
Имеем следующие статусы
1. Стутус частоты заказов. 
1.1 = 2, если пользователь за последние 14 дней заказывал не менее двух товаров в день как минимум 10 раз (дней)
1.2 = 1, если пользователь за последние 14 дней заказывал не менее одного товара в день как минимум 5 раз (дней)
1.3 = 0 во всех други случаях
2. Статус прогресса заказов
1.1 = 2, если пользователь за последние 5 дней заказал больше, чем за предыдушие 5 дней
1.2 = 1, если пользователь за последние 5 дней заказал товара меньше, чем за предыдушие 5 дней, но не более чем на 20% (меньше)
1.3 = 0  во всех других случаях

Цитата

ЗЫ. Отвлеченный вопрос. Предполагается, что юзер может быть переименован (я про setName)? 

Это просто пример. Реально у нас есть поле identity, которое нельзя менять, а name менять можно т.к. он информационное.

Автор: georgiy11 20.9.2013, 12:58
Здесь не проблема в чистом или не чистом ООП. Проблема в том как модель User будет себя позиционировать в системе: включать бизнес логику (AR) и это как раз определение выше сказанных условий или будет обычным шлюзом к таблице в БД.
Обычно, если бизнес логика не велика (то есть при добавлении модель не будет раздута до небес), это вмещается и в AR, например я бы сделал так (это очень удобно)
1. Стутус частоты заказов. 
1.1 = 2, если пользователь за последние 14 дней заказывал не менее двух товаров в день как минимум 10 раз (дней) $user->isOrderedLeastTwoItems($period = 14, $countOrders = 10);
1.2 = 1, если пользователь за последние 14 дней заказывал не менее одного товара в день как минимум 5 раз (дней) $user->isOrderedLeastOneItems($period = 14, $countOrders = 5);
1.3 = 0 во всех други случаях

Что дают такие методы: избавляемся от статусов и инкапсулируем бизнес логику в методах (бизнес логика имеет структуру но настраиваема, хотя лучше конечно полностью инкапсулировать)
Тфжёлое в этом подобрать имена методам smile

Автор: BuShaRt 20.9.2013, 14:04
georgiy11, Я Вас чуть-чуть обманул. Я только сейчас узнал, что в Doctrine 1 использовался AR-паттерн, а в Doctrine 2 используется Data Mapper. Я написал, что мы работаем с Doctrine, но не уточнил с какой именно, видимо этим введя вас в заблуждение. Так вот мы работаем с Doctrine 2 и наши объекты не являються не AR, не шлюзами - они являются просто объектами, без бизнес логики, манипалирование которыми берет на себя Data Mapper. Но Data Mapper умеет манипулировать только элементарными данными этих объектов + явно выраженными связями. Т.е. он может заполнить объекты проксями к другим объектам или самими данным, но вот производить вычисление на базе данных он не умеет, да и не должен уметь судя по тому, как я понял его концепцию. 

Автор: georgiy11 20.9.2013, 14:29
Тогда это прослойка из стратегий, хорошо вписывается в архитектуру. Стратегии могут и должны содержать бизнес логику, и могут оперировать множеством сущностей.
То есть, фактически, стратегия будет инкапсулировать какой то объём бизнес логики с различными мапперами и их связями. 
Ещё более чистый ООП, подразумеваем инкапсулирование мапперов в сущностях Entites и соответственно связи производится будут между entity<->entity, в которых инкапсулированы данные от мапперов. Но мне кажется это слишком не нужный подход. Решение стратегий довольно гибкое для средних проектов (имею ввиду с более менее сложной бизнес логикой). Где отлично работает архитектура с сущностями и в них маппер, так это высоконагруженная бизнес-логика например игра браузерная. 
Это лично моё мнение smile

Автор: baldina 20.9.2013, 17:08
BuShaRt, значит имеем три связанные сущности - пользователь (User), его заказы (Orders) и его статусы (Status).
Класс Orders умеет получать информацию о заказах конкретного пользователя (посредством обращения к хранилищу - БД)
Класс Status умеет рассчитывать значение статуса на основании определенной статистики (заказов)
Класс User пользуется и тем и другим, связывая их и предоставляя интерфейс доступа к значению статусов.

Итого примерно такая схемка (вместо классов с одной функцией для краткости использую функции):

Код

// вернуть число заказов пользователя $user_id за последние $ndays дней, содержащих не менее $nproducts продуктов
// (или без учета продуктов, если $nproducts=null
function get_num_orders ($user_id, $ndays, $nproducts=null); 
function get_freq_status  (array $norders_by_nproducts); 
function get_prgr_status  ($ratio); 

class User {
  ...
  function getFreqStatus () {
    $no1 = get_num_orders ($this->id, 14, 2);
    $no2 = get_num_orders ($this->id, 14, 1);
    return get_freq_status (['2'=>$no1, '1'=>$no2]);
  }

  function getPrgrStatus () {
    $no1 = get_num_orders ($this->id, 5);
    $no2 = get_num_orders ($this->id, 10);
    return get_prgr_status ($no1 / ($no2-$no1));
  }
}



где функции статуса могут быть например такие

Код

function get_freq_status (array $odata) {
  $rules = ['2' => [2=>10], '1'=>[1=>5]];
  foreach ($odata as $nprods=>$norders)
    foreach ($rules as $status => list ($np,$no))
       if ($nprods >= $np && $norders >= $no)
          return $status;
  return 0;
}

function get_prgr_status ($ratio) {
  $rules = ["2" => 1, "1"=> 0.8];
  foreach ($rules as $status => $r)
     if ($ratio >= $r)
        return $status;
  return 0;
}


Добавлено через 7 минут и 35 секунд
Цитата(georgiy11 @  20.9.2013,  14:29 Найти цитируемый пост)
Ещё более чистый ООП, подразумеваем инкапсулирование мапперов в сущностях Entites и соответственно связи производится будут между entity<->entity, в которых инкапсулированы данные от мапперов.

ужос))))

Автор: georgiy11 21.9.2013, 10:28
Цитата

Цитата(georgiy11 @  20.9.2013,  14:29 Найти цитируемый пост)
Ещё более чистый ООП, подразумеваем инкапсулирование мапперов в сущностях Entites и соответственно связи производится будут между entity<->entity, в которых инкапсулированы данные от мапперов.

ужос))))


http://design-pattern.ru/patterns/data-mapper.html
В чём то есть правильность подхода.

Кстати, а можно ли наследовать маппер и делать дочерний класс AR.

Автор: baldina 21.9.2013, 14:41
Цитата(georgiy11 @  21.9.2013,  10:28 Найти цитируемый пост)
В чём то есть правильность подхода.

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

Добавлено через 10 минут и 39 секунд
BuShaRt, у вас уже были хорошие решения - 1 и 3 из первого поста. Какое выбрать зависит от вкусовых пристрастий и необходимости дальнейшего расширения проекта. 1й простой и расширяемый, но требует часть концепции в голове держать. 3й это модный DI (при злоупотреблении вносящий больше путаницы чем пользы). Идите по пути ослабления связей между классами, это всегда оставляет возможность для изменений без переписывания всего.

Автор: georgiy11 22.9.2013, 01:10
[B]baldina[/B
Цитата

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


 smile  по секрету, тихо, ООП и есть техническое решение
Есть решение принять функциональный подход к задаче
Есть решение ООП подход,
Есть решение совмешённый ООАП, ООП, функциональный, процедурный ....

Цитата

только ООП тут непричем

Сори, датамапер ни к какой парадигме не подойдёт как к ООП, если ВЫ конечно не оспорите smile

Цитата

Какое выбрать зависит от вкусовых пристрастий


Вкусовые пристрастия .... ну ну .... smile если будем ориентироваться на вкусы что же будем получать в результате?

Автор: baldina 22.9.2013, 14:57
Цитата(georgiy11 @  22.9.2013,  01:10 Найти цитируемый пост)
по секрету, тихо, ООП и есть техническое решение

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

Цитата(georgiy11 @  22.9.2013,  01:10 Найти цитируемый пост)
если будем ориентироваться на вкусы что же будем получать в результате?

писать так
Код

if (cond) {
}
или так
Код

if (cond)
{
}
дело вкуса
или вам известен Единственно Верный Путь?

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)