Skip to content

Commit a5f1318

Browse files
author
Julien Letrouit
committed
First koan in AboutInterfaces
1 parent 1b8b9fc commit a5f1318

11 files changed

Lines changed: 328 additions & 1 deletion

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package bonuses.english;
2+
3+
import java.util.List;
4+
5+
import engine.Locale;
6+
import engine.Sensei;
7+
import sensei.AboutInterfacesKoans;
8+
9+
public class AboutInterfaces {
10+
/**
11+
* # First interface implementations
12+
*
13+
* Write a class 'numbers.AddNumbers' which implements interface 'bonuses.teachingmaterial.Combining'.
14+
* The implementation of the combine() method should return the 2 numbers added together.
15+
* Write a class 'numbers.MultiplyNumbers' which also implements interface 'bonuses.teachingmaterial.Combining'.
16+
* This implementation of the combine() method should return the 2 numbers multiplied together.
17+
*
18+
* --------- TIPS --------------
19+
*
20+
* Consider the following situation:
21+
*
22+
* You are in the middle of a large, empty room, when a zombie suddenly attacks you.
23+
* You have no weapon.
24+
* Luckily, a fellow living human is standing in the doorway of the room.
25+
* "Quick!" you shout at him. "Throw me something I can hit the zombie with!"
26+
*
27+
* Now consider:
28+
* You didn't specify (nor do you care) exactly what your friend will choose to toss; ...But it doesn't matter, as long as:
29+
*
30+
* 1) It's something that can be tossed (He can't toss you the sofa)
31+
* 2) It's something that you can grab hold of (Not a wet soap)
32+
* 3) It's something you can use to bash the zombie's brains out (That rules out pillows and such)
33+
*
34+
* It doesn't matter whether you get a baseball bat or a hammer - as long as it implements your three conditions, you're good.
35+
*
36+
* In Java, you sometimes need an object that implements "conditions": one or more specific methods. But you don't care about its class. The interface is what will describe those methods.
37+
* We can see the interface as a kind of contract that an object would respect.
38+
*
39+
* For example, in a game where the situation above would happen, you could create the following interface in a game:
40+
*
41+
* public interface Weapon {
42+
* void hit(Monster monster);
43+
* }
44+
*
45+
* Now, whether your weapon is a sword inflicting 10 damage points, or a knife inflicting only 4 damage points to the monster, the player will be able to hit a monster with it.
46+
*
47+
* Respecting the contract of a Java interface is called 'implementing the interface'. A class can implement an interface this way:
48+
*
49+
* you declare that this class will implement the Weapon interface
50+
* vvvvvvvvvvvvvvvvv
51+
*
52+
* public class Sword implements Weapon {
53+
*
54+
* // Since Sword implements Weapon, it must implement the hit method.
55+
* @Override // This special annotation means the method is defined elsewhere (our interface in this case)
56+
* public void hit(Monster monster) {
57+
* // some code computing and applying damage to the monster, applying some tear and wear on the weapon, etc...
58+
* }
59+
*
60+
* }
61+
*
62+
*
63+
* Now, the code in hit() maybe complicated but it does not matter: it follows the contract of the interface, and you can call it.
64+
* For example, you could create an object allowing you to hit a zombie with the following code:
65+
*
66+
* Monster zombie = ...; // Code getting the object for the zombie in the middle of the room
67+
* Weapon tossedWeapon = new Sword(); // or new Hammer() or new Axe() or new WhateverImplementsWeapon()
68+
* tossedWeapon.hit(zombie); // use the Weapon interface
69+
*
70+
* Notice the type of the variable 'tossedWeapon': it is a Weapon, not a Sword. Interfaces, like classes, are types you can use for variables, fields, and parameters.
71+
* Because Sword implements Weapon, Java considers that a Sword object _is_ a Weapon. Having variables and parameters using the interface type allows you to work with any object implementing that interface.
72+
*
73+
* Take a look at the bonuses.teachingmaterial.Combining interface. It defines a method which can be implemented in various ways.
74+
* This exercise is about implementing that interface in 2 ways.
75+
*
76+
* -------------------------------
77+
*
78+
* Expected result:
79+
*
80+
* The following code:
81+
*
82+
* Combining combining = new AddNumbers();
83+
* System.out.println(combining.combine(3, 4));
84+
* combining = new MultiplyNumbers();
85+
* System.out.println(combining.combine(3, 4));
86+
*
87+
* Should display:
88+
*
89+
* 7
90+
* 12
91+
*
92+
*/
93+
94+
95+
public static void main(String[] args) {
96+
new Sensei(Locale.en, List.of(AboutInterfacesKoans.koans)).offerKoans();
97+
}
98+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package bonuses.french;
2+
3+
import java.util.List;
4+
5+
import engine.Locale;
6+
import engine.Sensei;
7+
import sensei.AboutInterfacesKoans;
8+
9+
public class AboutInterfaces {
10+
/**
11+
* # For loops
12+
*
13+
* Write a method named 'displayNumbers' with an integer parameter 'n', which displays numbers between 1 and n.
14+
* Use a 'for' loop instead of a 'while' loop. Use the shortest form of incrementation / decrementation.
15+
*
16+
* --------- TIPS --------------
17+
*
18+
* To do things multiple times in Java, you already know the 'while' loop.
19+
* Most of the time, a 'while' loop has very similar code structure:
20+
*
21+
* [Initialize a counter];
22+
* while ([Condition on the counter variable]) {
23+
* // Do stuff repetedly
24+
* [Modify the counter at the end of the loop];
25+
* }
26+
*
27+
* Ex:
28+
*
29+
* int times = 3; // Initialize a counter
30+
* while (times > 0) { // Condition on the counter variable
31+
* System.out.println("Still executing"); // Do stuff repetedly
32+
* times = times -1; // Modify the counter at the end of the loop
33+
* }
34+
*
35+
* Since this pattern is used again and again, there is a shortcut in Java to make it terser. It's called a 'for' loop, where all 3 parts seen above are grouped on a single line:
36+
*
37+
* for([Initialize a counter]; [Condition on the counter variable]; [Modify the counter at the end of the loop]) {
38+
* // do stuff repetedly
39+
* }
40+
*
41+
* Ex:
42+
*
43+
* for(int times = 3; times > 0; times = times -1) {
44+
* System.out.println("Still executing"); // Do stuff repetedly
45+
* }
46+
*
47+
* There is even another shortcut: since incrementing or decrementing a number is something that happens very often, there is a short form:
48+
*
49+
* a = a - 2; // Long form
50+
* a -= 2; // Short form
51+
* b = b + 2; // Long form
52+
* b += 2; // Short form
53+
*
54+
* When we increment / decrement by one, there is an even shorter form:
55+
*
56+
* a -= 1; // Short form
57+
* a--; // Shortest form
58+
* b += 1; // Short form
59+
* b++; // Shortest form
60+
*
61+
* Using this, our for loop can become even terser:
62+
*
63+
* for(int times = 3; times > 0; times--) {
64+
* System.out.println("Still executing"); // Do stuff repetedly
65+
* }
66+
*
67+
* -------------------------------
68+
*
69+
* Expected result:
70+
*
71+
* displayNumbers(3) should display:
72+
*
73+
* 1
74+
* 2
75+
* 3
76+
*
77+
*/
78+
79+
80+
public static void main(String[] args) {
81+
new Sensei(Locale.fr, List.of(AboutInterfacesKoans.koans)).offerKoans();
82+
}
83+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package bonuses.teachingmaterial;
2+
3+
/**
4+
* This file is used by the AboutInterfaces koans.
5+
*/
6+
public interface Combining {
7+
/**
8+
* somehow combines 2 integers a and b, and returns the result of this combination.
9+
*/
10+
int combine(int a, int b);
11+
}

src/main/java/engine/Assertions.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,4 +438,20 @@ public static BeforeTestAssertion assertField(final String className, final Stri
438438
return true;
439439
};
440440
}
441+
442+
public static BeforeTestAssertion assertImplementsInterface(final String className, final Class<?> interfaceClass) {
443+
return (p, locale, koan) -> {
444+
final var type = new Type(className);
445+
final var clasz = type.unsafeResolve();
446+
final var success = Arrays
447+
.stream(clasz.getInterfaces())
448+
.anyMatch(implemented -> implemented == interfaceClass);
449+
450+
if (!success) {
451+
p.println(format(EXPECTED_CLASS_TO_IMPLEMENT, Formats.Red, code(className), code(interfaceClass.getName())));
452+
}
453+
454+
return success;
455+
};
456+
}
441457
}

src/main/java/engine/Texts.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,5 +204,8 @@ public class Texts {
204204
public static Localizable<String> EXPECTED_FIELD_TO_BE_OF_TYPE =
205205
local("Expected '%s' field in class %s to be a '%s', but it is a '%s'.")
206206
.fr("Attendu à ce que le champ '%s' dans la classe %s soit un '%s', mais il est un '%s'.");
207+
public static Localizable<String> EXPECTED_CLASS_TO_IMPLEMENT =
208+
local("Expected class %s to implement %s, but it does not.")
209+
.fr("Attendu à ce la classe %s implémente %s, mais elle ne l'implémente pas.");
207210

208211
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package engine.test;
2+
3+
import static engine.Assertions.assertImplementsInterface;
4+
import static engine.ConsoleFmt.code;
5+
import static engine.ConsoleFmt.format;
6+
import static engine.Localizable.global;
7+
8+
import java.util.List;
9+
import java.util.function.IntConsumer;
10+
11+
import engine.Koan;
12+
import engine.Localizable;
13+
import engine.ConsoleFmt.Formats;
14+
import engine.test.simulation.SomeInterface;
15+
import engine.test.simulation.StudentSolutions;
16+
17+
import static engine.script.Expression.callKoanMethod;
18+
import static engine.Texts.*;
19+
import static engine.test.UnitTestExpectation.assertSuccess;
20+
import static engine.test.UnitTestExpectation.assertFailure;
21+
22+
public class ReflectionAssertionsUnitTests {
23+
private static Localizable<Class<?>> CLASS = global(StudentSolutions.class);
24+
25+
public static final List<UnitTest> items = List.of(
26+
new UnitTest(
27+
new Koan(CLASS, global("implements interface"))
28+
.beforeFirstTest(assertImplementsInterface("engine.test.simulation.SomeImplementation", SomeInterface.class))
29+
.when(callKoanMethod("simpleConsoleOutput")),
30+
assertSuccess()
31+
),
32+
new UnitTest(
33+
new Koan(CLASS, global("does not implement interface"))
34+
.beforeFirstTest(assertImplementsInterface("engine.test.simulation.SomeImplementation", IntConsumer.class))
35+
.when(callKoanMethod("simpleConsoleOutput")),
36+
assertFailure(
37+
new Line.Localized(format(EXPECTED_CLASS_TO_IMPLEMENT, Formats.Red, code("engine.test.simulation.SomeImplementation"), code("java.util.function.IntConsumer")))
38+
)
39+
)
40+
);
41+
}

src/main/java/engine/test/TestRunner.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
public class TestRunner {
1111
private static final List<List<UnitTest>> TO_RUN = List.of(
1212
ConsoleAssertionsUnitTests.items,
13-
EqualityAssertionsUnitTests.items
13+
EqualityAssertionsUnitTests.items,
14+
ReflectionAssertionsUnitTests.items
1415
);
1516
private static final Map<Boolean, String> SUCCESS_WORDING = Map.of(
1617
true, "success",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package engine.test.simulation;
2+
3+
public class SomeImplementation implements SomeInterface {
4+
@Override
5+
public int op(int a, int b) {
6+
return a + b;
7+
}
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package engine.test.simulation;
2+
3+
public interface SomeInterface {
4+
int op(int a, int b);
5+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package sensei;
2+
3+
import static engine.Assertions.assertReturnValueEquals;
4+
import static engine.Assertions.assertConstructorIsInvokable;
5+
import static engine.Assertions.assertImplementsInterface;
6+
import static engine.Localizable.localClass;
7+
import static engine.script.Expression.newObject;
8+
import static sensei.Texts.*;
9+
10+
import java.util.List;
11+
12+
import bonuses.teachingmaterial.Combining;
13+
import engine.Koan;
14+
import engine.Localizable;
15+
16+
17+
public class AboutInterfacesKoans {
18+
private static final Localizable<Class<?>> CLASS =
19+
localClass(bonuses.english.AboutInterfaces.class)
20+
.fr(bonuses.french.AboutInterfaces.class);
21+
22+
public static final List<Koan> koans = List.of(
23+
new Koan(CLASS, FIRST_INTERFACE_IMPLEMENTATIONS)
24+
.beforeFirstTest(
25+
assertConstructorIsInvokable("numbers.AddNumbers"),
26+
assertConstructorIsInvokable("numbers.MultiplyNumbers"),
27+
assertImplementsInterface("numbers.AddNumbers", Combining.class),
28+
assertImplementsInterface("numbers.MultiplyNumbers", Combining.class)
29+
)
30+
.when(
31+
newObject("numbers.AddNumbers").call("combine", 5, 7)
32+
)
33+
.then(
34+
assertReturnValueEquals(12)
35+
)
36+
.when(
37+
newObject("numbers.AddNumbers").call("combine", 0, -1)
38+
)
39+
.then(
40+
assertReturnValueEquals(-1)
41+
)
42+
.when(
43+
newObject("numbers.MultiplyNumbers").call("combine", 5, 7)
44+
)
45+
.then(
46+
assertReturnValueEquals(35)
47+
)
48+
.when(
49+
newObject("numbers.MultiplyNumbers").call("combine", 0, -1)
50+
)
51+
.then(
52+
assertReturnValueEquals(0)
53+
)
54+
);
55+
}

0 commit comments

Comments
 (0)