Книга е още в много ранна фаза на написване



страница40/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   36   37   38   39   40   41   42   43   ...   73

Интерфейси


Ключовата дума interface придвижва концепцията за абстрактност една стъпка на­пред. Може да си я мислим като “чисто” абстрактен клас. Тя позволява на съз­дателя да определи формата на класа: имената на методите, аргументните спи­съци и връщаните стойности, но не и телата на методите. Един interface мо­же също да съдържа даннови членове от примитивни типове, но те са им­пли­цит­но static и final. interface дава само форма, не реализация.

interface казва: “Така ще изглеждат класовете реализиращи този конкретен ин­тер­фейс.” Така всеки код който използва конкретен interface знае кои методи мо­гат да се викат с него interface и това е всичко. Така че interface се използва да се установи “протокол” между класове. (Някои ООП езици използват думата protocol за същата цел.)

За да се създаде interface, използвайте ключовата дума interface вместо клю­чо­ва­та дума class. Както за клас може да добавите ключовата дума public преди клю­човата дума interface (но само ако този interface е дефиниран във файл със съ­щото име) или да не го добавите, оставяйки “приятелски” статус.

За да се направи клас да отговаря на конкретен interface (или група от interface-и) използвайте ключовата дума implements. Вие казвате “interface-ът е за как изглежда а ето как работи.” Отделно от това има голямо сходство с на­сле­дяването. Диаграмата за примера с инструментите показва това:




Веднъж като реализирате interface, тази реализация става обикновен клас който мо­же да бъде разширяван по обичайния начин.

Може да изберете явно да декларирате методите в interface като public. Но те са public и ако не го направите. Така че когато implement-ирате един interface, ме­тодите от interface трябва да са дефинирани като public. Иначе ще станат по под­разбиране “приятелски” и по този начин ще ограничите достъпността им при наследяване, което не е позволено от Java компилатора.

Може да видите това в променена версия на примера с Instrument. Забележете че всеки метод в interface е само декларация, което е единственото нещо, поз­во­лявано от компилатора тук. В добавка никой от методите в Instrument5 не е де­клариран като public, но все пак те са автоматично public:

//: c07:Music5.java

// Interfaces

import java.util.*;
interface Instrument5 {

// Compile-time constant:

int i = 5; // static & final

// Cannot have method definitions:

void play(); // Automatically public

String what();

void adjust();

}
class Wind5 implements Instrument5 {

public void play() {

System.out.println("Wind5.play()");

}

public String what() { return "Wind5"; }



public void adjust() {}

}
class Percussion5 implements Instrument5 {

public void play() {

System.out.println("Percussion5.play()");

}

public String what() { return "Percussion5"; }



public void adjust() {}

}
class Stringed5 implements Instrument5 {

public void play() {

System.out.println("Stringed5.play()");

}

public String what() { return "Stringed5"; }



public void adjust() {}

}
class Brass5 extends Wind5 {

public void play() {

System.out.println("Brass5.play()");

}

public void adjust() {



System.out.println("Brass5.adjust()");

}

}


class Woodwind5 extends Wind5 {

public void play() {

System.out.println("Woodwind5.play()");

}

public String what() { return "Woodwind5"; }



}
public class Music5 {

// Doesn't care about type, so new types

// added to the system still work right:

static void tune(Instrument5 i) {

// ...

i.play();



}

static void tuneAll(Instrument5[] e) {

for(int i = 0; i < e.length; i++)

tune(e[i]);

}

public static void main(String[] args) {



Instrument5[] orchestra = new Instrument5[5];

int i = 0;

// Upcasting during addition to the array:

orchestra[i++] = new Wind5();

orchestra[i++] = new Percussion5();

orchestra[i++] = new Stringed5();

orchestra[i++] = new Brass5();

orchestra[i++] = new Woodwind5();

tuneAll(orchestra);

}

} ///:~



Останалата част работи по стария начин. Няма значение дали провите ъпкаст към “обикновен” клас с име Instrument5, abstract-ен клас наречен Instrument5, или към interface наречен Instrument5. Поведението е същото. Фак­тически може да видите в метода tune( ) че няма никакво свидетелство че Instrument5 е “обикновен” клас, abstract-ен клас или interface. Това е целта: Все­ки подход дава на програмиста различен начин за управление на съз­да­ва­не­то и използването на обекти.

“Многократно наследяване” в Java


interface не е просто “по-чиста” форма на abstract-ен клас. Той има по-важно пред­назначение от това. Понеже interface въобще няма реализация – тоест ня­ма памет асоциирана с interface – няма и нищо, което да пречи много interface-и да бъдат комбинирани. Това е ценно понеже по някой път е нужно да може да се каже “x е a и b и c.” В C++ комбинирането на много класове се нарича мно­го­кратно наследяване и носи малко ограничаващ действията багаж понеже все­ки клас си има реализация. В Java може да се направи същото, но само един от кла­совете може да има реализация, така че проблемът от C++ не се появява в Java когато се комбинират много интерфейси:




В извлечен клас не сте заставени да имате базов клас който е или abstract или “конкретен” (такъв който няма abstract-ни методи). Ако наследите от не-interface, може да наследите само от един. Всички останали базови елементи тряб­ва да бъдат interface-и. Слагате всички имена за наследяване след клю­чо­ва­та дума implements и ги разделяте със запетаи. Може да имате колкото ис­ка­те interface-и и всеки става независим тип, към който може да се прави кастинг. След­ния пример показва конкретен клас комбиниран с няколко interface-а за да се получи нов клас:

//: c07:Adventure.java

// Multiple interfaces

import java.util.*;
interface CanFight {

void fight();

}
interface CanSwim {

void swim();

}
interface CanFly {

void fly();

}
class ActionCharacter {

public void fight() {}

}
class Hero extends ActionCharacter

implements CanFight, CanSwim, CanFly {

public void swim() {}

public void fly() {}

}
public class Adventure {

static void t(CanFight x) { x.fight(); }

static void u(CanSwim x) { x.swim(); }

static void v(CanFly x) { x.fly(); }

static void w(ActionCharacter x) { x.fight(); }

public static void main(String[] args) {

Hero i = new Hero();

t(i); // Treat it as a CanFight

u(i); // Treat it as a CanSwim

v(i); // Treat it as a CanFly

w(i); // Treat it as an ActionCharacter

}

} ///:~



Може да се види че Hero комбинира конкретния клас ActionCharacter с ин­тер­фей­сите CanFight, CanSwim и CanFly. Като се комбинира конкретен клас с интерфейси по този начин, конкретният клас трябва да е пръв, после интер­фей­си­те. (Иначе компилаторът дава грешка.)

Забележете, че сигнатурата на fight( ) е същата в interface CanFight и класа ActionCharacter и че fight( ) не е дефиниран в Hero. Правилото за interface е че мо­же да наследявате от него (както ще видите след малко), но тогава по­лу­ча­ва­те друг interface. Ако искате да създадете обект от нов тип той трябва да бъде клас с всички необходими дефиниции. Въпреки че Hero не дава експлицитна де­фи­ниция за fight( ), тя идва от ActionCharacter автоматично така че е възможно съз­даването на обекти от Hero.

В клас Adventure може да видите четири метода които взимат за аргументи раз­лични интерфейси и конкретния клас. Когато се създаде обект от Hero той мо­же да се подаде на всеки от тези методи, което значи че ще се направи ъпкаст към всеки от интерфейсите interface по ред. Поради начина по който ин­тер­фей­си­те са проектирани в Java това работи без засечки и без никакво усилие от стра­на на програмиста.

Помнете че централната причина за съществуването на интерфейсите бе по­со­че­на в горния пример: да може да се прави ъпкаст към повече от един базов тип. Обаче втората причина да се използват интерфейси е същата като за abstract базови класове: да се предотврати възможността клиент-програмистът да направи обекти от този клас и да се посочи, че това е само интерфейс. Това до­вежда до въпроса: interface ли ще използвате или abstract-ен клас? interface да­ва ползите от abstract класа и ползите от interface, токо че ако е възможно да де­кларирате вашия клас без никакви методи и член-променливи винаги ще предпочитате interface-и пред abstract-ни класове. Фактически ако знаете че не­що ще става базов клас първо ще се опитате да го направите като interface, а са­мо ако се наложи да имате дефиниции на методи и/или член-променливи ще го преработите в abstract клас.


Разширяване на интерфейс с наследяване


Лесно може да се добавят декларации на методи в interface чрез наследяване и съ­що може да се комбинират няколко interfaces в нов interface с наследяване. В два­та случая се получава нов interface, както в следния пример:

//: c07:HorrorShow.java

// Extending an interface with inheritance
interface Monster {

void menace();

}
interface DangerousMonster extends Monster {

void destroy();

}
interface Lethal {

void kill();

}
class DragonZilla implements DangerousMonster {

public void menace() {}

public void destroy() {}

}
interface Vampire

extends DangerousMonster, Lethal {

void drinkBlood();

}
class HorrorShow {

static void u(Monster b) { b.menace(); }

static void v(DangerousMonster d) {

d.menace();

d.destroy();

}

public static void main(String[] args) {



DragonZilla if2 = new DragonZilla();

u(if2);


v(if2);

}

} ///:~



DangerousMonster е просто разширение на Monster каета дявя нов interface. То­ва е реализирано в DragonZilla.

Синтаксисът използван във Vampire работи само когато се наследяват ин­тер­фей­си. Нормално се използва extends със само един клас, но тъй като interface мо­же да се направи от много други интерфейси, extends може да се отнася за ня­колко други интерфейси от които се прави нов interface. Както може да се ви­ди interface имената просто са разделени със запетаи.


Групиране на константи


Понеже всички полета в interface са автоматично static и final, interface-ът е удо­бен инструмент за задаване на групи константи, много подобно на enum в C или C++. Например:

//: c07:Months.java

// Using interfaces to create groups of constants

package c07;


public interface Months {

int


JANUARY = 1, FEBRUARY = 2, MARCH = 3,

APRIL = 4, MAY = 5, JUNE = 6, JULY = 7,

AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,

NOVEMBER = 11, DECEMBER = 12;

} ///:~

Забележете Java стила на използване само на големи букви (с подчертаващо тире за разделяне на отделните думи в един идентификатор) за static final при­ми­тивите, които имат статични инициализатори – тоест, за константите по вре­ме на компилация.



Полетата в един interface са автоматично public, така че не е необходимо спе­циал­но да се указва това.

Сега може да използвате константите извън пакета им импортирайки c07.* или c07.Months както бихте импортирали всеки друг пакет и споменавайки стой­но­сти­те с изрази като Months.JANUARY. Разбира се, получавате int така че ня­ма­те допълнителната сигурност на типа от C++ enum, но тази (често из­полз­ва­на) техника сигурно е подобрение спрямо твърдото записване на числа във ва­ши­те програми. (Което често се нарича “магически числа” и дава много труден за поддръжка код.)

Ако искате допълнителна сигурност за типа, може да направите класа така:1

//: c07:Month2.java

// A more robust enumeration system

package c07;


public final class Month2 {

private String name;

private Month2(String nm) { name = nm; }

public String toString() { return name; }

public final static Month2

JAN = new Month2("January"),

FEB = new Month2("February"),

MAR = new Month2("March"),

APR = new Month2("April"),

MAY = new Month2("May"),

JUN = new Month2("June"),

JUL = new Month2("July"),

AUG = new Month2("August"),

SEP = new Month2("September"),

OCT = new Month2("October"),

NOV = new Month2("November"),

DEC = new Month2("December");

public final static Month2[] month = {

JAN, JAN, FEB, MAR, APR, MAY, JUN,

JUL, AUG, SEP, OCT, NOV, DEC

};

public static void main(String[] args) {



Month2 m = Month2.JAN;

System.out.println(m);

m = Month2.month[12];

System.out.println(m);

System.out.println(m == Month2.DEC);

System.out.println(m.equals(Month2.DEC));

}

} ///:~


Класът е наречен Month2 понеже вече има Month в стандартната библиотека на Java. Той е final клас с private конструктор така че никой не може да на­сле­дя­ва от него или да прави обекти. Единствените екземпляри са final static съз­да­дените в самия клас: JAN, FEB, MAR и т.н. Тези обекти са също използвани в масива month, което позволява да избирате месеците по номер вместо по име. (За­бележете допълнителния JAN в масива за да се даде отместване единици, та­ка че December да е 12-ти месец.) В main( ) може да видите сигурността на типа: m е Month2 обект така че може да се присвои само на Month2. В предишния при­мер Months.java имаше само int стойности, така че на int променливата коя­то щеше да представя месеците можеха да се дадат произволни стойности, кое­то не бе много сигурно.

Този подход също позволява да се използва == или equals( ) взаимозаменяемо, както е показано в края на main( ).


Инициализиране на полета в интерфейсите


Полетата декларирани в интерфейси са автоматично static и final. Те не могат да бъдат “blank finals,” но могат да се инициализират с неконстантни изрази. На­пример:

//: c07:RandVals.java

// Initializing interface fields with

// non-constant initializers

import java.util.*;
public interface RandVals {

int rint = (int)(Math.random() * 10);

long rlong = (long)(Math.random() * 10);

float rfloat = (float)(Math.random() * 10);

double rdouble = Math.random() * 10;

} ///:~


Понеже полетата са static, те се инициализират когато класът се натоварва за пръв път, при първия достъп до кое да е поле. Ето прост тест:

//: c07:TestRandVals.java


public class TestRandVals {

public static void main(String[] args) {

System.out.println(RandVals.rint);

System.out.println(RandVals.rlong);

System.out.println(RandVals.rfloat);

System.out.println(RandVals.rdouble);

}

} ///:~


Полетата, разбира се, не са част от интерфейса, а са запомнени в статичната па­мет отделена за този интерфейс.




Сподели с приятели:
1   ...   36   37   38   39   40   41   42   43   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница