Бизнес-правила - куда они идут в ООП? - PullRequest
3 голосов
/ 04 июня 2009

У меня есть класс: Расписание.

public class Schedule {

private int locationNum;
private int cost;
private String costReason; 
private Date weekOfChange;
private Date dayOfChange;
private String changeReason; 

// and all those getters and setters

public Schedule(int locationNum, int cost, String costReason, Date weekOfChange, Date dayOfChange, String changeReason) throws ApplicationException {
//change is all or nothing - all attributes are present or none
if((weekOfChange!=null && dayOfChange!=null && changeReason!=null) || (weekOfChange==null  && dayOfChange == null && changeReason == null))  {
this.weekOfChange = weekOfChange;
this.dayOfChange = dayOfChange;
this.changeReason = changeReason;
}
else { throw new ApplicationException();}
//similary another if block to ensure that if cost is specified 
//then there exists the corresponding reason code for it.
}
}

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

  • является locationNum действительным номером магазина в базе данных.
  • - это текст changeReason, один из этих 6 различных кодов changeReason в базе данных.
  • и т. Д. И т. Д. *

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

Теперь вот мои вопросы:

  1. Если бы вы рассматривали Schedule как POJO и утверждали, что объект не отвечает за проверку себя - мне пришлось бы переместить весь код в конструкторе в класс валидатора бизнес-уровня. Но если бы я должен был сделать это, не является ли График анемичным? Это то, что они называют нарушением принципа единой ответственности?
  2. Предположим, что я переместил код, который у меня есть в конструкторе, в класс бизнес-уровня, так что все виды проверок теперь находятся на моем бизнес-уровне. Предположим, что кто-то изменил dayOfChange на NULL в моем хранилище данных, и теперь я загружаю объекты из базы данных. Теперь, с такими объектами, мое приложение может сломаться, не так ли? Потому что я написал бы код, предполагая, что правила проверки соблюдены. Я предполагаю, что мой вопрос сбивает с толку, но я пытаюсь подчеркнуть, что в этом контексте я бы предпочел, чтобы эти проверки выполнялись в конструкторе, чтобы обеспечить целостность данных, скрытых классом Schedule.
  3. Как это обычно делается? Какова лучшая практика?

Благодарим вас за участие в этом обсуждении.

Ответы [ 5 ]

5 голосов
/ 04 июня 2009

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

Прежде чем беспокоиться о том, стоит ли проверять, возможно, проведите анализ вашего класса. Посмотрите на все его действительные состояния и инварианты для данных состояний. Тогда вы поймете, должен ли ваш класс быть изменчивым или неизменным. Там, где у вас есть взаимозависимые коварианты (например, weekOfChange, dayOfChannge и changeReason, имеет смысл объединить их в свой собственный класс и использовать композицию в классе Schedule. Это поместит «правила» этих полей в одно место и упростит расписание .

То же самое с другими областями сотрудничества (например, стоимость и причина стоимости). Затем расписание составляется из самоутверждаемых классов. Если оба они неизменны и Расписание является неизменным, то у вас есть намного более легкая область.

Итак, чтобы ответить на ваш вопрос: лучше, чтобы класс определял свои состояния и инварианты и предоставлял, по возможности, только минимальный уровень своим сотрудникам. Ответственность за внутреннее состояние Графика должна лежать на Графике, и с немного более продуманным дизайном это можно сделать с относительной легкостью.

Итак, у вас есть

    class Schedule {
         private Change change;
         private Cost cost;
         private Location location;

        public Schedule(Location location, Cost cost, Change change) {
           this.change = change;
           this.cost = cost;
           this.location = location;
        }
}

И коллобораторы типа

public class Change {
    private Date weekOfChange; //shoudln't these two fields be one, a date is a date after all??
    private Date dayOfChange; //shoudln't these two fields be one??
    private String changeReason; 

    public Change(Date weekOfChange Date dayOfChange, String changeReason) {
      this.weekOfChange = weekOfChange;
      ...etc.
    } 
}

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

4 голосов
/ 04 июня 2009

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

POJO только означает, что вам не нужно наследовать от определенного класса или использовать байт-кодировщик для получения желаемой функциональности, такой как сопоставление ИЛИ. Это НЕ означает, что объекты должны быть анемичными без какой-либо функциональности.

Теперь, с этим видом объекта, мой приложение может сломаться, не так ли? Потому что я бы написал код, предполагая, что правила проверки соблюдены. Похоже мой вопрос сбивает с толку, но Дело в том, что я пытаюсь сделать то, что в этом контексте я бы предпочел эти проверки сделаны в конструкторе обеспечить целостность данных, скрытых Расписание занятий.

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

Как это обычно делается? Что лучшая практика?

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

Хорошо Причины иметь отдельные классы правил:

  • Правил так много и они настолько сложны, что включение их всех в класс Schedule сделало бы его слишком большим.
  • Вы не всегда хотите применять все правила.
  • Правила могут быть повторно использованы для работы с различными классами, которые не обязательно находятся в одной иерархии наследования (т.е. они работают через интерфейс).
2 голосов
/ 04 июня 2009

Похоже, есть два разных поведения для конструктора Schedule. Следовательно, должно быть два конструктора. Возможно даже два класса реализации.

locationNum и changeReason. Они в настоящее время слабо напечатаны. У них, вероятно, должен быть свой класс. После вашего вопроса действительные значения зависят от контекста. На данный момент Schedule не имеет контекста. Вы можете сохранить Schedule независимым от контекста, в этом случае нет никаких ограничений базы данных для locationNum и changeReason. Если пойти другим путем, Schedule, locationNum и changeReason могут зависеть от контекста, и конструктор должен просто проверить, что все они находятся в одном контексте. Личный конструктор пакета (действующий как «бедный» friend) может пропустить проверки.

POJO подразумевает (хорошо написанный) объект и, следовательно, поведение. Вопрос, кажется, использует его, чтобы означать «структура». Избегайте структур.

Бизнес-уровень вводит в заблуждение. Похоже, что это бизнес-субъект, он должен иметь деловое поведение. Однако он не может содержать бизнес-процесс (услугу).

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

Обычная практика - иметь анемичные объекты с процедурным кодом. В большинстве случаев это не лучшая практика.

1 голос
/ 04 июня 2009

Почему locationNum - это int, если на самом деле вы ссылаетесь на экземпляр другого класса, скажем, Location? Если вы используете правильные ссылки на объекты, вы можете проверить их в расписании и , чтобы настроить ORM (если есть) для обеспечения ссылочной целостности.

0 голосов
/ 04 июня 2009

чтобы ответить на ваши вопросы:

1) Я не думаю, что ваш класс будет анемичным, скорее это будет DTO на данный момент, я не вижу проблем с этим.

2) Если ваша проверка находится на бизнес-уровне, это не значит, что вы получите недопустимый объект, потому что проверка все еще будет там, обычно ваш процесс проверки будет каким-то образом жаловаться на InvalidDataException или что-то еще не позволяют использовать «поврежденный» объект. Если нулевая дата не является приемлемым значением (нулевое значение обычно не может попасть в базу данных), то уровень business / dao должен перехватить эту ошибку и предотвратить использование объекта вашим приложением.

...