Интерфейсы C # и внедрение зависимостей

1

Я пытаюсь понять немного больше о введении интерфейса и о том, как все это происходит вместе.

Моя система имеет разные типы пользователей. Каждый пользователь увидит другой набор меню на главной странице. Вместо того, чтобы иметь большой оператор 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.
}
  • 4
    Вам действительно нужно несколько типов пользователей? Если нет другой причины, я бы придерживался использования доступа на основе ролей и назначал бы пользователям нужные им роли.
  • 1
    Если вам действительно нужны разные типы пользователей, то для того, чтобы действительно следовать хорошему OOD, у вас должен быть класс для каждого типа пользователей, производный от более общего базового класса пользователей. Если вы говорите об авторизации, которую имеет каждый пользователь, то система на основе ролей, как упоминал DavidG, будет лучшим выбором.
Показать ещё 8 комментариев
Теги:

2 ответа

1

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

Принцип заключается в следующем: у вас есть интерфейс, который возвращает общий интерфейс, который вам интересен

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();

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

  • 0
    Следует отметить, что при использовании текущего интерфейса вам необходимо преобразовать пользователя из IMenu в LoadMenu, например: IMenu userMenu = user as IMenu; if (userMenu != null) { userMenu.LoadMenu() }
  • 1
    @AWinkle Я предполагал, что IUser будет содержать метод LoadMenu, поэтому мой последний пункт, где я рекомендую не загружать меню от пользователя, а скорее загружать меню, передавая ему пользователя или правильную абстракцию ( IHaveCredentials может быть хорошим именем :))
0

Я немного смущен вашей концепцией, что избегать операторов 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 (во второй, хотя я бы, вероятно, избавился от этого перечисления), чтобы учесть новый доступ и ваш набор.

  • 0
    Не могли бы вы объяснить, что public IMenuLoad(UserAccessLevel accessLevel){}' means? A constructor of type Конструктор интерфейса public IMenuLoad(UserAccessLevel accessLevel){}' means? A constructor of type IMenuLoader`?
  • 0
    @ user3860564: К сожалению, извините за опечатку.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню