使用哪种设计模式将允许的操作分配给用户类型?

Hus*_*lil 5 .net c# design-patterns access-control

我正在开发一个C#(.NET 4.0)系统,它将被各种用户使用.

每个用户类型只允许以特定方式与应用程序交互.例如,用户A应该只能执行操作1,2和3.用户B应该只能执行操作2,3和4等.

我决定实现一个名为"Role"的接口,它由各种类实现,例如"Administrator","Reader"等.用户可能有多个角色(定义为"角色"列表).

我希望每个角色("管理员","读者"等)都有一个允许的操作列表.我想在某个地方定义一个enum,它将包含应用程序中可能的所有操作.然后,每个"角色"将具有在该枚举中找到的项目列表.

我觉得这个没有经过优化,因为我必须始终确保在我的应用程序中实现新功能时保持这个enum的最新状态.

你会建议我使用什么方法?有没有可以帮助我的设计模式?

Moo*_*ice 6

我喜欢这个问题,这是一个很好的问题。每当我遇到这样的问题时,我总是开始考虑接口、动作以及它们如何协同工作。通常,一两个工厂会突然决定在最终解决方案中需要感受到它的存在。那么让我们来看看您的问题包含的一些接口。

首先,我们有一个角色。正如您的问题所述,角色可以是任何内容,并且完全特定于业务。所以,这个角色实际上可以很简单:

public interface IRole
{
    public string Name { get; }
}
Run Code Online (Sandbox Code Playgroud)

(正如你可能会说的,我保持这个非常简单)。这就是一个角色,真的。标题。您可能认为仅仅拥有一个具有名称参数的接口是过分的,但角色在稍后可能会变得更加复杂。

好的,我们已经定义了一个角色的抽象概念。我想我们会有某种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
Run Code Online (Sandbox Code Playgroud)

现在,我们有一个角色的定义、一个具体的 User 类以及向特定用户添加和撤销角色的能力。是时候定义一些具体的角色了:

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

public class ReaderRole : IRole
{
    public string Name { get { return "Reader"; } }
}
Run Code Online (Sandbox Code Playgroud)

我们唯一真正缺少的是可以执行的操作。所以,让我们定义一个新的接口:

public interface IAction
{
    string Name { get; }
    void Perform();
}
Run Code Online (Sandbox Code Playgroud)

这与操作一样简单。我们将制作一个虚拟的:

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

     public InvoiceAction(/* Some pertinent details*) { }
}
Run Code Online (Sandbox Code Playgroud)

是的,我们已经准备好了基本的接口和具体的类。我们现在需要想出一种方法可以将动作与角色相关联。出于本示例的目的,我将使用一个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
Run Code Online (Sandbox Code Playgroud)

好的,这允许我们做什么?它允许我们使用 RoleValidation 类将动作与角色相关联......

RoleValidation rv = new RoleValidation();
rv.AssignActionToRole<Administrator>(new InvoiceAction());
rv.AssignActionToRole<Reader>(new OtherAction());
Run Code Online (Sandbox Code Playgroud)

我意识到可能存在重复的动作,这留给读者作为练习,因为您可能会创建一个动作工厂并在这里使用两个泛型,例如:

cv.AssignActionToRole<Administrator, InvoiceAction>();
Run Code Online (Sandbox Code Playgroud)

并且基本上有一个类型的映射,而不是具体的实例。那将是我首选的做事方式。

现在是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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我承认,我在没有实际编译和构建工作示例的情况下将这个答案放在一起,所以如果有错误,我深表歉意。这是您可以用来解决此问题并具有动态操作的一种方法,分配给任意角色和具有多个角色的用户。如果我们考虑角色可能从另一个角色衍生出来,它会变得更有趣,但那是另一回事了:)

  • 绝对惊人的回复。你不知道我有多么感激你花时间输入所有这些。该解决方案也经过深思熟虑。谢谢你,我希望我能给你几百分。干杯! (2认同)