Какой шаблон проектирования использовать для назначения разрешенных операций пользовательским типам? - PullRequest
5 голосов
/ 10 ноября 2011

Я разрабатываю систему на C # (.NET 4.0), которая будет использоваться различными пользователями.

Каждому типу пользователей будет разрешено взаимодействовать с приложением только определенным образом.Например, пользователь A должен иметь возможность выполнять только операции 1, 2 и 3. Пользователь B должен иметь возможность выполнять только операции 2, 3 и 4. И т. Д.

Я решил реализовать интерфейс под названием «Роль».", который реализуется различными классами, такими как" Администратор "," Читатель "и т. д. У пользователя может быть несколько ролей (определенных как список" Ролей ").

Я хотел бы каждую роль («Администратор», «Читатель» и т. Д.), Чтобы иметь список разрешенных операций.Я думал об определении где-нибудь перечисления, которое будет содержать все возможные операции в приложении.Каждая «Роль» будет иметь список элементов, найденных в этом перечислении.

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

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

Ответы [ 4 ]

4 голосов
/ 10 ноября 2011

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

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

public interface IRole
{
    public string Name { get; }
}

(Как вы, вероятно, можете сказать, я держу это очень просто). Вот и все, действительно, роль. Заголовок. Вы можете подумать, что излишне просто иметь интерфейс с параметром имени, но роли могут стать более сложными на более позднем этапе.

Хорошо, мы определили абстрактную концепцию роли. Я предполагаю, что у нас будет какой-то класс User, которому дается куча ролей. Вот простой:

public class User
{
    private List<IRole> roles_ = new List<IRole>();

    /* .. usual suspects */

    public void AddRole(IRole role)
    {
        Debug.Assert(role != null);
        Debug.Assert(!HasRole(role));
        roles_.Add(role);
    }

    public void RevokeRole(IRole role)
    {
        Debug.Assert(role != null);
        roles_.Remove(role);
    }

    public bool HasRole(IRole role)
    {
         Debug.Assert(role);
         return roles_.Contains(role);
    }


    public bool CanPerform(IAction action)  /* to come */ 
}  // eo class User

Прямо сейчас у нас есть определение роли, конкретный класс User и возможность добавлять и отзывать роли для конкретного пользователя. Время определить некоторые конкретные роли:

public class AdminRole : IRole
{
    public string Name { get { return "Admin"; } }
}

public class ReaderRole : IRole
{
    public string Name { get { return "Reader"; } }
}

Единственное, чего нам действительно не хватает, так это действий, которые можно выполнить. Итак, давайте определим новый интерфейс:

public interface IAction
{
    string Name { get; }
    void Perform();
}

Это примерно так же просто, как и действие. Мы сделаем фиктивный:

public class InvoiceAction : IAction
{
     public string Name { get { return "Invoicing"; } }
     public void Perform()
     {
         /* Invoice somebody! */
     }

     public InvoiceAction(/* Some pertinent details*) { }
}

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

public sealed class RoleValidation
{
    private Dictionary<System.Type, List<IAction>> roleMap_ = new Dictionary<System.Type, List<IAction>>(); // this is our mapping of roles to actions

    // ctor ignored for brevity

    public void AssignActionToRole<ROLE>(IAction action)
    {
        Debug.Assert(action != null);
        if(roleMap_.ContainsKey(typeof(ROLE)))
            roleMap_[typeof(ROLE)].Add(action);
        else
        {
            List<IAction> actions = new List<IAction>();
            actions.Add(action);
            roleMap_[typeof(ROLE)] = actions;
        }
    }

    // Nice generic function for ease of use
    public List<IAction> GetActions<ROLE>() where ROLE : IRole
    {
        foreach(KeyValuePair<System.Type, IAction> pair in roleMap_)
        {
            if(typeof(ROLE).IsAssignableFrom(pair.Key.GetType()))
                return pair.Value;
        }
        return null;
    }

    // for everyone else, non-explicit
    public List<IAction> GetActions(System.Type type)
    {
        foreach(KeyValuePair<System.Type, IAction> pair in roleMap_)
        {
            if(type.IsAssignableFrom(pair.Key.GetType()))
                return pair.Value;
        }
        return null;
    }
} // eo class RoleValidation

Хорошо, что это позволяет нам делать? Это позволяет нам использовать наш класс RoleValidation для связи действий с ролями ....

RoleValidation rv = new RoleValidation();
rv.AssignActionToRole<Administrator>(new InvoiceAction());
rv.AssignActionToRole<Reader>(new OtherAction());

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

cv.AssignActionToRole<Administrator, InvoiceAction>();

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

Теперь это тип для реализации этой функции CanPerform в нашем пользовательском классе:

public class User
{
    /* you know the rest */
    public bool CanPerform(IAction action)
    {
        /* code assumes RoleValidation is available via some method or whatever */
        foreach(IRole role in roles_)
        {
            if(RoleValidation.Instance.GetActions(typeof(role)).Contains(action))
                return true;
        }
        return false;
    }
}

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

2 голосов
/ 10 ноября 2011

Посмотрите на Proxy Pattern.Это должно дать вам подсказки о том, как подходить к этому сценарию: http://en.wikipedia.org/wiki/Proxy_pattern

Защитный прокси-сервер контролирует доступ к чувствительному главному объекту.«Суррогатный» объект проверяет, что у вызывающей стороны есть права доступа, требуемые до пересылки запроса.

http://sourcemaking.com/design_patterns/proxy

1 голос
/ 10 ноября 2011

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

Я бы посмотрел поставщика ролей для работы с ролями.Хороший дизайн позволит пользователю выполнять несколько ролей, будь то администратор, читатель и т. Д. И т. Д ...

0 голосов
/ 10 ноября 2011

А как насчет шаблона проектирования состояний? Каждый тип пользователя будет генерировать свое собственное первое состояние меню, например, фабрику, после того как вы измените состояние в соответствии с вашим текущим состоянием.

...