CSC/ECE 517 Fall 2011/ch1 2b ns
Access Control in Object-Oriented Languages
Introduction
A fundamental precept of O-O systems is that an object should not expose any of its implementation details. In other words, access to an object’s functionality must be tightly regulated. The access control policies of any object-oriented language specify the rules and capabilities for enforcing this regulation. Thus, the concept of Access control ties in deeply with the underlying principles of Object Oriented Programming.
Purpose of Access Control
Object-oriented programming fundamentally revolves around the principle of Data Hiding and Encapsulation. In a nutshell, Data Hiding is the practice of securing data by constraining access to it, and Encapsulation ensures that the secure data (and the respective member functions) are bundled together as an individual module. All object oriented languages thus need to control the level of access to their critical data (and functions) which is achieved using Access Modifiers or Access Specifiers <ref>http://en.wiktionary.org/wiki/access_specifier</ref>.
The need to strictly control who can access what is very crucial in Software development. For example, if you make an instance variable 'public', then you can't change the field as the class evolves over time since you might break any external code that uses the field. This is because outside code having complete access to this field, has tied itself indelibly to the inside implementation of the (former) class and is now closely dependent on it. Changing the said field thus entails changing all the other code that have a dependency on it, which is cumbersome, unwieldy and contrary to the principles of an Object Oriented approach which strives for Low Coupling.
Thus, the ‘Implementation hiding principle’ leads to a good acid test of an O-O system's quality: Can you make massive changes to a class definition—even throw out the whole thing and replace it with a completely different implementation—without impacting any of the code that uses that class's objects? This sort of modularization is the central premise of object orientation and makes maintenance much easier. Without implementation hiding, there's little point in using other O-O features. Regulation of access - Access Control - is at the heart of Object oriented programming<ref>http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html</ref>.
Evolution of Access Control and proliferation of Accessor methods
Access control has been a part of the OOP movement ever since its inception. Smalltalk is arguably one of first and purest of the many O-O languages and was developed by Xerox PARC in the Learning Research Group in the 1970s <ref>http://www.zdnet.com/blog/murphy/the-tattered-history-of-oop/1157</ref>. Rather than having explicit access specifiers, smalltalk objects operated more in the sense of 'states' i.e. it would hold the 'state' of object(s) which is always private to that object. Any changes required to be made to this state, had to be sent as a request message to that object. This is the most primitive form of access control - having private data members and public member functions - the only difference being that the access levels are not being explicitly specified.
Access control has evolved over time to become highly complicated <ref>http://www.exforsys.com/tutorials/oops/the-use-of-access-specifiers-in-object-oriented-programming.html</ref> because the requirements of computing and software development have ballooned considerably in last decade and a half. Complex software requirements mandates complexity in languages, and this has indeed affected the access control policies, which have evolved into something comprehensive, yet - at times - convoluted. Unfortunately today's developers often lose sight of the fundamental motivation behind the existence of extensive access control and lose out on much of the benefits of having a highly cohesive, low coupled system <ref>http://en.wikipedia.org/wiki/Coupling_(computer_programming)</ref>.
To elaborate, it has become somewhat of an involuntary practice for present programmers to make all data members of a class private and provide accessor methods (getters/setters) to all of them (data members). Developers have come to accept this to be a contemporary programmatic-paradigm and by supplying setters and getters for everything, defeat the very purpose of making class-fields private. A long time ago programmers discovered that reducing the scope (visibility) of data as much as possible leads to more reliable and maintainable code<ref>http://typicalprogrammer.com/?p=23</ref>.
Every getter and setter in one’s code represents a failure to encapsulate and creates unnecessary coupling. A profusion of getters and setters is a sign of a poorly-designed set of classes. Java <ref>http://www.eclipse-blog.org/eclipse-ide/auto-generating-getters-and-setters.html</ref> and C# <ref>http://forums.asp.net/t/941864.aspx/1</ref> developers have IDEs that generate getters and setters automatically, implying that accessors are a good idea and thus contribute to that popular belief. That is not to say that one must completely abstain from accessors, but rather re-analyze and - if required - redesign the class structure to involve as little exposure of its data members as can be achieved.
Access Control: A typical example
Empirically, the data members of a class are held private, and the member functions (public) act as the end points of this class. Via these public methods, one can access the ‘state’ of an object. The following example illustrates this point and shows how access to the data members of a class are specified and controlled by the access modifiers.
UML Diagram of a BankAccount Class.
The “(-)/(+)” preceding the members of this class represent the associated access levels. (-) indicates private and (+) indicates public. The data members ‘accountNumber’, ‘routingNumber’ and ‘balance’ are maintained as private. Thus, an instance of a ‘Customer’ Object (say) cannot directly access or modify this data. Customer has to interact with the public behavior bundled in this module which allows him to view his account information and/or withdraw/deposit money i.e. his visibility of the class is only restricted to that which is made public by the Class.
Using such access specifiers, we can control the level of security and visibility associated with the members of a class. Thus, an Access Specifier can be roughly defined as ‘A keyword applied to a variable, method, etc. that indicates which other parts of the code are permitted to access it’ <ref>http://en.wiktionary.org/wiki/access_specifier</ref>.
Common Access Specifiers in O-O languages
Although all access specifiers essentially provide the same functionality, their semantics differ among different O-O languages. Here is how access control is implemented in some of the more common O-O languages.
C++
- private:All members of the Class that are declared as private can only be accessed by the class's member functions and friends of the class.
- protected:All members of the Class that are declared as protected can be accessed by that class's member functions and friends (classes or functions) of the class. Moreover, classes derived from the class also have access to these elements.
- public:Any class or method can have access to these type of elements.
Java
Java offers four access specifiers, listed below in order of decreasing accessibility:
- public: All public classes, methods, and fields are the least secure. The Public access specifier must be used if you explicitly want to offer access to these entities and if this access cannot do any conceivable harm.
- protected: Protected methods and fields can only be accessed within the same class to which the methods and fields belong, within its subclasses, and within classes present in the same Package. Use of protected access specifier thus only allows its fields to be accessed by those classes which have some semblance of correlation to it - be it in the form of subclasses (correlation through inheritance) or classes within the same package (logical and/or functional correlation i.e. classes in the same package have something logical/functional common to them <ref>http://download.oracle.com/javase/tutorial/java/concepts/package.html</ref>).
- default (no specifier): Default access specifier is Java’s fail-safe way to handle security. Via the default access specifier, a class, method, or field will be accessible from inside the same package to which the class, method, or field belongs, but not from outside this package. Default access is thus suited when the source code is compartmentalized and organized into packages.
- private: Private access specifier is a way to secure vital members of a class. Private methods and fields can only be accessed within the same class to which the methods and fields belong. Private methods and fields are not visible within subclasses and are not inherited by subclasses.
Ruby
Ruby gives you three levels of protection, listed below in order of decreasing accessibility:
- public:Public methods can be called by everyone - no access control is enforced. A class's instance methods are public by default.
- protected:Protected methods can be invoked only by objects of the defining class and its subclasses. Access is kept within the family.
- private:Private methods cannot be called with an explicit receiver - the receiver is always self. Thus private methods can be called only in the context of the current object and cannot be invoked by another object's private methods.
Access control in Ruby is determined dynamically, as the program runs, not statically. You will get an access violation only when the code attempts to execute the restricted method<ref>http://rubylearning.com/satishtalim/ruby_access_control.html</ref>.
C#
The following access modifiers specify the accessibility levels in C#
- public:Any public method or member can be accessed by any other code in the same or different package.
- private:The method or member can only be accessed from within the same class.
- protected:The method or member can be accessed only from within the same Class or from within any of the subclasses.
- internal:The type or member can be accessed by any code in the same assembly, but not from another assembly. An assembly in C# is the rough equivalent of a package in java.
- protected internal:The type or member can be accessed by any code in the Assembly (also see this) in which it is declared, or from within a derived class in another assembly. Access from another assembly must take place within a class declaration that derives from the class in which the protected internal element is declared, and it must take place through an instance of the derived class type.
Summary of Access Specfiers in C++, Java, Ruby and C#:
Access Level | C++ | Java | Ruby | C# |
---|---|---|---|---|
Public | Anything can access | Anything can access | Anything can access | Anything can access |
Protected | Class, Subclasses and friends | Class, Subclasses and classes of same package | Class and Subclasses | Class and Subclasses |
Private | Within Class | Within Class | Within Class | Within Class |
Internal | -- | -- | -- | Any Class from same Assembly |
Protected internal | -- | -- | -- | Any Class from same Assembly and Subclass from any Assembly. |
Examples of Access Specifiers
The following examples exhibit the use of the access specifiers and getter methods for the aforementioned Bank Account example.
C++
class BankAccount { private: char routingNumber; char accountNumber; protected: int balance; public: BankAccount(char *, char *); void changeAccountNumber(char *); void changeRoutingNumber(char *); void depositMoney(int); void withdrawMoney(int); friend void displayAccountInfo(); } BankAccount::BankAccount(char *rNo, char *accNo) { Strcpy(routingNumber, rNo); Strcpy(accountNumber, accNo); balance = 0; } BankAccount::void changeAccountNumber(char *accNo){ Strcpy(accountNumber, accNo); } BankAccount::void changeRoutingNumber(char *rNo){ Strcpy(routingNumber, rNo); } BankAccount::void depositMoney(int amount) { balance += amount; } BankAccount::void withdrawMoney(int amount) { balance -= amount; } // Friend function can access both the private and protected members // of BankAccount. void displayAccountInfo(BankAccount ba) { cout << “Account Number: ” << ba.accountNumber << endl; cout << “Routing Number: ” << ba.routingNumber << endl; cout << “Account Balance: ” << ba.balance << endl << endl; } // CheckingAccount inherits from BankAccount. Hence it can access only the // public and protected members of BankAccount. It cannot access the private // members of BankAccount. class CheckingAccount: public BankAccount { private: char accountType[20]; public: CheckingAccount(char *, char *); void resetBalance(); } CheckingAccount::CheckingAccount(char *rNo, char *accNo) { Strcpy(accountType, "Checking"); changeRoutingNumber(rNo); changeAccountNumber (accNo); balance = 0; } // Since balance is a protected field, it can be accessed by CheckingAccount CheckingAccount::void resetBalance() { balance = 0; } int main() { CheckingAccount ca("888", "999"); ca.depositMoney(100); displayAccountInfo(ca); ca.withdrawMoney(50); displayAccountInfo(ca); ca.resetBalance(); displayAccountInfo(ca); }
OUTPUT: Account Number: 888 Routing Number: 999 Account Balance: 100 Account Number: 888 Routing Number: 999 Account Balance: 50 Account Number: 888 Routing Number: 999 Account Balance: 0
The data members routingNumber, accountNumber are made private and balance is protected. This makes it impossible to directly access or change routingNumber, accountNumber from outside code. Access to these attributes are strictly through the public methods changeRoutingNumber() and changeAccountNumber(). However, private and protected members are accessible through friend functions, so displayAccountInfo() function is able to access all the mentioned fields. Since balance is protected, it can be accessed from a subclass (CheckingAccount) directly as shown above. But the private members have to be accessed through the functions changeRoutingNumber() and changeAccountNumber() instead of being accessed directly.
Thus, it purely at the discretion of the programmer to allow access to these data members through such accessor functions. In the absence of such accessors, there would be no way for any code outside the class to view/modify the private attributes.
Java
public class BankAccount { String accountNumber; private String routingNumber; private Integer balance; //setter methods for accountNumber allows public acccess to it. public void setAccountNumber(String acNo){ this.accountNumber=acNo; } //setter methods for routingNumber allows public acccess to it. public void setRoutingNumber(String rtNo){ this.routingNumber=rtNo; } //setter methods for balance allows only 'protected' access to it (see definition). protected void setBalance(Integer bal){ this.balance=bal; } // Getter method allows accountNumber to be read publicly public String getAccountNumber(){ return this.accountNumber; } // Getter method allows routingNumber to be read publicly public String getRoutingNumber(){ return this.routingNumber; } // Getter method allows balance to be read only in in a 'protected' fashion (see definition). protected Integer getBalance(){ return this.balance; } public void showAccountNumber(){ System.out.println(this.accountNumber); } public void showRoutingNumber(){ System.out.println(this.routingNumber); } public void showBalance(){ System.out.println(this.balance); } public void withdraw(Integer amount){ this.balance-=amount; } public void deposit(Integer amount){ this.balance+=amount; } public static void main(String args[]){ BankAccount Steve = new BankAccount(); //public access to setRoutingNumber/getRoutingNumber Steve.setRoutingNumber("1234"); String steveRtNo=Steve.getRoutingNumber(); System.out.println(steveRtNo); //protected access to the method setBalance/getBalance Steve.setBalance(5000); System.out.println(Steve.getBalance()); } }
Class BankAccount in the above Java code has public getter/setter methods to access the private data member ‘routingNumber’. Thus, we can invoke these methods using an instance of the BankAccount object: Steve. The methods setBalance()/ getBalance() are protected. As can be seen, they behave like public methods if invoked in the same package. Protected methods act like private methods if invoked by any instance of a class outside the package. However, if there is a class ‘InternationalBankAccount’ in a different package such that:
public class InternationalBankAccount extends BankAccount { public String internationalBankAccNo; }
InternationalBankAccount Anna = new InternationalBankAccount(); Anna.setBalance(10000);
The above is a valid syntax and Anna can access protected member functions of the parent class. Data member ‘accountNumber’ has default access specifier. Default access specifier is used in Java to support package driven development. Thus ‘accountNumber’ can be accessed from anywhere within the package. However no classes from other packages can access ‘accountNumber’. Data member balance is a private data member and if it is invoked explicitly as follows,
Steve.balance=1000;
then the output would be an unresolved compilation error of the form:
Unresolved compilation problem: The field bankAccount.balance is not visible
Ruby
The following code illustrates the use of access specifiers in Ruby.
class BankAccount #Instance Variables def balance @balance end def routingNumber @routingNumber end def accountNumber @accountNumber end #Initialize the instance variables def initialize(acNo, rtNo, bal) @accountNumber = acNo @routingNumber = rtNo @balance = bal end #method to compare protected data member, balance of 2 objects def compare_balance(other) if other.balance > balance "The other object has more bank balance." else "This object has more bank balance." end end #access_specifier:class_member_name to define the access control protected:balance public:routingNumber private:accountNumber end
#Creating objects and initializing variables Steve = BankAccount.new(713, 123, 1000) Anna = BankAccount.new(923, 125, 2000) #Accessing public data member puts Steve.routingNumber() #Comparing protected field: balance of Steve and Anna puts Steve.compare_balance (Anna) #Unsuccessful access of private data member raises “NoMethodError” puts Steve.accountNumber()
Output: 123 The other object has more bank balance. NoMethodError: private method `accountNumber' called for #<BankAccount:0x2b75320>
‘routingNumber’ is a public function. Thus, when we access object Steve’s public method ‘routing number’, we successfully get ‘123’ as the output. Furthermore, this snippet provides a mechanism for the comparison of one account holder’s balance with another. This comparison involves a call to the method ‘balance’. The object performing the comparison has to ask the other object to execute its balance method. So, balance cannot be private. With ‘balance’ changed to protected instead of private, Steve can ask Anna to execute ‘balance’, because both are instances of the same class. But if we try to call the ‘balance’ method of a bankAccount object when ‘self’ is anything other than a bankAccount object, the method will fail. A protected method is thus just like a private method, but with the exemption for those cases where the class of ‘self’ and the class of the respective object (having the method called on it) are the same.
The ‘accountNumber’ method is private. Thus user gets a ‘NoMethodError’ on invoking object Steve’s ‘accountNumber’ method. Initialize method in a class is private by default. Whenever the new method is invoked, initialize method is called and instances are created. If we try to invoke the ‘initialize’ method explicitly, we get the same error as in the case when we tried to invoke object Steve’s private ‘accountNumber’ method.
Ruby : The send method
The send method of Ruby has always been a potential security leak and has thus been continually revised and changed across the different versions of Ruby. The Ruby send method has been de-mystified to an extent below.
In Ruby calling methods is similar to passing messages to an object. Consider the simple code below:
class TestSend def hello puts “hello world” end public:hello end
Now the hello method of TestSend can be invoked in 2 ways as follows:
t = TestSend.new #Conventional invocation of public methods t.hello #Invoking a method using send t.send :hello
Both these ways of calling the hello method prints out "hello world" as expected. Parameters can also be passed while invoking a method using the send method. However in Ruby, using send we can also access the private methods of a class. Consider the following snippet for an illustration. Here we have slightly modified the TestSend class we had defined above.
class TestSend def hello puts “hello world” end private:hello end
The hello method has been changed to private. Lets again consider 2 ways of invoking this private method hello:
t=TestSend.new #Conventional invocation of public methods t.hello #Invoking a method using send t.send :hello
As expected 't.hello' results in an 'NoMethodError' whereas 't.send :hello' successfully prints "hello world". Thus, the send method provides a workaround to accessing private member functions of a class. This is a distinction that Ruby possesses in comparison to other O-O languages. The state of send has changed several times as and when versions of Ruby have been released. For a while, Ruby 1.9 changed the implementation of send to only allow public methods to be called, introducing a new 'send!' method which continued to allow private methods to be called. However, this has now been removed from Ruby 1.9 in favour of keeping the functionality of send as is. Instead, a new public_send method has been introduced which will only call public methods <ref>http://deaddeadgood.com/2008/11/17/rubys-send-method/</ref>. Another example.
C#
class BankAccount { public BankAccount(string accountNumber, string routingNumber, int balance) { this.accountNumber = accountNumber; this.routingNumber = routingNumber; this.balance = balance; } private string accountNumber; protected string routingNumber; private int balance; // accountNumber is private, so it can only be modified from within this // class. Also, since the following method is private, it can only be called // from within this class. So this field is truly private. private void setAccountNumber(string acNo) { this.accountNumber=acNo; } // routingNumber is protected, so it can be directly modified by objects of // a subclass. But the contents of this field are made accessible as public // so anyone can 'read' the value of this field. public string getRoutingNumber() { return this.routingNumber; } // Provides access to modify the private member balance, but this function // can only be called by subclasses (and from within this class). protected initBalance() { this.balance = 0; } // Provides access to 'read' the contents of the private field to everyone. public void getBalance(int balance) { return this.balance; } } class Customer : BankAccount { Customer(string accountNumber, string routingNumber) { this.accountNumber = accountNumber; this.routingNumber = routingNumber; initBalance(); } } class Startup { public static void main() { Customer cust = new Customer(“999”, “888”, 100); System.console.WriteLine(cust.getBalance); } }
The Customer class inherits from the BankAccount class. All protected data members and methods of BankAccount class are accessible directly by an object of the Customer class. The private fields such as accountNumber and routingNumber are not directly accessible. Thus it is possible to call only the initBalance and getBalance methods from Customer. The initBalance method cannot be called by anyone outside the BankAccount class. Also see this for another example.
Conclusion
Access control is an integral component of object oriented languages and it underlines and emphasizes some of the principal ideals central to the object-oriented approach. It is possible to maximize the benefits of any O-O language by adhering to, and fully exploiting the capabilities of its access specifiers. Access control is in no way a peripheral add-on or redundant feature of O-O languages, and programmers would only stand to gain by fully appreciating, respecting and understanding their presence.
References
<references/>