CSC/ECE 517 Fall 2011/ch1 2b qu
Access Control in o-o Languages
O-o languages have different approaches to controlling access to variables and methods. Ruby, for example, doesn't allow an object to access the private features of another object, even if the other object is an instance of the same class. Java 1.0 had a "private protected" access specifier that allowed subclasses to access a variable, but not non-subclasses in the same package. It was dropped, probably because it was confusing. Have philosophies about allowing access become more restrictive over the years, as accessor methods have become more prominent?
Introduction
When declaring public members, you do not have the ability to determine when and how the data members are altered. The practicalities are also revealed to the programmer thereby making the interface overcomplicated and can lead to a dispersion of class rules. Access control was the solution to many of these issues, which are evident when allowing uncontrolled access to all of an object's data members. Access control is a feature that is available in many programming languages that allows the programmer to define a set of rules that govern how the elements in a program (for example methods or attributes in a class) can be used from different contexts. While some programming languages like C++, JAVA etc have predefined policies for access control (public/protected/private) allowing structured but limited power in the hands of the programmer, others such as Eiffel provide more flexible access control scheme (selective export). Here, we will look at access control policies of some object-oriented programming languages and how these policies have changed over the years.
Why Access Control?
Access control is a key component of Encapsulation/Data Hiding principle in object-oriented languages. The data hiding principle emphasizes on the importance of separating the design decisions from the rest of the program so that when a decision is changed, the rest of the program does not have to be extensively modified. In o-o languages this is done by hiding the internals/implementation details of a class from the user. The access to selected components/functions of class is allowed through an interface (for example a method) thus prohibiting their direct modification by the user. This protects the object's integrity by preventing users from setting the internal data of the object into an invalid or inconsistent state.
For example, a class Employee may have methods such as name, department, position and salary, that return the employee's name, department, position within the organization and salary respectively. The details about how the name method gets the name of the employee or how is the salary calculated should not be available to the users of the Employee class. The name method may connect to the database to retrieve the values or the salary method may query another database that records the employees hourly attendance to calculate the current salary. The codes for performing these tasks is not relevant to the user and should not be exposed. Access control allows the programmer to selectively hide the data/implementation details.
Consider another example where the programmer wants to create a queue class which can contain integers. The programmer can use an array to implement the queue class, which is hidden from the user of the class. The programmer can then write the push() method that can insert an element into the queue and pop() method that can retrieve an element from the queue. Since the array variable is hidden, the user cannot access the array directly. Now, if the programmer suddenly decides that he wants to implement the queue using a linked list instead of an array then he can do so by changing array type to linked list and modifying the push() and pop() methods to manipulate the linked list instead of an array. The user's code does not need any modification in this scenario because the interface that he used to access the queue is still the same.
Access control in different o-o languages
Different languages have different policies for access control. The most common form of access control is the public/protected/private specification for methods and fields. However, more powerful forms of access control also exist that can differentiate between and class and instance level protection, grant access to specific classes and even use the dynamic circumstances to grant/deny access. Access control policies for some of the popular o-o languages are given below.
C++
C++ supports three levels of access for member data and functions: private, protected, and public. By default, all class members have private access. However, default access to struct and union is public. The meaning and scope of each access level is given below
For Members of a Class
private: Class members declared as private can be used only by member functions and friends (classes or functions) of the class.
protected: Class members declared as protected can be used by member functions and friends (classes or functions) of the class. Additionally, they can be used by classes derived from the class.
public: Class members declared as public can be used by any function.
For Classes
C++ also allows access specifiers for base classes. If class B is a derived class from class A, then class B can also specify either private, protected or public level access to members in base class. An example is shown below
class A { public: int PublicFunc(); // Declare a public member. protected: int ProtectedFunc(); // Declare a protected member. private: int PrivateFunc(); // Declare a private member. }; // Declare two classes derived from A. class B1: public A{ }; class B2: protected A{ }; class B3: private A{ };
In B1, the access level for all the functions in A remains as they are declared since A is a public base class. In B2, since A is protected base class, access level to PublicFunc becomes protected and the access level to other two functions remain unchanged. And in B3 , since A is a private base class, all the functions become private functions. If a derived class is declared without an access level to base class, the default access level is private if the derived class uses the keyword class, and public if the derived class uses the keyword struct.
In multiple inheritance lattices, the compiler chooses the path that gives the most access. Consider the previous example. If we had a class C that inherits from both classes B1 and B2; class A is always reached through B1 since B1 gives the most access.
JAVA
JAVA supports access specifiers for classes, methods, constructors and variables. Similar to C++, JAVA supports three type of access modifiers: private, protected and public. But unlike C++, the default access level (when no access modifier is mentioned) in JAVA is the package level access. These four access levels are explained below
For methods, variables and constructors
private: Only the class itself can access the member
protected: The class, subclass and any other class in the same package can access this member
public: Anyone can access this member
(default): Only the class and other classes in the same package can access this member. Also known as package access
For Classes
Only the public specifier can be used on classes in JAVA. Any single file can contain only class in JAVA and the name for that class should be the name of the file. This is the only class that can be declared with the public keyword.
In an interface every field and method defined is implicitly public. Thus you cannot use private or protected keywords for members in interfaces. Furthermore, default access for interface members is public not package. There is no way to hide interface's members however, the interface itself can have a package access thus restricting other packages from using the interface. This the recommended setting in JAVA.
Smalltalk
Smalltalk is one of the earliest o-o languages that came around before C++, JAVA and C#. Unlike most of the o-o languages today that use keywords as the access control modifiers, Smalltalk used a simple static policy - all the fields in a class are private and all the methods are public. There are no access control modifiers in Smalltalk. Furthermore, private and public are defined differently in Smalltalk compared to other programming languages. Consider an example where we have a class Person which has a subclass Engineer. Now consider two instances of Engineer class: E1 and E2. These two instances E1 and E2 are viewed as two different objects in smalltalk thus the fields of E1 cannot be accessed by fields of E2. However, object E1 can access all the fields in the Person class because they are also a part of E1 thus falling within the scope of E1. This definition of private in smalltalk is closer to protected than private.
Eiffel
The Eiffel programming language has a more powerful approach to access control than other languages. It is called selective export and allows members to be accessible to any other class. The default access level of a member is public. However, an export clause can be defined for any method in a class which explicitly list classes that are allowed to access the method.
class CLIENT feature Watch( dvd: DVD ) is ... end; dvd : DVD; feature {CLIENT , DVD} RentDVD (dvd: DVD) is ... end; end -- CLIENT class DVD feature Matt: CLIENT; feature {DVD, CLIENT} SetClient (Matt: Client) is ... end; end -- DVD
In the preceding example we can see both CLIENT and DVD classes use selective export. In CLIENT class the member function RentDVD is accessible by both CLIENT class and DVD class. Similarly the method SetClient in DVD class is also set to be accessed by DVD class and CLIENT class. The keyword feature is used to select the classes that can access the method. One can use the reserved keywords {ANY} and {NONE} to define a fully public method and completely private method. Note that if a {NONE} keyword is specified even the defining class cannot access the method. The method can only be accessed from a given instance of a class.
Eiffel features are always visible from derived classes, so there is no private access level between base and derived classes like there is in C++, C# or Java.
Ruby
As with C++ and JAVA, Ruby also offers three levels of access control: private, protected and public. Since Ruby is a dynamically typed language, access violations are determined during the runtime. In Ruby, public, private and protected apply only to methods. Instance and class variables are encapsulated and effectively private, and constants are effectively public. There is no way to make an instance variable accessible from outside a class (except by defining an accessor method). And there is no way to define a constant that is inaccessible to outside use.
For Methods in a Class
private: Only the defining class has access to private method. The initialize method is always private.
protected: Only the defining class and its subclasses can access a protected method. Access is kept within the family. However, protected level access is rarely used in Ruby
public: No access control is enforced and everyone can access these methods. A class's instance methods are public by default; anyone can call them.
If a method is not defined within a class definition, it belongs to the object class which is the superclass of all other classes. Thus every class has access to the method. However the method is treated as a private method in all the classes.
Though private methods cannot normally be accessed by any other class other than the one defining it, subclasses can inherit these methods. This means that the subclass can invoke and also override the private method by defining a method with the same name.
For Classes
In ruby, all instance and class variables are encapsulated and thus private and all constants are public thus an instance variable cannot be directly accessed from outside a class and there is no way to define a constant that is inaccessible to other classes.
What are accessor methods?
Accessor methods are implemented as way of enforcing data encapsulation, where encapsulation is an instrument for controlling access to an object's components that may be deemed vulnerable, thereby requiring the use of facilities such as accessor methods to access or alter the state of that sensitive data. Accessor methods are An accessor method is commonly a small and straightforward way to obtain information about the state of a private member of an object such as returning the value of the private member. Commonly, these methods can be easily identified, since the function is usually prefixed with the word get and often referred to as "getters". Other types of accessor methods also include those that alter the state of the object, which are called mutator methods often referred to as "setters". Mutator methods are used to control changes made to a particular member are the only public member functions that can alter the state of the member. For instance, when trying to change the value of a private member, in addition to updating the private member, the mutator method can also validate the desired value to ensure that it is a valid value, prior to updating the value, and altering the desired value to bring it within the valid range, if necessary.
Accessor Methods in Different Object-Oriented Languages
Accessor methods will be illustrated using the BankAccount class. The BankAccount class has a getter method called getBalance, which returns the current balance. It also has two setter methods, Deposit, which updates the value of the balance by adding the amount being deposited to the current balance, if the amount is greater than zero, and Withdraw , which updates the value of the balance by deducting the amount being withdrawn from the current balance, as well as, deducting an overdraft fee, if the amount being withdrawn is greater than the current balance.
C++
In file BankAccount.h
#ifndef BANKACCOUNT_H #define BANKACCOUNT_H class BankAccount { public : BankAccount(void); float getBalance(void); // getter void Deposit(float); // setter void Withdraw(float); // setter private : float current_balance; float overdraft_fee; } #endif
In file BankAccount.cpp
#include BankAccount.h BankAccount::BankAccount(float initialBalance){ current_balance = 0.0; overdraft_fee = 29.00; } float BankAccount::getBalance(void){ return current_balance; } void BankAccount::Deposit(float amount) { if( amount > 0.0 ) current_balance += amount; } void BankAccount::Withdraw(float amount) { current_balance -= amount; if( current_balance < 0 ) { std::cerr << "Account is overdrawn!" << std::endl; current_balance -= overdraft_fee; } }
JAVA
In BankAccount.java
public class BankAccont { //Private fields private float overdraft_fee; private float current_balance; //Constructor method public BankAccount() { this.current_balance = 0.0; this.overdraft_fee = 29.0; } // Accessors for current_balance public float getBalance() { return current_balance; } public void Deposit(float amount) { if( amount > 0.0 ) balance = balance + amount; } public void Withdraw(float amount) { balance = balance - amount; if( balance < 0 ) { System.out.println("Account is overdrawn!"); balance = balance - overdraft_fee; } } }
Smalltalk
In BankAccount.st
Object subclass: BankAccount [ | current_balance | <comment: 'An entity to deposit and withdraw money'> ] BankAccount class extend [ new [ | var | <category: 'instance creation'> var := super new. var init. ^var ] ] BankAccount extend [ init [ <category: 'initialization'> current_balance := 0. overdraft_fee := 29.0. ] // Accessors for current_balance getBalance [ <category:'get balance'> ^current_balance. ] withdraw: amount [ <category: 'deduct money'> current_balance := current_balance - amount. ( current_balance < 0 ) ifTrue: [ Transcript show: 'Account is overdrawn' ]. ] deposit: amount [ <category: 'add money'> (amount > 0 ) ifTrue: [ current_balance := current_balance + amount]. ] ]
Eiffel
In BankAccount.e
class BANKACCOUNT creation initialize feature current_balance: REAL; overdraft_fee: REAL; -- constructor initialize is do current_balance := 0.0 overdraft_fee := 29.0 end -- accessors deposit(amount: REAL) is do if amount > 0 then current_balance := current_balance + amount end end withdraw(amount: REAL) is do current_balance := current_balance - amount if current_balance < 0 then io.put_string("Account is overdrawn!") end end getBalance is do Result := current_balance end end -- class BANKACCOUNT
Ruby
In BankAccount.rb
class BankAccount def initialize @current_balance = 0.0 @overdraft_fee = 29.0 end def getBalance @current_balance end def withdraw=(amount) @current_balance = @current_balance - amount if current_balance < 0 print "Account is overdrawn!" end end def deposit=(amount) if amount > 0 @current_balance = @current_balance + amount end end end
How has access control changed in the recent years?
Philosophies regarding access control have not only become more restrictive, but also more flexible. Usually, object methods are either public, which is less secure by allowing open access to members, or private, which is the most restrictive leaving access restricted to other methods of the same object. Since accessor methods allow access to private member through use of public methods, other objects can now access what once was off limits. However, by use of accessor methods, objects can also control how the state of its private members are changed.
Some Object-Oriented languages developed an additional class, namely protected, to give a middle ground between private and public. For instance, in C++, the protected class was developed to alleviate the issues that arose from the use of inheritance, where the protected status allows members and friends of a derived class of a base class to have access to the private members of the base class, whereas, the alternative was to use public members to allow access.
Conclusion
Access control is intrinsically control over who can access a resource and how the resource can be accessed. In Object-Oriented Programming, the resource is the sensitive data within the object, which also consists of the operations defined on the data members. Access control is a way of implementing data encapsulation, or information hiding, which is the fundamental principle of object-oriented programming necessitating that the object's methods should be used for all manipulation of the object's data protecting parts that should be secure.
References
- http://web.cs.mun.ca/~donald/bsc/node13.html
- http://en.wikipedia.org/wiki/Information_hiding
- http://jvoegele.com/software/langcomp.html
- http://java.sun.com/developer/onlineTraining/Programming/BasicJava2/oo.html#access
- http://www.utwente.nl/ewi/trese/b_referaat/brinke1.docx/
- http://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming)
- http://msdn.microsoft.com/en-us/library/kktasw36(v=VS.80).aspx
- http://www.webopedia.com/TERM/D/derived_class.html
- http://www.artima.com/objectsandjava/webuscript/PackagesAccess1.html
- http://martinfowler.com/bliki/AccessModifier.html
- http://ac.inf.elte.hu/Vol_030_2009/041.pdf
- http://rubylearning.com/satishtalim/ruby_access_control.html
- http://en.wikipedia.org/wiki/Mutator_method
- http://journals.ecs.soton.ac.uk/java/tutorial/java/strings/accessors.html
- http://www.gnu.org/software/smalltalk/manual/html_node/Documenting-the-class.html#Documenting-the-class
- http://archive.eiffel.com/eiffel/nutshell.html
- http://www.usenix.org/publications/compsystems/1989/sum_stroustrup.pdf