CSC/ECE 517 Fall 2011/ch1 2b qu

From Expertiza_Wiki
Revision as of 22:08, 18 September 2011 by Qrjones (talk | contribs) (→‎References)
Jump to navigation Jump to search

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

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. 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?

Conclusion

References