Last active
January 12, 2019 18:41
-
-
Save btforsythe/b60f153f43ff4a052ef9ffb53c3d3b15 to your computer and use it in GitHub Desktop.
Feeding and water virtual pets: from names classed to method references
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package virtualpet; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.function.Consumer; | |
import java.util.function.Function; | |
import java.util.function.Predicate; | |
import java.util.stream.Stream; | |
public class VirtualPetShelter { | |
private Map<String, VirtualPet> pets = new HashMap<>(); | |
/** | |
* Feed with for loop and cast. Using this approach, we see similar code | |
* duplicated throughout the class for water, play, etc. Testing becomes | |
* onerous, especially if one is properly test driving. | |
*/ | |
public void feedWithFor() { | |
for (VirtualPet p : pets.values()) { | |
if (p instanceof Organic) { | |
((Organic) p).feed(); | |
} | |
} | |
} | |
/** | |
* Here we see the duplication in the water method. | |
*/ | |
public void waterWithFor() { | |
for (VirtualPet p : pets.values()) { | |
if (p instanceof Organic) { | |
((Organic) p).water(); | |
} | |
} | |
} | |
/** | |
* The way to go about feeding all organics with objects defined as named | |
* classes in the old school fashion. An immediate benefit is that once we have | |
* verified that a function works, we need not verify that the same function | |
* works for other uses (functional composition). We probably would make these | |
* objects instance variables, but I'm keeping them here for (what I hope is) | |
* clarity. | |
*/ | |
public void feedWithObjectsFromNamedClasses() { | |
OrganicSelector selectOrganics = new OrganicSelector(); | |
PetToOrganicCaster castToOrganic = new PetToOrganicCaster(); | |
Feeder feeder = new Feeder(); | |
pets.values().stream().filter(selectOrganics).map(castToOrganic).forEach(feeder); | |
} | |
private class OrganicSelector implements Predicate<VirtualPet> { | |
@Override | |
public boolean test(VirtualPet p) { | |
return p instanceof Organic; | |
} | |
} | |
// we could also do this with a cast inside Feeder, but this is cleaner | |
private class PetToOrganicCaster implements Function<VirtualPet, Organic> { | |
@Override | |
public Organic apply(VirtualPet p) { | |
return (Organic) p; | |
} | |
} | |
private class Feeder implements Consumer<Organic> { | |
@Override | |
public void accept(Organic p) { | |
p.feed(); | |
} | |
} | |
/** | |
* Here is an obvious refactoring. We can now do various things with organic | |
* pets by reusing organicPets(). | |
*/ | |
public void feedWithObjectsFromNamedClassesRefactored() { | |
Feeder feeder = new Feeder(); | |
organicPetsFromNamed().forEach(feeder); | |
} | |
private Stream<Organic> organicPetsFromNamed() { | |
OrganicSelector selectOrganics = new OrganicSelector(); | |
PetToOrganicCaster castToOrganic = new PetToOrganicCaster(); | |
return pets.values().stream().filter(selectOrganics).map(castToOrganic); | |
} | |
/** | |
* Now let's do it with instances of anonymous inner classes. This is common | |
* when our functions are simple and not reproduced. Creating them as instance | |
* variables this time, cleaner. | |
*/ | |
private Predicate<VirtualPet> selectOrganicsAnonymous = new Predicate<VirtualPet>() { | |
@Override | |
public boolean test(VirtualPet p) { | |
return p instanceof Organic; | |
} | |
}; | |
private Function<VirtualPet, Organic> castToOrganicAnonymous = new Function<VirtualPet, Organic>() { | |
@Override | |
public Organic apply(VirtualPet p) { | |
return (Organic) p; | |
} | |
}; | |
private Consumer<Organic> feederAnonymous = new Consumer<Organic>() { | |
@Override | |
public void accept(Organic p) { | |
p.feed(); | |
} | |
}; | |
public void feedWithObjectsFromAnonymousClasses() { | |
pets.values().stream().filter(selectOrganicsAnonymous).map(castToOrganicAnonymous).forEach(feederAnonymous); | |
} | |
/** | |
* Again the obvious refactoring. | |
*/ | |
public void feedWithObjectsFromAnonymousClassesRefactored() { | |
organicPetsAnonymous().forEach(feederAnonymous); | |
} | |
private Stream<Organic> organicPetsAnonymous() { | |
return pets.values().stream().filter(selectOrganicsAnonymous).map(castToOrganicAnonymous); | |
} | |
/** | |
* Which allows me to do something like this. All I would need to do for testing | |
* purposes (assumed I had already test-driven organic pets selection in feed or | |
* otherwise) would be to check that the watering Consumer works. | |
*/ | |
public void waterAnonymous() { | |
organicPetsAnonymous().forEach(new Consumer<Organic>() { | |
@Override | |
public void accept(Organic p) { | |
p.water(); | |
} | |
}); | |
} | |
/** | |
* In Java, we can treat any interface with only one method as a functional | |
* interface, whether or not they are annotated with | |
* {@link FunctionalInterface}. | |
*/ | |
public void feedWithLambdas() { | |
Predicate<VirtualPet> selectOrganics = p -> p instanceof Organic; | |
Function<VirtualPet, Organic> castToOrganic = p -> (Organic) p; | |
Consumer<Organic> feeder = p -> p.feed(); | |
pets.values().stream().filter(selectOrganics).map(castToOrganic).forEach(feeder); | |
} | |
/** | |
* We would often inline these. | |
*/ | |
public void feedWithLambdasConcise() { | |
pets.values().stream().filter(p1 -> p1 instanceof Organic).map(p2 -> (Organic) p2).forEach(p -> p.feed()); | |
} | |
/** | |
* Again, this refactoring. | |
*/ | |
public void feedWithLambdasConciseRefactored() { | |
organicPetsLambda().forEach(p -> p.feed()); | |
} | |
private Stream<Organic> organicPetsLambda() { | |
return pets.values().stream().filter(p -> p instanceof Organic).map(p -> (Organic) p); | |
} | |
/** | |
* If we extract a method that allows us to select pets of a given type, we can | |
* avoid duplication of that concept. To support that, I'll first replace the | |
* instanceof with {@link Class#isAssignableFrom(Class)} and the cast with a | |
* method reference to {@link Class#cast(Object)}. (I might have gotten the | |
* isAssignableFrom backwards.) | |
*/ | |
private Stream<Organic> organicPetsLambdaRefactored() { | |
return pets.values().stream().filter(p -> Organic.class.isAssignableFrom(p.getClass())) | |
.map(Organic.class::cast); | |
} | |
/** | |
* Now I can extract a method, passing it Organic.class. We are creating a | |
* closure when we do this. | |
*/ | |
private Stream<Organic> organicPets() { | |
return petsThatAre(Organic.class); | |
} | |
private <T> Stream<T> petsThatAre(Class<T> type) { | |
return pets.values().stream().filter(p -> type.isAssignableFrom(p.getClass())).map(type::cast); | |
} | |
/** | |
* Final versions. I'm using method references rather than lambda expressions | |
* because they seem more expressive / easier to parse in this case. | |
*/ | |
public void feed() { | |
organicPets().forEach(Organic::feed); | |
} | |
public void water() { | |
organicPets().forEach(Organic::water); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment