Download Especificación y Desarrollo de Sistemas de Software

Document related concepts
no text concepts found
Transcript
Enunciados y materiales de
prácticas de la asignatura
“Especificación y Desarrollo de
Sistemas de Software”
Grado en Matemáticas y Grado en
Ingeniería Informática
Ana Romero Ibáñez
Laureano Lambán Pardo
Universidad de La Rioja
Curso 2011/2012
Todo este material está disponible en la página web:
www.unirioja.es/cu/anromero/edss.html
Índice
Práctica 1 – Dominó………………………………………………….. ..3
Práctica 2 – Juego de la guerra……………………………….……….5
Práctica 3 – Resolución de sudokus…………………………………..7
Materiales para la práctica 3: Colecciones en Java………………..13
Práctica 4 – Expresiones booleanas…………………………………18
Especificación y Desarrollo de Sistemas de Software.
Curso: 2011-2012.
Práctica 1ª: “Partida de dominó”
La realización de esta práctica está previsto que ocupe tres sesiones.
Las partes de la asignatura con las que está directamente relacionada son las siguientes:
especificación de tipos de datos y diseño orientado a objetos. En particular, el diseño
planteado va a necesitar del mecanismo “clase abstracta”.
Se pretenden diseñar en esta práctica las clases a las que pertenecen los diferentes
objetos que intervienen en una "partida de dominó", aunque en una versión muy
simplificada, evitando lo relativo a estrategias de jugador.
Las clases que se proponen son: Ficha, Mesa, Jugador y Partida.
Ficha. Como guía, una ficha debe proporcionar métodos para: girar, informar de su
valor (suma), mostrarse, informar de si contiene un determinado número, informar de si
se trata de una doble, etc.
Mesa. Una mesa de dominó consta de dos “montones”, uno con las fichas que no están
en posesión de ningún jugador y el otro con las fichas ya colocadas (organizadas
correctamente). Tendrá que proporcionar métodos para colocar fichas en ambos
montones, extraer aleatoriamente fichas del montón de las que no han entrado en juego,
informar de si están o no vacíos esos montones, informar de los extremos de las
colocadas, mostrar las fichas colocadas, etc.
Jugador. Como vamos a permitir jugadores humanos y jugadores gestionados por la
máquina, la diseñaremos como clase abstracta. Un jugador posee un conjunto de fichas.
Los métodos abstractos van a limitarse a la forma en la que un jugador elige la ficha a
jugar (recibiendo como información un objeto de la clase mesa), y la forma en la que se
gestiona la acción recibir ficha (cada tipo de jugador organiza sus fichas como quiere).
El resto de métodos no son abstractos, proporcionando funcionalidades básicas como:
saber si puede o no jugar (según el estado de la mesa), suma de las fichas, número de
fichas, saber si ha finalizado, etc.
Partida. En una partida intervienen una mesa y un conjunto de jugadores. Otro aspecto
importante es el turno (qué jugador debe jugar). Al crear la partida se debe conocer el
número de jugadores de cada tipo que van a intervenir en ella. Entre otros, debe
proporcionar métodos para: iniciar partida (repartir fichas y generar el turno inicial),
realizar una jugada, informar de la posible finalización, dar el ganador, dar la
clasificación, etc.
Especificación y Desarrollo de Sistemas de Software.
Curso: 2011-2012.
Práctica 2ª: “Juego de la guerra”.
La realización de esta práctica está previsto que ocupe tres sesiones.
Las partes de la asignatura con las que está directamente relacionada son las siguientes:
especificación de tipos de datos, orientación a objetos y tipos de datos, diseño basado en
objetos. Además se hará uso de la definición de clases genéricas en Java.
Parte 1.
Pilas de enteros.
Escribid una especificación del tipo de datos "pila de enteros" y dad una clase Java que
implemente el tipo correspondiente.
Parte 2.
Colas genéricas.
Escribid una especificación del tipo de datos "cola" y dad una clase Java que represente
“colas genéricas”, es decir, colas cuyos elementos puedan ser objetos de cualquier clase.
Parte 3.
Un juego de cartas sencillo.
Se propone simular el juego de cartas conocido como "La Guerra". A continuación, se
explica brevemente la dinámica del juego.
Tras repartir todas las cartas entre los jugadores, una jugada consiste en que cada
jugador (que no haya sido previamente eliminado) arroja su primera carta sobre la mesa.
El ganador de la jugada es el que ha echado la carta más alta (en caso de empate, los
jugadores que han arrojado las cartas de mayor valor siguen jugando hasta deshacer el
empate). El ganador de la jugada recoge todas las cartas que se han arrojado en ella y las
incorpora como últimas cartas de su mazo. Un jugador queda eliminado cuando, siendo
su turno para echar carta, su mazo está vacío. El final del juego se produce cuando
queda un único jugador sin eliminar.
Para programar el juego siguiendo una "metodología orientada a objetos", se
recomienda:
Desarrollad una clase en Java cuyos objetos representen a "jugadores de guerra". Pensad
en cómo gestiona un jugador la recogida y la eliminación (echar sobre la mesa) de
cartas.
Desarrollad una clase Java cuyos objetos representen a "mesas del juego de la guerra".
Desarrollad una pequeña clase cuyos objetos representen a las "barajas españolas".
Desarrollad una clase Java cuyas instancias sean “partidas del juego de la guerra”. En
ella colaborarán las clases anteriores.
Parte 4.
Un paso de abstracción.
Sólo se pretende que se reflexione en las posibilidades que nos ofrece el diseño
orientado objetos. Por ejemplo, parece que se puede generalizar la idea de juego de
cartas (mediante clases abstractas), quedando por concretar la forma en la que se
desarrolla una jugada, el valor de las cartas en cada juego, la forma en que gestionan sus
cartas los jugadores, etc. Así, sería factible realizar software bastante general, que luego
se concretara para juegos particulares. Se anima a intentar hacer pequeñas
generalizaciones: barajas abstractas, jugadores abstractos, etc.
Como asunto relacionado, se recomienda reflexionar sobre cómo diseñar algunos
solitarios sencillos, y se anima a que se pase de la reflexión al papel (y del papel al
ordenador).
Especificación y Desarrollo de Sistemas Software.
Curso: 2011-2012.
Práctica 3ª: “Resolución de sudokus”
La realización de esta práctica está previsto que ocupe tres sesiones.
Las partes de la asignatura con las que está directamente relacionada son las siguientes:
especificación de tipos de datos y diseño orientado a objetos. En particular, se van a
manejar algunos conceptos fundamentales en programación orientada a objetos, como
herencia, reescritura de métodos, tipos parametrizados, etc. Además se presentarán
algunas clases del marco de colecciones de Java.
En la práctica se pretenden desarrollar las clases que luego permitirían construir un
entorno sencillo para gestionar y resolver sudokus. La idea se basa en la relación que
existe entre encontrar la solución de un sudoku y resolver el problema de colorear el
grafo asociado a ese sudoku.
El grafo asociado a un sudoku tiene tantos vértices como casillas tiene el sudoku y
tiene una arista por cada pareja de casillas relacionadas (están en la misma fila, en la
misma columna o en la misma “región”). Por otro lado, colorear un grafo usando un
número k de colores diferentes, consiste en asignar un color a cada vértice (un valor
entre 1 y k), de forma que dos vértices conectados por una arista tengan siempre color
diferente. Así, resolver un sudoku de “tamaño n” (con n2 casillas) equivale a colorear
con n colores su grafo asociado.
La forma habitual de trabajar con sudokus supone que algunas casillas tienen un
valor inicial fijo, luego su grafo asociado ya tiene algunos colores asignados de partida.
A continuación pasamos a describir las clases que van a intervenir en el problema:
Grafo, GrafoConColores, Sudoku y SudokuConSolucion.
Grafo. Nos vamos a limitar a grafos cuyos vértices son números enteros. Las
operaciones habituales sobre un grafo son, entre otras: añadir un nuevo vértice, añadir
una nueva arista, eliminar un vértice
(*)
, eliminar una arista
(*)
, informar del número de
vértices, decidir si dos vértices son adyacentes, decidir si un vértice está o no en un
grafo, dar la lista de todos los vértices del grafo y dar la lista de vértices adyacentes a un
vértice dado.
Aunque hay otras representaciones posibles para el estado de un grafo,
recomendamos representar un grafo mediante una tabla (las claves son los vértices y el
valor asociado a una clave es la lista de sus vértices adyacentes).
GrafoConColores. Un grafo con colores es un grafo con información adicional
(algunos de sus vértices tienen asignado un color, un entero en nuestro caso). Además
de las operaciones de grafo, se incorporan métodos para: dar color a un vértice, eliminar
el color de un vértice, informar del color de un vértice, borrar todos los colores, dar la
lista de todos los vértices que tienen un color asignado, decidir si un color es válido para
un vértice y colorear un grafo.
El “método colorear” termina de colorear un “grafo con colores” con un número de
colores pasado como argumento, siempre que ello sea posible. Existen métodos
refinados para colorear, pero el que se entrega en la documentación resulta sencillo
(aunque ineficiente). Ello va a limitar el tamaño de los sudokus que vamos a poder tratar
(tamaño 9, el habitual en pasatiempos).
Sudoku: Por simplificar, nos limitaremos a sudokus cuadrados. Los representaremos
como una matriz cuadrada de enteros, donde las entradas serán 0 (para casillas vacías) o
el valor del entero que esté en la casilla (comprendido entre 1 y el tamaño). Respecto de
los operadores, se debe disponer de métodos para: informar del tamaño, añadir un
número a una casilla (indicando la fila y la columna), eliminar un número, dar el valor
inicial de una casilla, volver al estado inicial y decidir si es correcto añadir un
determinado valor en una casilla (*).
SudokuConSolucion. La visión externa de un objeto de esta clase será la misma que la
que nos da un sudoku, pero añadiendo dos nuevos métodos. El primero observa si es
posible resolver el sudoku desde el estado actual y el segundo lo resuelve (si es posible).
Para ello, además del estado del sudoku, dispondrá de un atributo privado de la clase
GrafoConColores. Además de los dos operadores nuevos, será necesario reescribir los
métodos heredados de sudoku, modificando adecuadamente el grafo con colores
asociado al sudoku. Se incluye en la documentación un método privado para construir el
grafo con colores asociado a un sudoku.
(*) Los operadores marcados no hace falta que se implementen (no se van a usar ).
ALGORITMO PARA EL COLOREADO DE GRAFOS
public void colorear(int numColores){
List<Integer> listaVertices;
// lista auxiliar en la que colocaré todos los vértices
/* Para poder aplicar el algoritmo de coloración de un grafo necesito
tener los vértices almacenados en orden.
En primer lugar colocaré los vértices que tienen ya un color asignado
(este color no podrá modificarse). A continuación colocaré en la lista el
resto de vértices, a los que el algoritmo de coloración irá asignando
diferentes colores hasta dar con una combinación correcta.
*/
List<Integer> listaVerticesColoreados=this.listaVerticesColoreados( );
List<Integer> listaVerticesNoColoreados= this.listaVertices( ); //todos
listaVerticesNoColoreados.removeAll(listaVerticesColoreados); //quito los
//coloreados
// vuelco los vértices en el nuevo vector, en el orden correcto
listaVertices=new Vector<Integer>( );
listaVertices.addAll(listaVerticesColoreados);
listaVertices.addAll(listaVerticesNoColoreados);
int k=listaVerticesColoreados.size( );
boolean b=coloreoConRetroceso(listaVertices, k, numColores);
if (b== false) {
// no se ha podido colorear el grafo
// vuelvo a la situación inicial
for (int i = 0; i < listaVerticesNoColoreados.size( ); i++) {
tablaColores.remove(listaVerticesNoColoreados.get(i));
}
}
}
private boolean aceptable(List<Integer> listaVertices, int color, int posicion){
/*
devuelve true si al vértice que ocupa la posición k en listaVertices
puedo asignarle el color k de modo que no haya ningún vértice en las
posiciones anteriores que sea adyacente y que tenga el mismo color asignado.
*/
boolean acept=true;
for (int i=0; i<posicion && acept; i++){
if (this.contieneArista(listaVertices.get(i), listaVertices.get(posicion)) &&
getColor(listaVertices.get(i))== color)
acept=false;
}
return acept;
}
private boolean coloreoConRetroceso(List<Integer> listaVertices, int k, int
numColores){
/*
Supongo que a los vértices situados en las posiciones 0..k-1
de listaVertices ya les he asignado color.
Busco un color para el vértice en la posición k que sea compatible
con los anteriores.
*/
if (k==listaVertices.size( ))
return true;
else {
for (int c=1; c<=numColores; c++){
if (this.aceptable(listaVertices,c, k)) {
tablaColores.put(listaVertices.get(k), c);
boolean b=coloreoConRetroceso(listaVertices,k + 1, numColores);
if (b)
return b;
}
}
}
// he recorrido todas las combinaciones y ninguna es válida, devuelvo falso.
return false;
}
ALGORITMO PARA CONSTRUIR EL GRAFO INICIAL
ASOCIADO A UN SUDOKU
private void construirGrafoInicial( ){
int numFilas=this.getNumFilas( );
int numVertices=numFilas*numFilas;
for (int v=1; v<=numVertices; v++)
gr.anadirVertice(v);
//gr es el atributo de Sudoku que contiene el grafo con colores
//Añado aristas para todas las parejas de vértices que están en la misma
// fila
for (int i = 0; i < numFilas ; i++) {
for (int j = 0; j < numFilas; j++) {
for (int k = j + 1; k < numFilas ; k++) {
int v1=numFilas*i + j+1;
int v2=numFilas*i + k+1;
gr.anadirArista(v1,v2);
}
}
}
//Añado aristas para todas las parejas de vértices que están en la misma
// columna
for (int j = 0; j < numFilas; j++) {
for (int i = 0; i < numFilas ; i++) {
for (int k = i + 1; k < numFilas ; k++) {
int v1=numFilas*i + j+1;
int v2=numFilas*k + j+1;
gr.anadirArista(v1,v2);
}
}
}
//Añado aristas para todas las parejas de vértices que están en la misma
// región
int n = (int)Math.sqrt(numFilas);
for (int i = 0; i < n ; i++) {
for (int j = 0; j < n; j++) {
int i0 = i * n;
int j0 = j * n;
// (i0,j0) es la esquina superior izquierda de la región
for (int i1 = i0; i1 < i0 + n; i1++) {
for (int j1 = j0; j1 < j0 + n; j1++) {
for (int i2 =i0; i2<i0+n; i2++){
for (int j2 = j0; j2 < j0 + n; j2++) {
int v1 = numFilas * i1 + j1 + 1;
int v2 = numFilas * i2 + j2 + 1;
if (v1 != v2)
gr.anadirArista(v1, v2);
}
}
}
}
}
}
// Por último añado los colores a los vértices correspondientes a los
// valores iniciales del sudoku
for (int i=0; i<numFilas; i++){
for (int j=0; j<numFilas; j++){
if (this.valorInicial(i,j)!=0)
gr.setColor(i*numFilas+j+1,this.valorInicial(i,j));
}
}
}
Especificación y Desarrollo de Sistemas de Software. Curso 2011/2012. Prácticas 3 y 4.
COLECCIONES EN JAVA
El marco de colecciones (Collection Framework) de Java contiene una serie de
interfaces y clases que permiten almacenar y organizar objetos de una forma cómoda y
sencilla y con un acceso eficiente. La mayor parte de estas clases las encontramos en el
paquete java.util, y entre otras cosas nos van a permitir trabajar con conjuntos, listas
y aplicaciones.
•
•
•
•
El marco de colecciones de Java está formado por:
interfaces que nos dan los métodos para todas las operaciones comunes;
implementaciones de los interfaces (clases);
algoritmos para realizar determinadas operaciones habituales sobre colecciones,
como ordenaciones o búsquedas;
iteradores que nos permiten recorrer las colecciones.
Todos los interfaces y clases del marco de colecciones son genéricas, aunque
con objeto de mantener la compatibilidad con versiones anteriores de Java estos
interfaces y clases genéricos conviven con versiones no genéricas de los mismos. Si no
indicamos el parámetro de tipo, se supondrá que estamos trabajando con
colecciones/aplicaciones sobre la clase Object, con lo que tendremos que hacer las
coerciones necesarias.
LOS INTERFACES BÁSICOS
A continuación mostramos un diagrama UML con los interfaces básicos del
marco de colecciones y las relaciones entre ellos. Se distinguen dos jerarquías
diferentes, los interfaces que heredan de Collection<T> y los que lo hacen de
Map<K,V> (que llamaremos aplicaciones).
Veamos las características y métodos principales de algunos de ellos:
Collection<T>
Es el interfaz más general; define las operaciones que implementan todas las
clases que representan colecciones de objetos. Podemos encontrarnos una gran variedad
de colecciones de objetos, con características distintas: unas colecciones serán
ordenadas y otras no, unas permitirán elementos repetidos y otras no, etc; pero todas
ellas van a disponer de una serie de operaciones comunes, que son las que quedan
recogidas en el interfaz genérico Collection<T>.
1
Especificación y Desarrollo de Sistemas de Software. Curso 2011/2012. Prácticas 3 y 4.
Sus métodos más importantes son (mirar JavaDoc):
int size(): devuelve el número de objetos almacenados en la colección
boolean isEmpty(): dice si la colección está vacía
boolean add(T elem): añade un elemento a la colección
boolean addAll(Collection<? extends T> c): añade un grupo de
objetos (otra colección)
void clear(): vacía la colección
boolean contains(Object elem): chequea si un elemento se encuentra
dentro de la colección
boolean remove(Object elem): quita un elemento de la colección
Iterator<T> iterator(): devuelve un java.util.Iterator que permite
recorrer todos los elementos de la colección
T[] toArray(T[]a): devuelve un array con los elementos de la colección
List<T>
Sirve como interfaz para clases que representen listas de datos o vectores cuya
longitud es variable. Sus elementos son accesibles por medio de un índice, y tiene los
métodos de colección y algunos más, entre ellos:
void add(int i, T elem): añade un elemento en una posición determinada
T get(int i): devuelve el elemento que ocupa una posición determinada
T remove(int i): elimina un elemento que ocupa una posición determinada
int indexOf(Object elem): devuelve el índice de la primera aparición del
elemento en la lista (o –1 si no está)
T set(int i, T elem): cambia el elemento que ocupa una posición
determinada por otro elemento
Al añadir un objeto con el método add(T elem) de Collection<T>, el
elemento se añade al final. Al eliminar un objeto con remove(Object elem), todos los
demás se desplazan para no dejar huecos.
Los objetos List<T> más habituales son Vector<T> y ArrayList<T>. Sus
métodos y características principales se pueden encontrar en la documentación de Java.
Set<T>
Sirve como interfaz para clases que representen conjuntos de datos donde no
puede haber elementos repetidos, y cuyos elementos no se almacenan necesariamente
siguiendo un orden particular. Por tanto sus elementos no son accesibles por un índice.
Tiene los mismos métodos que Collection<T> (no añade ningún método
nuevo). Las clases Set<T> más habituales son HashSet<T> y TreeSet<T> (ver
JavaDoc).
2
Especificación y Desarrollo de Sistemas de Software. Curso 2011/2012. Prácticas 3 y 4.
Map<K,V>
Define las operaciones que implementarán las clases que representan
aplicaciones de claves a valores, es decir, representa lo que se denomina tablas de datos.
Un mapa se compone de un conjunto de “entradas”, compuestas a su vez de:
• una clave, que sirve para recuperar el elemento;
• y un valor.
Una aplicación no puede contener claves duplicadas, y cada clave se puede emparejar
con, a lo sumo, un valor.
Los métodos más importantes de este interfaz son (mirar JavaDoc):
int size(): devuelve el número de objetos almacenados
boolean isEmpty(): dice si el Map está vacío
void clear(): vacía la estructura
V put(K clave, V valor): almacena un objeto en el mapa con una clave
determinada. Si la clave ya está almacenada se sustituye el objeto asociado por el
nuevo
V get(K clave): permite recuperar un elemento a partir de su clave
V remove(Object clave): elimina un objeto de clave determinada
boolean containsKey(Object clave): devuelve true si hay algún objeto
almacenado con esa clave
boolean containsValue(Object valor): devuelve true si el Map contiene
ese objeto
Set<K> keySet(): devuelve un conjunto con el conjunto de claves del Map
Collection<V> values(): devuelve el conjunto de valores almacenados en
el Map
Una aplicación no es una colección; por eso Map<K,V> no hereda de
Sin embargo, a partir de un Map podremos obtener colecciones
correspondientes al conjunto de claves, al conjunto de valores y al conjunto de pares
<clave, valor>. Los Maps se implementan como tablas Hash; las clases Map<K,V> más
habituales son HashMap<K,V> y Hashtable<K,V>.
Collection<T>.
Ejemplo:
java.util.Map<String,Persona> agenda=
new java.util.Hashtable<String,Persona>();
Persona p=new Persona(“Pepe”),
agenda.put(“16444444E”,p);
agenda.put(“55555555L”, new Persona(“Luis”);
System.out.println(“Hay alguien con DNI 55555555L?”
+ agenda.containsKey(“55555555L”)); //true
Persona pr=agenda.get(“16444444E”); //Debe ser Pepe
System.out.println(“¿Es Pepe?” + pr.equals(p)); // true
System.out.println(“El tamaño de la agenda es ”+agenda.size());
3
Especificación y Desarrollo de Sistemas de Software. Curso 2011/2012. Prácticas 3 y 4.
IMPLEMENTACIONES DE LOS INTERFACES BÁSICOS
El paquete java.util proporciona implementaciones de los interfaces genéricos
Collection, List, Set y Map (entre otros). En la siguiente figura se muestran algunas
de estas clases y las relaciones entre ellas. Recordar que no hay relación entre
Collection<T> y Map<K,V>. Además, podemos observar que no hay una
implementación directa de la interfaz Collection<T>.
En nuestro caso será especialmente conveniente conocer el funcionamiento de
las clases Vector<T>, HashSet<T> y Hashtable<K,V> (ver JavaDoc). En particular, nos
resultarán útiles los métodos de Hashtable<K,V> que devuelven enumeraciones de las
claves y los valores de una tabla:
Enumeration<V> elements()
Enumeration<K> keys()
ITERADORES
Un iterador es un objeto que nos permite obtener uno a uno todos los elementos
de una colección, independientemente de cómo estén organizados. En concreto, los
métodos del interfaz Iterator<T> nos proporcionan un modo sencillo para recorrer
colecciones como Set en las que no tenemos acceso por índice. Además, vamos a poder
gestionar la eliminación de objetos.
IMPORTANTE: los iteradores se utilizan para recorrer colecciones en las que
no tenemos acceso por índice (como Set), pero NO DEBEN usarse para recorrer objetos
de las interfaces o clases List, Vector, ArrayList, etc, en los que podemos acceder a cada
una de sus componentes por medio del método get(int i).
Los métodos del interfaz Iterator<T> son:
boolean hasNext(): devuelve true si el iterador tiene más elementos
T next(): devuelve el siguiente elemento de la iteración. Cada vez que se
invoca a este método se avanza en el recorrido de la colección
void remove(): elimina el último elemento devuelto por la iteración
4
Especificación y Desarrollo de Sistemas de Software. Curso 2011/2012. Prácticas 3 y 4.
Ejemplo:
Collection<String> c= ....;
Iterator<String> iter=c.iterator();
while(iter.hasNext())
System.out.print(iter.next() + “ “);
Otro interfaz similar que nos va a permitir recorrer colecciones es el interfaz
cuyos métodos son:
Enumeration<T>,
boolean hasMoreElements()
T nextElement()
ESTRUCTURA FOR PARA RECORRER COLECCIONES DIRECTAMENTE
A partir de Java 1.5 hay una manera simplificada de recorrer una Collection mediante
un nuevo uso del keyword for (siempre que no se quiera modificar la propia colección):
Collection<String> col= ....;
for (String s: col)
System.out.print(s);
Internamente este código no hace otra cosa que obtener el iterador, pero queda mucho
más elegante y legible de esta manera.
5
Especificación y Desarrollo de Sistemas de Software.
Curso: 2011-2012.
Práctica 4ª: “Expresiones booleanas”.
Esta práctica ocupará tres sesiones. En ella se van a manejar algunos conceptos
fundamentales en programación orientada a objetos. Por ejemplo, usaremos clases
abstractas, herencia, tipos parametrizados, polimorfismo y recursividad. En la
implementación aparecerán además algunas de las utilidades de Java (Map, Set, etc.).
El problema concreto a resolver es la construcción y evaluación de expresiones
booleanas. Por simplificar, nos vamos a limitar a expresiones construidas a partir de
expresiones atómicas (constantes booleanas y variables booleanas) mediante los
operadores: negación (not), conjunción (and) y disyunción (or).
El objetivo final es conseguir objetos con funcionalidad suficiente para realizar
evaluaciones de expresiones. El estado de dichos “entornos de evaluación” estará
básicamente determinado por una expresión y un contexto (lo que en Lógica se
denomina valoración de variables).
Se propone diseñar las siguientes clases:
Expresión. Una expresión dispondrá de métodos para informar de su número de
variables, de si contiene o no una determinada variable, de si es o no es evaluable en un
contexto, del conjunto de variables que aparecen en ella, etc. Habrá además métodos
abstractos (que se implementarán en las distintas subclases de expresión): evaluar en un
contexto, sustituir una variable por un valor booleano y renombrar una variable por otra.
Las subclases a las que no referimos son: expresión atómica, expresión unaria
(negación de una expresión) y expresión binaria (a su vez con dos subclases: ExpAnd
y ExpOr). En expresión atómica se pretende englobar tanto las constantes booleanas
(true y false), como las expresiones que son variables booleanas (b, encontrado,
esFiesta, etc.).
Contexto. Un contexto es una familia de parejas formadas por un nombre de variable
y un valor booleano. Debe ofrecer métodos para formar y modificar el contexto (añadir
y eliminar valoraciones de variables), informar del valor de una variable, decidir si dos
contextos son coherentes, negar un contexto, etc.
Evaluador. Debe permitir evaluar una expresión en un contexto. Las operaciones
permitirán realizar sustituciones en la expresión, modificarla, modificar el contexto,
evaluar, decidir si se puede evaluar, etc.