2016-08-08 4 views
6

У меня есть User объект. Я хотел бы иметь несколько «типов» этого объекта с разными менеджерами и репозиториями. Все User лица всех типов будут делиться только UserInterface. Теперь я ищу хороший способ организовать все. Первое, что пришло мне на ум, чтобы создать что-то вроде этого:PHP: шаблон проектирования для управления типами объектов

interface UserTypeManagerInterface 
{ 
    public function addUserType($name, RepositoryInterface $repository, ManagerInterface $manager); 
    public function hasType($name); 
    public function getRepository($type); 
    public function getManager($type); 
} 

Тогда в тех местах, где я хотел бы управлять несколькими типами User сразу, я бы впрыснуть это, и в тех местах, где я хотел бы управлять определенным типом пользователя, я мог бы вводить только определенные объекты репозитория и менеджера для его типа.

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

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

interface UserTypeManagerInterface { 
    public function addUserType($name, RepositoryInterface $repository, ManagerInterface $manager); 
} 

/** 
* My class managing multiple types of user. 
*/ 
class ManageMultipleTypesOfUsers implements UserTypeManagerInterface { 
    // ... 
} 

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

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

Возможно ли какое-либо среднее место здесь?

+0

mo, Мне не нравится идея того, как пользовательский объект должен знать, как хранить себя или иметь менеджера или нет. Но им нужно знать, какой тип они есть. Таким образом, все, что получает экземпляр пользователя, может узнать, какие другие отношения он имеет, посмотрев в классе «тип пользователя»? Другой вариант: вы можете рассмотреть шаблон стратегии для «пользовательского объекта» и ввести объект «пользовательский тип» по мере необходимости (с помощью сеттера)? Это сделало бы тестирование/изменение того, что было очень легко из-за развязки? –

ответ

1

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

Насколько я вижу, Пользователь - это объект с чистой стоимостью? Это хорошее начало. Это означает, что тривиально манипулировать без побочных эффектов.

Теперь рассмотрим эти переменные, которые будут влиять на ваш дизайн:

  • ли интерфейс только один метод? [Или все его методы тривиальные обертки для одного метода?]
  • Является ли интерфейс означающим, что пользователи вашей библиотеки определяют пользовательские реализации?
  • Влияет ли реализаций только на утверждения (например, для интерфейса требуется метод delete(User $user) - разрешены только администраторы, другие реализации, которые просто бросают, проверяет основные разрешения) или используют совершенно другой код?
  • Сколько у нас избыточного инженерного и непроверенного беспорядка?

Итак, как эти переменные влияют на наше решение?

  • Если у вас когда-либо был только один [меньший] центральный метод, то интерфейсы плюс классы, вероятно, будут излишними и, как правило, засоряют вашу кодовую базу множеством небольших файлов. Часто пропускание Closure также приводит к такому же эффекту.
  • Кроме того, при наличии простых Замыканий/вызываемых вызовов открытый API всегда должен быть интерфейсом.
  • Когда реализации просто добавляют утверждения, вы, как правило, лучше с одним классом, отправляющим запрос диспетчеру управления доступом [который читает/кэширует разрешения из базы данных].
  • сверхреализованный vs untestable: 50 файлов с тремя строками различного кода лучше объединяются в один файл с 300 строками.

Поскольку я не знаю, каковы будут требования, я не могу дать вам идеальное решение. Решение (второе), которое вы предложили, подходит для «перепроектированного» случая.

Более умеренное решение будет функциональный подход:

function addAdminUser($name, RepositoryInterface $repository, ManagerInterface $manager) { /* ... */ } 
function addNormalUser($name, RepositoryInterface $repository, ManagerInterface $manager) { /* ... */ } 
// you even can pass this around as a callable ($cb = "addAdminUser"), or (pre-binding): 
$cb = function($name) use ($repo, $mgr) { addAdminUser($name, $repo, $mgr); }; 

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

function addUser($name, $type, RepositoryInterface $repository, ManagerInterface $manager) { 
    /* ... common logic ... */ 
    if ($type == IS_ADMIN_USER) { /* ... */ } 
} 

Если это слишком радикально, можно также вводить обратный вызов в AddUser:

function addUser($name, $cb, RepositoryInterface $repository, ManagerInterface $manager) { 
    $user = new User; 
    /* ... common logic ... */ 
    $cb($user, $repository, $manager); 
} 

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

TL; DR: PHP предоставляет вам более функциональный подход, но менее проверяемый. Иногда это прекрасный компромисс, иногда нет. Это зависит от вашего конкретного кода, на котором лежит средняя земля.

P.s .: Единственное, что действительно пахнет немного мне в вашем втором предложении, оказывает RepositoryInterface $repository, ManagerInterface $manager в методе addUserType подписи. Вы уверены, что это не часть конструктора?

+0

Я еще не могу обмотать голову, не могли бы вы привести мне пример? Предположим, у нас есть пользователи нескольких типов, с их собственными репозиториями и менеджерами. И у нас есть класс 'ShowUsers', роль которого состоит в том, чтобы отобразить всех пользователей, отсортированных по типу, в одной таблице. Как этот функциональный подход можно использовать здесь, чтобы дать этому классу всю информацию, необходимую для его задачи? –

+0

Предположим, что 'class User {public $ id; public $ name; public $ type; общедоступные данные; } ', причем' $ data' является настраиваемой информацией о типе (может быть другой класс, массив, независимо от того, что понимает конкретный менеджер). Теперь вы можете просто упорядочить массив пользователей с помощью функции typeSortUsers ($ users) {usort ($ users, function ($ a, $ b) {return $ a-> type <=> $ b-> type;}); вернуть $ users; } '. Как хорошее упражнение, просто попробуйте спроектировать архитектуру, как если бы это были все чистые объекты данных и простые функции (например, C, как без указателей функций внутри структур), а затем сравните его с вашим дизайном OO и выберите среднюю площадку. – bwoebi

Смежные вопросы