统一访问原则

计算机编程统一访问原则(英语:Uniform access principle)是由伯特兰·迈耶在《Object-Oriented Software Construction英语Object-Oriented Software Construction》中提出。它指出“一个模块提供的所有服务都应该通过统一的符号提供,与它们是通过存储还是通过计算实现无关”。[1]这个原则普遍适用于面向对象编程语言的语法。使用属性、预先计算的属性或对象的方法/查询实现,调用时没有语法差异。

虽然大多数示例都聚焦在该原则的“读取”方面(即检索值),但伯特兰·迈耶表示,该原则“写入”(即修改值)方面更难实现。[2]

解释

伯特兰·迈耶解决的问题涉及大型软件项目或软件库的维护。有时在开发或维护软件时,有必要在大量代码到位后,把简单的属性访问转换为方法调用的方式。编程语言通常使用不同的语法来访问属性和调用方法。例如,object.somethingobject.something()。在当今流行的编程语言中,语法变化需要修改所有使用该属性的地方的源代码。这可能需要在大量源代码中的许多不同位置修改源代码。或者更糟的是,如果修改是在数百个客户使用的对象库中进行的,那么这些客户中的每一个都必须找到并修改在他们自己的代码中使用该属性的所有位置,然后重新编译他们的程序。

以相反的方向(从方法调用到简单属性访问)确实不是问题,因为人们总是可以只保留函数并让它简单地返回属性值。

伯特兰·迈耶认识到软件开发人员需要以这样一种方式编写代码,以最大限度地减少或消除由于将对象属性转换为方法调用或反之亦然的更改而导致的代码级联修改。为此,他提出了统一访问原则。

许多编程语言并不严格支持统一访问原则,但支持它自己的某种形式。多种编程语言提供的属性以不同的方式解决了伯特兰·迈耶用统一访问原则解决的问题。属性提供了一种调用对象方法的方法,而不是只提供一种统一表示法。属性对实例的方法调用提供了与特性(attribute)访问相同的表示法。当然,传统的方法调用语法仍然可用。

统一访问原则的例子

如果编程语言使用了方法调用的语法形式,看起来类似于:

// Assume print displays the variable passed to it, with or without parens
// Set Foo's attribute 'bar' to  value 5.
Foo.bar(5)
print Foo.bar()

执行时显示:

5

如果语言使用了attribute语法,看起来如下:

Foo.bar = 5
print Foo.bar

这里无论是调用了方法还是简单使用了attribute,主调者被隐藏了这些差异。

编程语言示例

Python

Python的属性允许以访问特性(attribute)那样调用方法。因此统一访问原则要求的特性访问与方法调用采用相同表示法,可以被满足。[3]

'''
>>> egg = Egg(4.0, "white")
>>> egg.color = "green"
>>> print(egg)
Egg(4.0, green)
'''

class Egg:
    def __init__(self, weight, color) -> None:
        self.weight = weight
        self.color = color
    def __str__(self) -> str:
        return f'{__class__.__name__}({self.weight}, {self.color})'


# ...(snip)...
class Egg:
    def __init__(self, weight_oz: float, color_name: float) -> None:
        self.weight = weight_oz
        self.color = color_name
        
    @property
    def color(self) -> str:
        '''Color of the Egg'''
        return to_color_str(self._color_rgb)

    @color.setter
    def color(self, color_name: str) -> None:
        self._color_rgb = to_rgb(color_name)   

    @property
    def weight(self) -> float:
        '''Weight in Ounces'''
        return self._weight_gram / 29.3

    @weight.setter
    def weight(self, weight_oz: float) -> None:
        self._weight_gram = 29.3 * weight_oz

    # ...(snip)...


C#

C#语言支持类的properties, 这提供了定义成员变量的getset操作(getterssetters)的方法。访问与修改属性的语法与访问普通的类成员变量是相同的。但实际实现可以是简单读写或者定义为一个函数:

public class Foo
{
    private string _name;

    // Property
    public int Size
    {
        get;    // Getter
        set;    // Setter
    }

    // Property
    public string Name
    {
        get { return _name; }     // Getter
        set { _name = value; }    // Setter
    }
}

忽略set操作,则属性只读。忽略get操作,则属性只写。

只用这种满足统一访问原则的属性的代码:

    public Foo CreateFoo(int size, string name)
    {
        var foo = new Foo();
        foo.Size = size; // Property setter
        foo.Name = name; // Property setter
        return foo;
    }

C++

C++既不支持统一访问原则也不支持属性。当从对字段的直接访问变为一对函数(getA, setA),任何对该对象属性的访问 (x = obj.colorobj.color = x) 必须改为函数调用: (x = obj.getColor()obj.setColor(x)). 使用模板或者运算符重载可能作出属性的效果,但非常复杂,不是语言的直接支持。

JavaScript

JavaScript从2009年开始支持计算属性.[4]

参考文献

  1. ^ The UniformAccessPrinciple. c2 wiki. [6 August 2013]. (原始内容存档于2013-07-19). 
  2. ^ Meyer, Bertrand. EiffelWorld Column: Business plus pleasure. [6 August 2013]. (原始内容存档于2008-11-20). 
  3. ^ Official Python Docs, built-in functions. [2021-07-09]. (原始内容存档于2021-07-12). 
  4. ^ w3schools.com, Javascript Accessors. [2021-07-09]. (原始内容存档于2020-01-04).