欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

最小权限原则应用于面向对象的软件设计开发

程序员文章站 2022-03-14 17:05:09
...
偶然看到Wiki百科的 Lua 词条, 注意到它的这个特性:

引用
Lua allows ...; and full lexical scoping allows fine-grained information hiding to enforce the principle of least privilege.


不过比较遗憾的是, 通过这里链接过去的 http://en.wikipedia.org/wiki/Principle_of_least_privilege 词条中的描述, 似乎这个 最小权限原则 没怎么被用于软件设计开发方法学, 虽然很早就提出来了, 但主要描述的是在系统设计领域的应用.

我最近正好在做 HBI 的 编程语言/平台 互操作性架构, 目前在做 Java 和 ActionScript 3.0 的互操作框架. 而发现 AS3 很多有意思的地方. 其中可以在程序开发中用来实现 最小权限原则 的就是 AS3 语言中的 namespace 机制. 刚看到这个机制的时候我是眼前一亮的感觉, 因为很早以前就觉得 Java 在语法元素方面缺乏这个层次的 information hiding 能力, 但是一直没机会研究这些其他的语言, 也就没有了解到已经存在的实现, 只是脑子里模模糊糊的有个感觉, 如果xxx那样增强一下会更好. 而在最近出来的 ActionScript 3.0 里, 已经有了明确成熟的实现了.

这里很简单的描述一下OO时如果没有足够好 information hiding 的话所带来的后果, 一个非常简单且常见的例子就是在Hibernate中, 你必须在你的持久Java类中定义一个 identity 字段用作 primary key, 并且要暴露一个 public 的 setter 方法, 好让 Hibernate 去给它赋值; 类似的, 对于关联对象你也要自己定义这么一个集合field, 然后暴露 public setter 方法, 只是为了给 Hibernate 方便. 显然所有的人都能看到这些个 public 的方法, 也可以完全没有编译错误的去调用它们, 而只有脑袋里绷住了一根弦儿, 说这个是给 H 用的, 不是给我用的, 才不至于犯傻或者手误去敲错代码, 盛行的带自动提示的Java IDE让这种错误的机率又提高了不少.

这个看起来不像是很严重的问题, 但是在大规模的软件开发维护中毕竟小的出错率也能贡献大量的错误, 况且要占用每个程序员大脑中的一根神经, 不能更好的集中到业务逻辑上去. 就像现在很多流行编程语言, 通过扔掉指针, 实现垃圾回收器从而把程序员从释放内存的负担中解放出来一样, 早晚也得把人类程序员从考虑各种 API 的调用顺序, 适用性 以及 Side Effect的负担中解放出来, 有更多精力专注于业务逻辑的正确实现.

而这里考虑的 最小权限原则, 是其中一个方面. 那么怎么实现呢?

说明在 ActionScript 3.0 中如何通过 namespace 实现最小权限原则之前, 简单介绍一下AS3的背景:

在 3.0 以前, ActionScript 2.0 和 1.0 一直是 Flash Player 的专有编程语言. 最开始并没有版本, 而 AS2 出来的时候加入了大量的实质性面向对象元素, 所以为了加以区别, 就把以前版本的AS称为 1.0. 其实到目前的 AS 3.0 为止它作为Flash的专有语言的情况也没有改变, 只不过其新主 Adobe 和 Mozilla 联手, 准备以开源的形式发展 AS3. 名义上是两家共同开发 ECMAScript 第4版 的规范以及虚拟机实现, 然后Adobe把它用在Flash里, Mozilla把它用在FireFox的SpiderMonkey里, 所以AS3的规范称自己是 "基于" ECMA 规范. 而实际上 ECMAScript 第4版 现在还只是draft, AS3 却已经投入市场了, 并且 Mozilla 上关于 es4 的wiki内容也全是从Adobe资料导入的. 可见整个局势主要还是由Adobe推动的.

另外也必须说一下 AS3 的革命性. 如果你看一段官方推荐风格的 AS3 代码, 就会发现它已经 非常 非常 像 Java. 最早的 ActionScript (1.0) 只是一些最简单的脚本, 那时主要也是给不是程序员的艺术创作人员用的. 到了 AS 2.0, 面向对象被引入了, 但它实质上是动态脚本语言, 虽然已经有了类的概念和class关键字, 但对象支持还是基于类似JavaScript的prototype机制, 动态继承. 而到了 AS 3.0, 按照官方的说法, 它同时支持静态类型, 基于类的继承方式, 以及动态类型, 基于prototype的继承方式; 虽然后者是AS 3以前唯一支持的方式, 但是在 AS3 中, 并不推荐使用这种方式, 而是建议全部采用前者的静态类型方式. 涉及到这么做的动机时, 官方资料有提到说这是因为用Flash开发的应用规模越来越大. 而即使是在受支持的动态类型机制下, AS3 也并不是完全兼容 AS2, 在事件模型和语法含义上都有打破兼容性的地方. 这在商业意义上的损失无疑是巨大的, 即使 AS3 应用的执行效率要远远高于以前版本, 兼容性方面的损失恐怕也难以弥补. 另外虽然号称是基于 ECMAScript 4, 但是 AS3 默认支持的是 strict dialect 而非 standard es4. strict dialect 实质上主要就是 静态类型+严格语法检查. 由此可以看出 AS3 已经不是原本的动态类型脚本语言, 而是一种 动静结合, 静态为主, 动态为辅 的程序设计语言. 并且其静态语言特性是Adobe鼎力支持的重点.

基本了解了 AS3 的历史和现状之后, 我们可以确信, 它目前很多特性并非只能在动态语言中实现, 而是有可能在静态语言中重现的. 至少 namespace 是如此. 那么回到 最小权限原则 的话题, 看看它可以怎么实现.

在 AS3 中也有 public, private, protected, internal (==default) 这些权限修饰符, 并且各自含义与Java中的同名修饰符完全相同. 但是, 或许有点难以理解, 在 AS3 中它们都是内置的 namespace 而非仅仅是关键字, 编译器通过维护这些内置 namespace 的 可见/适用范围 来实现其语义. 最重要的, namespace 是 AS3 中的一等公民, 也就是说应用程序可以像定义class一样的定义自己的 namespace. 自定义的 namespace 可以放在 public, private, protected, internal 相同的位置用来限定相应语法元素的可访问性, 包括 类, 类成员方法 和 类成员变量.

有了这件利器, 要解决前面提到的Hibernate持久类setter方法过度暴露的问题, 就显得非常容易了. 假如我们在 AS3 中实现一个 ORM 的话(虽然目前来说意义还不大, AS 因为没有线程机制, 在很多场合还不适用), 就可以定义一个 orm 专用的 namespace:

package orm
{
  public namespace ormstub;

  public class ObjectStore
  {
     ...
     public function save(o:Object):void
     {
        var pk:Object = o.getID();
        if(!pk)
        {
          pk = assignID();
          o.ormstub::setID(pk);
        }
        ...
     }
     ...
  }
}


然后应用的持久类就可以利用这个 namespace 把只暴露给orm的setter方法向系统其他部分隐藏起来:

package app
{
  import orm.ormstub;

  public class User
  {
     private var id:Object;

     public function getID():Object
     {
        return this.id;
     }

     ormstub function setID(id:Object):void
     {
        this.id = id;
     }

     ...
  }
}


或者更具 AS3 风格的程序可以写成:

package orm
{
  public namespace ormstub;

  use namespace ormstub;

  public class ObjectStore
  {
     ...
     public function save(o:Object):void
     {
        var pk:Object = o.id;
        if(!pk)
        {
          pk = assignID();
          o.id = pk;
        }
        ...
     }
     ...
  }
}

package app
{
  import orm.ormstub;

  public class User
  {
     private var _id:Object;

     public function get id():Object
     {
        return this._id;
     }

     ormstub function set id(pk:Object):void
     {
        this._id = pk;
     }

     ...
  }
}