多態(tài)枚舉

迭代1:快速實(shí)現(xiàn)

需求1:實(shí)現(xiàn)一個(gè)計(jì)算器,完成加減乘除運(yùn)算

public final class Operations {
  public final static int PLUS = 0;
  public final static int MINUS = 1;
  public final static int TIMES = 2;
  public final static int DIVIDE = 3;      
  
  public static int apply(int op, int x, int y) {
    switch (op) {
      case PLUS:   return x + y;
      case MINUS:  return x - y;
      case TIMES:  return x * y;
      case DIVIDE: return x / y;
    }
    throw new AssertionError("Unknown op: " + op);
  }
  
  private Operations() {
  }
}

這是一個(gè)很糟糕的設(shè)計(jì)晤揣,存在很多的壞味道屋确。

類型不安全

一般地纳击,用戶按照規(guī)則傳遞正確的op常量续扔。

import static cn.codingstyle.Operations.*;
assertThat(apply(PLUS, 1, 1), equalTo(2));

但不排除用戶傳遞錯(cuò)誤的op值,導(dǎo)致運(yùn)行時(shí)失敗焕数,拋出AssertionError纱昧。

int op = ...
assertThat(apply(op, 1, 1), equalTo(2));

違背OCP原則

對于增加新的操作類型,或者增加行為堡赔,將導(dǎo)致大量的重復(fù)代碼识脆,給后期維護(hù)帶來很大的傷害。

需求2:輸出操作符的字符串表示

可以增加operator方法善已,用于輸出操作符表示的字符串灼捂,設(shè)計(jì)存在大量重復(fù)。

public static int apply(int op, int x, int y) {
  switch (op) {
    case PLUS:   return x + y;
    case MINUS:  return x - y;
    case TIMES:  return x * y;
    case DIVIDE: return x / y;
  }
  throw new AssertionError("Unknown op: " + op);
}

public static String operator(int op) {
  switch (op) {
    case PLUS:   return "+";
    case MINUS:  return "-";
    case TIMES:  return "*";
    case DIVIDE: return "/";
  }
  throw new AssertionError("Unknown op: " + op);
}

需求3:支持求模運(yùn)算

每況愈下换团,如果再增加一個(gè)求模數(shù)的操作類型悉稠,則需要散彈式地修改程序。

public static int apply(int op, int x, int y) {
  switch (op) {
    case PLUS:   return x + y;
    case MINUS:  return x - y;
    case TIMES:  return x * y;
    case DIVIDE: return x / y;
    case MOD:    return x % y;
  }
  throw new AssertionError("Unknown op: " + op);
}

public static String operator(int op) {
  switch (op) {
    case PLUS:   return "+";
    case MINUS:  return "-";
    case TIMES:  return "*";
    case DIVIDE: return "/";
    case MOD:    return "%";
  }
  throw new AssertionError("Unknown op: " + op);
}

當(dāng)需求變化時(shí)艘包,應(yīng)遵循良好的正交設(shè)計(jì)原則的猛,避免做乘法,只做加法辑甜,甚至是減法衰絮。

迭代2:類型安全

為了使得類型安全,約束用戶行為磷醋,可以將常量定義為強(qiáng)類型的枚舉猫牡。

public enum Operation {
  PLUS, MINUS, TIMES, DIVIDE, MOD;
}

重構(gòu)是一個(gè)高度危險(xiǎn)的實(shí)踐活動(dòng),步子越小越安全邓线。

Operationsapply, operator方法類型約束得到了改善淌友,但實(shí)現(xiàn)依然充斥壞味道。

public final class Operations {
  public static int apply(Operation op, int x, int y) {
    switch (op) {
      case PLUS:   return x + y;
      case MINUS:  return x - y;
      case TIMES:  return x * y;
      case DIVIDE: return x / y;
      case MOD:    return x % y;
    }
    throw new AssertionError("Unknown op: " + op);
  }
  
  public static String operator(Operation op) {
    switch (op) {
      case PLUS:   return "+";
      case MINUS:  return "-";
      case TIMES:  return "*";
      case DIVIDE: return "/";
      case MOD:    return "%";
    }
    throw new AssertionError("Unknown op: " + op);
  }
  
  private Operations() {
  }
}

迭代3:引入多態(tài)

Operation重構(gòu)為具有多態(tài)能力枚舉類骇陈,遵循了OCP良好的設(shè)計(jì)原則震庭,使得程序更加具有彈性,但存在較多的「語法噪聲」你雌。

public enum Operation {
  PLUS("+") {
    @Override
    public int apply(int x, int y) {
      return x + y;
    }
  }, 
  
  MINUS("-") {
    @Override
    public int apply(int x, int y) {
      return x - y;
    }
  },

  TIMES("*") {
    @Override
    public int apply(int x, int y) {
      return x * y;
    }
  }, 
  
  DIVIDE("/") {
    @Override
    public int apply(int x, int y) {
      return x / y;
    }
  };

  MOD("%") {
    @Override
    public int apply(int x, int y) {
      return x % y;
    }
  };
  
  public abstract int apply(int x, int y);
  
  public String operator() {
    return op;
  }
  
  private Operation(String op) {
    this.op = op;
  }
  
  private String op;
}

迭代4:消除噪聲

為了進(jìn)一步消除語法噪聲器联,可以使用Java8 Lambda進(jìn)一步改善結(jié)構(gòu),使得成設(shè)計(jì)更加直觀明了婿崭。

import java.util.function.BinaryOperator;

public enum Operation {
  PLUS("+",   (x, y) -> x + y), 
  MINUS("-",  (x, y) -> x - y),
  TIMES("*",  (x, y) -> x * y),
  DIVIDE("/", (x, y) -> x / y),
  MOD("%",    (x, y) -> x % y);
  
  public int apply(int x, int y) {
    return f.apply(x, y);
  }
  
  public String operator() {
    return operator;
  }
  
  private Operation(String operator, BinaryOperator<Integer> f) {
    this.operator = operator;
    this.f = f;
  }
  
  private BinaryOperator<Integer> f;
  private String operator;
}

迭代5:使用Scala

使用Scala拨拓,可進(jìn)一步將語法噪聲消除,并讓用戶接口更加人性化氓栈,程序更加簡單渣磷,漂亮。

sealed trait Operation(val operator: String, 
  op: (Int, Int) => Int) extends ((Int, Int) => Int) {
  override def apply(x: Int, y: Int) = op(x, y)
}

case object plus   extends Operation("+", _ + _) 
case object minus  extends Operation("-", _ - _)
case object times  extends Operation("*", _ * _)
case object divide extends Operation("/", _ / _)
case object mod    extends Operation("%", _ % _)

約束表達(dá)設(shè)計(jì)

sealed明確地聲明授瘦,外部不能再對Operation進(jìn)行擴(kuò)展了醋界,不僅僅使得「模式匹配」成為可能竟宋,更重要的是傳到了作者設(shè)計(jì)的意圖,并在編譯時(shí)約束用戶的行為形纺。

同樣地丘侠,final修飾case object,也是出于同樣的目的挡篓。

混血兒

(Int, Int) => Int)等價(jià)于Function2[Int, Int, Int]婉陷,而Operation重寫了Function2.apply方法,當(dāng)用戶使用plus, minus, times, divide時(shí)官研,其表現(xiàn)得更像函數(shù)的行為秽澳。

scala > plus(1, 1)
res0: Int = 2

當(dāng)調(diào)用operator方法時(shí),又散發(fā)出OO的氣息戏羽,Scala就是一個(gè)與眾不同的「混血兒」担神。

scala > plus.operator
res1: String = +
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市始花,隨后出現(xiàn)的幾起案子妄讯,更是在濱河造成了極大的恐慌,老刑警劉巖酷宵,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亥贸,死亡現(xiàn)場離奇詭異,居然都是意外死亡浇垦,警方通過查閱死者的電腦和手機(jī)炕置,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來男韧,“玉大人朴摊,你說我怎么就攤上這事〈寺牵” “怎么了甚纲?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長朦前。 經(jīng)常有香客問我介杆,道長,這世上最難降的妖魔是什么韭寸? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任春哨,我火速辦了婚禮,結(jié)果婚禮上棒仍,老公的妹妹穿的比我還像新娘。我一直安慰自己臭胜,他們只是感情好莫其,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布癞尚。 她就那樣靜靜地躺著,像睡著了一般乱陡。 火紅的嫁衣襯著肌膚如雪浇揩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天憨颠,我揣著相機(jī)與錄音胳徽,去河邊找鬼。 笑死爽彤,一個(gè)胖子當(dāng)著我的面吹牛养盗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播适篙,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼往核,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嚷节?” 一聲冷哼從身側(cè)響起聂儒,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎硫痰,沒想到半個(gè)月后衩婚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡效斑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年非春,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鳍悠。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡税娜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出藏研,到底是詐尸還是另有隱情敬矩,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布蠢挡,位于F島的核電站弧岳,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏业踏。R本人自食惡果不足惜禽炬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望勤家。 院中可真熱鬧腹尖,春花似錦、人聲如沸伐脖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绎巨,卻和暖如春近尚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背场勤。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工戈锻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人和媳。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓格遭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親窗价。 傳聞我的和親對象是個(gè)殘疾皇子如庭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容