狀態(tài)機設計模式

初識

我第一次知道狀態(tài)機遮婶,是在大學學習《數字電子技術基礎》的時候旗扑。一塊控制芯片有若干輸入數據總線Data_in肩豁,一個CLK時鐘震蕩輸入清钥,還有一定數量的以高低電平組合來控制狀態(tài)的輸入。不同的狀態(tài)缕坎,芯片會對輸入的數據進行不同的處理谜叹。

再之后是讀研時跟著導師做課題,用Verilog HDL寫FPGA程序艳悔,仿真一些數字信號的處理算法猜年,其中也大量使用了狀態(tài)機編程乔外。


FPGA
FPGA

還記得有一次和導師溝通科研時杨幼,他提及說狀態(tài)機的這種編程模型尚卫,在軟件行業(yè)也是有所應用的欣喧。當時我還是個編程戰(zhàn)五渣莲组,也不知道有設計模式這個東西,只是不以為意得應承地點點頭∏妈荆現(xiàn)在想想撵孤,還是蠻佩服導師的博學多知的。

再看狀態(tài)機

狀態(tài)機的官方定義如下:

The intent of the STATE pattern is to distribute state-specific logic across classes that represent an object’s state.
狀態(tài)模式是為了將與狀態(tài)有關的邏輯分寫在代表對象狀態(tài)的類中

我們來通過舉例理解這句話竭望。

想象你要實現(xiàn)一個登陸系統(tǒng)邪码,用戶將通過以下幾個步驟與系統(tǒng)交互。

  1. 連接進登陸界面咬清。

  2. 輸入用戶名密碼闭专,點擊登陸

  3. 登陸成功則順利進入系統(tǒng)旧烧,登陸失敗則斷開連接影钉。

  4. 注銷登錄,斷開連接掘剪。


    登錄流程圖
    登錄流程圖

    這些步驟我們抽象成狀態(tài)轉移圖來看會更加清晰


    登錄狀態(tài)轉移圖
    登錄狀態(tài)轉移圖

    更一般的平委,我們稍微增加些健壯性的操作。
    登錄狀態(tài)轉移健壯性增強
    登錄狀態(tài)轉移健壯性增強

    這樣簡單的邏輯夺谁,我們可以不假思索得很快的在一份代碼中完成廉赔。只要使用switch語法肉微,對對象當前的狀態(tài)做判斷,然后在給各個分支中寫上各自的邏輯蜡塌。但是碉纳,如果你需要增加一個中間狀態(tài),或者修改某一個分支的邏輯時馏艾,你將不得不修改這個類的代碼村象,增加case分支,修改邏輯攒至。這違反了軟件設計中的“開放封閉原則”厚者。為此,我們將狀態(tài)模式的概念付諸實施迫吐,將與指定狀態(tài)有關的邏輯操作分別寫在對應的可代表狀態(tài)的類里库菲。

狀態(tài)機模式

UML視圖
UML視圖

首先定義一個接口IState,指定所有的動作(Action)

/**
 * the interface of state, input parameter is target state machine,
 * and return the next state
 * @author simple
 * 2017年11月6日 上午10:29:58
 */
public interface IState {
    
    public IState connect(Context context);
    
    public IState beginToLogin(Context context);
    
    public IState loginFailure(Context context);
    
    public IState loginSuccess(Context context);
    
    public IState logout(Context context);
}

定義一個抽象類志膀,封裝一些公共方法和實例成員

public abstract class AbstractState implements IState{

    private StateEnum stateEnum;
    
    public AbstractState(StateEnum stateEnum)
    {
        this.stateEnum = stateEnum;
    }
    
    public StateEnum getStateEnum() {
        return stateEnum;
    }

    public void setStateEnum(StateEnum stateEnum) {
        this.stateEnum = stateEnum;
    }
    
    public String toString()
    {
        return(stateEnum.toString());
    }

}

StateEnum是一個枚舉類熙宇,用來限定狀態(tài)的類型。通過在構造器中傳入一個枚舉溉浙,來指明這個類代表什么狀態(tài)烫止。

public enum StateEnum {

    UNCONNECTED(0, "UNCONNECTED"),
    
    CONNECTED(1, "CONNECTED"),
    
    LOGINING(2, "LOGINING"),
    
    LOGIN_INTO_SYSTEM(3, "LOGIN_INTO_SYSTEM");
    
    private int key;
    
    private String stateStr;
    
    StateEnum(int key, String stateStr)
    {
        this.key = key;
        
        this.stateStr = stateStr;
    }
    
    void printState()
    {
        System.out.println(String.format("current state: %d: %s", this.key, this.stateStr));
    }
}

通過繼承AbstractState來定義IState的多個實現(xiàn)類,表示不同的狀態(tài)戳稽。所有狀態(tài)都需要實現(xiàn)IState的方法馆蠕。不同的狀態(tài),對不同操作有不一樣的實現(xiàn)惊奇。

  1. 未連接狀態(tài)
public class UnconnectedState extends AbstractState{

    public UnconnectedState(StateEnum stateEnum) {
        super(stateEnum);
    }
    @Override
    public IState connect(Context context) {
        IState nextState = Context.CONNECTED_STATE;
        System.out.println(String.format("Switch state from %s to %s", context.getState(), nextState));
        return nextState;
    }

    @Override
    public IState beginToLogin(Context context) {
        throw new RuntimeException("還沒有連接互躬,不能登錄");
        
    }

    @Override
    public IState loginFailure(Context context) {
        throw new RuntimeException("還沒有連接,不能登錄");
    }

    @Override
    public IState loginSuccess(Context context) {
        throw new RuntimeException("還沒有連接颂郎,不能登錄");
    }

    @Override
    public IState logout(Context context) {
        throw new RuntimeException("還沒有連接吼渡,不能登錄");
    }

}
  1. 連接狀態(tài)
public class ConnectedState extends AbstractState {

    public ConnectedState(StateEnum stateEnum)
    {
        super(stateEnum);
    }
    
    @Override
    public IState connect(Context context) {
        IState nextState = Context.CONNECTED_STATE;
        System.out.println(String.format("已經連接了,Switch state from %s to %s", context.getState(), nextState));
        return nextState;
    }

    @Override
    public IState beginToLogin(Context context) {
        IState nextState = Context.LOGINING_STATE;
        System.out.println(String.format("Switch state from %s to %s", context.getState(), nextState));
        return nextState;
    }

    @Override
    public IState loginFailure(Context context) {
        throw new RuntimeException("不是正在登錄狀態(tài)");
    }

    @Override
    public IState loginSuccess(Context context) {
        throw new RuntimeException("不是正在登錄狀態(tài)");
    }

    @Override
    public IState logout(Context context) {
        throw new RuntimeException("不是正在登錄狀態(tài)");
    }
}
  1. 正在登陸狀態(tài)
public class LoginingState extends AbstractState {

    public LoginingState(StateEnum stateEnum) {
        super(stateEnum);
    }
    @Override
    public IState connect(Context context) {
        throw new RuntimeException("處在登錄中");
    }

    @Override
    public IState beginToLogin(Context context) {
        IState nextState = Context.LOGINING_STATE;
        System.out.println(String.format("已經連接并且正在登錄乓序,Switch state from %s to %s", context.getState(), nextState));
        return nextState;
    }

    @Override
    public IState loginFailure(Context context) {
        IState nextState = Context.UNCONNECTED_STATE;
        System.out.println(String.format("Switch state from %s to %s", context.getState(), nextState));
        return nextState;
    }

    @Override
    public IState loginSuccess(Context context) {
        IState nextState = Context.LOGIN_INTO_SYSTEM_STATE;
        System.out.println(String.format("Switch state from %s to %s", context.getState(), nextState));
        return nextState;
    }

    @Override
    public IState logout(Context context) {
        throw new RuntimeException("處在登錄中");
    }
}
  1. 進入系統(tǒng)狀態(tài)
public class LoginIntoSystem extends AbstractState {
    
    public LoginIntoSystem(StateEnum stateEnum) {
        super(stateEnum);
    }
    @Override
    public IState connect(Context context) {
        throw new RuntimeException("已經登錄進系統(tǒng)");

    }

    @Override
    public IState beginToLogin(Context context) {
        throw new RuntimeException("已經登錄進系統(tǒng)");

    }

    @Override
    public IState loginFailure(Context context) {
        throw new RuntimeException("已經登錄進系統(tǒng)");

    }

    @Override
    public IState loginSuccess(Context context) {
        IState nextState = Context.LOGIN_INTO_SYSTEM_STATE;
        System.out.println(String.format("已經登錄進系統(tǒng)了寺酪,Switch state from %s to %s", context.getState(), nextState));
        return nextState;
    }

    @Override
    public IState logout(Context context) {
        IState nextState = Context.UNCONNECTED_STATE;
        System.out.println(String.format("Switch state from %s to %s", context.getState(), nextState));
        return nextState;
    }
}

幾個狀態(tài)類中,有些操作的實現(xiàn)時沒有意義的替劈,比如在UnconnectedState寄雀,進行l(wèi)ogout操作是不符合邏輯的,于是直接拋出異常抬纸。

最后需要定義個“環(huán)境”類咙俩,用來感知當前狀態(tài),你可以理解為就是一個狀態(tài)機。

public class Context {
    // 將各種狀態(tài)定義成Context的類成員變量阿趁,保持單例
    public static final IState UNCONNECTED_STATE = new UnconnectedState(StateEnum.UNCONNECTED);
    
    public static final IState CONNECTED_STATE = new ConnectedState(StateEnum.CONNECTED);
    
    public static final IState LOGINING_STATE = new LoginingState(StateEnum.LOGINING);
    
    public static final IState LOGIN_INTO_SYSTEM_STATE = new LoginIntoSystem(StateEnum.LOGIN_INTO_SYSTEM);
    
    private IState state;
    
    public Context(IState initState)
    {
        initState(initState);
    }
    
    public void connect()
    {
        setState(this.state.connect(this));
    }
    
    public void beginToLogin()
    {
        setState(this.state.beginToLogin(this));
    }
    
    public void loginFailure()
    {
        setState(this.state.loginFailure(this));
    }
    
    public void loginSuccess()
    {
        setState(this.state.loginSuccess(this));
    }
    
    public void logout()
    {
        setState(this.state.logout(this));
    }
    
    public void initState(IState state)
    {
        this.setState(state);
    }
    
    public void setState(IState state)
    {
        this.state = state;
    }
    
    public IState getState()
    {
        return this.state;
    }
}

Context類中有與IState接口類似的方法膜蛔。其內部實現(xiàn)時交由當前狀態(tài)類來實現(xiàn)的。IState接口接收一個Context類實例脖阵,在IState的實現(xiàn)類中對其做相應的邏輯處理皂股,再返回給Context下一個狀態(tài),并交由Context實例對象進行狀態(tài)的切換命黔。當然呜呐,你也可以直接就在狀態(tài)類中進行狀態(tài)切換,就目前而言悍募,我覺得也ok蘑辑。

通過一個客戶端,讓我們來看看效果

    public static void main(String[] args) {
        
        Context context = new Context(Context.UNCONNECTED_STATE);
        context.connect();
        context.beginToLogin();
        context.loginFailure();
        context.connect();
        context.beginToLogin();
        context.loginSuccess();
        context.logout();
    }
>>>>>>>>>>>>>>>輸出>>>>>>>>>>>>>>>>>>>>>
Switch state from UNCONNECTED to CONNECTED
Switch state from CONNECTED to LOGINING
Switch state from LOGINING to UNCONNECTED
Switch state from UNCONNECTED to CONNECTED
Switch state from CONNECTED to LOGINING
Switch state from LOGINING to LOGIN_INTO_SYSTEM
Switch state from LOGIN_INTO_SYSTEM to UNCONNECTED

發(fā)現(xiàn)問題坠宴!

寫到這里洋魂,我重新審視開發(fā)-封閉原則:開放擴展,封閉修改喜鼓。我們現(xiàn)在如果要增加一個狀態(tài)副砍,登錄超時。我們可以增加一個類繼承AbstractState庄岖,然后實現(xiàn)各個操作的邏輯豁翎。還要在StateEnum中增加一種類型,在Context增加一個類成員變量隅忿,同時心剥,為了讓這個類派上用場,需要修改與之相關聯(lián)的狀態(tài)類的邏輯硼控,讓狀態(tài)有可能轉移到登錄超時刘陶。最少要修改3個類,好吧牢撼,這時你心里可能會冒一句:去他丫的開放封閉原則。
那如果突然有個需求疑苫,你的登錄系統(tǒng)需要有一個輸入驗證碼的Action熏版。你會需要修改IState接口,增加一個驗證碼輸入方法捍掺。WTF撼短,所有的實現(xiàn)類都要修改了。這狀態(tài)模式好像只是解耦了狀態(tài)和持有狀態(tài)的對象挺勿,將邏輯封裝進對應狀態(tài)類中曲横。但是如果要增加某個狀態(tài)或者動作,非常有可能面臨大量的修改。

此外禾嫉,StateEnum枚舉類有些雞肋灾杰,我們只是通過枚舉來限定可能的狀態(tài),但此外好像就沒什么用了熙参。增加狀態(tài)時艳吠,還需要額外修改這個類。能不能利用下枚舉類的單例特性呢孽椰?最好能夠將Context中的表示狀態(tài)的類成員也解耦昭娩。

這個我想到了辦法,之前是通過在實例化狀態(tài)類是傳入StateEnum枚舉來限定狀態(tài)黍匾。我現(xiàn)在反過來栏渺,在枚舉對象實例化時傳入狀態(tài)類,這樣每個枚舉類本身就封裝了一個狀態(tài)類锐涯,而且絕對是單例的迈嘹。

public enum StateEnum {

    UNCONNECTED(0, "UNCONNECTED" , new UnconnectedState()),
    
    CONNECTED(1, "CONNECTED", new ConnectedState()),
    
    LOGINING(2, "LOGINING", new LoginingState()),
    
    LOGIN_INTO_SYSTEM(3, "LOGIN_INTO_SYSTEM", new LoginIntoSystem());
    
    private final int key;
    
    private final String stateStr;
    
    private final IState state;
    
    StateEnum(int key, String stateStr, IState state)
    {
        this.key = key;
        
        this.stateStr = stateStr;
        
        this.state = state;
    }
    
    void printState()
    {
        System.out.println(String.format("current state: %d: %s", this.key, this.stateStr));
    }
    
    public IState getState()
    {
        return state;
    }
}

但又有一個問題,假如對于某個狀態(tài)全庸,我有多種可選的實現(xiàn)類時(比如UnconnectedState1, UnconnectedState2)秀仲,這個時候想要替換這個類的實現(xiàn)時,我就需要修改StateEnum類了壶笼。小菜雞寫的代碼神僵,還是沒辦法盡善盡美啊。

好在有大牛給出了最佳實踐——Spring state machine——可以供大家觀摩學習覆劈。

Spring中的狀態(tài)機

Spring有一個專門實現(xiàn)了狀態(tài)機的子項目——spring-statemachine-core保礼,在spring應用中添加如下依賴,開箱即用

        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-core</artifactId>
            <version>1.2.0.RELEASE</version>
        </dependency>

使用spring來實現(xiàn)狀態(tài)機责语,能夠更進一步解耦功能類炮障,讓代碼結構層次更加清晰。下面大致實現(xiàn)一個小的Demo坤候。

  1. 定義狀態(tài)枚舉
public enum RegStatusEnum {
    // 未連接
    UNCONNECTED,
    // 已連接
    CONNECTED,
    // 正在登錄
    LOGINING,
    // 登錄進系統(tǒng)
    LOGIN_INTO_SYSTEM;
}
  1. 定義事件枚舉胁赢,事件的發(fā)生觸發(fā)狀態(tài)轉換
public enum RegEventEnum {
    // 連接
    CONNECT,
    // 開始登錄
    BEGIN_TO_LOGIN,
    // 登錄成功
    LOGIN_SUCCESS,
    // 登錄失敗
    LOGIN_FAILURE,
    // 注銷登錄
    LOGOUT;
}
  1. 配置狀態(tài)機,通過注解打開狀態(tài)機功能白筹。配置類一般要繼承EnumStateMachineConfigurerAdapter類智末,并且重寫一些configure方法以配置狀態(tài)機的初始狀態(tài)以及事件與狀態(tài)轉移的聯(lián)系。
import static com.qyz.dp.state.events.RegEventEnum.BEGIN_TO_LOGIN;
import static com.qyz.dp.state.events.RegEventEnum.CONNECT;
import static com.qyz.dp.state.events.RegEventEnum.LOGIN_FAILURE;
import static com.qyz.dp.state.events.RegEventEnum.LOGIN_SUCCESS;
import static com.qyz.dp.state.events.RegEventEnum.LOGOUT;
import static com.qyz.dp.state.state.RegStatusEnum.CONNECTED;
import static com.qyz.dp.state.state.RegStatusEnum.LOGINING;
import static com.qyz.dp.state.state.RegStatusEnum.LOGIN_INTO_SYSTEM;
import static com.qyz.dp.state.state.RegStatusEnum.UNCONNECTED;

import java.util.EnumSet;

import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;

import com.qyz.dp.state.events.RegEventEnum;
import com.qyz.dp.state.state.RegStatusEnum;

@Configuration
@EnableStateMachine // 開啟狀態(tài)機配置
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<RegStatusEnum, RegEventEnum>{

    /**
     * 配置狀態(tài)機狀態(tài)
     */
    @Override
    public void configure(StateMachineStateConfigurer<RegStatusEnum, RegEventEnum> states) throws Exception {
        states.withStates()
        // 初始化狀態(tài)機狀態(tài)
        .initial(RegStatusEnum.UNCONNECTED)
        // 指定狀態(tài)機的所有狀態(tài)
        .states(EnumSet.allOf(RegStatusEnum.class));
    }

    /**
     * 配置狀態(tài)機狀態(tài)轉換
     */
    @Override
    public void configure(StateMachineTransitionConfigurer<RegStatusEnum, RegEventEnum> transitions) throws Exception {
        // 1. connect UNCONNECTED -> CONNECTED
        transitions.withExternal()
            .source(UNCONNECTED)
            .target(CONNECTED)
            .event(CONNECT)
        // 2. beginToLogin CONNECTED -> LOGINING
        .and().withExternal()
            .source(CONNECTED)
            .target(LOGINING)
            .event(BEGIN_TO_LOGIN)
        // 3. login failure LOGINING -> UNCONNECTED
        .and().withExternal()
            .source(LOGINING)
            .target(UNCONNECTED)
            .event(LOGIN_FAILURE)
        // 4. login success LOGINING -> LOGIN_INTO_SYSTEM
        .and().withExternal()
            .source(LOGINING)
            .target(LOGIN_INTO_SYSTEM)
            .event(LOGIN_SUCCESS)
        // 5. logout LOGIN_INTO_SYSTEM -> UNCONNECTED
        .and().withExternal()
            .source(LOGIN_INTO_SYSTEM)
            .target(UNCONNECTED)
            .event(LOGOUT);
    }
}
  1. 配置事件監(jiān)聽器徒河,事件發(fā)生時會觸發(fā)的操作
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;

@Configuration
@WithStateMachine
public class StateMachineEventConfig {

    @OnTransition(source = "UNCONNECTED", target = "CONNECTED")
    public void connect() {
        System.out.println("Switch state from UNCONNECTED to CONNECTED: connect");
    }

    @OnTransition(source = "CONNECTED", target = "LOGINING")
    public void beginToLogin() {
        System.out.println("Switch state from CONNECTED to LOGINING: beginToLogin");
    }

    @OnTransition(source = "LOGINING", target = "LOGIN_INTO_SYSTEM")
    public void loginSuccess() {
        System.out.println("Switch state from LOGINING to LOGIN_INTO_SYSTEM: loginSuccess");
    }

    @OnTransition(source = "LOGINING", target = "UNCONNECTED")
    public void loginFailure() {
        System.out.println("Switch state from LOGINING to UNCONNECTED: loginFailure");      
    }
    
    @OnTransition(source = "LOGIN_INTO_SYSTEM", target = "UNCONNECTED")
    public void logout()
    {
        System.out.println("Switch state from LOGIN_INTO_SYSTEM to UNCONNECTED: logout");
    }
}
  1. 通過注解自動裝配一個狀態(tài)機系馆,這里寫了一個rest接口來觸發(fā)狀態(tài)機變化
@RestController
public class WebApi {

    @Autowired
    private StateMachine<RegStatusEnum, RegEventEnum> stateMachine;
    
    @GetMapping(value = "/testStateMachine")
    public void testStateMachine()
    {
        stateMachine.start();
        stateMachine.sendEvent(RegEventEnum.CONNECT);
        stateMachine.sendEvent(RegEventEnum.BEGIN_TO_LOGIN);
        stateMachine.sendEvent(RegEventEnum.LOGIN_FAILURE);
        stateMachine.sendEvent(RegEventEnum.LOGOUT);
    }
}
>>>>>>>>>>>>>>>>>>>>>>>輸出結果>>>>>>>>>>>>>>>>>>>>>>>>>>
Switch state from UNCONNECTED to CONNECTED: connect
Switch state from CONNECTED to LOGINING: beginToLogin
Switch state from LOGINING to UNCONNECTED: loginFailure

從輸出可以看到,雖然send了4個事件顽照,但只有三條輸出由蘑。原因是最后一個LOGOUT事件發(fā)生時,狀態(tài)機是UNCONNECTED狀態(tài),沒有與LOGOUT事件關聯(lián)的狀態(tài)轉移尼酿,故不操作爷狈。

使用spring實現(xiàn)的狀態(tài)機將類之間的關系全部交由了IOC容器做管理,實現(xiàn)了真正意義上的解耦谓媒。果然Spring大法好啊淆院。

參考

Spring狀態(tài)機參考文檔

服務端指南 | 狀態(tài)機設計

狀態(tài)模式——State (更好的實現(xiàn)狀態(tài)機)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市句惯,隨后出現(xiàn)的幾起案子土辩,更是在濱河造成了極大的恐慌,老刑警劉巖抢野,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拷淘,死亡現(xiàn)場離奇詭異,居然都是意外死亡指孤,警方通過查閱死者的電腦和手機启涯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恃轩,“玉大人结洼,你說我怎么就攤上這事〔骢耍” “怎么了松忍?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長筷厘。 經常有香客問我鸣峭,道長,這世上最難降的妖魔是什么酥艳? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任摊溶,我火速辦了婚禮,結果婚禮上充石,老公的妹妹穿的比我還像新娘莫换。我一直安慰自己,他們只是感情好赫冬,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布浓镜。 她就那樣靜靜地躺著,像睡著了一般劲厌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上听隐,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天补鼻,我揣著相機與錄音,去河邊找鬼。 笑死风范,一個胖子當著我的面吹牛咨跌,可吹牛的內容都是我干的。 我是一名探鬼主播硼婿,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼锌半,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了寇漫?” 一聲冷哼從身側響起刊殉,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎州胳,沒想到半個月后记焊,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡栓撞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年遍膜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓤湘。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡瓢颅,死狀恐怖,靈堂內的尸體忽然破棺而出弛说,到底是詐尸還是另有隱情挽懦,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布剃浇,位于F島的核電站巾兆,受9級特大地震影響,放射性物質發(fā)生泄漏虎囚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一淘讥、第九天 我趴在偏房一處隱蔽的房頂上張望圃伶。 院中可真熱鬧,春花似錦蒲列、人聲如沸窒朋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侥猩。三九已至,卻和暖如春抵赢,著一層夾襖步出監(jiān)牢的瞬間欺劳,已是汗流浹背唧取。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留划提,地道東北人枫弟。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像鹏往,于是被迫代替她去往敵國和親淡诗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理伊履,服務發(fā)現(xiàn)韩容,斷路器,智...
    卡卡羅2017閱讀 134,628評論 18 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,773評論 6 342
  • 懂道理 最近自己一直在考慮的一個問題湾碎,比如我宙攻。我在犯什么錯誤的時候,...
    f961ff2e749a閱讀 446評論 4 0
  • 人與人之間的關系總是很奇妙介褥,有的人即使相識頗久座掘,也不過是能見面打聲招呼,而有的人卻是相見恨晚的柔滔。 真兒姐姐與我一般...
    筱夢依866閱讀 470評論 0 0
  • 決定我們生活方式的并不是過去的經歷溢陪,而是我們自己賦予經歷的意義。(無論過去發(fā)生什么睛廊,那都不起決定作用形真,過去有沒有精...
    掉魚的貓閱讀 332評論 0 0