Как разделить / отделить создание экземпляра дочерних элементов при создании базовой модели в DDD - PullRequest
0 голосов
/ 19 июня 2019

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

  • Разрешить учителю планировать курс.
  • Разрешить учителю планировать различные действия в указанном курсе.
  • Показать список занятийстуденту для выбранного курса в указанном диапазоне дат.

Приведенные выше требования приводят меня к созданию двух агрегатов.

  • CourseAggregate
  • ActivityAggregate

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

protected abstract class Activity
{
    public Guid Id {get; private set;}
}
protected class Training : Activity
{
..... Addiontal properties 
}
protected class Exam : Activity
{
....Addiontal properties and behavior.
    public bool AllowGrading => true;
}
.... Other childern of activity..hence more classes. 

Вопросы:

  • Правильный ли подход к наследованию?
  • Поскольку я пометил конструктор как защищенный, клиентский код не будет использовать оператор new и не будет иметь непосредственных знаний о дочерних элементах.Я изо всех сил пытаюсь выяснить, как клиент должен создать экземпляр действия.Например:
[Test]
public void ActivityFactoryShouldCreateCorrectActivityType(){
   var activity= ActivityFactory.CreateActivity(activityType:"Training", title:"Training", DueDate: "date".......)

}

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

Как решить, правильно ли ее реализовать или какой шаблон лучше подходит здесь?

Ответы [ 2 ]

0 голосов
/ 02 июля 2019

Спасибо, что нашли время, чтобы ответить на вопрос подробно и с прекрасным пониманием. Предположим, мы разрабатываем сайт https://coursera.org. Есть две основные цели высокого уровня, которых должна достичь система. - Учитель / Создатель курса должен уметь создавать (планировать) курс. С точки зрения создателя, он / она хочет добавить экзамен, обучение или другие действия в курс. Но он / она будет называть это «Я планирую экзаменационную работу в этом курсе на следующие даты, с указанием следующих критериев неуспешности / сдачи» или «Я планирую учебную деятельность в этом курсе». Теперь, если мы перейдем к подходу интерфейса IActivity вместе с ActvityType Enum, весь код клиента будет использовать switch или if / else, чтобы увидеть, какой это вид деятельности, и это будет перетекать на верхний уровень, т.е. в пользовательский интерфейс или даже в контроллеры. или потребительские классы,

if(activity.type==exam){ ((Exam)IActivity).DoSomething();}

Но это выглядит приемлемым, если нет хорошей альтернативы, но она действительно загромождает ваш код. - С точки зрения студента, он / она заинтересован только в следующем - покажи мне список всех видов деятельности, которые я должен выполнить, но скажи мне, что это за виды деятельности - После того, как я попытаюсь / сделаю задание, он / она также ожидает иного поведения, например, на тренинге нет привязки, как на экзамене. --- Экзамен разрешается сдавать только один раз. --- Итоговая оценка экзамена отличается от полной экзамена. --- Итоговый экзамен Разрешить позднюю отправку, в то время как экзамен вообще не имеет этой функции.

Теперь снова, чтобы вызвать правильное поведение IActivity, enum полезен, но он загромождает кодовую базу на всех уровнях, где необходимо принять решение. И IActivity вообще не знает о поведении экзамена, и экзамен может быть нескольких типов, что увеличивает сложность, поэтому еще одно перечисление, чтобы увидеть, какой это экзамен, так как итоговый экзамен и полный экзамен отличаются только поведением оценки, и все остальное такое же. Теперь с этим, другой оператор switch или if / else для всех классов потребителей. * Фабрики помогут с этим, но я волнуюсь, что это станет слишком сложным, имея разные методы на фабриках, так как экзамен может быть в действительном состоянии (черновик) с другой комбинацией свойств. Таким образом, система заинтересована как в Деятельности, так и в конкретных типах, т.е. в экзамене, обучении и т. Д., Но в другом объеме.

** Дополнительная сложность: что, если учитель захочет создать новый вид деятельности, который говорит: «Активность на пути», экзамен доступен только тогда, когда учащийся проходит это обучение ». Теперь студенту все еще интересно посмотреть список всех видов деятельности, просто хочу узнать его тип (в виде ярлыка).

В последнее время я думал о композиции вместо наследования, где есть только один тип, Activity, и у него есть набор коллекций функций. Каждая функция содержит свое собственное поведение в своем классе. Не делали этого раньше, не уверены, существует ли такой подход или даже полезен ли он.

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

0 голосов
/ 22 июня 2019

Это одна из проблем, которая часто возникает при использовании таких языков, как C # или Java.Это проблема реализации больше, чем проблема моделирования.

Дело в том, что у вас есть эти понятия: Exam, Training и т. Д., Которые являются конкретными.С другой стороны, вы можете вывести для них общую концепцию: Activity.

Вот пара вопросов, которые нам нужно задать, прежде чем мы рассмотрим реализацию.

  • Что этонужно делать с этими понятиями?
  • Как это работает с ними?
  • Сколько частей системы интересуется конкретными концепциями Exam, Training и т. Д. И сколько из них интересуется общей концепцией Activity?
  • DoВы ожидаете добавить еще много концепций, которые будут Activities?Это будет влиять на то, как вы будете развивать свою систему.

Допустим, ваша система не использует концепцию Activity и в нее не будет добавлено много дополнительных действий.В этом случае мы можем просто игнорировать Activity и просто использовать конкретные концепции.Это говорит о том, что в их создании нет проблем.

Допустим, ваша система будет использовать концепцию Activity, и вам нужно добавить больше типов действий.

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

Такого родалогики показывает проблему с тем, что мы думаем, когда мы используем язык ООП, такой как C # Java.Мы обучены как разработчики.обычно люди говорят, что кастинг плохой.Вы могли бы как-то определить базовый класс или интерфейс и позволить подклассам реализации интерфейса определить поведение, а другие части системы не должны знать конкретный тип.

И это верно для некоторых частей системыи для будущих концепций.Возьмите например Serializer.Вы можете определить интерфейс ISerializer с помощью метода Serialize.Система, использующая сериализатор, может использовать интерфейс без необходимости знать конкретный тип, поскольку каждый класс, реализующий интерфейс ISerializer, будет добавлять различную реализацию того же interface.

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

Концепция Duck Typing интересна: "Если он ходит как утка и крякает как утка, то ондолжна быть утка "

Если ваша система должна работать с Exam, она должна работать с ней, а не с Activity.Если у него есть Activity, то должно быть в состоянии понять, что это действительно Exam, потому что это то, что ему нужно.

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

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

Вот пример:

public enum ActivityType { Exam, Trainig, Project, Task }

public class Activity {

    public Guid ID { get; private set; }
    public abstract ActivityType Type { get; }
    // other stuff
}

public class Exam : Activity {

    public override ActivityType Type {
        get { return ActivityType.Exam; }
    }
    // other stuff
}

public class SomeClass {

    public void SomeAction(Activity activity) {

        if(activity.Type == ActivityType.Exam) {
            var examActivity = (Exam)activity;
            // do something with examActivity
        }
    }
}

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

public class ExamFactory {

    public Exam CreateSummerExam(string name, //other stuff) {
        // Enfore constraints
        return new Exam(new Guid(), name,....);
    }
}

Или добавить Factory к конкретному типу:

public class Exam : Activity {

    public static Exam CreateSummerExam() { 
       // Enfore constraints
        return new Exam();
    }

    private Exam() { }
}

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

Если вы действительно хотите скрыть классы, чтобы позволить себе некоторую свободу реализации, тогда используйте интерфейсы:

// **Domain.dll**

public enum ActivityType { Exam, Training }

public interface IActivity {

    ActivityType Type { get; }
}

public interface IExam : IActivity { }

internal class Exam : IExam { }

public class ActivityFactory {

    public IExam CreateExam() { return new Exam(); }
    public ITraining CreateTraining() { return new Training(); }
    // other concrete activities
}

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

Вот хорошая статья по этому вопросу.В нем Мартин Фаулер говорит:

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

РЕДАКТИРОВАТЬ:

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

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

Существует множество систем, которые выполняют подобные действия.Вот несколько примеров:

  • В компьютерных играх используется то, что называется Entity Component System . Эти системы ориентированы на данные, где Entity состоит из различных Components.Затем каждая система проверяет, присоединен ли Component к Entity.Например, у вас есть Rendering system, который отображает вашу сцену со всеми игроками и прочим.Эта система проверит, подключен ли объект 3D model componentЕсли он есть, он отобразит его.

  • Тот же самый подход используется в Поточное программирование .Он также управляется данными, когда вы отправляете информационные пакеты, которые состоят из разных свойств.Эти свойства могут быть простыми или сложными.Тогда у вас есть Processes, которые связаны и передают данные друг другу.Каждый Process будет искать определенный тип данных в IP-адресе, чтобы проверить, поддерживается ли он им.

  • Unity также поддерживает использование Entity Component System.Но он также поддерживает другой подобный подход к наличию активных компонентов, которые содержат поведение и логику вместо пассивных данных, которые обрабатываются из внешних систем.

  • Программирование на основе функций .Использует понятие функций, которые вы можете добавить к объекту.Он используется в системах CAD / CAM, банковских системах и многих других

Это хороший подход для использования при наличии динамических данных, которые необходимо обработать.К сожалению, это не избавит от необходимости делать if/else и swich.Как уже упоминалось, когда вам нужно обработать наборы Activities, вам нужно будет выполнить некоторую проверку.

Обратите внимание, что системы выше не пытаются этого избежать.Напротив.Они принимают этот факт и используют динамические данные.Это не отличается от того, что у вас есть тип деятельности и switching.Просто их подход дает более гибкую систему за счет большого количества проверок.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...