Мне нравится этот вопрос, он хороший. Всякий раз, когда я сталкиваюсь с такими проблемами, я всегда начинаю думать об интерфейсах, действиях и о том, как они работают вместе. Обычно фабрика или два внезапно решают, что их присутствие должно быть ощутимо в конечном решении. Итак, давайте посмотрим на некоторые интерфейсы, из которых состоит ваша проблема.
Прежде всего, у нас есть роль. Роль может быть любой и полностью зависит от бизнеса, как говорится в вашем вопросе. Итак, роль на самом деле может быть довольно простой:
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;
}
}
Признаюсь, я собрал этот ответ, фактически не компилируя его и не создавая рабочий пример, поэтому я прошу прощения, если есть ошибки. Это один из подходов, который вы могли бы использовать для решения этой проблемы, имея динамические действия, назначенные произвольным ролям и пользователям с несколькими ролями. Это может стать интереснее, если учесть, что роли могут быть связаны друг с другом, но это другая история:)