У меня есть опыт работы с C, и я новичок в C ++. У меня есть основной вопрос дизайна. У меня есть класс (я назову его «шеф-повар», потому что проблема, которая у меня есть, очень похожа на это, как с точки зрения сложности, так и с точки зрения проблем), которая в основном работает следующим образом.
class chef
{
public:
void prep();
void cook();
void plate();
private:
char name;
char dish_responsible_for;
int shift_working;
etc...
}
в псевдокоде это реализуется в соответствии с:
int main{
chef my_chef;
kitchen_class kitchen;
for (day=0; day < 365; day++)
{
kitchen.opens();
....
my_chef.prep();
my_chef.cook();
my_chef.plate();
....
kitchen.closes();
}
}
Класс шеф-повара здесь, кажется, класс монстров, и может стать им. Шеф-повар также, кажется, нарушает принцип единственной ответственности, поэтому вместо этого у нас должно быть что-то вроде:
class employee
{
protected:
char name;
int shift_working;
}
class kitchen_worker : employee
{
protected:
dish_responsible_for;
}
class cook_food : kitchen_worker
{
public:
void cook();
etc...
}
class prep_food : kitchen_worker
{
public:
void prep();
etc...
}
а также
class plater : kitchen_worker
{
public:
void plate();
}
так далее…
По общему признанию, я все еще борюсь с тем, как реализовать это во время выполнения, так что, если, например, Плейтер (или «шеф-повар в своем качестве плеттера») решит вернуться домой на полпути через ужин, тогда шеф-повар должен работать в новую смену ,
Похоже, это связано с более широким вопросом, который у меня возникает: если в этом примере один и тот же человек неизменно готовит, готовит и подает блюдо, каково реальное практическое преимущество наличия такой иерархии классов для моделирования того, что делает один повар? Я предполагаю, что это сталкивается с «страхом добавления классов», но в то же время, сейчас или в обозримом будущем, я не думаю, что поддерживать класс шеф-повара в целом ужасно громоздко. Я также думаю, что наивному читателю кода в реальном смысле легче увидеть три различных метода в объекте chef и двигаться дальше.
Я понимаю, что это может угрожать стать громоздким, когда / если мы добавим такие методы, как «cut_onions ()», «cut_carrots ()» и т. Д., Возможно, каждый со своими собственными данными, но, кажется, с ними можно справиться, если функция prep (), скажем, более модульная. Более того, кажется, что SRP, доведенный до логического завершения, создаст класс «onion_cutters», «carrot_cutters» и т. Д., И мне все еще трудно понять значение этого, учитывая, что каким-то образом программа должна убедиться, что один и тот же работник режет лук и морковь, что помогает поддерживать переменную состояния одинаковой в разных методах (например, если работник режет свой палец, резая лук, он больше не имеет права резать морковь), тогда как в классе шеф-поваров объекта-монстра кажется, что обо всем, что заботится.
Конечно, я понимаю, что тогда становится менее важно иметь осмысленный «объектно-ориентированный дизайн», но мне кажется, что если мы должны иметь отдельные объекты для каждой из задач шеф-повара (что кажется неестественным, учитывая, что один и тот же человек выполнение всех трех функций), то это, кажется, расставляет приоритеты при разработке программного обеспечения над концептуальной моделью. Я чувствую, что объектно-ориентированный дизайн здесь полезен, если мы хотим иметь, скажем, «meat_chef», «sous_chef», «three_star_chef», которые, вероятно, являются разными людьми. Кроме того, проблема времени выполнения связана с тем, что при сложном применении принципа единой ответственности возникает сложность, связанная с дополнительными издержками, которая должна обеспечить изменение базовых данных, составляющих сотрудника базового класса, и что это изменение отражено в последующих временных шагах.
Поэтому я довольно склонен оставить это более или менее как есть. Если бы кто-то мог объяснить, почему это было бы плохой идеей (и если у вас есть предложения о том, как лучше поступить), я был бы очень благодарен.
Чтобы избежать злоупотребления классовыми иерархиями сейчас и в будущем, вам следует использовать его только тогда, когда является отношения присутствуют. Как и вы сами, «это повар_ еда кухонник». Это, очевидно, не имеет смысла в реальной жизни и в коде тоже. «cook_food» — это действие, поэтому может иметь смысл создать класс действия и вместо этого создать подкласс.
Наличие нового класса просто для добавления новых методов, таких как cook()
а также prep()
Во всяком случае, это не улучшение исходной проблемы — все, что вы сделали, — обернули метод внутри класса. Что вы действительно хотели, так это сделать абстракцию для выполнения любого из этих действий — так что вернемся к классу действий.
class action {
public:
virtual void perform_action()=0;
}
class cook_food : public action {
public:
virtual void perform_action() {
//do cooking;
}
}
Затем шеф-повар может получить список действий, которые нужно выполнить в указанном вами порядке. Скажем, например, очередь.
class chef {
...
perform_actions(queue<action>& actions) {
for (action &a : actions) {
a.perform_action();
}
}
...
}
Это более известно как Шаблон стратегии. Он продвигает принцип открытого / закрытого, позволяя вам добавлять новые действия без изменения существующих классов.
Альтернативный подход, который вы могли бы использовать, это Шаблонный метод, где вы указываете последовательность абстрактных шагов и используете подклассы для реализации определенного поведения для каждого из них.
class dish_maker {
protected:
virtual void prep() = 0;
virtual void cook() = 0;
virtual void plate() = 0;
public:
void make_dish() {
prep();
cook();
plate();
}
}
class onion_soup_dish_maker : public dish_maker {
protected:
virtual void prep() { ... }
virtual void cook() { ... }
virtual void plate() { ... }
}
Другой тесно связанный образец, который мог бы быть подходящим для этого, является Образец Строителя
Эти модели могут также уменьшить Последовательная связь anti-pattern, так как слишком легко забыть вызывать некоторые методы или вызывать их в правильном порядке, особенно если вы делаете это несколько раз. Вы также можете рассмотреть возможность помещения ваших kitchen.opens () и closes () в похожий метод шаблона, чем вам не нужно беспокоиться о вызове closes ().
При создании отдельных классов для onion_cutter и carrot_cutter это на самом деле не логический вывод SRP, а фактически его нарушение — потому что вы создаете классы, отвечающие за вырезание, и храните некоторую информацию о том, что они резки. Как режущий лук, так и морковь можно абстрагировать в одно режущее действие — и вы можете указать, какой объект вырезать, и добавить перенаправление для каждого отдельного класса, если вам нужен определенный код для каждого объекта.
Одним из шагов было бы создание абстракции, чтобы сказать, что что-то можно вырезать. является отношения для подклассов является кандидатом, так как морковь является Cuttable.
class cuttable {
public:
virtual void cut()=0;
}
class carrot : public cuttable {
public:
virtual void cut() {
//specific code for cutting a carrot;
}
}
Режущее действие может взять режущий объект и выполнить любое обычное режущее действие, которое применимо ко всем режущим объектам, а также может применить определенное поведение резки каждого объекта.
class cutting_action : public action {
private:
cuttable* object;
public:
cutting_action(cuttable* obj) : object(obj) { }
virtual void perform_action() {
//common cutting code
object->cut(); //specific cutting code
}
}
Других решений пока нет …