Управлението на достъпа често се свързва със скриване на реализацията. Вграждането на данни и методи в класовете (комбинирано със скриване на реализацията често наричано капсулиране) дава даннов тип с характеристики и поведение, но контролът на достъпа слага граници в този даннов тип по две важни причини. Първата е да се зададе какво клиентът програмист може и какво не може да използва. Не може да изградите вътрешните механизми в структура и да очаквате клиент-програмиста да не я смята за част от интерфейса.
Това подхранва направо следващата причина, която е: да се раздели интерфейса от реализацията. Ако структурата се използва в няколко програми, като потребителите могат само да изпращат съобщения до public интерфейса, тогава може да се променя всичко което не е public (т.е. “приятелско,” protected или private) без да са необходими промени в техния код.
Сега сме в света на ООП, където class фактически описва “клас от обекти,” както бихте описали класа на рибите и класа на птиците (в биологията - бел.пр.). Всеки обект от класа ще споделя единни характеристики и поведение. Класът е описание на начина на действие и поведението на обектите от този клас.
В първия ООП език, Simula-67, ключовата дума class беше използвана за описание на нов даннов тип. Същата дума се използва в повечето ООП езици. Фокусната точка на целия език е: създаване на нови даннови типове които са повече от просто кутии за данни и методи.
Класът е фундаментална ОО концепция в Java. Това е една от ключовите думи която няма да бъде с удебелени букви в тази книга – дразнещо става, ако се подчертава толкова често срещене дума като “class.”
За яснота може да предпочетете маниер на създаване на класове при който public членовете са в началото, следвани от protected, friendly и private членовете. Хубавото е, че потребителят като започне да чете отгоре надолу ще срещне това, което му трябва първо (public членовете, понеже те могат да се викат извън файла) и ще спре да чете, когато види непублични членове, които са част от вътрешната реализация. Обаче с коментарите за документация поддържани от javadoc (описан в глава 2) въпросът за четимостта на програмите става по-малко важен.
public class X {
public void pub1( ) { /* . . . */ }
public void pub2( ) { /* . . . */ }
public void pub3( ) { /* . . . */ }
private void priv1( ) { /* . . . */ }
private void priv2( ) { /* . . . */ }
private void priv3( ) { /* . . . */ }
private int i;
// . . .
}
Това може да помогне на четенето само частично, защото все още интерфейсът и реализацията са смесени. Тоест все още се вижда сорсът – реализацията – понеже си е в класа. Извеждането на интерфейса за потребителя на класа си е работа на class browser-а, инструмент, чиято работа е да гледа класовете и да ви показва какво може да правите с тях (т.е. какви членове имат) по полезен начин. Когато четете тази книга, добри обектни броузъри трябва да се обакват във всички добри Java среди за развой.
Достъп до клас
В Java спецификаторите на достъпа може да се използват и за да се посочи кои класове в библиотека ще бъдат достъпни за потребителите на същата библиотека. Ако искате клас да бъде достъпен за клиент-програмиста слагате ключовата дума public някъде преди отварящата фигурна скоба на тялото на класа. Това управлява даже дали клиентът може да създаде обект от класа.
За да се управлява достъпът до клас, спецификаторът трябва да се появи преди class. Така може да се напише:
public class Widget {
Тоест името на библиотеката е mylib и всеки клиент може да има достъп до Widget чрез
import mylib.Widget;
или
import mylib.*;
Има обаче двойка допълнителни ограничения:
-
Може да има само един public за компилационна единица (файл). Идеята е че всяка компилационна единица има единствен интерфейс представян от този клас. Може да има колкото трябват “приятелски” спомагателни класове. Ако имате повече от един public в компилационната единица компилаторът ще издаде съобщение за грешка.
-
Името на public класа трябва точно да съвпада с името на компилационната единица, която го съдържа, включително капитализацията. Така за Widget името на файла трябва да е Widget.java, не widget.java или WIDGET.java. Ще излезе грешка при компилация ако те не съвпадат.
-
Възможно е, макар и да не е типично, да има компилационна единица без никакъв публичен клас. В този случай може да я наречете както желаете.
Какво ако сте вкарали клас в mylib който използвате за изпълнение на работите на Widget или друг public клас в mylib? Не искате да правите документация за клиент-програмист и смятате след време да промените нещата и да махнете изцяло този клас, замествайки го с друг. За да имате тази гъвкавост трябва да осигурите че клиент-програмистът не зависи от конкретната реализация скрита в mylib. За да се направи това махате public ключовата дума от класа, с което той става приятелски. (Такъв клас може да се използва само в пакета.)
Забележете че клас не може да бъде private (което ще го направи недостъпен за всичко освен за него), или protected.4 Така че имате два избора за достъп до клас: “приятелски” или public. Ако не искате никой да има достъп до този клас, може да направите всички конструктори private, предотвратявайки всеки освен вас, от static член на класа, да може да направи обект.5 Ето пример:
//: c05:Lunch.java
// Demonstrates class access specifiers.
// Make a class effectively private
// with private constructors:
class Soup {
private Soup() {}
// (1) Allow creation via static method:
public static Soup makeSoup() {
return new Soup();
}
// (2) Create a static object and
// return a reference upon request.
// (The "Singleton" pattern):
private static Soup ps1 = new Soup();
public static Soup access() {
return ps1;
}
public void f() {}
}
class Sandwich { // Uses Lunch
void f() { new Lunch(); }
}
// Only one public class allowed per file:
public class Lunch {
void test() {
// Can't do this! Private constructor:
//! Soup priv1 = new Soup();
Soup priv2 = Soup.makeSoup();
Sandwich f1 = new Sandwich();
Soup.access().f();
}
} ///:~
До сега повечето от методите връщаха или void или първичен тип, така че дефиницията:
public static Soup access() {
return ps1;
}
може да изглежда малко смущаваща на пръв поглед. Думата преди името на метода (access) казва какво той връща. До тук най-често това беше void, което значи че не връща нищо. Но също би могло да се върне манипулатор на обект, както е в случая. Този метод връща манипулатор на обект Soup.
Класът Soup показва как да се избегне възможността за непосредствено създаване на обекти като се направят всички конструктори private. Помнете че ако не направите явно поне един конструктор, конструктор по подразбиране (конструктор без аргументи) ще бъде създаден автоматично. Като напишете конструктор по подразбиране той няма да се саздаде автоматично. Като го направите private никой не може да създаде обект от него клас. Но как може да се използва класа? Горният пример показва две възможности. Първо, static метод да се саздаде, който създава Soup и връща манипулатор към него. Това може да бъде полезно ако изпълнявате допълнителни операции над Soup преди да го върнете, или искате да запазите броя на създаваните Soup обекти (може би за регистрация на популацията им).
Втората възможност използва т.н. design pattern, което ще се дискутира по-късно в тази книга. Този конкретно шаблон се нарича “singleton” понеже позволява да се създаде само един обект изобщо. Обектът от клас Soup се създава котж static private член на Soup, така че има един и само един и не може да се доберете до него освен с public метода access( ).
Както вече беше споменато, ако не сложите спецификатор за достъп той става по подразбиране “приятелски.” Това значи че обект от този клас може да се създаде от всеки друг клас в пакета, но не и от клас извън него. (Помнете, всичките файлове в една директория които нямат явни package декларации са неявно част от пакета по подразбиране за същата директория.) Обаче ако static член от този клас е public, клиент-програмистът има достъп до static члена въпреки че не може да създаде обект от този клас.
Сподели с приятели: |