場(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);
}
解決思路
login
或twiterLogin
生產(chǎn)User
對(duì)象绊序,扮演生產(chǎn)者的角色硕舆;而twoFactor
消費(fèi)User
對(duì)象,扮演消費(fèi)者的角色骤公。
最難處理的就是login
或twiterLogin
可能會(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();
}
其中捕犬,Success
與Failure
包內(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ì)
Try
是Monad
的一個(gè)應(yīng)用翩概,使得異常的處理可以在流水線上傳遞牲距。使用Scala
的Try
簡(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)
}
}
}