[go: nahoru, domu]

Jump to content

Uniform access principle: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
m Reverted edits by Dsumera (talk) to last version by Halogenandtoast
 
(47 intermediate revisions by 27 users not shown)
Line 1: Line 1:
{{Short description|Computer programming principle}}
{{Use American English|date = March 2019}}
{{Refimprove|date=January 2010}}
{{Refimprove|date=January 2010}}
The '''Uniform Access Principle''' was put forth by [[Bertrand Meyer]]. It states "All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation".<ref name=c2>{{cite web|title=The UniformAccessPrinciple|url=http://c2.com/cgi/wiki?UniformAccessPrinciple|work=c2 wiki|accessdate=6 August 2013}}</ref> This principle applies generally to [[object-oriented]] [[programming languages]]. In simpler form, it states that there should be no difference between working with an [[Attribute (computing)|attribute]], precomputed [[Property (programming)|property]], or [[Method (computer science)|method]]/[[Information retrieval|query]].
The '''uniform access principle''' of [[computer programming]] was put forth by [[Bertrand Meyer]] (originally in his book ''[[Object-Oriented Software Construction]]''). It states "All services offered by a [[modular programming|module]] should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation."<ref name="meyer1997">{{cite book
|first=Bertrand
|last=Meyer
|authorlink=Bertrand Meyer
|date=1997
|title=Object-Oriented Software Construction
|title-link=Object-Oriented Software Construction
|edition=second
|publisher=Prentice Hall
|page=57
|isbn=978-0-13-629155-8
}}</ref><ref name=c2>{{cite web|title=The UniformAccessPrinciple|url=http://c2.com/cgi/wiki?UniformAccessPrinciple|work=c2 wiki|access-date=6 August 2013}}</ref> This principle applies generally to the [[syntax (programming languages)|syntax]] of [[object-oriented]] [[programming languages]]. In simpler form, it states that there should be no syntactical difference between working with an [[Attribute (computing)|attribute]], pre-computed [[Property (programming)|property]], or [[Method (computer science)|method]]/[[Information retrieval|query]] of an object.


While most examples focus on the "read" aspect of the principle, Meyer shows that the "write" implications of the principle are harder to deal with in his monthly column on the [[Eiffel (programming language)|Eiffel programming language]] official website.<ref>{{cite web|last=Meyer|first=Bertrand|title=EiffelWorld Column: Business plus pleasure|url=http://www.eiffel.com/general/monthly_column/2005/Sept_October.html|accessdate=6 August 2013}}</ref>
While most examples focus on the "read" aspect of the principle (i.e., retrieving a value), Meyer shows that the "write" implications (i.e., modifying a value) of the principle are harder to deal with in his monthly column on the [[Eiffel (programming language)|Eiffel programming language]] official website.<ref>{{cite web|last=Meyer|first=Bertrand|title=EiffelWorld Column: Business plus pleasure|url=http://www.eiffel.com/general/monthly_column/2005/Sept_October.html|access-date=6 August 2013}}</ref>


==Explanation==
==Explanation==
The problem being addressed by Meyer involves the maintenance of large software projects or software libraries. Sometimes when developing or maintaining software it is necessary, after much code is in place, to change a class or object in a way that transforms what was simply an attribute access into a method call. Programming languages often use different syntax for attribute access and invoking a method, (e.g. obj.something versus obj.something()). The syntax change would require, in popular programming languages of the day, changing the source code in all the places where the attribute was used. This might require changing source code in many different locations throughout a very large volume of source code. Or worse, if the change is in an object library used by hundreds of customers, each of those customers would have to find and change all the places the attribute was used in their own code and recompile their programs.
The problem being addressed by Meyer involves the maintenance of large software projects or software libraries. Sometimes when developing or maintaining software it is necessary, after much code is in place, to change a class or object in a way that transforms what was simply an attribute access into a method call. Programming languages often use different syntax for attribute access and invoking a method, (e.g., {{code|object.something}} versus {{code|object.something()}}). The syntax change would require, in popular programming languages of the day, changing the source code in all the places where the attribute was used. This might require changing source code in many different locations throughout a very large volume of source code. Or worse, if the change is in an object library used by hundreds of customers, each of those customers would have to find and change all the places the attribute was used in their own code and recompile their programs.


Going the reverse way (from method to simple attribute) really wasn't a problem, as one can always just keep the function and have it simply return the attribute value.
Going the reverse way (from method to simple attribute) really was not a problem, as one can always just keep the function and have it simply return the attribute value.


Meyer recognized the need for software developers to write code in such a way as to minimize or eliminate cascading changes in code that result from changes which convert an object attribute to a method call(or vice versa). For this he developed the Uniform Access Principle.
Meyer recognized the need for software developers to write code in such a way as to minimize or eliminate cascading changes in code that result from changes which convert an object attribute to a method call or vice versa. For this he developed the Uniform Access Principle.


Many programming languages do not strictly support the UAP but do support forms of it. Properties, which are provided in a number of programming languages, address the problem Meyer was addressing with his UAP in a different way. Instead of providing a single uniform notation, properties provide a way to invoke a method of an object while using the same notation as is used for attribute access. The separate method invocation syntax is still available.
Many programming languages do not strictly support the UAP but do support forms of it. Properties, which are provided in a number of programming languages, address the problem Meyer was addressing with his UAP in a different way. Instead of providing a single uniform notation, properties provide a way to invoke a method of an object while using the same notation as is used for attribute access. The separate method invocation syntax is still available.


== UAP Example ==
== UAP example ==
If the language uses the method invocation syntax it may look something like this.
If the language uses the method invocation syntax it may look something like this.
<pre>
<pre>
//Assume print displays the variable passed to it, with or without parens
// Assume print displays the variable passed to it, with or without parens
//Set Foo's attribute 'bar' to value 5.
// Set Foo's attribute 'bar' to value 5.
Foo.bar(5)
Foo.bar(5)
print Foo.bar()
print Foo.bar()
Line 25: Line 38:
5
5
</pre>
</pre>
Whether or not Foo.bar(5) invokes a function or simply sets an attribute is hidden from the caller.
Whether or not {{code|Foo.bar(5)}} invokes a function or simply sets an attribute is hidden from the caller.
Likewise whether Foo.bar() simply retrieves the value of the attribute, or invokes a function
Likewise whether {{code|Foo.bar()}} simply retrieves the value of the attribute, or invokes a function
to compute the value returned, is an implementation detail hidden from the caller.
to compute the value returned, is an implementation detail hidden from the caller.


Line 39: Line 52:
== Problems ==
== Problems ==


However, UAP itself can lead to problems, if used in places where the differences between access methods are ''not'' negligible, such as when the returned value is expensive to compute or will trigger cache operations.<ref name=c2/> It might not matter in principle to the client how the value of 42 was obtained, but if computing it [[Deep Thought (The Hitchhiker's Guide to the Galaxy)|requires running a city-sized computer for 7.5 million years]], the client ought to know what to expect.
However, UAP itself can lead to problems, if used in places where the differences between access methods are ''not'' negligible, such as when the returned value is expensive to compute or will trigger cache operations.<ref name=c2/>

== Language examples ==
=== Python ===

[[Python syntax and semantics#Properties|Python properties]] may be used to allow a method
to be invoked with the same syntax as accessing an attribute. Whereas Meyer's UAP would have
a single notation for both attribute access and method invocation (method invocation syntax),
a language with support for properties still supports separate notations for attribute
and method access. Properties allow the attribute notation to be used, but to hide the
fact that a method is being invoked instead of simply retrieving or setting a value.

As such, Python leaves the option of adherence to UAP up to the individual programmer. The built-in {{code|@property}} function provides a simple way to [[Closure_(computer_science)|decorate]] any given method in attribute access syntax, thus abstracting away the syntactical difference between method invocations and attribute accesses.<ref>[https://docs.python.org/3/library/functions.html?highlight=decorator#property Official Python Docs, ''built-in functions'']</ref>

In Python, we may have code that access an {{code|Egg}} object that could be defined such that weight and color are simple attributes as in the following
<syntaxhighlight lang="python">
"""
>>> 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})"
</syntaxhighlight>
Or the Egg object could use properties, and invoke getter and setter methods instead
<syntaxhighlight lang="python">
# ...(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)...
</syntaxhighlight>
{| class="mw-collapsible mw-collapsed" style="border:none;padding-left:0;"
|+ class="nowrap" style="text-align:left;" |Snipped codes are as following:
|<syntaxhighlight lang="python">
import webcolors

# class Egg:

def to_color_str(rgb: webcolors.IntegerRGB) -> str:
try:
return webcolors.rgb_to_name(rgb)
except ValueError:
return webcolors.rgb_to_hex(rgb)
def to_rgb(color_name: str) -> webcolors.IntegerRGB:
try:
return webcolors.name_to_rgb(color_name)
except ValueError:
return webcolors.hex_to_rgb(color_name)


if __name__ == "__main__":
import doctest
doctest.testmod()
</syntaxhighlight>
|}

Regardless of which way {{code|Egg}} is defined, the calling code can remain the same. The implementation of {{code|Egg}} can switch from one form to the other without affecting code that uses the Egg class. Languages which implement the UAP have this property as well.


== Language Examples ==
=== Ruby ===
=== Ruby ===
Consider the following
Consider the following
<source lang="ruby">
<syntaxhighlight lang="ruby">
y = Egg.new( "Green")
y = Egg.new("Green")
y.color = "White"
y.color = "White"
puts y.color
puts y.color
</syntaxhighlight>
</source>
Now the Egg class could be defined as follows
Now the Egg class could be defined as follows
<source lang="ruby">
<syntaxhighlight lang="ruby">
class Egg
class Egg
attr_accessor :color
attr_accessor :color
def initialize( color )
def initialize(color)
@color = color
@color = color
end
end
end
end
</syntaxhighlight>
</source>
The above initial code segment would work fine with the Egg being defined as such. The Egg
The above initial code segment would work fine with the Egg being defined as such. The Egg
class could also be defined as below, where color is instead a method. The calling code would
class could also be defined as below, where color is instead a method. The calling code would
still work, unchanged if Egg were to be defined as follows.
still work, unchanged if Egg were to be defined as follows.
<source lang="ruby">
<syntaxhighlight lang="ruby">
class Egg
class Egg
Line 85: Line 182:
end
end
end
end
</syntaxhighlight>
</source>


Note how even though <code>color</code> looks like an attribute in one case and a pair of methods
Note how even though <code>color</code> looks like an attribute in one case and a pair of methods
in the next, the interface to the class remains the same. The person maintaining the Egg class can switch from one form to the other without fear of breaking any caller's code.
in the next, the interface to the class remains the same. The person maintaining the Egg class can switch from one form to the other without fear of breaking any caller's code.
Ruby enforces UAP, the <code>attr_accessor :color</code> only acts as [[syntactic sugar]] for generating accessor/setter methods for <code>color</code>. There is no way in Ruby to retrieve an instance variable from an object without calling a method on it.
Ruby follows the revised UAP, the <code>attr_accessor :color</code> only acts as [[syntactic sugar]] for generating accessor/setter methods for <code>color</code>. There is no way in Ruby to retrieve an instance variable from an object without calling a method on it.


Strictly speaking, Ruby does not follow Meyer's original UAP in that the syntax for accessing an attribute is different from the syntax for invoking a method. But here, the access for an attribute will always actually be through a function which is often automatically generated. So in essence, either type of access invokes a function and the language does follow Meyer's revised Uniform Access Principle.
=== Python ===


=== C# ===
[[Python (programming language)#Objects|Python properties]] may be used to allow a method
to be invoked with the same syntax as accessing an attribute. Whereas Meyer's UAP would have
a single notation for both attribute access and method invocation (method invocation syntax),
a language with support for properties still supports separate notations for attribute
and method access. Properties allow the attribute notation to be used, but to hide the
fact that a method is being invoked instead of simply retrieving or setting a value.


The [[C Sharp (programming language)|C#]] language supports class ''properties'', which provide a means to define {{code|get}} and {{code|set}} operations (''getters'' and ''setters'') for a member variable. The syntax to access or modify the property is the same as accessing any other class member variable, but the actual implementation for doing so can be defined as either a simple read/write access or as functional code.
In the strict sense, Python does NOT follow the UAP because there is a syntax difference
<syntaxhighlight lang="C#">
between normal method invocations and attribute access.
public class Foo
{
private string _name;


// Property
In Python, we may have code that access an object as follows
public int Size
<source lang="python">
{
egg = Egg( 4, "White")
get; // Getter
egg.color = "Green"
set; // Setter
print egg.weight, egg.color, egg.quack() # prints: 4 Green quack
}
</source>
A Egg object could be defined such that weight and color are simple attributes as in the following
<source lang="python">
class Egg(object):
def __init__(self, weight, color):
self.weight = weight
self.color = color
def quack(self):
return "quack"
</source>
Or the Egg object could use properties, and invoke methods instead
<source lang="python">
class Egg(object):
def __init__(self, weight, color):
self.__weight = toGrams(weight)
self.__color = toRGB(color)


// Property
def setColor(self, colorname):
public string Name
self.__color = toRGB(colorname)
{
get { return _name; } // Getter
set { _name = value; } // Setter
}
}
</syntaxhighlight>


In the example above, class {{code|Foo}} contains two properties, {{code|Size}} and {{code|Name}}. The {{code|Size}} property is an integer that can be read (get) and written (set). Similarly, the {{code|Name}} property is a string that can also be read and modified, but its value is stored in a separate (private) class variable {{code|_name}}.
def getColor(self):
return toColorName(self.__color)
color = property(getColor, setColor, doc="Color of the Egg")


Omitting the {{code|set}} operation in a property definition makes the property read-only, while omitting the {{code|get}} operation makes it write-only.
def setWeight(self, weightOz);
self.__weight = 29.3*weightOz


Use of the properties employs the UAP, as shown in the code below.
def getWeight(self):
<syntaxhighlight lang="C#">
return self.__weight/29.3;
public Foo CreateFoo(int size, string name)

{
weight = property(setWeight, getWeight, doc="Weight in Ounces")
var foo = new Foo();

foo.Size = size; // Property setter
def quack(self):
foo.Name = name; // Property setter
return "quack"
return foo;
</source>
}
Regardless of which way Egg is defined, the calling code can remain the same. The implementation of Egg can switch from one form to the other without affecting code that uses the Egg class. Languages which implement the UAP have this property as well.
</syntaxhighlight>

=== Common Lisp ===
[[Common Lisp]] does not enforce UAP, but it provides language constructs that make it possible. That, together with the fact that the [[Common Lisp Object System]] makes no distinction between function invocation, object attribute, and method syntax, means that UAP-conformant style is customary and it is very unusual to see interfaces that fail to provide it. In fact, Common Lisp includes the concept of extensible "places", which allows arbitrary expressions to be made into writeable locations.<ref>{{cite book|last=Seibel|first=Peter|title=Practical common Lisp|year=2005|publisher=Apress|location=Berkeley, Calif.|isbn=978-1590592397|url=http://www.gigamonkeys.com/book/variables.html#generalized-assignment|edition=New}}</ref><ref>{{cite web|title=Macro SETF, PSETF|url=http://www.lispworks.com/documentation/HyperSpec/Body/m_setf_.htm|work=Common Lisp HyperSpec|accessdate=6 August 2013}}</ref><ref>{{cite web|title=5.1 Generalized Reference|url=http://www.lispworks.com/documentation/HyperSpec/Body/05_a.htm|work=Common Lisp HyperSpec|accessdate=6 August 2013}}</ref>

For example:

<source lang="lisp">
(defvar *egg* (make-instance 'egg :weight 4 :color "white"))
(setf (color *egg*) "green")

(format t "~A ~A ~A" (weight *egg*) (color *egg*) (quack *egg*)) ; prints: 4 green quack
</source>

The egg class could be defined as follows:

<source lang="lisp">
(defclass egg ()
;; "%WEIGHT" is just a naming convention to distinguish the slot names from
;; their accessors' names, to make it harder to access raw slots and thus
;; violate UAP accidentally. These names live in separate namespaces in Common
;; Lisp, and there is nothing preventing us from naming the slot "WEIGHT".
;; There are multiple different conventions used, depending on the programmer.
((%weight :accessor weight :initarg :weight :initform 0 :type number)
(%color :accessor color :initarg :color :initform nil :type (or string null))))

(defmethod quack ((egg egg))
"quack")
</source>

The <code>:accessor weight</code> part in the above definition instructs CLOS to generate reader and writer methods named <code>WEIGHT</code> and <code>(SETF WEIGHT)</code> respectively (the latter name signifies that it is used by the <CODE>SETF</code> operator in so called ''SETF expansions''). These auto-generated methods simply retrieve and store unmodified values in the object's slots.

If we later decide that the weight for example should always be rounded to the nearest multiple of 2, we can change the definitions:

<source lang="lisp">
(defclass egg ()
;; We no longer want the writer to be auto-generated, only reader
((%weight :reader weight :initarg :weight :initform 0 :type number)
...))

(defmethod (setf weight) (new-weight (egg egg))
;; ROUND takes optional second argument to divide by
(setf (slot-value egg '%weight) (* 2 (round new-weight 2))))

(setf (weight *egg*) 16.9)
(format t "~A" (weight *egg*)) ; prints: 16
</source>

However, there is nothing to prevent the programmer from accessing and setting the slot value directly if they wish to:<ref>Strictly speaking, the use of [[metaclass]]es allows a much greater control over object slots. That is however a heavy-weight mechanism and is not normally used for this kind of trivial enforcement. Common Lisp style is to define boundaries by convention and prevent only accidental overstepping; determined tinkerers should be allowed to do what they wish, including shooting themselves in the foot.</ref>

<source lang="lisp">
(setf (slot-value *egg* '%weight) (* 3 (sqrt 2)))
(format t "~A" (weight *egg*)) ; prints: 4.2426405
</source>

It is bad form and unwise to do so without very good reasons, but the language does not forbid it. It is also for this reason that slot names are conventionally named differently than their accessors, so as to make ''accidental'' violation of UAP difficult, but ''deliberate'' possible (this also has the side benefit of allowing export of accessor names from the package while keeping the slot names internal, raising the barrier for confusion even more).


=== C++ ===
=== C++ ===
[[C++]] has neither the UAP nor properties, when an object is changed such that an attribute (color) becomes a pair of functions ({{mono|getA, setA}}). Any place in that uses an instance of the object and either sets or gets the attribute value ({{code|1= x = obj.color}} or {{code|1=obj.color = x}}) must be changed to invoke one of the functions. ({{code|1= x = obj.getColor()}} or {{code|1=obj.setColor(x)}}). Using templates and [[operator overloading]], it is possible to fake properties, but this is more complex than in languages which directly support properties. This complicates maintenance of C++ programs. Distributed libraries of C++ objects must be careful about how they provide access to member data.


=== JavaScript ===
C++ has neither the UAP nor properties, when an object is changed such that an attribute (color) becomes a pair of functions (getA, setA). Any place in that uses an instance of the object and either sets or gets the attribute value ( x = obj.color or obj.color= x) must be changed to invoke one of the functions. ( x = obj.getColor() or obj.setColor(x)). Using templates and operator overloading, it is possible to fake properties, but this is more complex than in languages which directly support properties. This complicates maintenance of C++ programs. Distributed libraries of C++ objects must be careful about how they provide access to member data.
JavaScript has had support for computed properties since 2009.<ref>[https://www.w3schools.com/js/js_object_accessors.asp w3schools.com, ''Javascript Accessors'']</ref>

=== Scala ===

[[Scala (programming language)|Scala]] allows methods of arity 0 to be accessed as plain properties. More specifically, it is allowed (and encouraged) to reference a method with an empty argument list like a normal property. Methods which modify the object's state are by convention written with empty parens to signify intent.

<source lang="scala">
class Rectangle(var width: Double, var height: Double) {
def area: Double = width * height
def randomize(): Unit = ??? // mutate width/height
}

// REPL-session follows:

scala> val rect = new Rectangle(3.0, 4.0)
rect: Rectangle = Rectangle@59328357

scala> rect.area
res0: Double = 12.0
</source>


==References==
==References==
Line 230: Line 242:


{{DEFAULTSORT:Uniform Access Principle}}
{{DEFAULTSORT:Uniform Access Principle}}
[[Category:Articles with example Python code]]
[[Category:Articles with example Python (programming language) code]]
[[Category:Software design]]
[[Category:Software design]]
[[Category:Programming paradigms]]
[[Category:Programming paradigms]]

Latest revision as of 07:45, 30 August 2023

The uniform access principle of computer programming was put forth by Bertrand Meyer (originally in his book Object-Oriented Software Construction). It states "All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation."[1][2] This principle applies generally to the syntax of object-oriented programming languages. In simpler form, it states that there should be no syntactical difference between working with an attribute, pre-computed property, or method/query of an object.

While most examples focus on the "read" aspect of the principle (i.e., retrieving a value), Meyer shows that the "write" implications (i.e., modifying a value) of the principle are harder to deal with in his monthly column on the Eiffel programming language official website.[3]

Explanation[edit]

The problem being addressed by Meyer involves the maintenance of large software projects or software libraries. Sometimes when developing or maintaining software it is necessary, after much code is in place, to change a class or object in a way that transforms what was simply an attribute access into a method call. Programming languages often use different syntax for attribute access and invoking a method, (e.g., object.something versus object.something()). The syntax change would require, in popular programming languages of the day, changing the source code in all the places where the attribute was used. This might require changing source code in many different locations throughout a very large volume of source code. Or worse, if the change is in an object library used by hundreds of customers, each of those customers would have to find and change all the places the attribute was used in their own code and recompile their programs.

Going the reverse way (from method to simple attribute) really was not a problem, as one can always just keep the function and have it simply return the attribute value.

Meyer recognized the need for software developers to write code in such a way as to minimize or eliminate cascading changes in code that result from changes which convert an object attribute to a method call or vice versa. For this he developed the Uniform Access Principle.

Many programming languages do not strictly support the UAP but do support forms of it. Properties, which are provided in a number of programming languages, address the problem Meyer was addressing with his UAP in a different way. Instead of providing a single uniform notation, properties provide a way to invoke a method of an object while using the same notation as is used for attribute access. The separate method invocation syntax is still available.

UAP example[edit]

If the language uses the method invocation syntax it may look something like this.

// 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()

When executed, should display :

5

Whether or not Foo.bar(5) invokes a function or simply sets an attribute is hidden from the caller. Likewise whether Foo.bar() simply retrieves the value of the attribute, or invokes a function to compute the value returned, is an implementation detail hidden from the caller.

If the language uses the attribute syntax the syntax may look like this.

Foo.bar = 5
print Foo.bar

Again, whether or not a method is invoked, or the value is simply assigned to an attribute is hidden from the calling method.

Problems[edit]

However, UAP itself can lead to problems, if used in places where the differences between access methods are not negligible, such as when the returned value is expensive to compute or will trigger cache operations.[2]

Language examples[edit]

Python[edit]

Python properties may be used to allow a method to be invoked with the same syntax as accessing an attribute. Whereas Meyer's UAP would have a single notation for both attribute access and method invocation (method invocation syntax), a language with support for properties still supports separate notations for attribute and method access. Properties allow the attribute notation to be used, but to hide the fact that a method is being invoked instead of simply retrieving or setting a value.

As such, Python leaves the option of adherence to UAP up to the individual programmer. The built-in @property function provides a simple way to decorate any given method in attribute access syntax, thus abstracting away the syntactical difference between method invocations and attribute accesses.[4]

In Python, we may have code that access an Egg object that could be defined such that weight and color are simple attributes as in the following

"""
>>> 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})"

Or the Egg object could use properties, and invoke getter and setter methods instead

# ...(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)...
Snipped codes are as following:
import webcolors

# class Egg:

def to_color_str(rgb: webcolors.IntegerRGB) -> str:
    try:
        return webcolors.rgb_to_name(rgb)
    except ValueError:
        return webcolors.rgb_to_hex(rgb)
    
def to_rgb(color_name: str) -> webcolors.IntegerRGB:
    try:
        return webcolors.name_to_rgb(color_name)
    except ValueError:
        return webcolors.hex_to_rgb(color_name)


if __name__ == "__main__":
    import doctest
    doctest.testmod()

Regardless of which way Egg is defined, the calling code can remain the same. The implementation of Egg can switch from one form to the other without affecting code that uses the Egg class. Languages which implement the UAP have this property as well.

Ruby[edit]

Consider the following

y = Egg.new("Green")
y.color = "White" 
puts y.color

Now the Egg class could be defined as follows

class Egg
  attr_accessor :color
  def initialize(color)
    @color = color
  end
end

The above initial code segment would work fine with the Egg being defined as such. The Egg class could also be defined as below, where color is instead a method. The calling code would still work, unchanged if Egg were to be defined as follows.

class Egg
  
  def initialize(color)
    @rgb_color = to_rgb(color)
  end

  def color
     to_color_name(@rgb_color)
  end
 
  def color=(color) 
     @rgb_color = to_rgb(color)
  end

  private
  def to_rgb(color_name)
     .....
  end

  def to_color_name(color)
     ....
  end
end

Note how even though color looks like an attribute in one case and a pair of methods in the next, the interface to the class remains the same. The person maintaining the Egg class can switch from one form to the other without fear of breaking any caller's code. Ruby follows the revised UAP, the attr_accessor :color only acts as syntactic sugar for generating accessor/setter methods for color. There is no way in Ruby to retrieve an instance variable from an object without calling a method on it.

Strictly speaking, Ruby does not follow Meyer's original UAP in that the syntax for accessing an attribute is different from the syntax for invoking a method. But here, the access for an attribute will always actually be through a function which is often automatically generated. So in essence, either type of access invokes a function and the language does follow Meyer's revised Uniform Access Principle.

C#[edit]

The C# language supports class properties, which provide a means to define get and set operations (getters and setters) for a member variable. The syntax to access or modify the property is the same as accessing any other class member variable, but the actual implementation for doing so can be defined as either a simple read/write access or as functional code.

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
    }
}

In the example above, class Foo contains two properties, Size and Name. The Size property is an integer that can be read (get) and written (set). Similarly, the Name property is a string that can also be read and modified, but its value is stored in a separate (private) class variable _name.

Omitting the set operation in a property definition makes the property read-only, while omitting the get operation makes it write-only.

Use of the properties employs the UAP, as shown in the code below.

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

C++[edit]

C++ has neither the UAP nor properties, when an object is changed such that an attribute (color) becomes a pair of functions (getA, setA). Any place in that uses an instance of the object and either sets or gets the attribute value (x = obj.color or obj.color = x) must be changed to invoke one of the functions. (x = obj.getColor() or obj.setColor(x)). Using templates and operator overloading, it is possible to fake properties, but this is more complex than in languages which directly support properties. This complicates maintenance of C++ programs. Distributed libraries of C++ objects must be careful about how they provide access to member data.

JavaScript[edit]

JavaScript has had support for computed properties since 2009.[5]

References[edit]

  1. ^ Meyer, Bertrand (1997). Object-Oriented Software Construction (second ed.). Prentice Hall. p. 57. ISBN 978-0-13-629155-8.
  2. ^ a b "The UniformAccessPrinciple". c2 wiki. Retrieved 6 August 2013.
  3. ^ Meyer, Bertrand. "EiffelWorld Column: Business plus pleasure". Retrieved 6 August 2013.
  4. ^ Official Python Docs, built-in functions
  5. ^ w3schools.com, Javascript Accessors