Выбрать главу

Контроль над доступом часто называют сокрытием реализации. Помещение дан­ных и методов в классы в комбинации с сокрытием реализации часто называют инкапсуляцией. В результате появляется тип данных, обладающий характери­стиками и поведением.

Доступ к типам данных ограничивается по двум причинам. Первая причи­на — чтобы программисту-клиенту знать, что он может использовать, а что не может. Вы вольны встроить в структуру реализации свои внутренние меха­низмы, не опасаясь того, что программисты-клиенты по случайности использу­ют их в качестве части интерфейса.

Это подводит нас непосредственно ко второй причине — разделению интер­фейса и реализации. Если в программе использована определенная структура, но программисты-клиенты не могут получить доступ к ее членам, кроме от­правки сообщений риЬНс-интерфейсу, вы можете изменять все, что не объявле­но как public (члены с доступом в пределах пакета, protected и private), не нару­шая работоспособности изменений клиентского кода.

Для большей ясности при написании классов можно использовать такой стиль: сначала записываются открытые члены (public), затем следуют защищен­ные члены (protected), потом — с доступом в пределах пакета и наконец закры­тые члены (private). Преимущество такой схемы состоит в том, что при чтении исходного текста пользователь сначала видит то, что ему важно (открытые чле­ны, доступ к которым можно получить отовсюду), а затем останавливается при переходе к закрытым членам, являющимся частью внутренней реализации:

//. access/OrganizedByAccess.java public class OrganizedByAccess {

public void publO {

/*

*/ }

public void pub2() {

/*

*/ }

public void pub3() {

/*

. */ }

private void privlO

{ /* ■

*/

private void priv20

{ /* •

*/

private void priv30

{ /*

*/

private int i;

// ..

} III -

Такой подход лишь частично упрощает чтение кода, поскольку интерфейс и реализация все еще совмещены. Иначе говоря, вы все еще видите исходный код — реализацию — так, как он записан прямо в классе. Вдобавок документа­ция в комментариях, создаваемая с помощью javadoc, снижает необходимость в чтении исходного текста программистом-клиентом.

Доступ к классам

В Java с помощью спецификаторов доступа можно также указать, какие из клас­сов внутри библиотеки будут доступны для ее пользователей. Если вы хотите, чтобы класс был открыт программисту-клиенту, то добавляете ключевое слово public для класса в целом. При этом вы управляете даже самой возможностью создания объектов данного класса программистом-клиентом.

Для управления доступом к классу, спецификатор доступа записывается пе­ред ключевым словом class:

public class Widget {

Если ваша библиотека называется, например, access, то любой програм­мист-клиент сумеет обратиться извне к классу Widget:

import access Widget: или

import access *;

Впрочем, при этом действуют некоторые ограничения:

•        В каждом компилируемом модуле может существовать только один от­крытый (public) класс. Идея в том, что каждый компилируемый модуль содержит определенный открытый интерфейс и реализуется этим откры­тым классом. В модуле может содержаться произвольное количество вспомогательных классов с доступом в пределах пакета. Если в компили­руемом модуле определяется более одного открытого класса, компилятор выдаст сообщение об ошибке.

•        Имя открытого класса должно в точности совпадать с именем файла, в котором содержится компилируемый модуль, включая регистр симво­лов. Поэтому для класса Widget имя файла должно быть Widget.java, но никак не widget.java или WIDGET.java. В противном случае вы снова по­лучите сообщение об ошибке.

•        Компилируемый модуль может вообще не содержать открытых классов (хотя это и не типично). В этом случае файлу можно присвоить любое имя по вашему усмотрению. С другой стороны, выбор произвольного имени создаст трудности у тех людей, которые будут читать и сопровож­дать ваш код.

Допустим, в пакете access имеется класс, который всего лишь выполняет не­которые служебные операции для класса Widget или для любого другого ри- ЬИс-класса пакета. Конечно, вам не хочется возиться с созданием лишней доку­ментации для клиента; возможно, когда-нибудь вы просто измените структуру пакета, уберете этот вспомогательный класс и добавите новую реализацию. Но для этого нужно точно знать, что ни один программист-клиент не зависит от конкретной реализации библиотеки. Для этого вы просто опускаете ключе­вое слово public в определении класса; ведь в таком случае он ограничивается пакетным доступом, то есть может использоваться только в пределах своего пакета.

При создании класса с доступом в пределах пакета его поля все равно реко­мендуется помечать как private (всегда нужно по максимуму перекрывать дос­туп к полям класса), но методам стоит давать тот же уровень доступа, что имеет и сам класс (в пределах пакета). Класс с пакетным доступом обычно использу­ется только в своем пакете, и делать методы такого класса открытыми (public) стоит только при крайней необходимости — а о таких случаях вам сообщит компилятор.

Заметьте, что класс нельзя объявить как private (что сделает класс недоступ­ным для окружающих, использовать он сможет только «сам себя») или protected[2]. Поэтому у вас есть лишь такой выбор при задании доступа к классу: в пределах пакета или открытый (public). Если вы хотите перекрыть доступ к классу для всех, объявите все его конструкторы со спецификатором private, соответственно, запретив кому бы то ни было создание объектов этого класса. Только вы сами, в статическом методе своего класса, сможете создавать такие объекты. Пример:

//. access/Lunch.java // Спецификаторы доступа для классов. // Использование конструкторов, объявленных private, // делает класс недоступным при создании объектов.

class Soupl {

private SouplО {}

// (1) Разрешаем создание объектов в статическом методе: public static Soupl makeSoupO { return new SouplO;

}

}

class Soup2 {

private Soup2() {}

// (2) Создаем один статический объект и // по требованию возвращаем ссылку на него, private static Soup2 psl = new Soup2(): public static Soup2 accessO { return psclass="underline"

}

public void f() {}

}

// В файле может быть определен только один public-класс: public class Lunch {

void testPrivateO {

// Запрещено, т.к конструктор объявлен приватным: //! Soupl soup = new SouplO:

}

void testStaticO {

Soupl soup = Soupl.makeSoupO;

}

void testSingletonO {

Soup2.access О f();

}

)

До этого момента большинство методов возвращало или void, или один из примитивных типов, поэтому определение:

public static Soupl makeSoupO { return new SouplO:

} на первый взгляд смотрится немного странно. Слово Soupl перед именем мето­да (makeSbup) показывает, что возвращается методом. В предшествующих при­мерах обычно использовалось обозначение void, которое подразумевает, что ме­тод не имеет возвращаемого значения. Однако метод также может возвращать ссылку на объект; в данном случае возвращается ссылка на объект класса Soupl.

Классы Soupl H'Soup2 наглядно показывают, как предотвратить прямое соз­дание объектов класса, объявив все его конструкторы со спецификатором pri­vate. Помните, что без явного определения хотя бы одного конструктора компи­лятор сгенерирует конструктор по умолчанию (конструктор без аргументов). Определяя конструктор по умолчанию в программе, вы запрещаете его автома­тическое создание. Если конструктор объявлен со спецификатором private, ни­кто не сможет создавать объекты данного класса. Но как же тогда использовать этот класс? Рассмотренный пример демонстрирует два способа. В классе Soupl определяется статический метод, который создает новый объект Soupl и возвра­щает ссылку на него. Это бывает полезно в ситуациях, где вам необходимо про­вести некоторые операции над объектом перед возвратом ссылки на него, или при подсчете общего количества созданных объектов Soupl (например, для ог­раничения их максимального количества).