Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Expand
titleDel 1 – Unit-klassen (35%)
 Oppgave a)
Unit-klassen er utformet slik at instanser ikke skal kunne endres etter at de er opprettet. Hva er generelle fordeler og ulemper med klasser som gir ikke-modifiserbare (immutable) instanser?
Expand
titleLF

Klassen blir enklere, og minsker bl.a. behovet for validering. Instanser kan brukes av flere deler av et program, uten risiko for at en del endrer på dem og ødelegger for en annen del. Ulempen er at en må lage nye instanser hvis de må rettes på, istedenfor å endre dem direkte.

Oppgave b)
Skriv ferdig de tre konstruktørene og definer nødvendige felt. Hva er hensikten med bruken av throws-nøkkelordet, slik det er brukt her? Er det strengt tatt nødvendig og evt. hvorfor/hvorfor ikke?
Expand
titleLF


Code Block
private final String symbol;
public Unit(String symbol) throws IllegalArgumentException {
	this(symbol, null, 1.0, 0.0);
}
private final Unit base;
private final double factor, offset;
public Unit(String symbol, Unit base, double factor, double offset) throws IllegalArgumentException {
	for (int i = 0; i < symbol.length(); i++) {
		char c = symbol.charAt(i);
		if (! Character.isAlphabetic(c)) {
			throw new IllegalArgumentException(c + " is an illegal symbol character");
		}
	}
	this.symbol = symbol;
	this.base = base;
	this.factor = factor;
	this.offset = offset;
}
public Unit(String symbol, Unit base, double factor) throws IllegalArgumentException {
	this(symbol, base, factor, 0.0);
}

throws-deklarasjonen forteller leseren av koden at konstruktørene kan utløse unntak. Siden unntakene er en subklasse av RuntimeException og dermed ikke checked, så er det ikke nødvendige.

 

Oppgave c)
Hva er hensikten med å definere en toString()-metode?
Expand
titleLF

toString()-metoden brukes implisitt når Java lager String-objekter av instanser ifm. bruk av + og IO og sikrer at tilstanden til instanser blir presentert på en nyttig måte.

Se også toString()-metoden

Oppgave d)

Nederst i klassen defineres en del meter-relaterte Unit-instanser (m, km og dm og cm). Tegn objektdiagram som illustrerer objektstrukturen som disse instansene utgjør.

Expand
titleLF

Her er verdiene til feltene og kjeden av base-linker vesentlig.

PlantUML Macro
object "~#m : Unit" as m {
   symbol = "m"
   factor = 0.0
   offset = 0.0
}
object "~#km : Unit" as km {
   symbol = "km"
   factor = 1000.0
   offset = 0.0
}
km --> m: base
object "~#dm : Unit" as dm {
   symbol = "dm"
   factor = 0.1
   offset = 0.0
}
dm --> m: base
object "~#cm : Unit" as cm {
   symbol = "cm"
   factor = 0.1
   offset = 0.0
}
cm --> dm: base



Se også Objektdiagrammer

Oppgave e)
Metoden findCommonBaseUnit er sentral i konvertering av verdier mellom ulike enheter. Den skal virke slik at dm.findCommonBaseUnit(km) returnerer m-instansen. Skriv ferdig metoden.
Expand
titleLF


Code Block
themeEclipse
public Unit findCommonBaseUnit(Unit other) {
	Unit unit1 = this;
	while (unit1 != null) {
		Unit unit2 = other;
		while (unit2 != null) {
			if (unit2 == unit1) {
				return unit1;
			}
			unit2 = unit2.base;
		}
		unit1 = unit1.base;
	}
	return null;
}


Oppgave f)
convert-metoden er ferdigskrevet og bruker de to hjelpemetodene convertToBase og convertFromBase. De to hjelpemetodene kaller også seg selv. Forklar hvilke kall som gjøres til disse (inkludert de til seg selv) og hvilke argumenter de får og verdier de returnerer, i løpet av utførelsen av dm.convert(2.0, km).
Expand
titleLF
...
Oppgave g)
valueOf-metoden ”oversetter” fra et enhetssymbol til en Unit-instans, litt på samme måten som Double.valueOf lager en Double-verdi fra en String. Men merk at Unit.valueOf ikke skal lage nye instanser, men returnere en av de predefinerte! Skriv ferdig valueOf-metoden.
Expand
titleLF


Code Block
themeEclipse
public static Unit valueOf(String symbol) {
	for (Unit unit : ALL_UNITS) {
		if (symbol.equals(unit.symbol)) {
			return unit;
		}
	}
	return null;
}


Lenke til løsningskode: Unit.java (og testen: UnitTest.java)

 

...

Expand
titleDel 4 – Testing (10%)

I denne delen skal du skrive testkode for Value- og ValueSeries-klassene. Hensikten er å vise at du behersker testmetodikken, og du kan, men trenger ikke bruke JUnit-rammeverket.  

Oppgave a)

Skriv testkode for Value sin valueOf-metode.

Expand
titleLF


Code Block
themeEclipse
public void testValueOf() {
	Value value = Value.valueOf("2.0m");
	assertEquals(Unit.valueOf("m"), value.getUnit());
	assertEquals(2.0, value.getValue());
}

Se også Testing av unntak


Oppgave b)

 Forklar med tekst og kode hvordan du kan teste at ValueSeries håndterer lyttere (av typen ValuesListener) og implementerer observerbarhet riktig.

Expand
titleLF


Code Block
themeEclipse
public class ValueSeriesTest extends TestCase implements ValuesListener {
	private ValueSeries values;
	
	protected void setUp() throws Exception {
		super.setUp();
		values = new ValueSeries(new Unit("m"));
		values.addValuesListener(this);
	}
	public void testValuesChanged() {
		values.appendValue(11);
		assertEquals(values, notified);
		notified = null;
		values.removeValuesListener(this);
		values.appendValue(12);
		assertEquals(null, notified);
	}
	
	private Values notified = null;
	
	@Override
	public void valuesChanged(Values values) {
		notified = values;
	}
}


Se også Enhetstesting med JUnit

Appendix: Specification of interfaces and classes and their methods. Some implementation details are provided, while other details are not given (since these are your task).
Expand
titleAppendix


Code Block
languagejava
themeEclipselanguagejava
 package kont2015;
/**
 * A class for (scientific) units like meter (m), grams (g) and Kelwin (K).
 * Units may be derived from each other by a linear formula, e.g. km from m and C (Celcius) from Kelwin.
 * @author hal
 *
 */
public class Unit {
	
	/**
	 * Constructor for base units, e.g. meter, gram, kelwin etc.
	 * Initialises with the symbol, e.g. "m" for meters, "g" for grams, "K" for kelwin.
	 * @param symbol The symbol of this Unit, must contain only alphabetic characters.
	 * @throws IllegalArgumentException if the symbol contains characters that are not alphabetic
	 */
	public Unit(String symbol) throws IllegalArgumentException {
		...
	}
	
	/**
	 * Constructor for derived units, e.g. kilometer, milligram and Celcius, derived from meter, gram and Kelwin respectively.
	 * A derived unit includes the factor and offset for the linear formula for computing the base unit from the derived one.
	 * base-unit-value = derived-unit-value * factor + offset
	 * @param symbol The symbol for the derived Unit
	 * @param base The base unit, e.g. meter for kilometer, gram for milligram
	 * @param factor The factor in the formula, e.g. 1000 for km to m or 0.001 for mg to g.
	 * @param offset The offset in the formula.
	 * @throws IllegalArgumentException if the symbol contains characters that are not alphabetic
	 */
	public Unit(String symbol, Unit base, double factor, double offset) throws IllegalArgumentException {
		...
	}
	
	/**
	 * Constructor for derived units, e.g. kilometer, milligram and Celcius, derived from meter, gram and Kelwin respectively.
	 * A derived unit includes the factor and offset for the linear formula for computing the base unit from the derived one.
	 * base-unit-value = derived-unit-value * factor + offset
	 * @param symbol The symbol for the derived Unit
	 * @param base The base unit, e.g. meter for kilometer, gram for milligram
	 * @param factor The factor in the formula, e.g. 1000 for km to m or 0.001 for mg to g. The offset is set to 0.0.
	 * @throws IllegalArgumentException if the symbol contains characters that are not alphabetic
	 */
	public Unit(String symbol, Unit base, double factor) throws IllegalArgumentException {
		...
	}
	
	@Override
	public String toString() {
		return symbol;
	}
	
	/**
	 * Finds the first common unit from which both this and the other Unit is derived.
	 * If other is derived from this, then this is returned, or if this is derived from other, then other is returned.
	 * Otherwise it finds the first base unit that both are derived from.
	 * @param other The other unit.
	 * @return The first common unit that is a common base unit for both this and other.
	 */
	public Unit findCommonBaseUnit(Unit other) {
		...
	}
	/**
	 * Converts value from this unit to the other unit.
	 * @param value The value to convert.
	 * @param other The other unit, that value is converted to.
	 * @return value converted from this unit to the other unit
	 * @throws IllegalArgumentException if there is no common base unit.
	 */
	public double convert(double value, Unit other) throws IllegalArgumentException {
		Unit base = findCommonBaseUnit(other);
		if (base == null) {
			throw new IllegalArgumentException("Cannot convert from " + this + " to " + other);
		}
		double baseValue = convertToBase(value, base);
		return other.convertFromBase(baseValue, base);
	}
	/**
	 * Helper method for converting from this unit to a specific base unit.
	 * @param value The value to convert.
	 * @param base The base unit to convert to.
	 * @return The converted value.
	 */
	private double convertToBase(double value, Unit base) {
		if (this == base) {
			return value;
		}
		if (this.base == null) {
			throw new IllegalArgumentException(base + " is not a base for " + this);
		}
		return this.base.convertToBase(value * factor + offset, base);
	}
	/**
	 * Helper method for converting from a specific base unit to this unit.
	 * @param value The value to convert.
	 * @param base The base unit to convert from.
	 * @return The converted value.
	 */
	private double convertFromBase(double value, Unit base) {
		if (this == base) {
			return value;
		}
		if (this.base == null) {
			throw new IllegalArgumentException(base + " is not a base for " + this);
		}
		return this.base.convertFromBase(value, base) / factor - offset / factor;
	}
	// The currently supported predefined units, that are considered by the valueOf method
	private static Unit
		m = new Unit("m"),
		km = new Unit("km", m, 1000.0),
		dm = new Unit("dm", m, 0.1),
		cm = new Unit("cm", dm, 0.1),
		ALL_UNITS[] = {m, km, dm, cm};
	/**
	 * Finds the Unit for the given symbol among all predefined units.
	 * Currently supported units are m, km, dm, cm 
	 * @param symbol the symbol to search for, e.g. "m" or "dm"
	 * @return the Unit with the given symbol, or null, of no such Unit was found
	 */
	public static Unit valueOf(String symbol) {
		...
	}
}
package java.util.function;
/**
 * Represents a function that accepts two arguments and produces a result.
 * This is the two-arity specialization of {@link Function}.
 */
public interface BinaryOperator<T> {
    /**
     * Applies this function to the given arguments.
     *
     * @param t the first function argument
     * @param u the second function argument
     * @return the function result
     */
    T apply(T t1, T t2); 
}
package kont2015;
import java.util.function.BinaryOperator;
/**
 * A class representing a double value in a certain Unit.
 * Instances are immutable, i.e. cannot be modified once created.
 * @author hal
 *
 */
public class Value implements Comparable<Value> {
	
	/**
	 * Creates a new value with the provided Unit and (double) value.
	 * @param unit the Unit of the new Value
	 * @param value the double value of the new Value
	 */
	public Value(Unit unit, double value) {
		...
	}
	@Override
	public String toString() {
		return getValue() + unit.toString();
	}
	
	/**
	 * Returns this Value's Unit
	 * @return this Value's Unit
	 */
	... get... {
		...
	}
	/**
	 * Returns this Value's double value
	 * @return this Value's double value
	 */
	... get... {
		...
	}
	
	/**
	 * Returns a Value instance from the provided String.
	 * The format is a double (as parsed by Double.valueOf) followed by a Unit symbol (as parsed by Unit.valueOf), e.g. "1.0m" or "2.5km".
	 * @param s
	 * @return the String parses as a Value instance
	 */
	public static Value valueOf(String s) {
		...
	}
	
	/**
	 * Computes the sum of this and other (both Value objects).
	 * The Unit of the returned Value is the common base Unit of this and other's Units.
	 * Hence, both Value object's double values are properly converted before adding. 
	 * @param other the other Value
	 * @return a new Value object representing the sum of this and other
	 */
	public Value add(Value other) {
		...
	}
	/**
	 * Computes a new value that is the combination of this Value's double value and the provided double.
	 * The double values are combined using the provided BinaryOperator.
	 * The Unit of the returned Value is the this Value's Unit.
	 * @param other the double value to combine with this
	 * @return a new Value object representing the sum of this and other
	 */
	public Value compute(BinaryOperator<Double> op, double other) {
		...
	}	
	/**
	 * Computes the product of this Value and other (a double) using the compute method.
	 * @param other the other factor
	 * @return the product of this Value's double value and other (also a double)
	 */
	public Value mult(double other) {
		...
	}
	
	/**
	 * Compares this Value with other according the the requirements of Comparable.
	 * Note that this Value and other may have different Units.
	 * @throws IllegalArgumentException if this and other don't have a common base Unit.
	 */
	@Override
	public int compareTo(Value other) {
		...
	}
}
package kont2015;
/**
 * Interface implemented by classes wanting to be notified of changes to a Values object.
 * @author hal
 *
 */
public interface ValuesListener {
	public void valuesChanged(Values values);
}

package kont2015;
/**
 * An interface representing a sequence of doubles, all with the same Unit.
 * Implementing classes may support adding and removing elements. If modifiable,
 * the class must support notifying ValuesListeners.
 * @author hal
 *
 */
public interface Values extends Iterable<Double> {
	/**
	 * @return this Values' Unit
	 */
	public Unit getUnit();
	/**
	 * @return the number of values in the sequence.
	 */
	public int size();
	
	/**
	 * Computes the average of this Values object's double values.
	 * @return the average as a double (implicitly in this Values' Unit).
	 */
	public double average();
	
	/**
	 * Creates a new Values object where each element is the sum of corresponding elements in this and the other Values object.
	 * @param other the other Values
	 * @return a new Values object that represents the sum of this and other.
	 */
	public Values add(Values other);
	
	// ValuesListener support
	/**
	 * Adds a ValuesListener to be notified of changes to this Values object. 
	 * @param listener
	 */
	...
	/**
	 * Removes a previously added ValuesListener. 
	 * @param listener
	 */
	...
}
package kont2015;
/**
 * A class representing series of doubles, all with the same (unmodifiable) Unit.
 * A Values object can only be modified by appending another value.
 * @author hal
 *
 */
public class ValueSeries implements Values {
	/**
	 * Constructs a new ValueSeries object, with the provided Unit.
	 * @param unit
	 */
	public ValueSeries(...) {
		...
	}
	/**
	 * Appends the provided double to this ValueSeries. The double value is assumed to be in the Unit of this ValueSeries.
	 * @param value the double to append
	 */
	public void appendValue(double value) {
		...
	}
	/**
	 * Appends the provided Value to this ValueSeries, by first converting it to this ValueSeries' unit and then appending it.
	 * @param value the Value to append, after converting it to this ValueSeries' Unit.
	 */
	public void appendValue(Value value) {
		...
	}
	// Values methods
	...
}


...