Herança e Composição/Exercício 04: Arabian Nights in Java

From Wiki**3

< Herança e Composição

Problema

Aplicando os conceitos OO que já conhece, concretize as classes necessárias para representar a funcionalidade que se descreve de seguida. Pode ser necessária a criação de classes adicionais não descritas abaixo. A correcta execução dos testes depende da estrita aderência à nomenclatura das classes apresentadas. Todas as classes indicadas devem pertencer à "package" arabiannights.

Funcionalidade da lâmpada mágica (classe "MagicLamp")

Uma lâmpada mágica liberta génios quando esfregada (método rub). Os génios podem ser bem ou mal-humorados. O humor dos génios é determinado pelas condições da lâmpada: sempre que a lâmpada tiver sido esfregada um número par de vezes (sem contar a actual), o génio sai mal-humorado. A quantidade de génios disponíveis é determinada no momento de encantamento da lâmpada (criação). Depois de esgotados os génios disponíveis, já não adianta esfregar a lâmpada para obter um génio, bem ou mal-humorado: nestas condições, a lâmpada cria um pequeno demónio que responde a pedidos de forma literal mas perversa. Devido a requisitos de sustentabilidade ambiental, as normas de produção exigem que as lâmpadas sejam recarregáveis. Assim, é possível voltar a obter génios quando se esfrega a lâmpada (em número igual ao inicial). O processo de recarregamento exige apenas que um demónio seja alimentado à lâmpada (método feedDemon).

Quando se cria uma nova lâmpada é necessário indicar a quantidade inicial de génios que é possível invocar. É possível saber quantos génios ainda estão disponíveis na lâmpada (método getGenies). É ainda possível saber quantas vezes a lâmpada já foi recarregada (método getDemons). Quando se esfrega a lâmpada, deve-se indicar quantos desejos se espera que o génio realize (independentemente de o génio os poder negar).

A classe MagicLamp deve saber comparar as suas instâncias (através do método equals). Considera-se que duas lâmpadas são iguais se tiverem a mesma capacidade e se os valores retornados pelos métodos getGenies e getDemons forem iguais.

Nota: a lâmpada liberta apenas um génio de cada vez.

Funcionalidade do génio bem-humorado (classe "FriendlyGenie")

O génio bem-humorado concede todos os desejos que lhe forem colocados (método grantWish e retorno true), até ao limite com que foi chamado da lâmpada. Depois do limite já não são concedidos desejos (retorno false). É possível saber quantos desejos já foram concedidos (método getGrantedWishes) e quantos ainda existem disponíveis (método getRemainingWishes).

Nota: o génio concede apenas um desejo de cada vez.

Funcionalidade do génio mal-humorado (classe "GrumpyGenie")

O génio mal-humorado concede apenas o primeiro desejo que lhe for colocado (método grantWish e retorno true), independentemente do limite com que foi chamado da lâmpada (retorno false após o primeiro). É possível saber se o desejo já foi realizado (método getGrantedWishes retorna 1).

Funcionalidade do demónio reciclável (classe "RecyclableDemon")

O demónio concede todos os desejos que lhe forem colocados (método grantWish e retorno true), independentemente do limite com que foi chamado da lâmpada. Se o demónio for recolocado na lâmpada (para a recarregar), já não pode realizar mais desejos (retorno false). É possível saber quantos desejos já foram concedidos (método getGrantedWishes).

Nota: o demónio concede apenas um desejo de cada vez.

Observações

Todas as classes devem ter métodos de acesso (get e set) (quando apropriado) para os seus atributos.

O método toString, aplicado aos génios e ao demónio, deve devolver uma das seguintes cadeias de caracteres:

  • Friendly genie has granted # wishes and still has # to grant. (os # representam os contadores apropriados)
  • Grumpy genie has granted a wish. (se já concedeu o pedido)
  • Grumpy genie has a wish to grant. (se ainda não concedeu o pedido)
  • Recyclable demon has granted # wishes. (antes de recarregar uma lâmpada)
  • Demon has been recycled. (depois de recarregar uma lâmpada)

A classe cliente, de nome ArabianNights deve possuir um método (static) main que execute a seguinte sequência de operações:

  1. Criar uma lâmpada mágica com capacidade para 4 génios.
  2. Esfregar 5 vezes a lâmpada, indicando os números de desejos 2, 3, 4, 5, 1.
  3. Invocar e imprimir o resultado do método toString sobre cada um dos génios.
  4. Pedir um desejo a cada um dos génios.
  5. Invocar e imprimir o resultado do método toString sobre cada um dos génios.
  6. Pedir um desejo a cada um dos génios.
  7. Invocar e imprimir o resultado do método toString sobre cada um dos génios.
  8. Colocar o demónio reciclável na lâmpada.
  9. Esfregar a lâmpada, indicando 7 como número de desejos.
  10. Invocar e imprimir o resultado do método toString sobre o génio obtido.

Solution

The following corresponds to a possible telling of a special version of the Arabian Nights stories. In any implementation, the following concepts will exist in one form or another: MagicLamp, Genie (a general type of genie, which may also describe the recyclable demon), FriendlyGenie, GrumpyGenie, and RecyclableDemon.

UML Class Diagram

Diagrama de classes

PO-ArabianNights-Java-1.png

Class MagicLamp

Ficheiro MagicLamp.java
  1 package arabiannights;
  2 
  3 public class MagicLamp {
  4 
  5 	/**
  6 	 * Maximum number of wishes (does not mean that the genie will grant them all).
  7 	 */
  8 	private int _limit;
  9 
 10 	/** Total number of rubs. */
 11 	private int _totalRubs;
 12 
 13 	/** Number of rubs since last charge. */
 14 	private int _rubs;
 15 
 16 	/** Number of recharges. */
 17 	private int _demons;
 18 
 19 	/**
 20 	 * @param limit
 21 	 *            how many genies can come out.
 22 	 */
 23 	public MagicLamp(int limit) {
 24 		_limit = limit;
 25 	}
 26 
 27 	/**
 28 	 * @return the maximum number of genies.
 29 	 */
 30 	public int getLimit() {
 31 		return _limit;
 32 	}
 33 
 34 	/**
 35 	 * @return how many times it was rubbed since last change.
 36 	 */
 37 	public int getRubs() {
 38 		return _rubs;
 39 	}
 40 
 41 	/**
 42 	 * @return total number of rubs.
 43 	 */
 44 	public int getTotalRubs() {
 45 		return _totalRubs;
 46 	}
 47 
 48 	/**
 49 	 * @return number of available genies.
 50 	 */
 51 	public int getGenies() {
 52 		return getLimit() - getRubs();
 53 	}
 54 
 55 	/**
 56 	 * @return number of demons.
 57 	 */
 58 	public int getDemons() {
 59 		return _demons;
 60 	}
 61 
 62 	/**
 63 	 * Ask the lamp for a genie. If the number of rubs is even, return a grumpy
 64 	 * genie. If the number of rubs is more than the limit, return a demon.
 65 	 * Otherwise, return a normal friendly genie.
 66 	 * 
 67 	 * @param wishes
 68 	 *            maximum number of wishes the genie is able to grant (may not
 69 	 *            be the actual number of granted wishes).
 70 	 * 
 71 	 * @return a genie.
 72 	 */
 73 	public Genie rub(int wishes) {
 74 		if (_rubs < _limit) {
 75 			_rubs++;
 76 			_totalRubs++;
 77 			if (_totalRubs % 2 == 0)
 78 				return new FriendlyGenie(wishes);
 79 			return new GrumpyGenie(wishes);
 80 		}
 81 		return new RecyclableDemon(wishes);
 82 	}
 83 
 84 	/**
 85 	 * Recharge the magic lamp by feeding it a demon. A demon can only be used
 86 	 * if not previously recycled.
 87 	 * 
 88 	 * @param demon
 89 	 *            the recyclable demon.
 90 	 */
 91 	public void feedDemon(RecyclableDemon demon) {
 92 		if (!demon.recycled()) {
 93 			demon.recycle();
 94 			_rubs = 0;
 95 			_demons++;
 96 		}
 97 	}
 98 
 99 	/**
100 	 * This is the default equality method inherited from class Object.
101 	 * 
102 	 * @see java.lang.Object#equals(java.lang.Object)
103 	 */
104 	@Override
105 	public boolean equals(Object o) {
106 		return o instanceof MagicLamp && equals((MagicLamp) o);
107 	}
108 
109 	/**
110 	 * This is a local method (i.e., NOT INHERITED) that is only able to compare
111 	 * this lamp with another.
112 	 * 
113 	 * @return true if, in addition to the same capacity, the number of
114 	 *         available genies and the number of recharges are the same; false,
115 	 *         otherwise.
116 	 */
117 	public boolean equals(MagicLamp l) {
118 		return getLimit() == l.getLimit() && getGenies() == l.getGenies()
119 				&& getDemons() == l.getDemons();
120 	}
121 
122 }

Class Genie

Class Genie defines the basic behaviour for all genies.

Note that, since Genie is a class whose methods are to be redefined by subclasses and whose references will only point to subclass instances.

Ficheiro Genie.java
 1 package arabiannights;
 2 
 3 /**
 4  * General genie. Grants a number of wishes under certain conditions.
 5  */
 6 public abstract class Genie {
 7 
 8 	/**
 9 	 * Maximum number of wishes (does not mean that the genie will grant them all).
10 	 */
11 	private int _limit;
12 
13 	/** Number of wishes granted. */
14 	private int _granted;
15 
16 	/**
17 	 * @param limit the maximum number of wishes to grant.
18 	 */
19 	protected Genie(int limit) {
20 		_limit = limit;
21 	}
22 
23 	/**
24 	 * @return the maximum number of wishes.
25 	 */
26 	public int getLimit() {
27 		return _limit;
28 	}
29 
30 	/**
31 	 * @return the number of wishes already granted.
32 	 */
33 	public int getGrantedWishes() {
34 		return _granted;
35 	}
36 
37 	/**
38 	 * Increment wish counter.
39 	 */
40 	public void incrementGranted() {
41 		_granted++;
42 	}
43 
44 	/**
45 	 * Ask the genie to grant a wish. Never allow more grants than the initial limit.
46 	 * 
47 	 * @return true if the wish is granted; false otherwise.
48 	 */
49 	public boolean grantWish() {
50 		if (canGrantWish()) {
51 			incrementGranted();
52 			doGrantWish();
53 			return true;
54 		}
55 		return false;
56 	}
57 
58 	/**
59 	 * Decide if genie can grant a wish.
60 	 * 
61 	 * @return true or false (depends on special circumstances).
62 	 */
63 	public boolean canGrantWish() {
64 		return _granted < _limit;
65 	}
66 
67 	/**
68 	 * Do actual wish granting. By default, does not do anything. Subclasses
69 	 * will do something specific.
70 	 */
71 	public void doGrantWish() {
72 		// NOTHING TO DO
73 	}
74 
75 }

Class FriendlyGenie

A friendly genie is almost like a general genie (it differs in the output messages).

Ficheiro FriendlyGenie.java
 1 package arabiannights;
 2 
 3 /**
 4  * A friendly genie is actually a normal genie, but with special descriptive text.
 5  */
 6 public class FriendlyGenie extends Genie {
 7 
 8 	/**
 9 	 * @param limit
10 	 *            the maximum number of wishes to grant.
11 	 */
12 	public FriendlyGenie(int limit) {
13 		super(limit);
14 	}
15 
16 	/**
17 	 * @return the number of wishes left to grant.
18 	 */
19 	public int getRemainingWishes() {
20 		return getLimit() - getGrantedWishes();
21 	}
22 
23 	/**
24 	 * @see java.lang.Object#toString()
25 	 */
26 	@Override
27 	public String toString() {
28 		return "Friendly genie has granted " + getGrantedWishes() + " wishes and still has "
29 				+ getRemainingWishes() + " to grant.";
30 	}
31 
32 }

Class GrumpyGenie

Redefines a few methods.

Ficheiro GrumpyGenie.java
 1 package arabiannights;
 2 
 3 /**
 4  * Behaves like any other genie, but with some differences (only one wish).
 5  */
 6 public class GrumpyGenie extends Genie {
 7 
 8 	/**
 9 	 * @param limit
10 	 *            the number of wishes to grant (ignored: always grants only one wish).
11 	 */
12 	public GrumpyGenie(int limit) {
13 		super(1);
14 	}
15 
16 	/**
17 	 * @see java.lang.Object#toString()
18 	 */
19 	@Override
20 	public String toString() {
21 		return (getGrantedWishes() == 1) ? "Grumpy genie has granted a wish."
22 				: "Grumpy genie has a wish to grant.";
23 	}
24 
25 }

Class RecyclableDemon

The RecyclableDemon is a genie that implements some of the control methods in a special way (but still adheres to the template method defined by class Genie).

Ficheiro RecyclableDemon.java
 1 package arabiannights;
 2 
 3 /**
 4  * Recyclable demon.
 5  */
 6 public class RecyclableDemon extends Genie {
 7 
 8 	/** Has been recycled? */
 9 	private boolean _recycled = false;
10 
11 	/**
12 	 * @param limit
13 	 *            the number of wishes to grant (ignored: always grants wishes).
14 	 */
15 	public RecyclableDemon(int limit) {
16 		super(limit);
17 	}
18 
19 	/**
20 	 * Recyclable demons always grant wishes (unless they have been recycled).
21 	 * 
22 	 * @return true if not yet recycled; false, otherwise.
23 	 */
24 	@Override
25 	public boolean canGrantWish() {
26 		return !_recycled;
27 	}
28 
29 	/**
30 	 * @return whether a demon has been recycled.
31 	 */
32 	public boolean recycled() {
33 		return _recycled;
34 	}
35 
36 	/**
37 	 * Recycle a demon (called by a magic lamp).
38 	 */
39 	public void recycle() {
40 		_recycled = true;
41 	}
42 
43 	/**
44 	 * @see java.lang.Object#toString()
45 	 */
46 	@Override
47 	public String toString() {
48 		return _recycled ? "Demon has been recycled." : "Recyclable demon has granted "
49 				+ getGrantedWishes() + " wishes.";
50 	}
51 
52 }

The Application

In this case, the main class is straightforward.

Numbers in comments correspond to the specified steps in the problem above.

Ficheiro ArabianNights.java
 1 import arabiannights.*;
 2 
 3 /**
 4  * The application class (main function).
 5  */
 6 public class ArabianNights {
 7 
 8 	/**
 9 	 * @param args command line arguments.
10 	 */
11 	public static void main(String args[]) {
12 
13 		/* 01 */ MagicLamp m = new MagicLamp(4);
14 		/* 02 */ Genie gv[] = { m.rub(2), m.rub(3), m.rub(4), m.rub(5), m.rub(1) };
15 		/* 03 */ for (Genie g : gv) System.out.println(g);
16 		
17 		System.out.println("======================");
18 		
19 		/* 04 */ for (Genie g : gv) g.grantWish();
20 		/* 05 */ for (Genie g : gv) System.out.println(g);
21 		
22 		System.out.println("======================");
23 		
24 		/* 06 */ for (Genie g : gv) g.grantWish();
25 		/* 07 */ for (Genie g : gv) System.out.println(g);
26 		
27 		System.out.println("======================");
28 		
29 		// recyclable demon is the last one in the array
30 		/* 08 */ m.feedDemon((RecyclableDemon) gv[4]);
31 		/* 09 */ Genie g = m.rub(7);
32 		/* 10 */ System.out.println(g);
33 		
34 		System.out.println("======================");
35 
36 	}
37 
38 }

Compiling and Running

How to Compile?

The compilation is as follows:

javac arabiannights/MagicLamp.java
javac arabiannights/Genie.java
javac arabiannights/FriendlyGenie.java
javac arabiannights/GrumpyGenie.java
javac arabiannights/RecyclableDemon.java
javac ArabianNights.java

In fact, compiling ArabianNights.java would cause the rest of them be compiled as well (the Java compiler accounts for all explicit class dependencies).

We assume that the CLASSPATH environment variable contains the current directory

Running

The program starts at a main function (in this case, contained in the ArabianNights class):

java ArabianNights

See Also