4

在我的应用程序中,用户可以执行许多不同的操作,例如(书籍/图书馆示例只是为了使其更清晰;我的应用程序中的操作要复杂得多):

  • 租一本书
  • 读一本书
  • 写一本书
  • 搬一本书
  • 查一本书
  • ...

我的应用程序还包含一些安全检查,以防止其中一些操作。这些检查与动作没有直接关系,而是与动作分开表示概念,例如:

  • 是否允许打开一本书
  • 是否允许修改
  • 是否允许查看一本书的存在与否

目前,不同的操作(租用、阅读等)包含如下代码:

void readBook (Book &book)
   {
   if (!checkSecurity (book, OPENBOOK|SEEBOOK)) return;
   ...
   }

void writeBook (Book &book)
   {
   if (!checkSecurity (book, MODIFYBOOK)) return;
   ...
   }

这种方法使得以动态方式添加新类型的安全检查变得非常困难。例如,动态插件可能会添加额外的安全检查。

我目前正在研究的解决方案是不同的操作调用一个如下所示的界面:

class ISecurityChecker
   {
   public:
      bool isReadBookAllowed() const = 0;
      bool isWriteBookAllowed() const = 0;
      ...
   };

然后一个复合类实现这个接口。该接口的其他实现可以添加到组合中。这样,插件或应用程序的动态部分可以在需要时简单地添加新的安全检查。

这种方法也有一些缺点。主要问题是接口变得非常大。我打算看看我是否可以合并一些接口方法(因为它们基本上执行相同或相似的操作),但不确定这是否可能。

还有其他改善解耦的方法吗?或有关如何改进此设计的建议?

4

2 回答 2

3

我可以建议两种可能的更改……和一条评论。

首先,将您的原始支票尽可能地推到链条上。抛出异常以传达授权错误。

void readBook (Book &book)
{
   // readbook doesn't need to perform any checks of it's own.
   // the more primitive actions will scream and yell if the user
   // isn't allowed to perform sub-action X.

   openBook(book);
   BookData d = getBookData(book);

   // etc.
}

void openBook (Book &book)
{
  if (!checkSecurity (book, OPENBOOK))
    throw new SecurityException("User X is not allowed to open this book!");

  // etc.
}

BookData getBookData (Book &book)
{
  if (!checkSecurity (book, SEEBOOK))
    throw new SecurityException("User X is not allowed to read this book's data!");

  // etc.
}

其次,将您的安全行动映射到实际行动。如果您愿意,您甚至可以在数据中执行此操作。例如 ...

class Security {

  // this check get tricky.
  // if an "real action" isn't listed anywhere, does the user have implicit permission?
  // (i'm assuming not, for this example.)
  public static Check(String realAction, Boolean requireAll = true) {
    Int32 required = 0;
    Int32 userHas = 0;

    foreach (KeyValuePair<String, List<String>> pair in Actions) {
      if (pair.Value.Contains(realAction))
      {
        required++;
        if (Security.CurrentUser.Actions.Contains(pair.Key))
        {
          userHas++;
        }
      }
    }

    if (requireAll)
    {
      return userHas > 0 && userHas == required;
    }
    else
    {
      return userHas > 0;
    }
  }

  // hardcoded here, but easily populated from a database or config file
  public static Dictionary<String, List<String>> Actions {
    {"OpenBook", new List<String>() { "readBook", "writeBook" }},
    {"SeeBook", new List<String>() { "readBook", "writeBook" }}
  }

}

void readBook(Book &book) {
  if (!Security.Check("readBook")) return false;
  // etc.
}

这里的Check()方法接受一个requireAll参数,但映射本身可以很容易地更新为“坚持”或“偏好”存在于它们隐含的“真实动作”中。

我的评论是:不要过度详细说明您的安全性。一些安全规则暗示了其他规则,这些规则本身可能没有意义。例如,READBOOKandWRITEBOOK都暗示了一种能力OPENBOOK,并且OPENBOOK可能本身没有意义。虽然对于用户来说,能够OPENBOOKWRITEBOOK没有诸如SEEBOOKCOVERand之类的东西可能看起来很愚蠢SEEBOOKINSEARCHRESULTS,但我建议在读书时唯一相关的权限是READBOOK.

于 2013-05-07T19:31:04.143 回答
1

使用命令-查询分离 (CQS) 方法。一般的想法是定义一个这样的命令类:

public class Command
{
    public Command(Action<object> act, Func<object, bool> canExecute)
    {
        this.act = act;
        this.canExecute = canExecute;
    }
    private Action<object> act;
    private Func<object, bool> canExecute;
    public void Execute()
    {
        if (CanExecute())
        {
            act(this);
        }
        else
        {
            throw new InvalidOperationException("Command cannot be executed");
        }
    }

    public bool CanExecute()
    {
        if (this.canExecute != null)
        {
            return this.canExecute(this);
        }
        else return true;
    }
}

然后在页面中,您使用Command.Execute()而不是常规book.Write/book.Print等。

用法:

Command readBookCommand = new Command(
    k =>
    {
        book.Read();
    },
    l =>
    {
        CanReadBook();
    }
);
readBookCommand.Execute();

那里可能有更好的 CQS 实施。这只是一个简单的说明。

于 2013-05-08T05:38:58.230 回答