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


Управление на клонируемостта



страница71/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   65   66   67   68   69   70   71   72   73

Управление на клонируемостта


Би могло да се помисли че за премахване на клонируемостта просто трябва да се направи метода clone( )да бъде private, но няма да стане така понеже не мо­же да се направи метод в базовия клас по-private в извлечен клас. Така че не е тол­кова просто. И все пак, необходимо е да се управлява клонируемостта на обек­тите. Трябва да се обърне внимание на няколко неща в тази връзка:

  1. Безразличие. Не правите нищо за да бъде вашият клас клонируем, но наследниците му могат да се направят да бъдат ако е необходимо. Това ра­бо­ти само ако Object.clone( ) по подразбиране прави необходимото с поле­тата във вашия клас.

  2. Поддръжка на clone( ). Следвайте стандартната практика на прилагане на Cloneable и подтискане на clone( ). В подтиснатия clone( ) викате super.clone( ) и прихващате всички изключения (така че подтиснатия clone( ) не изхвърля изключения).

  3. Условно поддържане на клонирането. Ако вашият клас съдържа ма­ни­пу­ла­то­ри които могат да бъдат или да не бъдат клонируеми (един пример е клас-колекцията), може да се опитате да клонирате всички тях, а ако из­хвър­лят изключения - просто да ги отминете. Например да вземем спе­циа­лен вид ArrayList който се опитва да клонира всички съдържани в него обек­ти. Когато пишете такъв ArrayList, не знаете що за обекти ще сложи въ­тре клиент-програмистът във вашия ArrayList, така че не знаете дали са кло­нируеми.

  4. Не прилагайте Cloneable ами подтиснете clone( ) като protected, давайки ко­ректно поведение три копирането на всички полета. По този начин всеки който наследява този клас може да подтисне clone( ) и извика super.clone( ) за получаване на коректно поведение прикопирането. Забележете че ва­ша­та реализация може и трябва да вика super.clone( ) даже ако методът очак­ва Cloneable обект (иначе ще се изхвърли изключение), понеже никой няма да го вика от обект точно с вашия тип. Той ще се вика само от наследен клас, който, за да работи успешно, прилага Cloneable.

  5. Опитайте се да предотвратите клонирането чрез неприлагане на Cloneable и подтискане на clone( ) за изхвърляне на изключение. Това е успешно само ако всеки извлечен клас вика super.clone( ) в своята повторна дефиниция на clone( ). Иначе програмистът би могъл да го заобиколи.

  6. Предотвратете клонирането чрез правене на вашия клас final. Ако clone( ) не е бил подтиснат в някой от предшестващите ваши класове, не може и да бъде вече. Ако е бил, подтиснете го и изхвърлете CloneNotSupportedException. Правенето на класа final е единствения на­чин за гарантирана невъзможност за клониране. Освен това когато имате ра­бота по сигурността или други случаи когато трябва да се управлява броя на създаваните обекти, ще направите всичките конструктори private и ще дадете един или повече специални методи за създаване на обекти. По то­зи начин тези митоди могат да въведат ограничения върху броя на обек­ти­те и условията при които те се създават. (Частен случай на това е singleton шаблона описан в глава 16.)

Ето пример който показва как могат да се реализират различните начини за кло­ниране и после, надолу по йерархията, да бъдат “изключени:”

//: c12:CheckCloneable.java

// Checking to see if a handle can be cloned
// Can't clone this because it doesn't

// override clone():

class Ordinary {}
// Overrides clone, but doesn't implement

// Cloneable:

class WrongClone extends Ordinary {

public Object clone()

throws CloneNotSupportedException {

return super.clone(); // Throws exception

}

}
// Does all the right things for cloning:



class IsCloneable extends Ordinary

implements Cloneable {

public Object clone()

throws CloneNotSupportedException {

return super.clone();

}

}


// Turn off cloning by throwing the exception:

class NoMore extends IsCloneable {

public Object clone()

throws CloneNotSupportedException {

throw new CloneNotSupportedException();

}

}


class TryMore extends NoMore {

public Object clone()

throws CloneNotSupportedException {

// Calls NoMore.clone(), throws exception:

return super.clone();

}

}


class BackOn extends NoMore {

private BackOn duplicate(BackOn b) {

// Somehow make a copy of b

// and return that copy. This is a dummy

// copy, just to make the point:

return new BackOn();

}

public Object clone() {



// Doesn't call NoMore.clone():

return duplicate(this);

}

}
// Can't inherit from this, so can't override



// the clone method like in BackOn:

final class ReallyNoMore extends NoMore {}


public class CheckCloneable {

static Ordinary tryToClone(Ordinary ord) {

String id = ord.getClass().getName();

Ordinary x = null;

if(ord instanceof Cloneable) {

try {


System.out.println("Attempting " + id);

x = (Ordinary)((IsCloneable)ord).clone();

System.out.println("Cloned " + id);

} catch(CloneNotSupportedException e) {

System.out.println(

"Could not clone " + id);

}

}

return x;



}

public static void main(String[] args) {

// Upcasting:

Ordinary[] ord = {

new IsCloneable(),

new WrongClone(),

new NoMore(),

new TryMore(),

new BackOn(),

new ReallyNoMore(),

};

Ordinary x = new Ordinary();



// This won't compile, since clone() is

// protected in Object:

//! x = (Ordinary)x.clone();

// tryToClone() checks first to see if

// a class implements Cloneable:

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

tryToClone(ord[i]);

}

} ///:~



Първият клас, Ordinary, представя видовете кладове които сме срещали по протежение на книгата: без поддръжка на клониране, но както излиза, също и без предотвратяване на клонирането. Но ако имате манипулатор към Ordinary обект който може да е бил ъпкастнат от по-извлечен (по-нататък в йерархията - б.пр.) клас, не може да кажете дали е клонируем или не.

Класът WrongClone показва некоректен начин за реализиране на клонирането. Той подтиска Object.clone( ) и го прави public, но не прилага Cloneable, та ко­га­то се извика super.clone( ) (което резултира в извикване на Object.clone( )), CloneNotSupportedException се изхвърля така че клонирането не работи.

В IsCloneable може да видите всички правилни действия свързани с клонирането: clone( ) е подтиснато и Cloneable се прилага. Обаче този clone( ) ме­тод и няколко други нататък в примера не прихващат CloneNotSupportedException, а вместо това го подават на извикващия метод, та трябва да има трай-хвани блок около него. Във вашия собствен clone( ) ме­тод типично ще прихващате CloneNotSupportedException вътре в clone( ) на­мес­то да го подавате нататък. Както ще видите, за този пример е по-ин­фор­ми­ра­що да се предаде изключението.

Класът NoMore се опитва да “изключи” по начина желан от дизайнерите на Java: в извлечения клас clone( ) изхвърляте CloneNotSupportedException. Ме­то­дът clone( ) в класа TryMore правилно вика super.clone( ), това довежда NoMore.clone( ), който изхвърля изключение и предотвратява клонирането.

Ами ако прогамистът не следва “правилния” начин за викане на super.clone( ) в под­тиснатия метод clone( )? В BackOn може да видите това да се случи. Този клас използва отделен метод duplicate( ) за да направи копие на текущия обект и вика този метод вътре в clone( ) вместо да вика super.clone( ). Не се изхвърля из­ключение и новият клас е клонируем. Не може да разчитате на изхвърлянето на изключения за предотвратяването на клонируемостта на клас. Единственото ис­тинско решение е посочено в ReallyNoMore, който е final и затова не може да бъде наследяван. Това значи че ако clone( ) изхвърля изключение във final клас това не може да се промени чрез наследяване и махавето на кло­ни­руе­мост­та е гарантирано. (Не може явно да извикате Object.clone( ) от клас, който има произ­волно ниво на наследяване; ограничени сте до super.clone( ), който има до­стъп само до прекия базов клас.) Така, ако направите обекти които са свър­за­ни с въпроси на сигурността, ще вземете да ги направите final.

Първия метод който виждате в класа CheckCloneable е tryToClone( ), който взема Ordinary обект и проверява дали той е клонируем с instanceof. Акое, пра­ви каст към IsCloneable, вика clone( ) и прави каст на резултатите обратно към Ordinary, прихващайки всички изхвърлени изключения. Забележете из­полз­ването на идентификация на типа по време на изпълнение (виж глава 11) за из­веждане на името на класа, та да видите какво става.

В main( ), различни типове от Ordinary се създават и ъпкастват Ordinary в де­фи­ницията на масива. Първите две линии след това създават прост Ordinary обект и се опитват да го клонират. Този код обаче няма да се компилира понеже clone( ) е protected метод в Object. Останалата част от кода пробягва масива и се опитва да клонира всеки обект, докладвайки за успеха или неуспеха на всяко на­чинание. Изходът е:

Attempting IsCloneable

Cloned IsCloneable

Attempting NoMore

Could not clone NoMore

Attempting TryMore

Could not clone TryMore

Attempting BackOn

Cloned BackOn

Attempting ReallyNoMore

Could not clone ReallyNoMore

Като резюме, ако искате клас да бъде клонируем:



  1. Прилагате Cloneable интерфейс.

  2. Подтискате clone( ).

  3. Викате super.clone( ) вътре в clone( ).

  4. Хващате изключенията вътре в clone( ).

Това ще доведе до най-подходящия ефект.

Копи-конструкторът


Клонирането изглежда сложен за организиране процес. Може да изглежда, че е необ­ходима алтернатива. Единият подход който може да ви хареса (особено ако сте C++ програмист) е да се направи специален конструктор, който да ду­бли­цира класовете. В C++ това се нарича копи(ращ) конструктор. Отначало всич­ко изглежда очевидно. Ето пример:

//: c12:CopyConstructor.java

// A constructor for copying an object

// of the same type, as an attempt to create

// a local copy.
class FruitQualities {

private int weight;

private int color;

private int firmness;

private int ripeness;

private int smell;

// etc.

FruitQualities() { // Default constructor



// do something meaningful...

}

// Other constructors:



// ...

// Copy constructor:

FruitQualities(FruitQualities f) {

weight = f.weight;

color = f.color;

firmness = f.firmness;

ripeness = f.ripeness;

smell = f.smell;

// etc.

}

}


class Seed {

// Members...

Seed() { /* Default constructor */ }

Seed(Seed s) { /* Copy constructor */ }

}
class Fruit {

private FruitQualities fq;

private int seeds;

private Seed[] s;

Fruit(FruitQualities q, int seedCount) {

fq = q;


seeds = seedCount;

s = new Seed[seeds];

for(int i = 0; i < seeds; i++)

s[i] = new Seed();

}

// Other constructors:



// ...

// Copy constructor:

Fruit(Fruit f) {

fq = new FruitQualities(f.fq);

seeds = f.seeds;

// Call all Seed copy-constructors:

for(int i = 0; i < seeds; i++)

s[i] = new Seed(f.s[i]);

// Other copy-construction activities...

}

// To allow derived constructors (or other



// methods) to put in different qualities:

protected void addQualities(FruitQualities q) {

fq = q;

}

protected FruitQualities getQualities() {



return fq;

}

}


class Tomato extends Fruit {

Tomato() {

super(new FruitQualities(), 100);

}

Tomato(Tomato t) { // Copy-constructor



super(t); // Upcast for base copy-constructor

// Other copy-construction activities...

}

}
class ZebraQualities extends FruitQualities {



private int stripedness;

ZebraQualities() { // Default constructor

// do something meaningful...

}

ZebraQualities(ZebraQualities z) {



super(z);

stripedness = z.stripedness;

}

}
class GreenZebra extends Tomato {



GreenZebra() {

addQualities(new ZebraQualities());

}

GreenZebra(GreenZebra g) {



super(g); // Calls Tomato(Tomato)

// Restore the right qualities:

addQualities(new ZebraQualities());

}

void evaluate() {



ZebraQualities zq =

(ZebraQualities)getQualities();

// Do something with the qualities

// ...


}

}
public class CopyConstructor {

public static void ripen(Tomato t) {

// Use the "copy constructor":

t = new Tomato(t);

System.out.println("In ripen, t is a " +

t.getClass().getName());

}

public static void slice(Fruit f) {



f = new Fruit(f); // Hmmm... will this work?

System.out.println("In slice, f is a " +

f.getClass().getName());

}

public static void main(String[] args) {



Tomato tomato = new Tomato();

ripen(tomato); // OK

slice(tomato); // OOPS!

GreenZebra g = new GreenZebra();

ripen(g); // OOPS!

slice(g); // OOPS!

g.evaluate();

}

} ///:~



Това изглежда малко странно на пръв поглед. Плодът има качества сигурно, но защо просто не се сложат даннови членове които ги представят направо в класа Fruit? Има две потенциални причини. Първата е че може да искате бързо и лес­но да променяте количествата. Забележете че Fruit има protected метод addQualities( ) за да позволи на извлечените класове да направят това. (Може да помислите че е логично да има protected конструктор на Fruit който приема FruitQualities аргумент, но конструкторите не се наследяват и той ще бъде безпо­лезен във второто и по-високо ниво на йерархията.) Чрез правене на ка­че­ства­та на плодовете отделен клас се получава голяма гъвкавост включително въз­можността да се променят по време на живота на конкретен обект.

Има втора причина да се направи FruitQualities отделен обект в случай че смя­та­те да добавяте количества или да променяте поведението чрез наследя­ва­не и по­лиморфизъм. Забележете че за GreenZebra (което в действителност е вид домати – развъждал съм ги и те са баснословни), конструкторът вика addQualities( )и му подава ZebraQualities обект, който е извлечен от FruitQualities така че може да бъде присъединен към FruitQualities ма­ни­пу­ла­то­ра на базовия клас. Разбира се, когато GreenZebra използва FruitQualities тряб­ва да се направи даункаст към провилния тип (както се вижда в evaluate( )), но той винаги знае че типът е ZebraQualities.

Ще видите също че има Seed клас и че Fruit (който по дефиниция си има се­ме­на) съдържа масив от Seed-ове.

Накрая забележете че всеки клас си има копи-конструктор и че всеки копиращ кон­структор трябва да се грижи за викането на копиконструктора на базовия клас и членовете необходими за дълбокото копиране. Копи конструкторът е проб­ван вътре в класа CopyConstructor. Методът ripen( ) взима Tomato ар­гу­мент и изпълнява копиращо конструиране с него за да дублицира обекта:

t = new Tomato(t);

докато slice( ) взима по-родовия Fruit и така го дублицира:

f = new Fruit(f);

Те са пробвани с радлични видове Fruit в main( ). Ето изхода:

In ripen, t is a Tomato

In slice, f is a Fruit

In ripen, t is a Tomato

In slice, f is a Fruit

Ето тука се показва проблемът. След копиращата конструкция която става с Tomato вътре в slice( ), резултатът не е вече Tomato обект, а просто Fruit. Той е за­губил всичко доматено. По-нататък, когато вземате GreenZebra, както ripen( ) така и slice( ) се превръщат в Tomato и Fruit, респективно. Така, за не­ща­стие, схемата на копиращите конструктори не е полезна за нас в Java когато се опитваме да направим локално копие на обект.

Защо работи в C++ и не работи в Java?


Копи констукторът е основна част от C++, понеже автоматично прави локално ко­пие на обект. И все пак гопният пример показва че това не работи в Java. За­що? В Java всичко с което работим е манипулатор, докато в C++ може да имате при­лични на манипулатори същности и също може да подадете обект направо. За това е копи-конструкторът в C++: когато искате да вземете обект и да го по­да­дете по стойност, дублицирайки по този начин обекта. Това работи чудесно в C++, но ще запомните че тази схема се проваля в Java, така че не я използвайте.




Сподели с приятели:
1   ...   65   66   67   68   69   70   71   72   73




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

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