Skip to content

Commit 33759c1

Browse files
author
Julien Letrouit
authored
Merge pull request #50 from jletroui/interfaces-koans
Interfaces koans
2 parents 2c5f7e2 + 49affc1 commit 33759c1

39 files changed

Lines changed: 1737 additions & 100 deletions

CONTRIBUTING.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ We expressed above a concern for saving student's attention / motivation / time.
6464
### Design goals
6565

6666
- Strive to work on a bare WPILib installation: on VSCode with no need for a plugin.
67-
- Simple start: no dependency other than the Java standard library, so as to avoid a build step with a dependency management tool.
67+
- Simple start: no dependency other than the Java standard library, so as to avoid a build step with a dependency management tool. This has consequences: the project has to includes a mini test framework for example.
6868
- Java 17, because as of 2024, this is the version used by default in WPILib's VSCode.
6969

7070
### Compromises and limitations
@@ -79,3 +79,15 @@ We expressed above a concern for saving student's attention / motivation / time.
7979
Pull requests for translations, curiculum tweaks or new capabilities are welcome.
8080

8181
When submitting bugs, please submit a zip file of the koans in a state exhibiting the issue.
82+
83+
## Testing
84+
85+
Testing this kind of project is challenging for a few reasons, main ones being:
86+
87+
1. By design, we don't have a build tool (Gradle or Maven for example), nor access to any libraries. So no JUnit on hand.
88+
2. We would not want to include solutions to the koans within the project, because the students might stumble on them, which would affect their learning.
89+
90+
For 1., we have created a mini test framework in `engine.test.runner` in order to run unit and integration tests of the koans engine. Tests are located in `engine.test`. To run those tests, simply run the `engine.test.runner.TestRunner.main` method.
91+
92+
For 2., we have created a [brother project](https://github.com/jletroui/FrcJavaKoansTests) testing the koans themselves.
93+

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ To get started you will need to either install VS Code or use GitHub Codespaces,
2828

2929
To install VS Code, you will need to install [WPILib](https://docs.wpilib.org/en/stable/docs/zero-to-robot/step-2/wpilib-setup.html) first to run the Java Koans for the FRC.
3030

31-
Once installed, download the [latest release](https://github.com/jletroui/FrcJavaKoans/releases/download/v1.2/FrcJavaKoans.zip) of the Java Koans.
31+
Once installed, download the [latest release](https://github.com/jletroui/FrcJavaKoans/releases/download/v2.0/FrcJavaKoans.zip) of the Java Koans.
3232

3333
Then, extract it somewhere on your computer. Go to the folder where you have downloaded the koans, righ-click on the koans zip file, and choose 'Extract All'. Choose your destination folder, for example, a `/src` folder within your `Documents` folder.
3434

@@ -347,7 +347,7 @@ Pour commencer, vous devrez soit installer VS Code, soit utiliser GitHub Codespa
347347

348348
Si ce n'est déjà fait, tu vas devoir installer [WPILib](https://docs.wpilib.org/en/stable/docs/zero-to-robot/step-2/wpilib-setup.html) pour pouvoir exécuter les Koans Java pour la FRC.
349349
350-
Une fois installé, télécharge [la dernière version](https://github.com/jletroui/FrcJavaKoans/releases/download/v1.2/FrcJavaKoans.zip) des Koans Java.
350+
Une fois installé, télécharge [la dernière version](https://github.com/jletroui/FrcJavaKoans/releases/download/v2.0/FrcJavaKoans.zip) des Koans Java.
351351
352352
Ensuite, décompresse les quelque part sur ton ordinateur. Pour ce faire, va dans le répertoire où tu as téléchargé les koans, clic-droit dessus, et choisis "Extraire Tout". Choisis un répertoire de destination, par exemple, un répertoire `/src` dans ton répertoire `Documents`.
353353
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
package bonuses.english;
2+
3+
import java.util.List;
4+
import java.util.function.IntPredicate;
5+
6+
import bonuses.teachingmaterial.Combining;
7+
import engine.Locale;
8+
import engine.Sensei;
9+
import sensei.AboutInterfacesKoans;
10+
11+
public class AboutInterfaces {
12+
/**
13+
* # First interface implementations
14+
*
15+
* Write a class 'numbers.AddNumbers' which implements interface 'bonuses.teachingmaterial.Combining'.
16+
* The implementation of the combine() method should return the 2 numbers added together.
17+
* Write a class 'numbers.MultiplyNumbers' which also implements interface 'bonuses.teachingmaterial.Combining'.
18+
* This implementation of the combine() method should return the 2 numbers multiplied together.
19+
*
20+
* --------- TIPS --------------
21+
*
22+
* Consider the following situation:
23+
*
24+
* You are in the middle of a large, empty room, when a zombie suddenly attacks you.
25+
* You have no weapon.
26+
* Luckily, a fellow living human is standing in the doorway of the room.
27+
* "Quick!" you shout at him. "Throw me something I can hit the zombie with!"
28+
*
29+
* Now consider:
30+
* You didn't specify (nor do you care) exactly what your friend will choose to toss; ...But it doesn't matter, as long as:
31+
*
32+
* 1) It's something that can be tossed (He can't toss you the sofa)
33+
* 2) It's something that you can grab hold of (Not a wet soap)
34+
* 3) It's something you can use to bash the zombie's brains out (That rules out pillows and such)
35+
*
36+
* It doesn't matter whether you get a baseball bat or a hammer - as long as it implements your three conditions, you're good.
37+
*
38+
* In Java, you often need an object with specific methods, whatever its class is.
39+
* An interface as a kind of contract that an object would respect, by the object implementing the methods listed in the contract.
40+
*
41+
* For example, in a game where the situation above would happen, you could create the following interface:
42+
*
43+
* public interface Weapon {
44+
* void hit(Monster monster);
45+
* }
46+
*
47+
* 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.
48+
*
49+
* Respecting the contract of a Java interface is called 'implementing the interface'. A class can implement an interface this way:
50+
*
51+
* you declare that this class will implement the Weapon interface
52+
* vvvvvvvvvvvvvvvvv
53+
*
54+
* public class Sword implements Weapon {
55+
*
56+
* // Since Sword implements Weapon, it must implement the hit method.
57+
* @Override // This strange annotation tells Java the method is defined elsewhere (in our interface)
58+
* public void hit(Monster monster) {
59+
* // Some code computing and applying damage to the monster, applying some tear and wear on the weapon, etc...
60+
* }
61+
*
62+
* }
63+
*
64+
* Now, the code in 'hit()' maybe complicated but it does not matter: it follows the contract of the interface, and you can call it.
65+
* For example, you could create an object allowing you to hit a zombie with the following code:
66+
*
67+
* Monster zombie = ...; // Code getting the object for the zombie in the middle of the room
68+
* Weapon tossedWeapon = new Sword(); // or 'new Hammer()' or 'new Axe()' or 'new WhateverImplementsWeapon()'
69+
* tossedWeapon.hit(zombie); // use the Weapon interface
70+
*
71+
* Notice the type of the variable 'tossedWeapon' is a 'Weapon', not a 'Sword'. Interfaces, like classes, are types you can use for your variables, fields, and parameters.
72+
* Because 'Sword' implements 'Weapon', Java considers that a 'Sword' object _is_ a 'Weapon'. Having variables using the interface type allows it to take values from objects from multiple classes, as long as they all implement the interface.
73+
*
74+
* Take a look at the 'bonuses.teachingmaterial.Combining' interface. It defines a method which can be implemented in various ways.
75+
* This exercise is about implementing that interface in 2 ways.
76+
*
77+
* -------------------------------
78+
*
79+
* Expected result:
80+
*
81+
* The following code:
82+
*
83+
* Combining combining = new AddNumbers();
84+
* System.out.println(combining.combine(3, 4));
85+
* combining = new MultiplyNumbers();
86+
* System.out.println(combining.combine(3, 4));
87+
*
88+
* Should display:
89+
*
90+
* 7
91+
* 12
92+
*
93+
*/
94+
95+
96+
/**
97+
* # Anonymous interface implementation
98+
*
99+
* Write a method 'getAnonymousCombining' which returns an anonymous implementation of 'bonuses.teachingmaterial.Combining'.
100+
* The implementation of the combine() method should return the second number subtracted from the first.
101+
*
102+
* --------- TIPS --------------
103+
*
104+
* Sometimes, creating a file and a public class is a lot of work when we implement a simple interface, and we only use it in a single place.
105+
* In such a situation, you can implement the interface in an anonymous class. It is anonymous, because it does not have a name.
106+
* That class is instantiated immediately where it is created. For example:
107+
*
108+
* public Weapon toss() {
109+
* return new Weapon() {
110+
* @Override
111+
* public void hit(Monster monster) {
112+
* // Some code computing and applying damage to the monster, applying some tear and wear on the weapon, etc...
113+
* }
114+
* }
115+
* }
116+
*
117+
* When looking at this code, you could be tempted to believe there is a constructor for the interface Weapon, but there is not.
118+
* We are really creating a class, for which the only place we will create objects is this 'toss()' method.
119+
* The constructor with empty parameters is the one of this nameless class.
120+
* We can now get and use the tossed weapon this way:
121+
*
122+
* Weapon tossedWeapon = toss();
123+
* tossedWeapon.hit(zombie);
124+
*
125+
* -------------------------------
126+
*
127+
* Expected result:
128+
*
129+
* getAnonymousCombining().combine(3, 4) should return -1
130+
*
131+
*/
132+
133+
134+
/**
135+
* # Lambda methods
136+
*
137+
* Write a method 'getLambdaCombining' which returns an lambda method implementing 'bonuses.teachingmaterial.Combining'.
138+
* The implementation of the combine() method should return the first number subtracted from the second.
139+
*
140+
* --------- TIPS --------------
141+
*
142+
* When an interface have only one method, there is an even shorter form to implement it. You can create what is called a "lambda method".
143+
* A 'lambda method' is a stripped down version of a method. Since our example interface 'Weapon' has only a single method 'hit()', we can use this shortcut:
144+
*
145+
* For example:
146+
*
147+
* public Weapon toss() {
148+
* return (monster) -> {
149+
* // Some code computing and applying damage to the monster, applying some tear and wear on the weapon, etc..
150+
* };
151+
* }
152+
*
153+
* We can now get and use the tossed weapon this way:
154+
*
155+
* Weapon tossedWeapon = toss();
156+
* tossedWeapon.hit(zombie);
157+
*
158+
* The general syntax for lambda method having a body with multiple lines is:
159+
*
160+
* ([param1Name], [param2Name], ...) -> {
161+
* // Lambda method body here
162+
* }
163+
*
164+
* If your lambda is having a single expression, you can even skip the curly brackets and the 'return' statement:
165+
*
166+
* ([param1Name], [param2Name], ...) -> // expression here
167+
*
168+
* Here are some example of methods and their lambda equivalent (assuming the interface has only one of these methods in its contract):
169+
*
170+
* This interface implementation:
171+
*
172+
* public void sayHello() {
173+
* System.out.println("hello");
174+
* }
175+
*
176+
* Can be replaced by this lambda:
177+
*
178+
* () -> System.out.println("hello")
179+
*
180+
* This interface implementation:
181+
*
182+
* public int square(int x) {
183+
* return x * x;
184+
* }
185+
*
186+
* Can be replaced by this lambda:
187+
*
188+
* (x) -> x * x
189+
*
190+
* This interface implementation:
191+
*
192+
* public int min(int x, int y) {
193+
* if (x < y) {
194+
* return x;
195+
* }
196+
* return y;
197+
* }
198+
*
199+
* Can be replaced by this lambda:
200+
*
201+
* (x, y) -> {
202+
* if (x < y) {
203+
* return x;
204+
* }
205+
* return y;
206+
* }
207+
*
208+
* -------------------------------
209+
*
210+
* Expected result:
211+
*
212+
* getLambdaCombining().combine(3, 4) should return 1
213+
*
214+
*/
215+
216+
217+
/**
218+
* # Common lambda interfaces
219+
*
220+
* Write a method 'getIsEven' which returns a lambda method testing if an integer is even.
221+
*
222+
* --------- TIPS --------------
223+
*
224+
* Note: the {@link java.lang.String} notation allows to show a link to a class in a comment. To see the class, you can [CTRL] + clic on its name.
225+
*
226+
* Since lambda methods are so useful, a lot of simple interfaces already exist in the Java standard library, and we don't have to create them ourselves.
227+
* For example, an interface with a method having the same signature as the 'combine()' method already exists. It is called the {@link java.util.function.IntBinaryOperator}.
228+
*
229+
* Other examples:
230+
*
231+
* For a lambda taking no parameter, and returning nothing, {@link java.lang.Runnable}:
232+
*
233+
* Runnable sayHello = () -> System.out.println("Hello");
234+
*
235+
* For a lambda taking a 'int' parameter, and returning nothing, {@link java.util.function.IntConsumer}:
236+
*
237+
* IntConsumer displayInt = (anInt) -> System.out.println(anInt);
238+
*
239+
* The same exist for other parameter types. For example {@link java.util.function.DoubleConsumer}:
240+
*
241+
* DoubleConsumer displayDouble = (aDouble) -> System.out.println(aDouble);
242+
*
243+
* The reverse methods, taking no parameter, but returning something exist as well: {@link java.util.function.IntSupplier}, {@link java.util.function.DoubleSupplier}. etc...
244+
*
245+
* DoubleSupplier giveMePiPleeeaaase = () -> 3.14159;
246+
*
247+
* There is also a lot of case where you would need to test if a number respect a certain condition. This is where "predicate" interfaces like {@link java.util.function.IntPredicate} shine:
248+
*
249+
* IntPredicate isPositive = (number) -> number >= 0;
250+
*
251+
* For the exercise, you can use the modulo operator, %, which computes the remainder of an integer division:
252+
*
253+
* int remainder = 17 % 5; // remainder equals 2
254+
*
255+
* -------------------------------
256+
*
257+
* Expected result:
258+
*
259+
* getIsEven().test(4) should return true
260+
*
261+
*/
262+
263+
264+
public static void main(String[] args) {
265+
new Sensei(Locale.en, List.of(AboutInterfacesKoans.koans)).offerKoans();
266+
}
267+
}

0 commit comments

Comments
 (0)