Advanced Notions About Object Oriented Programming: Diferență între versiuni

De la WikiLabs
Jump to navigationJump to search
Linia 289: Linia 289:
* http://docs.oracle.com/javase/tutorial/java/IandI/polymorphism.html
* http://docs.oracle.com/javase/tutorial/java/IandI/polymorphism.html


== Încapsulare ==
== Encapsulation ==


Încapsularea se referă la modul de realizare a unei clase astfel încât implementarea să fie transparentă pentru utilizator. Astfel, elementele interne ale clasei sunt protejate la accesul neautorizat (încapsulate) și modificarea implementării nu influențează modul în care clasa este folosită în aplicații complexe.
Encapsulation is a technique of implementing a class transparently to the user so that only "what" the class does is known, and "how" it is done does not matter. The advantage is that this way, an implementation of the class can change as long as the end result, the behavior is the same.


Ca exemplu pentru noțiunea de încapsulare, vom da ca exemplu o clasă '''Sorter''':
As an example of encapsulation, we will use the class '''Sorter''':
<syntaxhighlight lang="java">
<syntaxhighlight lang="java">
public class Sorter{
public class Sorter{
Linia 329: Linia 329:
</syntaxhighlight>
</syntaxhighlight>


Se observă că singurele metode publice sunt constructorul și metoda ''sort()''. Astfel, implementarea efectivă a algoritmului de sortare este ascunsă. Dacă la un moment dat clasa '''Sorter''' va fi modificată astfel încât să folosească algoritmul Quick Sort în loc de Bubble Sort, orice clasă care folosește clasa '''Sorter''' va continua să funcționeze la fel ca și până atunci.
You can see that the only public methods are the constructor and the method '''sort()'''. So, the effective implementation of the sorting algorithm is hidden. If, at some point, the sorting algorithm is changed (in order to use QuickSort, for example), the behavior of the class will not change, so the application will continue to function, unaffected.


=== Getters & Setters ===
=== Getters & Setters ===


Un alt exemplu de încapsulare este practica de implementa pentru fiecare câmp câte două metode numite ''setter'' și ''getter''. Motivul pentru această abordare este faptul că dacă un câmp este declarat public acesta poate fi modificat fără restricții de orice altă clasă. În schimb, dacă acest câmp are restricții, atunci acestea pot fi aplicate doar cu ajutorul unei metode. Vom da ca exemplu o clasă '''Person''':
Another example of encapsulation is the practice of having two methods for each private field, called ''setter'' and ''getter''. The reason behind this approach is that when a field is declared public, it can be modified without restriction by any other class. However, turning the field private and using a setter, these restrictions can be enforced by the setter method. An example is a person's age, or name:
<syntaxhighlight lang="java">
<syntaxhighlight lang="java">
public class Person{
public class Person{
Linia 393: Linia 393:
</syntaxhighlight>
</syntaxhighlight>


Se observă că dacă accesul la cele două câmpuri ''fullName'' și ''age'' ar fi fost public, atunci acestea ar fi putut fi modificate cu orice valori invalide pentru scopul lor. Dar în acest fel, metoda ''setter'' verifică valoarea primită ca argument înainte de a fi atribuită câmpului.
You can see that if the access to the fields ''fullName'' and ''age'' would be public, then they could have been initialized with invalid values for their purpose. In this way, the ''setter'' method checks the value received as an argument before it is passed to the field.


<div class="conventie"><font color="#0000ff">Convenție:</font> Câmpurile unei clase se definesc private sau protejate, iar pentru cele se dorește acces public se implementează metode ''getter'' și ''setter'' publice.</div>
<div class="conventie"><font color="#0000ff">Convention:</font> The fields of a class are defined either private or protected, and for public access, a setter and/ or getter method is defined.</div>

Versiunea de la data 2 noiembrie 2013 17:10

Concepts of inheritance, encapsulation and polymorphism are object oriented programming concepts and thus any OO language will support them, not just Java. However, the following examples will be written in Java.

Class Hierarchies

One of the most important object oriented feature, for maintaining and developing an application, is the ability to create a class hierarchy. That means:

  • a class can extend another class, inheriting its functionality;
  • an object of a class B which extends another class A, is at the same time of type B and A (polymorphism).

Inheritance

The concept of inheritance is useful when the programmer needs a certain functionality which is partially implemented in a another class, or when he needs to define a more particular behavior without changing (breaking) the existing one. Java supports simple inheritance (of a single class).

We can use, as an example, a class Vehicle:

public class Vehicle{

    public static final float MAX_VEHICLE_SPEED = 300;

    protected float currentSpeed;
    private String name;
    private String color;

public Vehicle(String _name, String _color){
    name = _name;
    color = _color;
}

public void increaseSpeedBy(float _speed){
    // for all classes that inherit this method from
    // class Vehicle, MAX_VEHICLE_SPEED will always be 300
    // because even if this field is hidden by extending classes,
    // this method, being defined in class Vehicle, will use the
    // static field from class Vehicle, which is 300.
    if(currentSpeed + _speed <= MAX_VEHICLE_SPEED){
        currentSpeed += _speed;
    }
}

public String getName(){
    return name;
}

public String getColor(){
    return color;
}

public float getCurrentSpeed(){
    return currentSpeed;
}

}

... and three extended classes: Carriage

public class Carriage extends Vehicle{

    public static final float MAX_VEHICLE_SPEED = 40;

    private int horseCount;

public Carriage(String _color, int _horseCount){
    super("Carriage", _color);
    horseCount = _horseCount;
}

public int getHorseCount(){
    return horseCount;
}

}

... Automobile:

public class Automobile extends Vehicle{

    public static final float MAX_VEHICLE_SPEED = 250;

    private int cylinders;
    private String make;
    private String model; 

public Automobile(String _make, String _model, String _color, int _cylinders){
    super("Automobile", _color);
    make = _make;
    model = _model;
    cylinders = _cylinders;
}

public int getCylinders(){
    return cylinders;
}

public String getMake(){
    return make;
}

public String getModel(){
    return model;
}

}

... and Truck:

public class Truck extends Automobile{

    public static final float MAX_VEHICLE_SPEED = 150;
    private int maxCargo;

public Truck(String _make, String _model, String _color, int _cylinders, int _maxCargo){
    super(_make, _model, _color, _cylinders);
    maxCargo = _maxCargo;
}

public int getMaxCargo(){
    return maxCargo;
}

public String getName(){
    return "Truck";
}

}

The block diagram is presented below:

The hierarchy of the classes presented above

Rule: A Java class extends exactly one other class (if it's not specified, it is Object).


Rule: A Java class inherits from the super-class all non-static methods and fields that are declared public or protected, except for constructors, which are not inherited.

Overriding and Hiding

There are two very important aspects related to class extension, overriding and hiding:

  • overriding of a method refers to the definition, in the extended class, of a non-static method with the same signature (prototype) of a non-static method in the base class;
  • hiding of a method refers to the definition, in the extended class, of a static method, with the same signature (prototype) of a static method in the base class;
Rule: Always, when a method is called using a reference to an object, the method called is the one that is defined in the class of which the object is an instance of, not the class of the reference.

More about inheritance and overriding/ hiding, on Oracle's official web tutorial:

Polymorphism

Considering the example above, polymorphism refers to the fact that an object of type Truck is in the same time an object of type Automobile, an object of type Vehicle and an Object. So, the following constructs are possible:

public class MainClass{
    
public static void main(String _args[]){
    // this is a reference variable of type Truck, referring
    // an object of type Truck
    Truck _firstTruck = new Truck("Volvo", "Generic", "blue", 8000, 20); 

    // this is a reference variable of type Vehicle, referring
    // an object of type Vehicle
    Vehicle _firstVehicle = new Vehicle("Bicycle", "red");

    // since a Truck is also a Vehicle, we can have 
    // a reference variable of type Vehicle, referring
    // an object of type Truck
    Vehicle _secondVehicle = new Truck("Scania", "unknown", "green", 7000, 30);

    //better yet, we can do this:
    Vehicle _vehicles[] = new Vehicle[3];
    _vehicles[0] = _firstTruck; // a truck is a vehicle
    _vehicles[1] = _firstVehicle;
    _vehicles[2] = _secondVehicle;

    for(int i=0; i<_vehicles.length; i++){
        _vehicles[i].increaseSpeedBy(1.1f);
    }

    // this will NOT work, since an Object is not 
    // a Vehicle and will generate a compile time error
    //
    //_vehicles[0] = new Object();

}

}

The same thing can be achieved by using interfaces:

public interface Colored{
    public String getColor();
}


// all objects of type Vehicle and up are not Colored
public class Vehicle implements Colored{

//... same class content as before

}


public class MainClass{
    
public static void main(String _args[]){
    Colored _coloredItems[] = new Colored[3];
    _coloredItems[0] = new Vehicle("Submarine", yellow);
    _coloredItems[1] = new Carriage("brown", 6);    
    _coloredItems[2] = new Automobile("Dacia", "Sandero", "Yellow", 1600);    

    for(int i=0; i<_coloredItems.length; i++){
        System.out.println("Color of item " + i + " is " + _coloredItems[i].getColor());
    }

    // this will fail with a compile time error
    // since class Object does not implement 
    // interface Colored
    Colored _coloredItem = new Object();
}

}

Casting (upcast & downcast)

There are situations when the compiler needs additional information in order to accept the reference type change for an object:

public class MainClass{

public static readChar(){
    //... reads a char from keyboard
}
    
public static void main(String _args[]){
    Vehicle _vehicle;
    char c = readChar();

    if(c == 'a'){
        _vehicle = new Carriage("green", 2);
    }else{
        _vehicle = new Vehicle("Scooter", "red");
    }

    // this is called a down cast and it's done
    // automatically (cast from Vehicle down to Object):
    Object _obj = _vehicle;
    // same with:
    _obj = (Object)_vehicle; 

    // the following does NOT work since the compiler does
    // not have enough information about the reference
    // _vehicle. What it knows for sure is that it refers to
    // an object of type Vehicle. It COULD be a Carriage, but
    // it doesn't know:
    Carriage _carriage = _vehicle;

    // so the programmer needs to explicitly tell the compiler 
    // that the object is a Carriage, and this is called an
    // up cast (up from Vehicle to Carriage):
    Carriage _carriage = (Carriage)_vehicle;
    // however, _vehicle can still NOT be of type Carriage 
    // (if char c is not 'a'), and then a runtime exception
    // of type ClassCastException will be thrown
}

}


Rule: Up cast always needs to be explicit, regardless of how obvious the type is for the programmer:


String _someString = "Help!";
Object _obj = _someString; //implicit down cast
String _otherString = (String)_obj; //explicit up cast

More about polymorhism:

Encapsulation

Encapsulation is a technique of implementing a class transparently to the user so that only "what" the class does is known, and "how" it is done does not matter. The advantage is that this way, an implementation of the class can change as long as the end result, the behavior is the same.

As an example of encapsulation, we will use the class Sorter:

public class Sorter{

    private Comparable[] elements;

public Sorter(Comparable[] _elements){
    elements = _elements;
}

public Comparable[] sort(){
    return bubbleSort();
}

private Comparable[] bubbleSort(){
    Comparable[] _result = new Comparable[elements.length];
    System.arraycopy(_result, 0, elements, 0, _result.length);
    boolean _done;
    do{
        _done = true;
        for(int i=0; i<_result.length - 1; i++){
            if(_result[i].compareTo(_result[i + 1]) > 0){
                Comparable _temp = _result[i];
                _result[i] = _result[i + 1];
                _result[i + 1] = _temp;
                _done = false;
            }
        }
    }while(!_done);

    return _result;
}

}

You can see that the only public methods are the constructor and the method sort(). So, the effective implementation of the sorting algorithm is hidden. If, at some point, the sorting algorithm is changed (in order to use QuickSort, for example), the behavior of the class will not change, so the application will continue to function, unaffected.

Getters & Setters

Another example of encapsulation is the practice of having two methods for each private field, called setter and getter. The reason behind this approach is that when a field is declared public, it can be modified without restriction by any other class. However, turning the field private and using a setter, these restrictions can be enforced by the setter method. An example is a person's age, or name:

public class Person{

    public static final boolean MALE = true;
    public static final boolean FEMALE = false;

    private String fullName;
    private short age;
    private boolean gender;

public Person(String _fullName, short _age, boolean _gender){
    setName(_fullName);
    setAge(_age);
    gender = _gender;
}

// setter method for field fullName
public void setName(String _fullName){
    // make use of regular expressions to check for
    // a valid name
    if(_fullName.matches("[A-Z][a-zA-Z]+([ \\-][A-Z][a-zA-Z]+)+")){
        fullName = _fullName;
    }else{
        throw new RuntimeException("Invalid full name " + _fullName);
    }
}

// setter method for field age
public void setAge(short _age){
    if(_age >= 0 && _age <= 150){
        age = _age;
    }else{
        throw new RuntimeException("Invalid age " + _age);
    }
}

// setter method for field sex
public void setGender(boolean _gender){
    gender = _gender;
}

// getter method for field fullName
public String getName(){
    return fullName;
}

// getter method for field age
public short getAge(){
    return age;
}

// getter method for field gender
public boolean getGender(){
    return gender;
}

}

You can see that if the access to the fields fullName and age would be public, then they could have been initialized with invalid values for their purpose. In this way, the setter method checks the value received as an argument before it is passed to the field.

Convention: The fields of a class are defined either private or protected, and for public access, a setter and/ or getter method is defined.