Я пытаюсь понять немного больше о введении интерфейса и о том, как все это происходит вместе.
Моя система имеет разные типы пользователей. Каждый пользователь увидит другой набор меню на главной странице. Вместо того, чтобы иметь большой оператор switch, проверяющий тип меню пользователя и загрузки соответственно, я создал базовый класс "Пользователь", а производные классы реализуют интерфейс IMenu. Но в Page_Load()
мне все еще нужно знать тип User
для создания, прежде чем я могу вызвать метод LoadMenu()
. Мой вопрос в том, как я могу уйти от жесткого кодирования создания экземпляра объекта User
? Или даже когда я извлекаю данные из БД и создаю тип объекта User
, мне все равно нужно проверить тип пользователя и использовать switch
. Есть ли способ уйти от этого?
Ниже мой код
//base class
Public class User {
private string _Username;
Private string _Name;
}
Public Interface IMenu {
void LoadMenu();
}
Public class Manager : User, IMenu {
public override void LoadMenu(){
//loads manager menu
}
}
public class Employee: User, IMenu {
public override void LoadMenu(){
//loads employee menu
}
}
protected void Page_Load() {
//Retrieve user details from database
//Instantiate an object of derived 'User' type.
//Call 'LoadMenu()' method.
}
Если вы хотите использовать инъекцию зависимостей для загрузки различных типов, которые придерживаются общего интерфейса, вы должны изучить типизированные фабрики (по крайней мере, так, как это называется в castle-windsor - та же концепция должна быть найдена в других рамках)
Принцип заключается в следующем: у вас есть интерфейс, который возвращает общий интерфейс, который вам интересен
public interface IUserFactory {
IUser GetUser(string userType);
}
Затем вы регистрируете все свои типы, исходящие из общего интерфейса, который вы хотите разрешить, с дискриминирующей информацией; это может быть тип класса или какая-либо другая информация.
Наконец, вы сообщаете фабрике о связи между зарегистрированными типами и различающейся информацией; например, если бы мы использовали тип класса как имя компонента на фабрике Windsor, тогда мы могли бы сказать фабрике, что запрос на IUser
должен быть разрешен с использованием параметра, который мы передаем ему как имя компонента. В Windsor это можно сделать, наследуя от DefaultTypedFactoryComponentSelector
public class CustomTypedFactoryComponentSelector : DefaultTypedFactoryComponentSelector
{
protected override string GetComponentName(MethodInfo method, object[] arguments)
{
if(method.Name == "GetUser" && arguments.Length == 1 && arguments[0] is string)
{
return (string)arguments[0];
}
return base.GetComponentName(method, arguments);
}
}
Тогда вы получили бы что-то вроде
var dbUser = DB.LoadUser(1234);
IUser user = IUserFactory.GetUser(dbUser.Type);
user.LoadMenu();
В качестве стиля я бы рекомендовал не давать пользователю ответственность за загрузку меню; вместо этого вы, возможно, оказались бы в лучшем месте, если бы меню было загружено, передав его пользователю или даже лучший интерфейс, описывающий разрешенные действия. Таким образом вы можете загрузить меню для пользователя, но меню не ограничено им и загружается для машин, групп и т.д. Это страна архитектуры, поэтому я не буду отклоняться дальше, но комментарии по вашему вопросу хорошие указатели :)
IMenu userMenu = user as IMenu; if (userMenu != null) { userMenu.LoadMenu() }
IHaveCredentials
может быть хорошим именем :))
Я немного смущен вашей концепцией, что избегать операторов switch эквивалентно инъекции зависимостей. Рано или поздно вам нужно поместить логику того, что делать с вашим меню приложения, в зависимости от вашего текущего пользователя.
Инъекция зависимостей - это максимально возможная развязка вашей архитектуры от логики этого решения, а не избежание ее. Если все сделано правильно, даже если в будущем эта логика изменится, вам не потребуется переписывать половину кода.
Я не знаю, буду ли я делать это так, как вы планируете, но, основываясь на вашем подходе, я бы сделал что-то похожее на следующее (придумал очень быстро, так что он должен иметь дыры в нем, но вы должны получить идея):
public enum UserAccessLevel
{
None,
Employee,
Manager
}
public interface IUser
{
string Name { get; }
string UserName { get; }
UserAccessLevel AccessLevel { get; }
}
public interface IMenuLoader
{
void LoadMenu()
}
public class MenuLoaderFactory()
{
public IMenuLoader GetMenuLoader(UserAccesLevel accesLevel)
{
IMenuLoader menuLoader = null;
switch (accesLevel)
{
case UserAccessLevel.Employee
menuLoader = new EmployeeMenuLoader();
break;
case UserAccessLevel.Manager
menuLoader = new ManagerMenuLoader();
}
return menuLoader ;
}
}
public sealed class EmployeeMenuLoader: IMenuLoader {...}
public sealed class ManagerMenuLoader: IMenuLoader {...}
Теперь на главной странице вы должны содержать только ссылки на IUser, IMenuLoader. Если через несколько месяцев вы решите добавить новый уровень доступа, все сантехника останется в силе. Вам нужно будет только создать новый класс MenuLoader
и обновить логику в MenuLoaderFactory
и UserLevelAccess
Enum (во второй, хотя я бы, вероятно, избавился от этого перечисления), чтобы учесть новый доступ и ваш набор.
public IMenuLoad(UserAccessLevel accessLevel){}' means? A constructor of type
Конструктор интерфейса public IMenuLoad(UserAccessLevel accessLevel){}' means? A constructor of type
IMenuLoader`?