C, PHP, VB, .NET

Дневникът на Филип Петров


* Капсулация на данни

Публикувано на 11 октомври 2009 в раздел ПИК3 Java.

Досега ние неосъзнато сме използвали капсулация на данни в процедурното програмиране. Представете си например функцията "indexOf()" на клас String. Не са много учениците, пък и специалистите, които са се интересували как точно е реализиран този метод. В общи линии ние се интересуваме какви са входните му параметри и връщаната от него стойност. Самият алгоритъм, който извършва работата остава скрит за вас. Още повече - алгоритъмът може след време да бъде променен (подобрен), но това няма да промени нашите програми и те ще продължат да функционират (вземете например алгоритмите за сортиране на масив - можете да използвате най-различни, които се различават само и единствено по скорост на изпълнение, но резултатът е един и същи). Именно такъв вид капсулация на данни има във всички методи, които сте писали.

Тенденцията за "скриване на алгоритъма" от "външния свят" естествено се пренася и при обектно-ориентираното програмиране. Дотук във всички дадени примери използвахме ключова дума "public" при дефиниране на класове, техни полета и методи. Ключовата дума "public" се превежда като "публичен", а в програмирането има значение на "достъпен от всеки". Такива данни казваме, че не са "капсулирани", т.е. "не са скрити/защитени". За да капсулираме данни в даден клас, то ние използваме ключови думи "private" или "protected". Наричат се още "модификатори за достъп".

Нека да вземем следния пример - клас за автомобили:

public class myfirstprogram{
  public static void main(String[] args){
    Car c1 = new Car("BMW", "5", 220);
    System.out.println("The "+c1.manufacturer+" "+c1.model+
                       " car has max speed of "+c1.getSpeed());
  }
}

class Car{
  public int speed;
  public String manufacturer;
  public String model;
  public Car(String manufacturer, String model, int speed){
    this.setSpeed(speed);
    this.manufacturer = manufacturer;
    this.model = model;
  }
  public int getSpeed(){
    return this.speed;
  }
  public void setSpeed(int speed){
    while (speed<0){
      System.out.println("Speed must be positive integer!");
      try{
        java.util.Scanner s = new java.util.Scanner(System.in);
        System.out.print("Enter the correct value: ");
        speed = s.nextInt();
      }
      catch (java.util.InputMismatchException e){
        speed = -1;
      }
      finally{
        try{
          while(System.in.available() > 0) System.in.skip(System.in.available());
        }
        catch(java.io.IOException e){}
      }
    }
    this.speed = speed;
  }
}

Както виждате в метод "setSpeed(int speed)" сме вмъкнали функционалност, която гарантира, че винаги ще въвеждаме скорост на автомобила по-голяма от 0. Да, но полето "speed" в клас Car е публично. Това означава, че в главната програма ние можем лесно да направим следното:

    c1.speed = -200;

Така на практика ние "прескачаме" защитата, която сме направили в метод "setSpeed()" и вкарваме некоректни стойности. Когато пишем програма ние не трябва да позволяваме подобни неща. Затова ние можем да защитим данните от полетата, като ги направим например private:

public class myfirstprogram{
  public static void main(String[] args){
    Car c1 = new Car("BMW", "5", 220);
    System.out.println("The "+c1.manufacturer+" "+c1.model+
                       " car has max speed of "+c1.getSpeed());
    // c1.speed = -200; // Вече НЕ работи
  }
}

class Car{
  private int speed;
  public String manufacturer;
  public String model;
  public Car(String manufacturer, String model, int speed){
    this.setSpeed(speed);
    this.manufacturer = manufacturer;
    this.model = model;
  }
  public int getSpeed(){
    return this.speed;
  }
  public void setSpeed(int speed){
    while (speed<0){
      System.out.println("Speed must be positive integer!");
      try{
        java.util.Scanner s = new java.util.Scanner(System.in);
        System.out.print("Enter the correct value: ");
        speed = s.nextInt();
      }
      catch (java.util.InputMismatchException e){
        speed = -1;
      }
      finally{
        try{
          while(System.in.available() > 0) System.in.skip(System.in.available());
        }
        catch(java.io.IOException e){}
      }
    }
    this.speed = speed;
  }
}

От този пример се вижда ясно и каква е концепцията на т.нар. "get" и "set" методи. Вместо да добиваме директен достъп до полетата на даден клас, ние ги правим "private", а достъпът до тях осъществаваме чрез вътрешни за класа методи. По този начин алгоритмите в методите гарантират, че данните са коректни!

Това поведение се прехвърля не само при създаването на инстанция на клас, но и при наследяването:

public class usersexample{
  public static void main(String[] args){
    User pesho = new UserWithInfo("batman", "Petar Petrov");
    pesho.getId();
    pesho.getInfo();
    User.usersOnline();
    User ivan = new UserWithInfo("dexter", "Ivan Ivanov");
    ivan.getId();
    ivan.getInfo();
    User.usersOnline();
  }
}

class User{
  private String username;
  private int id;
  private static int userscount = 0;
  public User(String username){
    System.out.println("\nNew user entering...");
    this.username = username;
    this.setId();
  }
  private void setId(){
    userscount++;
    this.id = userscount;
  }
  public static void usersOnline(){
    System.out.println("Users online: "+userscount);
  }
  public void getId(){
    System.out.println("User id:"+this.id);
  }
  public void getInfo(){
    System.out.println("Username: "+this.username);
  }
}

class UserWithInfo extends User{
  private String name;
  public UserWithInfo(String username, String name){
    super(username);
    // id = 15; // НЯМА ДОСТЪП до private поле!
    // super.userscount = 10; // Също няма достъп!
    // super.setId(); // и пак няма достъп!
    this.name = name;
  }
  // Предефинираме метод
  public void getInfo(){
    super.getInfo();
    System.out.println("Name: "+this.name);
  }
}

Виждате, че наследникът на класа няма достъп до private полетата и методите на супер класа (родителския клас). По този начин казваме, че супер класа е капсулирал своите данни - в примера той не предоставя възможност да бъдат променяни идентификационните номера на потребителите или пък да бъде подменяна стойността на броят на влезлите потребители в системата.

Модификатор за достъп "protected" има малко по-различно значение от това, което познаваме в C++. Ако направите едно поле "protected", то ще бъде видимо не само от наследниците на класа (както е в C++), но и от всички други класове в текущия пакет (package). Ако нашата програма се разпростира само в един пакет (каквито програми сме писали досега), то public и protected ще имат едно и също действие. Разликата идва тогава, когато желаем да свържем класове от различни пакети. Независим клас от чужд пакет не може да достъпи protected данни. Това обаче не важи за наследник от чужд пакет на класа с protected данни (наследниците, независимо от пакета, на който принадлежат, виждат protected данните на своите родителски класове). Аналогичен на C++ protected модификатор (видимост само и единствено от наследниците) е имало в миналото чрез "private protected" модификатор в Java, но от Sun са го премахнали в съвременните версии на езика.

Почти аналогично на "protected" модификатор на достъп е "липсата на модификатор на достъп" (т.нар. "default"). Когато пред поле или метод не сложите нито една от ключовите думи private, protected или public, то полето/метода получават ниво на достъп default. Това е почти същото както protected достъп, т.е. всички класове и наследници в пакета могат да достъпват тези данни. Разликата е, че когато клас от друг пакет е наследник, то той няма достъп до тези данни. С други думи "default" данните на един клас са видими за текущия пакет, но са напълно скрити (private) за всички други пакети (независимо дали класът от тях който се опитва да ги достъпи е наследник или не).

Колкото до декларациите на класове имаме две възможности - това са default (без модификатор) и public. Когато един клас е публичен (public), то той е видим от чужди пакети. Когато един клас се декларира без модификатор, то само и единствено класове в текущия пакет могат да правят негови инстанции или да го наследяват. Важно за Java правило е, че ако искате да направите един клас публичен, то той трябва да стои в самостоятелен .java файл (тоест за него ще се компилира отделен .class файл).

В Java НЕ можем да правим множествено наследяване и приятелски функции, които са налични в C++. Това все пак не се смята за голям недостатък от общността, тъй като се счита, че такавива функционалности почти не се използват в реални програми.

 



4 коментара


  1. Здравейте,
    Благодаря за хубавите материали, които сте написали! Имам въпрос по тази тема. Може ли един метод да бъде get ако метода не връща стойност (тип void)? Ето пример:
    public void getInfo(){
    System.out.println("Username: "+this.username);
    }

  2. Няма изрично ограничение за това какво име ще сложиш на метода. Въпросът е да е адекватно и разбираемо. Най-добре е да се приеме конвенция и да се спазвя стриктно.

  3. С други думи „protected“ данните на един клас са видими за текущия пакет, но са напълно скрити (private) за всички други пакети (независимо дали класът от тях който се опитва да ги достъпи е наследник или не).

    protected или default?

Добави коментар

Адресът на електронната поща няма да се публикува


*