Del diagrama de clases al código: una flota de alquiler
Traduce a Java un diagrama de clases UML: una interfaz realizada, una clase abstracta con su método abstracto, dos subclases que redefinen métodos, una composición 1 a muchos y una asociación con multiplicidad; y úsalo para gestionar los alquileres y las revisiones de una flota de vehículos.
- Leer un diagrama de clases
- Realización de interfaces
- Clases y métodos abstractos
- Herencia y redefinición
- Composición y asociación
- Visibilidad (+ y -)
Enunciado
El diagrama de clases es el plano del programa: cada caja es una clase con sus atributos y sus métodos (- privado, + público, en cursiva o con {abstract} los abstractos) y cada flecha una relación. Pasar del diagrama al código es casi mecánico, y es lo que piden muchos exámenes de Entornos de Desarrollo y de Programación.
El diagrama de este ejercicio (en PlantUML, también en el código de partida; puedes pegarlo en el editor UML de la web para verlo dibujado) describe una empresa de alquiler: Revisable <|.. Vehiculo es una realización (implements), Vehiculo <|-- Coche una herencia (extends), Flota *-- Vehiculo una composición (la flota contiene sus vehículos, en una lista) y Alquiler --> Vehiculo una asociación navegable (el alquiler conoce su vehículo).
Qué tiene que hacer el programa
- Escribe la interfaz y las cinco clases del diagrama con los atributos, la visibilidad y los métodos que indica, y estas reglas:
precioDiade un coche es 30 € + 4 € por plaza y el de una furgoneta 50 € + 0,02 € por kg de carga;precio(dias)esprecioDia() × dias, salvo en la furgoneta, que desde 7 días tiene un 10 % de descuento sobre todo; un vehículonecesitaRevisionsi ha hecho 15.000 km o más desde la última revisión, yrevisarla pone en sus km actuales. Alquiler.importe()es el precio de los días más 0,15 € por cada km por encima de los 200 incluidos por día.Flota.altadevuelvefalsesi ya hay un vehículo con esa matrícula.toStringde un coche:Coche de P plazas; de una furgoneta:Furgoneta de C kg.- Órdenes:
coche M Pyfurgoneta M Cdan de alta (Alta: <toString> MoYa existe un vehículo con matrícula M);alquilar M D KescribeAlquiler de M (<toString>): D días, K km → X(1 día) y suma los km al vehículo;revisar MescribeM revisado a los K km; las dos conNo existe el vehículo Msi no está. pendientesescribePendientes de revisión:y las matrículas separadas por,en el orden de alta, oninguno.flotaescribeFlota (N vehículos):(1 vehículo) y, por cada uno,M · <toString> · K km · X/día, más· revisión pendientesi la necesita.- Una orden desconocida, con un número de datos distinto o con números no válidos (no enteros, plazas, carga o días de 0 o menos, km negativos):
Orden no válida: línea. Al final:Facturado: X en N alquileres(1 alquiler). Los importes, con dos decimales, coma y€.
Entrada
Una orden por línea: coche M P, furgoneta M C, alquilar M D K, revisar M, pendientes o flota.
Ejemplos de ejecución
Tu programa debe escribir exactamente esta salida para estas entradas. Las pruebas del editor incluyen estos ejemplos y otros casos ocultos.
Una flota con dos vehículos
Entrada
coche 1234ABC 5 furgoneta 5678DEF 1200 coche 1234ABC 7 alquilar 1234ABC 3 750 alquilar 5678DEF 7 16000 pendientes flota revisar 5678DEF pendientes
Salida por consola
Alta: Coche de 5 plazas 1234ABC Alta: Furgoneta de 1200 kg 5678DEF Ya existe un vehículo con matrícula 1234ABC Alquiler de 1234ABC (Coche de 5 plazas): 3 días, 750 km → 172,50 € Alquiler de 5678DEF (Furgoneta de 1200 kg): 7 días, 16000 km → 2656,20 € Pendientes de revisión: 5678DEF Flota (2 vehículos): 1234ABC · Coche de 5 plazas · 750 km · 50,00 €/día 5678DEF · Furgoneta de 1200 kg · 16000 km · 74,00 €/día · revisión pendiente 5678DEF revisado a los 16000 km Pendientes de revisión: ninguno Facturado: 2828,70 € en 2 alquileres
Órdenes erróneas
Entrada
coche 1111AAA cinco furgoneta 2222BBB alquilar 9999ZZZ 2 100 coche 3333CCC 4 alquilar 3333CCC 0 10 alquilar 3333CCC 1 150 volar 3333CCC revisar 0000XXX flota
Salida por consola
Orden no válida: coche 1111AAA cinco Orden no válida: furgoneta 2222BBB No existe el vehículo 9999ZZZ Alta: Coche de 4 plazas 3333CCC Orden no válida: alquilar 3333CCC 0 10 Alquiler de 3333CCC (Coche de 4 plazas): 1 día, 150 km → 46,00 € Orden no válida: volar 3333CCC No existe el vehículo 0000XXX Flota (1 vehículo): 3333CCC · Coche de 4 plazas · 150 km · 46,00 €/día Facturado: 46,00 € en 1 alquiler
Guía paso a paso
Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.
1. De la caja a la clase
Cada atributo con - es un private; cada método con + un public. {abstract} en un método obliga a declarar la clase abstract y a que cada subclase lo implemente.
abstract class Vehiculo implements Revisable {
private final String matricula;
private int km;
private int kmUltimaRevision;
public abstract double precioDia();
}2. Las flechas
Triángulo hueco con línea discontinua: implements; con línea continua: extends. Rombo relleno con 0..*: un atributo List<Vehiculo> creado y gestionado por la flota. Flecha simple hacia Vehiculo con multiplicidad 1: un atributo Vehiculo en Alquiler.
3. Redefinir con super
La furgoneta redefine precio reutilizando el de su madre: super.precio(dias) y aplica el descuento encima.
4. El main
Un switch con flechas sobre la orden y un try que capture IllegalArgumentException (que incluye NumberFormatException) para todas las órdenes mal escritas.
Resuélvelo aquí
El editor trae el esqueleto del programa. Pulsa «Ejecutar» para comprobarlo con los ejemplos y con 2 casos ocultos que buscan los errores típicos.
Ejemplo
coche 1234ABC 5 furgoneta 5678DEF 1200 coche 1234ABC 7 alquilar 1234ABC 3 750 alquilar 5678DEF 7 16000 pendientes flota revisar 5678DEF pendientes
Alta: Coche de 5 plazas 1234ABC Alta: Furgoneta de 1200 kg 5678DEF Ya existe un vehículo con matrícula 1234ABC Alquiler de 1234ABC (Coche de 5 plazas): 3 días, 750 km → 172,50 € Alquiler de 5678DEF (Furgoneta de 1200 kg): 7 días, 16000 km → 2656,20 € Pendientes de revisión: 5678DEF Flota (2 vehículos): 1234ABC · Coche de 5 plazas · 750 km · 50,00 €/día 5678DEF · Furgoneta de 1200 kg · 16000 km · 74,00 €/día · revisión pendiente 5678DEF revisado a los 16000 km Pendientes de revisión: ninguno Facturado: 2828,70 € en 2 alquileres
Solución explicada
Ver la solución completa
1import java.util.ArrayList;
2import java.util.List;
3import java.util.Locale;
4import java.util.Scanner;
5
6/*
7@startuml
8interface Revisable {
9 +necesitaRevision(): boolean
10 +revisar(): void
11}
12abstract class Vehiculo {
13 -matricula: String
14 -km: int
15 -kmUltimaRevision: int
16 +Vehiculo(matricula: String)
17 +getMatricula(): String
18 +getKm(): int
19 +recorrer(km: int): void
20 +{abstract} precioDia(): double
21 +precio(dias: int): double
22 +necesitaRevision(): boolean
23 +revisar(): void
24}
25class Coche {
26 -plazas: int
27 +Coche(matricula: String, plazas: int)
28 +precioDia(): double
29 +toString(): String
30}
31class Furgoneta {
32 -cargaKg: int
33 +Furgoneta(matricula: String, cargaKg: int)
34 +precioDia(): double
35 +precio(dias: int): double
36 +toString(): String
37}
38class Flota {
39 -vehiculos: List<Vehiculo>
40 +alta(v: Vehiculo): boolean
41 +buscar(matricula: String): Vehiculo
42 +getVehiculos(): List<Vehiculo>
43 +pendientesDeRevision(): List<Vehiculo>
44}
45class Alquiler {
46 -dias: int
47 -km: int
48 +Alquiler(vehiculo: Vehiculo, dias: int, km: int)
49 +importe(): double
50}
51Revisable <|.. Vehiculo
52Vehiculo <|-- Coche
53Vehiculo <|-- Furgoneta
54Flota "1" *-- "0..*" Vehiculo
55Alquiler "0..*" --> "1" Vehiculo : vehiculo
56@enduml
57*/
58
59interface Revisable {
60 boolean necesitaRevision();
61 void revisar();
62}
63
64abstract class Vehiculo implements Revisable {
65 static final int KM_ENTRE_REVISIONES = 15000;
66 private final String matricula;
67 private int km;
68 private int kmUltimaRevision;
69
70 Vehiculo(String matricula) {
71 this.matricula = matricula;
72 }
73
74 public String getMatricula() { return matricula; }
75 public int getKm() { return km; }
76
77 public void recorrer(int km) {
78 this.km += km;
79 }
80
81 public abstract double precioDia();
82
83 public double precio(int dias) {
84 return precioDia() * dias;
85 }
86
87 @Override
88 public boolean necesitaRevision() {
89 return km - kmUltimaRevision >= KM_ENTRE_REVISIONES;
90 }
91
92 @Override
93 public void revisar() {
94 kmUltimaRevision = km;
95 }
96}
97
98class Coche extends Vehiculo {
99 private final int plazas;
100
101 Coche(String matricula, int plazas) {
102 super(matricula);
103 this.plazas = plazas;
104 }
105
106 @Override
107 public double precioDia() {
108 return 30 + 4 * plazas;
109 }
110
111 @Override
112 public String toString() {
113 return "Coche de " + plazas + " plazas";
114 }
115}
116
117class Furgoneta extends Vehiculo {
118 private final int cargaKg;
119
120 Furgoneta(String matricula, int cargaKg) {
121 super(matricula);
122 this.cargaKg = cargaKg;
123 }
124
125 @Override
126 public double precioDia() {
127 return 50 + 0.02 * cargaKg;
128 }
129
130 /** Desde 7 días, un 10 % de descuento sobre todo el alquiler. */
131 @Override
132 public double precio(int dias) {
133 double p = super.precio(dias);
134 return dias >= 7 ? p * 0.9 : p;
135 }
136
137 @Override
138 public String toString() {
139 return "Furgoneta de " + cargaKg + " kg";
140 }
141}
142
143class Flota {
144 private final List<Vehiculo> vehiculos = new ArrayList<>();
145
146 public boolean alta(Vehiculo v) {
147 if (buscar(v.getMatricula()) != null) return false;
148 vehiculos.add(v);
149 return true;
150 }
151
152 public Vehiculo buscar(String matricula) {
153 for (Vehiculo v : vehiculos) if (v.getMatricula().equals(matricula)) return v;
154 return null;
155 }
156
157 public List<Vehiculo> getVehiculos() { return vehiculos; }
158
159 public List<Vehiculo> pendientesDeRevision() {
160 List<Vehiculo> r = new ArrayList<>();
161 for (Vehiculo v : vehiculos) if (v.necesitaRevision()) r.add(v);
162 return r;
163 }
164}
165
166class Alquiler {
167 static final int KM_INCLUIDOS_DIA = 200;
168 static final double PRECIO_KM_EXTRA = 0.15;
169 private final Vehiculo vehiculo;
170 private final int dias;
171 private final int km;
172
173 Alquiler(Vehiculo vehiculo, int dias, int km) {
174 this.vehiculo = vehiculo;
175 this.dias = dias;
176 this.km = km;
177 }
178
179 public double importe() {
180 int extra = Math.max(0, km - KM_INCLUIDOS_DIA * dias);
181 return vehiculo.precio(dias) + extra * PRECIO_KM_EXTRA;
182 }
183}
184
185public class Main {
186 static String euros(double x) {
187 return String.format(Locale.ROOT, "%.2f", x).replace('.', ',') + " €";
188 }
189
190 public static void main(String[] args) {
191 Scanner sc = new Scanner(System.in);
192 Flota flota = new Flota();
193 double facturado = 0;
194 int alquileres = 0;
195 while (sc.hasNextLine()) {
196 String linea = sc.nextLine().trim();
197 if (linea.isEmpty()) continue;
198 String[] p = linea.split("\\s+");
199 try {
200 switch (p[0]) {
201 case "coche", "furgoneta" -> {
202 if (p.length != 3) throw new IllegalArgumentException();
203 int n = Integer.parseInt(p[2]);
204 if (n <= 0) throw new IllegalArgumentException();
205 Vehiculo v = p[0].equals("coche") ? new Coche(p[1], n) : new Furgoneta(p[1], n);
206 System.out.println(flota.alta(v) ? "Alta: " + v + " " + p[1] : "Ya existe un vehículo con matrícula " + p[1]);
207 }
208 case "alquilar" -> {
209 if (p.length != 4) throw new IllegalArgumentException();
210 int dias = Integer.parseInt(p[2]), km = Integer.parseInt(p[3]);
211 if (dias <= 0 || km < 0) throw new IllegalArgumentException();
212 Vehiculo v = flota.buscar(p[1]);
213 if (v == null) {
214 System.out.println("No existe el vehículo " + p[1]);
215 break;
216 }
217 double importe = new Alquiler(v, dias, km).importe();
218 v.recorrer(km);
219 facturado += importe;
220 alquileres++;
221 System.out.println("Alquiler de " + p[1] + " (" + v + "): " + dias + (dias == 1 ? " día, " : " días, ") + km + " km → " + euros(importe));
222 }
223 case "revisar" -> {
224 if (p.length != 2) throw new IllegalArgumentException();
225 Vehiculo v = flota.buscar(p[1]);
226 if (v == null) {
227 System.out.println("No existe el vehículo " + p[1]);
228 break;
229 }
230 v.revisar();
231 System.out.println(p[1] + " revisado a los " + v.getKm() + " km");
232 }
233 case "pendientes" -> {
234 List<String> m = new ArrayList<>();
235 for (Vehiculo v : flota.pendientesDeRevision()) m.add(v.getMatricula());
236 System.out.println("Pendientes de revisión: " + (m.isEmpty() ? "ninguno" : String.join(", ", m)));
237 }
238 case "flota" -> {
239 int n = flota.getVehiculos().size();
240 System.out.println("Flota (" + n + (n == 1 ? " vehículo):" : " vehículos):"));
241 for (Vehiculo v : flota.getVehiculos()) {
242 System.out.println(" " + v.getMatricula() + " · " + v + " · " + v.getKm() + " km · " + euros(v.precioDia()) + "/día"
243 + (v.necesitaRevision() ? " · revisión pendiente" : ""));
244 }
245 }
246 default -> throw new IllegalArgumentException();
247 }
248 } catch (IllegalArgumentException e) { // NumberFormatException también lo es
249 System.out.println("Orden no válida: " + linea);
250 }
251 }
252 System.out.println("Facturado: " + euros(facturado) + " en " + alquileres + (alquileres == 1 ? " alquiler" : " alquileres"));
253 }
254}La interfaz Revisable la realiza Vehiculo, y por herencia también la cumplen Coche y Furgoneta: la flota pregunta a cada vehículo si necesita revisión sin saber de qué tipo es (polimorfismo).
La composición (rombo relleno) significa que los vehículos pertenecen a la flota: los crea, los guarda y los busca ella. La asociación de Alquiler es más débil: el alquiler solo apunta a un vehículo que existe por su cuenta.
El precio de la furgoneta muestra la redefinición: Alquiler.importe() llama a vehiculo.precio(dias) y Java ejecuta la versión de la clase real del objeto, con descuento o sin él.
Los atributos privados con métodos públicos que los usan (recorrer, revisar) son la encapsulación del diagrama: nadie puede bajar los km de un vehículo desde fuera.
Para ir más allá
- Añade una clase
Motoal diagrama y al código, sin tocarFlotaniAlquiler. - Dibuja el diagrama de secuencia de la orden
alquilar. - Guarda el historial de alquileres de cada vehículo (una nueva asociación
1 -- 0..*) y muestra el más caro.