Это одна из проблем, которая часто возникает при использовании таких языков, как 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
.Просто их подход дает более гибкую систему за счет большого количества проверок.
Если вашей системе не требуются такие динамические данные, вы можете просто использовать конкретные классы вместо объектов данных, которые могутхранить неограниченное количество вещей.Это упростит некоторые части вашего приложения.Если вам нужно составить разные объекты, вы можете использовать один из подходов выше.