Jumping with Try

場(chǎng)景

以一個(gè)簡(jiǎn)化了的用戶登錄的鑒權(quán)流程,流程大體如下:

  • 首先嘗試本站鑒權(quán),如果失敗隶校,再嘗試twiter的方式恢復(fù)仔涩;
  • 之后再進(jìn)行Two Factor認(rèn)證;

快速實(shí)現(xiàn)

按照流程定義,可以快速實(shí)現(xiàn)第一個(gè)版本佩研。這段代碼充滿了很多的壞味道柑肴,職責(zé)不單一,復(fù)雜的異常分支處理旬薯,流程的脈絡(luò)不夠清晰等等晰骑,接下來(lái)我們嘗試一種很特別的重構(gòu)方式來(lái)改善設(shè)計(jì)。

public boolean authenticate(String id, String passwd) {
  User user = null;    
  try {
   user = login(id, passwd);
  } catch (AuthenticationException e) {
    try {
      user = twiterLogin(id, passwd);
    } catch (AuthenticationException et) {
      return false;
    }
  }
    
  return twoFactor(user);
}

解決思路

logintwiterLogin生產(chǎn)User對(duì)象绊序,扮演生產(chǎn)者的角色硕舆;而twoFactor消費(fèi)User對(duì)象,扮演消費(fèi)者的角色骤公。

最難處理的就是logintwiterLogin可能會(huì)存在異常處理抚官。正常情況下它們生產(chǎn)User對(duì)象,而異常情況下阶捆,則拋出異常凌节。

重構(gòu)的思路在于將異常處理更加明晰化,讓生產(chǎn)者與消費(fèi)者之間的關(guān)系流水化趁猴。為此刊咳,可以將User對(duì)象,可能拋出的異常進(jìn)行容器化儡司,它們形成互斥關(guān)系娱挨,不能共生于這個(gè)世界。

容器化

public interface Try<T> {
  static <T, E extends Exception> 
  Try<T> trying(ExceptionalSupplier<T, E> s) {
    try {
      return new Success<>(s.get());
    } catch (Exception e) {
      return new Failure<>(e);
    }
  }
  
  boolean isFailure();
  
  default boolean isSuccess() {
    return !isFailure();
  }
  
  T get();
}

其中捕犬,SuccessFailure包內(nèi)私有跷坝,對(duì)外不公開。

final class Success<T> implements Try<T> {
  private final T value;
  
  Success(T value) {
    this.value = value;
  }
  
  @Override
  public boolean isFailure() {
    return false;
  }

  @Override
  public T get() {
    return value;
  }
}
import java.util.NoSuchElementException;

final class Failure<T> implements Try<T> {
  private final Exception e;
  
  Failure(Exception e) {
    this.e = e;
  }
  
  @Override
  public boolean isFailure() {
    return true;
  }

  @Override
  public T get() {
    throw new NoSuchElementException("Failure.get");
  }
}

生產(chǎn)者

JDK8標(biāo)準(zhǔn)庫(kù)中不一樣碉碉,它能處理受檢異常柴钻。

@FunctionalInterface
public interface ExceptionalSupplier<T, E extends Exception> {
  T get() throws E;
}

第一次重構(gòu)

import static Try.trying;

public boolean authenticate(String id, String passwd) {
  Try<User> user = trying(() -> login(id, passwd));
  if (user.isFailure()) {
    user = trying(() -> twiterLogin(id, passwd));
  }
 
  return user.isSuccess() && twoFactor(user.get());
}

鏈?zhǔn)紻SL

上述trying的應(yīng)用,使用狀態(tài)查詢Try.isFailure/isSuccess方法顯得有些笨拙垢粮,可以通過(guò)構(gòu)造鏈?zhǔn)降?code>DSL改善設(shè)計(jì)贴届。

public interface Try<T> {
  ......

  <U> Try<U> recover(Function<Exception, Try<U>> f);
}
final class Success<T> implements Try<T> {
  ......
   
  @Override
  @SuppressWarnings("unchecked")
  public <U> Try<U> recover(Function<Exception, Try<U>> f) {
    return (Try<U>)this;
  }
}
final class Failure<T> implements Try<T> {
  private final Exception e;
  
  Failure(Exception e) {
    this.e = e;
  }
   
  @Override
  public <U> Try<U> recover(Function<Exception, Try<U>> f) {
    try {
      return f.apply(e);
    } catch (Exception e) {
      return new Failure<U>(e);
    }
  }
}

第二次重構(gòu)

使用recover關(guān)鍵字,進(jìn)一步地改善表達(dá)力蜡吧。首先試圖login生產(chǎn)一個(gè)User毫蚓,如果失敗從twiterLogin中恢復(fù);最后由twoFactor消費(fèi)User對(duì)象昔善。

public boolean authenticate(String id, String passwd) {
  Try<User> user = trying(() -> login(id, passwd))
      .recover(e -> trying(() -> twiterLogin(id, passwd)));    
    
  return user.isSuccess() && twoFactor(user.get());
}

徹底鏈化

public interface Try<T> {
  ......

  default T getOrElse(T defaultValue) {
    return isSuccess() ? get() : defaultValue;
  }
    
  <U> Try<U> map(Function<T, U> f);
}
final class Success<T> implements Try<T> {
  ......
  
  @Override
  public <U> Try<U> map(Function<T, U> f) {
    try {
      return new Success<U>(f.apply(value));  
    } catch (Exception e) {
      return new Failure<U>(e);
    }
  }
}
final class Failure<T> implements Try<T> {
  ......

  @Override
  @SuppressWarnings("unchecked")
  public <U> Try<U> map(Function<T, U> f) {
    return (Try<U>)this;
  }
}

第三次重構(gòu)

public boolean authenticate(String id, String passwd) {
  return trying(() -> login(id, passwd))
    .recover(e -> trying(() -> twiterLogin(id, passwd)))
    .map(user -> twoFactor(user))
    .getOrElse(false);
}

應(yīng)用Scala

Java8 Lambda表達(dá)式() -> login(id, passwd)中空的參數(shù)列表頗讓人費(fèi)解元潘,但這是Java8語(yǔ)言本身限制的,應(yīng)用Scala表達(dá)力可進(jìn)一步提高君仆。

import scala.util.Try

def authenticate(id: String, passwd: String): Boolean = {
  Try(login(id, passwd))
    .recover{ case e: => twiterLogin(id, passwd) }
    .map(twoFactor)
    .getOrElse(false)
}

Try的本質(zhì)

TryMonad的一個(gè)應(yīng)用翩概,使得異常的處理可以在流水線上傳遞牲距。使用ScalaTry簡(jiǎn)化實(shí)現(xiàn)版本是這樣的。

sealed abstract class Try[+T] {
  def isSuccess: Boolean
  def get: T
}

final case class Failure[+T](val exception: Throwable) extends Try[T] {
  def isSuccess: Boolean = false
  def get: T = throw exception
}


final case class Success[+T](value: T) extends Try[T] {
  def isSuccess: Boolean = true
  def get = value
}

object Try {
  def apply[T](r: => T): Try[T] = {
    try Success(r) catch {
      case NonFatal(e) => Failure(e)
    }
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末钥庇,一起剝皮案震驚了整個(gè)濱河市牍鞠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌上沐,老刑警劉巖皮服,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異参咙,居然都是意外死亡龄广,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門蕴侧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)择同,“玉大人,你說(shuō)我怎么就攤上這事净宵∏貌牛” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵择葡,是天一觀的道長(zhǎng)紧武。 經(jīng)常有香客問(wèn)我,道長(zhǎng)敏储,這世上最難降的妖魔是什么阻星? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮已添,結(jié)果婚禮上妥箕,老公的妹妹穿的比我還像新娘。我一直安慰自己更舞,他們只是感情好畦幢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缆蝉,像睡著了一般宇葱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上刊头,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天贝搁,我揣著相機(jī)與錄音,去河邊找鬼芽偏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛弦讽,可吹牛的內(nèi)容都是我干的污尉。 我是一名探鬼主播膀哲,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼被碗!你這毒婦竟也來(lái)了某宪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤锐朴,失蹤者是張志新(化名)和其女友劉穎兴喂,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焚志,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡衣迷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了酱酬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壶谒。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖膳沽,靈堂內(nèi)的尸體忽然破棺而出汗菜,到底是詐尸還是另有隱情,我是刑警寧澤挑社,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布陨界,位于F島的核電站,受9級(jí)特大地震影響痛阻,放射性物質(zhì)發(fā)生泄漏菌瘪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一录平、第九天 我趴在偏房一處隱蔽的房頂上張望麻车。 院中可真熱鬧,春花似錦斗这、人聲如沸动猬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)赁咙。三九已至,卻和暖如春免钻,著一層夾襖步出監(jiān)牢的瞬間彼水,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工极舔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拆魏,地道東北人慈俯。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像刑峡,于是被迫代替她去往敵國(guó)和親突梦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子阳似,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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