蘋果篩選
1. 篩選綠色的蘋果
@Test
public void givenAppleRepoWhenFilterGreenAppleThenReturn3(){
//given
Apple apple1 = new Apple("green", "70");
Apple apple2 = new Apple("red", "100");
Apple apple3 = new Apple("red", "80");
Apple apple4 = new Apple("green", "170");
Apple apple5 = new Apple("green", "60");
List<Apple> appleRepo = asList(apple1,apple2,apple3,apple4,apple5);
AppleFilter filter = new AppleFilter();
//when
List<Apple> greenApples = filter.findGreenApple(appleRepo);
//then
assertThat(greenApples.size(),is(3));
}
public class AppleFilter {
public List<Apple> findGreenApple(List<Apple> appleRepo) {
List<Apple> result = new ArrayList<>();
for(Apple apple : appleRepo){
if(apple.getColor().equals("green")){
result.add(apple);
}
}
return result;
}
}
2.篩選紅色的蘋果
@Test
public void givenAppleRepoWhenFilterRedAppleThenReturn2(){
//given
Apple apple1 = new Apple("green", "70");
Apple apple2 = new Apple("red", "100");
Apple apple3 = new Apple("red", "80");
Apple apple4 = new Apple("green", "170");
Apple apple5 = new Apple("green", "60");
List<Apple> appleRepo = asList(apple1,apple2,apple3,apple4,apple5);
AppleFilter filter = new AppleFilter();
//when
List<Apple> redApples = filter.findRedApple(appleRepo);
//then
assertThat(redApples.size(),is(2));
}
@Test
public void givenAppleRepoWhenFilterGreenAppleThenReturn3(){
//given
Apple apple1 = new Apple("green", "70");
Apple apple2 = new Apple("red", "100");
Apple apple3 = new Apple("red", "80");
Apple apple4 = new Apple("green", "170");
Apple apple5 = new Apple("green", "60");
List<Apple> appleRepo = asList(apple1,apple2,apple3,apple4,apple5);
AppleFilter filter = new AppleFilter();
//when
List<Apple> greenApples = filter.findGreenApple(appleRepo);
//then
assertThat(greenApples.size(),is(3));
}
public List<Apple> findGreenApple(List<Apple> appleRepo) {
List<Apple> result = new ArrayList<>();
for(Apple apple : appleRepo){
if(apple.getColor().equals("green")){
result.add(apple);
}
}
return result;
}
public List<Apple> findRedApple(List<Apple> appleRepo) {
List<Apple> result = new ArrayList<>();
for(Apple apple : appleRepo){
if(apple.getColor().equals("red")){
result.add(apple);
}
}
return result;
}
**Duplicated is evil **
通過參數(shù)化牡拇,消除hard code 和重復(fù)
測(cè)試代碼同樣需要重構(gòu)懒闷,TDD很難被普及的一個(gè)原因之一,就是沒有持續(xù)的對(duì)Test進(jìn)行重構(gòu)
public class AppleFilterTest {
private List<Apple> appleRepo;
private AppleFilter filter;
@Before
public void givenAppleRepo(){
//given
Apple apple1 = new Apple("green", "70");
Apple apple2 = new Apple("red", "100");
Apple apple3 = new Apple("red", "80");
Apple apple4 = new Apple("green", "170");
Apple apple5 = new Apple("green", "60");
appleRepo = asList(apple1,apple2,apple3,apple4,apple5);
filter = new AppleFilter();
}
@Test
public void whenFilterGreenAppleThenReturn3(){
//when
List<Apple> greenApples = filter.findApple(appleRepo,"green");
//then
assertThat(greenApples.size(),is(3));
}
@Test
public void whenFilterRedAppleThenReturn2(){
//when
List<Apple> redApples = filter.findApple(appleRepo,"red");
//then
assertThat(redApples.size(),is(2));
}
}
public class AppleFilter {
public List<Apple> findApple(List<Apple> appleRepo,String color) {
List<Apple> result = new ArrayList<>();
for(Apple apple : appleRepo){
if(apple.getColor().equals(color)){
result.add(apple);
}
}
return result;
}
}
3.篩選重量大于100克的蘋果
public class AppleFilterTest {
private List<Apple> appleRepo;
private AppleFilter filter;
@Before
public void givenAppleRepo(){
//given
Apple apple1 = new Apple("green", "70");
Apple apple2 = new Apple("red", "100");
Apple apple3 = new Apple("red", "80");
Apple apple4 = new Apple("green", "170");
Apple apple5 = new Apple("green", "60");
appleRepo = asList(apple1,apple2,apple3,apple4,apple5);
filter = new AppleFilter();
}
@Test
public void whenFilterGreenAppleThenReturn3(){
//when
List<Apple> greenApples = filter.findApple(appleRepo,"green");
//then
assertThat(greenApples.size(),is(3));
}
@Test
public void whenFilterRedAppleThenReturn2(){
//when
List<Apple> redApples = filter.findApple(appleRepo,"red");
//then
assertThat(redApples.size(),is(2));
}
@Test
public void whenFilterGt100ThenReturn2(){
List gt100 = filter.findApple(appleRepo,100);
assertThat(gt100.size(),is(2));
}
}
public List<Apple> findApple(List<Apple> appleRepo,String color) {
List<Apple> result = new ArrayList<>();
for(Apple apple : appleRepo){
if(apple.getColor().equals(color)){
result.add(apple);
}
}
return result;
}
public List<Apple> findApple(List<Apple> appleRepo,int weight) {
List<Apple> result = new ArrayList<>();
for(Apple apple : appleRepo){
if(apple.getWeight()>=weight){
result.add(apple);
}
}
return result;
}
結(jié)構(gòu)性重復(fù)魄健,只是算法不一樣而已
通過策略模式老客,將算法封裝起來,消除重復(fù)润匙,使應(yīng)用滿足SRP和OCP
public interface Specification {
boolean satisfy(Apple apple);
}
public class ColorSpec implements Specification {
private String color;
public ColorSpec(String color) {
this.color = color;
}
@Override
public boolean satisfy(Apple apple) {
return apple.getColor().equals(color);
}
}
public class WeightGtSpec implements Specification{
private int weight;
public WeightGtSpec(int weight) {
this.weight = weight;
}
@Override
public boolean satisfy(Apple apple) {
return apple.getWeight()>=weight;
}
}
@Test
public void whenFilterGreenAppleThenReturn3(){
//when
List<Apple> greenApples = filter.findApple(appleRepo,new ColorSpec("green"));
//then
assertThat(greenApples.size(),is(3));
}
@Test
public void whenFilterRedAppleThenReturn2(){
//when
List<Apple> redApples = filter.findApple(appleRepo,new ColorSpec("red"));
//then
assertThat(redApples.size(),is(2));
}
@Test
public void whenFilterGt100ThenReturn2(){
List gt100 = filter.findApple(appleRepo,new WeightGtSpec(100));
assertThat(gt100.size(),is(2));
}
public List<Apple> findApple(List<Apple> appleRepo, Specification spec) {
List<Apple> result = new ArrayList<>();
for (Apple apple : appleRepo) {
if (spec.satisfy(apple)) {
result.add(apple);
}
}
return result;
}
4.篩選不是紅色的蘋果
@Before
public void givenAppleRepo(){
//given
Apple apple1 = new Apple("green", 70);
Apple apple2 = new Apple("red", 100);
Apple apple3 = new Apple("red", 80);
Apple apple4 = new Apple("green", 170);
Apple apple5 = new Apple("green", 60);
Apple apple6 = new Apple("blue", 60);
appleRepo = asList(apple1,apple2,apple3,apple4,apple5,apple6);
filter = new AppleFilter();
}
@Test
public void whenFilterNotRedThenReturn4(){
List notRed = filter.findApple(appleRepo,new NotSpec(new ColorSpec("red")));
assertThat(notRed.size(),is(4));
}
通過組合模式,完成原子策略模式的復(fù)用
public class NotSpec implements Specification {
private Specification spec;
public NotSpec(Specification spec) {
this.spec = spec;
}
@Override
public boolean satisfy(Apple apple) {
return !spec.satisfy(apple);
}
}
5.篩選既是紅色又大于100克的蘋果
@Test
public void whenFilterRedAndGt100ThenReturn1(){
List redAndGt100 = filter.findApple(appleRepo,new ColorAndWeightGtSpec(100,"red"));
assertThat(redAndGt100.size(),is(1));
}
public class ColorAndWeightGtSpec implements Specification {
private int weight;
private String color;
public ColorAndWeightGtSpec(int weight, String color) {
this.weight = weight;
this.color = color;
}
@Override
public boolean satisfy(Apple apple) {
return apple.getWeight()>=weight&& apple.getColor().equals(color);
}
}
當(dāng)出現(xiàn)And語義時(shí)唉匾,一般都是違反了SRP孕讳,而且該實(shí)現(xiàn)也比較僵化谷羞,如果需求新增即是紅色亚茬,又小于100克煌贴,該實(shí)現(xiàn)無法滿足需求恐仑。
利用組合模式繼續(xù)拆分
@Test
public void whenFilterRedAndGt100ThenReturn1(){
List redAndGt100 = filter.findApple(appleRepo,new AndSpec(new ColorSpec("red"),new WeightGtSpec(100)));
assertThat(redAndGt100.size(),is(1));
}
public class AndSpec implements Specification{
private Specification[] specs;
public AndSpec(Specification ...specs) {
this.specs = specs;
}
@Override
public boolean satisfy(Apple apple) {
for (Specification spec: specs)
{
if(!spec.satisfy(apple)){
return false;
}
}
return true;
}
}
6.篩選紅色或者藍(lán)色的蘋果
@Test
public void whenFilterRedOrBlueThenReturn3(){
List redOrBlue = filter.findApple(appleRepo,new OrSpec(new ColorSpec("red"),new ColorSpec("blue")));
assertThat(redOrBlue.size(),is(3));
}
public class OrSpec implements Specification {
private Specification[] specs;
public OrSpec(Specification ...specs) {
this.specs = specs;
}
@Override
public boolean satisfy(Apple apple) {
for (Specification spec : specs){
if(spec.satisfy(apple)){
return true;
}
}
return false;
}
}
從OO Design的角度來說,目前的實(shí)現(xiàn)已經(jīng)非常不錯(cuò)了响禽,符合SRP胖笛,OCP苍柏,能夠以組合的方式實(shí)現(xiàn)更復(fù)雜的需求變化肪康。
但是從調(diào)用的客戶端角度荚恶,調(diào)用的方式并不簡(jiǎn)單,需要new大量的spec對(duì)象梅鹦,閱讀性不是很友好。
7.通過引入Specifications輔助類和匿名類來簡(jiǎn)化客戶端調(diào)換用
我們的價(jià)值觀是客戶第一冗锁,個(gè)人理解齐唆,從程序的角度來說,我們的第一客戶是調(diào)用我們程序的客戶端代碼(有可能是你自己冻河,也有可能是別人寫的代碼箍邮。調(diào)用你程序的代碼被稱為客戶端代碼,也許就是這樣的理解而被大家這么命名的吧)叨叙,所以我們有責(zé)任讓我們的客戶使用起來更簡(jiǎn)單方便锭弊。
一般接口類都對(duì)應(yīng)一個(gè)工具類,例如Collection 對(duì)應(yīng) Collections擂错,Array對(duì)應(yīng)Arrays以輔助客戶端調(diào)用味滞。基于此,我們實(shí)現(xiàn)我們自己的Specifications輔助類剑鞍。
@Test
public void whenFilterGreenAppleThenReturn3(){
//when
List<Apple> greenApples = filter.findApple(appleRepo,color("green"));
//then
assertThat(greenApples.size(),is(3));
}
@Test
public void whenFilterRedAppleThenReturn2(){
//when
List<Apple> redApples = filter.findApple(appleRepo,color("red"));
//then
assertThat(redApples.size(),is(2));
}
@Test
public void whenFilterGt100ThenReturn2(){
List gt100 = filter.findApple(appleRepo,gtWeight(100));
assertThat(gt100.size(),is(2));
}
@Test
public void whenFilterNotRedThenReturn4(){
List notRed = filter.findApple(appleRepo,not(color("red")));
assertThat(notRed.size(),is(4));
}
@Test
public void whenFilterRedAndGt100ThenReturn1(){
List redAndGt100 = filter.findApple(appleRepo,and(color("red"),gtWeight(100)));
assertThat(redAndGt100.size(),is(1));
}
@Test
public void whenFilterRedOrBlueThenReturn3(){
List redOrBlue = filter.findApple(appleRepo,or(color("red"),color("blue")));
assertThat(redOrBlue.size(),is(3));
}
public class Specifications {
public static Specification color(String color){
return new Specification() {
@Override
public boolean satisfy(Apple apple) {
return apple.getColor().equals(color);
}
};
}
public static Specification gtWeight(int weight){
return new Specification() {
@Override
public boolean satisfy(Apple apple) {
return apple.getWeight()>=weight;
}
};
}
public static Specification or(Specification ...specs){
return new Specification() {
@Override
public boolean satisfy(Apple apple) {
for (Specification spec : specs){
if(spec.satisfy(apple)){
return true;
}
}
return false;
}
};
}
public static Specification and(Specification ...specs){
return new Specification() {
@Override
public boolean satisfy(Apple apple) {
for (Specification spec : specs){
if(!spec.satisfy(apple)){
return false;
}
}
return true;
}
};
}
public static Specification not(Specification spec){
return new Specification() {
@Override
public boolean satisfy(Apple apple) {
return !spec.satisfy(apple);
}
};
}
}
Java 8之前昨凡,這基本上已經(jīng)是非常漂亮的實(shí)現(xiàn)了。我們使用輔助類抽象出了dsl語句蚁署,來靈活的并且非常有表現(xiàn)力的實(shí)現(xiàn)我們的需求便脊。
Java 8以后,接口可以有static方法和default方法光戈。來是我們的類進(jìn)一步減少哪痰。
同時(shí)引入lamda繼續(xù)簡(jiǎn)化我們的代碼,使其更具有表現(xiàn)力
8.引入lamda
public class Specifications {
public static Specification color(String color){
return apple -> apple.getColor().equals(color);
}
public static Specification gtWeight(int weight){
return apple -> apple.getWeight()>=weight;
}
public static Specification or(Specification ...specs){
return apple -> {
for (Specification spec : specs){
if(spec.satisfy(apple)){
return true;
}
}
return false;
};
}
public static Specification and(Specification ...specs){
return apple -> {
for (Specification spec : specs){
if(!spec.satisfy(apple)){
return false;
}
}
return true;
};
}
public static Specification not(Specification spec){
return apple -> !spec.satisfy(apple);
}
}
9.接口靜態(tài)方法
public interface Specification {
boolean satisfy(Apple apple);
static Specification color(String color){
return apple -> apple.getColor().equals(color);
}
static Specification gtWeight(int weight){
return apple -> apple.getWeight()>=weight;
}
static Specification or(Specification ...specs){
return apple -> {
for (Specification spec : specs){
if(spec.satisfy(apple)){
return true;
}
}
return false;
};
}
static Specification and(Specification ...specs){
return apple -> {
for (Specification spec : specs){
if(!spec.satisfy(apple)){
return false;
}
}
return true;
};
}
static Specification not(Specification spec){
return apple -> !spec.satisfy(apple);
}
}
10.接口默認(rèn)方法
and(color("red"),gtWeight(100))這樣的語義是不符合我們的人類語言方式的久妆。
color("red").and(gtWeight(100))這樣的語義更像人類語言的表達(dá)方式晌杰。
public interface Specification {
boolean satisfy(Apple apple);
static Specification color(String color){
return apple -> apple.getColor().equals(color);
}
static Specification gtWeight(int weight){
return apple -> apple.getWeight()>=weight;
}
default Specification or(Specification spec){
return apple -> satisfy(apple) || spec.satisfy(apple);
}
default Specification and(Specification spec){
return apple -> satisfy(apple) && spec.satisfy(apple);
}
default Specification not(){
return apple -> !satisfy(apple);
}
}
@Test
public void whenFilterGreenAppleThenReturn3(){
//when
List<Apple> greenApples = filter.findApple(appleRepo,color("green"));
//then
assertThat(greenApples.size(),is(3));
}
@Test
public void whenFilterRedAppleThenReturn2(){
//when
List<Apple> redApples = filter.findApple(appleRepo,color("red"));
//then
assertThat(redApples.size(),is(2));
}
@Test
public void whenFilterGt100ThenReturn2(){
List gt100 = filter.findApple(appleRepo,gtWeight(100));
assertThat(gt100.size(),is(2));
}
@Test
public void whenFilterNotRedThenReturn4(){
List notRed = filter.findApple(appleRepo,color("red").not());
assertThat(notRed.size(),is(4));
}
@Test
public void whenFilterRedAndGt100ThenReturn1(){
List redAndGt100 = filter.findApple(appleRepo,color("red").and(gtWeight(100)));
assertThat(redAndGt100.size(),is(1));
}
@Test
public void whenFilterRedOrBlueThenReturn3(){
List redOrBlue = filter.findApple(appleRepo,color("red").or(color("blue")));
assertThat(redOrBlue.size(),is(3));
}
11. 泛型實(shí)現(xiàn)通用篩選
public interface Specification<T> {
boolean satisfy(T apple);
default Specification<T> or(Specification<T> spec) {
return t -> satisfy(t) || spec.satisfy(t);
}
default Specification<T> and(Specification<T> spec) {
return t -> satisfy(t) && spec.satisfy(t);
}
default Specification<T> not() {
return t -> !satisfy(t);
}
}
public interface AppleSpec extends Specification<Apple> {
static Specification<Apple> color(String color){
return apple -> apple.getColor().equals(color);
}
static Specification<Apple> gtWeight(int weight){
return apple -> apple.getWeight()>=weight;
}
}
通過泛型化我們的Specification不僅僅可以過濾Apple了,同時(shí)可以過濾所有的對(duì)象類型镇饺。
12. Predicate接口
很多我們遇到的問題乎莉,前人也都遇到過并且已經(jīng)解決了。我們不需要重復(fù)發(fā)明輪子奸笤。只需要站在巨人的肩膀繼續(xù)前行惋啃。
T -> boolean : Specification實(shí)際上是這樣一種函數(shù),給定一個(gè)參數(shù)监右,返回一個(gè)boolean類型边灭。而這個(gè)函數(shù)式接口是java8中提供的函數(shù)式接口中最常用的之一: Predicate<T>接口。
public interface AppleSpec extends Predicate<Apple> {
static Predicate<Apple> color(String color){
return apple -> apple.getColor().equals(color);
}
static Predicate<Apple> gtWeight(int weight){
return apple -> apple.getWeight()>=weight;
}
}
13.Stream內(nèi)循環(huán)
目前為止我們的代碼不僅簡(jiǎn)潔健盒,而且非常靈活绒瘦。我們通過AppleFilter來迭代按Predicate進(jìn)行過濾。
Java 8的Stream提供了內(nèi)部循環(huán)扣癣,減少了臨時(shí)變量惰帽,并且提供了免費(fèi)的并行化。
@Test
public void whenFilterGreenAppleThenReturn3(){
//when
List<Apple> greenApples = appleRepo.stream().filter(color("green")).collect(Collectors.toList());
//then
assertThat(greenApples.size(),is(3));
}
@Test
public void whenFilterRedAppleThenReturn2(){
//when
List<Apple> redApples = appleRepo.stream().filter(color("red")).collect(Collectors.toList());
//then
assertThat(redApples.size(),is(2));
}
@Test
public void whenFilterGt100ThenReturn2(){
List gt100 = appleRepo.stream().filter(gtWeight(100)).collect(Collectors.toList());
assertThat(gt100.size(),is(2));
}
@Test
public void whenFilterNotRedThenReturn4(){
List notRed = appleRepo.stream().filter(color("red").negate()).collect(Collectors.toList());
assertThat(notRed.size(),is(4));
}
@Test
public void whenFilterRedAndGt100ThenReturn1(){
List redAndGt100 = appleRepo.stream().filter(color("red").and(gtWeight(100))).collect(Collectors.toList());
assertThat(redAndGt100.size(),is(1));
}
@Test
public void whenFilterRedOrBlueThenReturn3(){
List redOrBlue = appleRepo.stream().filter(color("red").or(gtWeight(100))).collect(Collectors.toList());
assertThat(redOrBlue.size(),is(3));
}
14.結(jié)束了嗎父虑?
目前為止我們只寫了一個(gè)實(shí)現(xiàn)了Predicate<T>接口的AndSpec接口该酗。
函數(shù)式編程中,函數(shù)第一士嚎,所以其實(shí)我們連AndSpec也沒有必要實(shí)現(xiàn)呜魄。
@Test
public void whenFilterGreenAppleThenReturn3(){
//when
List<Apple> greenApples = appleRepo.stream().filter(apple -> apple.getColor().equals("green")).collect(Collectors.toList());
//then
assertThat(greenApples.size(),is(3));
}
@Test
public void whenFilterRedAppleThenReturn2(){
//when
List<Apple> redApples = appleRepo.stream().filter(apple -> apple.getColor().equals("red")).collect(Collectors.toList());
//then
assertThat(redApples.size(),is(2));
}
@Test
public void whenFilterGt100ThenReturn2(){
List gt100 = appleRepo.stream().filter(apple -> apple.getWeight()>=100).collect(Collectors.toList());
assertThat(gt100.size(),is(2));
}
@Test
public void whenFilterNotRedThenReturn4(){
List notRed = appleRepo.stream().filter(apple -> !apple.getColor().equals("red")).collect(Collectors.toList());
assertThat(notRed.size(),is(4));
}
@Test
public void whenFilterRedAndGt100ThenReturn1(){
Predicate<Apple> red = apple -> apple.getColor().equals("red");
Predicate<Apple> gt100 = apple -> apple.getWeight()>=100;
List redAndGt100 = appleRepo.stream().filter(red.and(gt100)).collect(Collectors.toList());
assertThat(redAndGt100.size(),is(1));
}
@Test
public void whenFilterRedOrBlueThenReturn3(){
Predicate<Apple> red = apple -> apple.getColor().equals("red");
Predicate<Apple> gt100 = apple -> apple.getWeight()>=100;
List redOrBlue = appleRepo.stream().filter(red.or(gt100)).collect(Collectors.toList());
assertThat(redOrBlue.size(),is(3));
}
自此,從服務(wù)代碼的角度來說莱衩,我們一行代碼也沒有寫爵嗅,即完成了客戶的需求。
資料引用
- https://codingstyle.cn/topics/12
- Test Driven Java Development
- Test Driven Development with Mockito
- Pragmatic Unit Testing in Java 8 with Junit
- Java 8 in action
Git Repository
https://github.com/jerrywalker0435/apple-filter
對(duì)應(yīng)分支
- 1-filter-green-apple
- 2-filter-red-apple-1
- 2-filter-red-apple-2
- 3-filter-with-specification
- 3-filter-with-weight-1
- 4-filter-not-spec
- 5-filter-color-and-weight-1
- 5-filter-color-and-weight-2
- 6-red-or-blue
- 7-specifications
- 8-lambda
- 9-static-method
- 10-default-method
- 11-generic-specification
- 12-predicate
- 13-stream
- 14-no-code
后記
-
馬丁福勒2017年中國(guó)行給程序員的三個(gè)建議:
- 學(xué)習(xí)業(yè)務(wù)領(lǐng)域知識(shí)
- 將學(xué)習(xí)重點(diǎn)放在軟件開發(fā)原則與模式上笨蚁,而不是具體技術(shù)上
- 提升自身與其他團(tuán)隊(duì)成員之間的溝通能力
-
TDD發(fā)明人之一Kent Beck的三個(gè)建議
- No matter the circumstance you can always improve.
- You can always start improving with yourself.
- You can always start improving today.