訪問者模式

訪問者模式

案例

張三所在公司欲為某高校開發(fā)一套獎勵審批系統(tǒng)惹谐,該系統(tǒng)可以實現教師獎勵和學生獎勵的審批(Award Check),如果教師發(fā)表論文數超過10篇或者學生論文超過2篇可以評選科研獎,如果教師教學反饋分大于等于90分或者學生平均成績大于等于90分可以評選成績優(yōu)秀獎窟哺。該系統(tǒng)主要用于判斷候選人集合中的教師或學生是否符合某種獲獎要求悠反。張三想了想就開始動手寫起來了。

1.首先他定義了一個父類:

// 父類寝优,主要存放一些公共字段
public class Person {
    // 姓名
    private String name;
    // 論文數
    private int paperNums;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPaperNums() {
        return paperNums;
    }

    public void setPaperNums(int paperNums) {
        this.paperNums = paperNums;
    }
}

2.然后分別是兩個實體類:

老師類:

// 老師類
public class Teacher extends Person {
    // 教學反饋分
    private int feedbackScore;

    public Teacher(String name, int paperNums, int feedbackScore) {
        this.setName(name);
        this.setPaperNums(paperNums);
        this.feedbackScore = feedbackScore;
    }

    public int getFeedbackScore() {
        return feedbackScore;
    }

    public void setFeedbackScore(int feedbackScore) {
        this.feedbackScore = feedbackScore;
    }
}

學生類:

// 學生類
public class Student extends Person {
    // 平均成績
    private int averageScore;

    public Student(String name, int paperNums, int averageScore) {
        this.setName(name);
        this.setPaperNums(paperNums);
        this.averageScore = averageScore;
    }

    public int getAverageScore() {
        return averageScore;
    }

    public void setAverageScore(int averageScore) {
        this.averageScore = averageScore;
    }
}

3.獎勵審批系統(tǒng)關鍵代碼:

// 獎勵審批系統(tǒng)
public class AwardCheckSystem {
    // 存放元素的容器
    private List<Person> personList = new ArrayList<>();

    // 添加元素方法
    public void addPerson(Person person) {
        personList.add(person);
    }

    // 系統(tǒng)判斷評選資格核心代碼
    public void awardCheck(String prize) {
        if (prize.equals("research")) {
            for (Person person : personList) {
                int paperNums = person.getPaperNums();
                if (person instanceof Teacher && paperNums > 10) {
                    System.out.println(person.getName() + "老師發(fā)表論文數為:" + paperNums + ",擁有評選科研獎資格");
                } else if (person instanceof Student && paperNums > 2) {
                    System.out.println(person.getName() + "同學發(fā)表論文數為:" + paperNums + "枫耳,擁有評選科研獎資格");
                }
            }
        } else if (prize.equals("excellent")) {
            for (Person person : personList) {
                if (person instanceof Teacher && ((Teacher) person).getFeedbackScore() >= 90) {
                    System.out.println(person.getName() + "老師發(fā)表教學反饋分為:" + ((Teacher) person).getFeedbackScore() + "乏矾,擁有評選成績優(yōu)秀獎資格");
                } else if (person instanceof Student && ((Student) person).getAverageScore() >= 90) {
                    System.out.println(person.getName() + "同學平均成績?yōu)椋? + ((Student) person).getAverageScore() + ",擁有評選成績優(yōu)秀獎資格");
                }
            }
        }
    }
}

4.客戶端使用:

public class Main {
    public static void main(String[] args) {
        AwardCheckSystem awardCheckSystem = new AwardCheckSystem();
        awardCheckSystem.addPerson(new Teacher("張三", 9, 91));
        awardCheckSystem.addPerson(new Teacher("李四", 11, 89));
        awardCheckSystem.addPerson(new Student("王五", 1, 92));
        awardCheckSystem.addPerson(new Student("趙六", 3, 88));
        System.out.println("擁有評選科研獎資格的人有:");
        awardCheckSystem.awardCheck("research");
        System.out.println("----------------------------------------------");
        System.out.println("擁有評選成績優(yōu)秀獎資格的人有:");
        awardCheckSystem.awardCheck("excellent");
    }
}

5.使用結果:

擁有評選科研獎資格的人有:
李四老師發(fā)表論文數為:11迁杨,擁有評選科研獎資格
趙六同學發(fā)表論文數為:3钻心,擁有評選科研獎資格
----------------------------------------------
擁有評選成績優(yōu)秀獎資格的人有:
張三老師發(fā)表教學反饋分為:91,擁有評選成績優(yōu)秀獎資格
王五同學平均成績?yōu)椋?2铅协,擁有評選成績優(yōu)秀獎資格

張三很快就寫出了獎勵審批系統(tǒng)中最核心的代碼捷沸,但是他覺得在awardCheck()方法中通過獎項名稱和人員類型判斷是否有資格評選獎項的代碼看上去很是復雜,他想要改進一下狐史。剛好在設計模式中對于這種集合對象中存在多種不同元素痒给,同時對于這些不同元素不同的處理者會有不同的處理方式的情況可以使用訪問者模式對其進行改進。

模式介紹

訪問者模式(Visitor Pattern):提供一個作用于某對象結構中的各元素的操作表示骏全,它使我們可以在不改變各元素的類的前提下定義作用于這些元素的新操作苍柏。訪問者模式是一種對象行為型模式。

角色構成

  • Vistor(抽象訪問者):抽象訪問者為對象結構中每一個具體元素類ConcreteElement聲明一個訪問操作姜贡,從這個操作的名稱或參數類型可以清楚知道需要訪問的具體元素的類型试吁,具體訪問者需要實現這些操作方法,定義對這些元素的訪問操作楼咳。
  • ConcreteVisitor(具體訪問者):具體訪問者實現了每個由抽象訪問者聲明的操作熄捍,每一個操作用于訪問對象結構中一種類型的元素。
  • Element(抽象元素):抽象元素一般是抽象類或者接口母怜,它定義一個accept()方法余耽,該方法通常以一個抽象訪問者作為參數〔谏辏【稍后將介紹為什么要這樣設計宾添〈遥】
  • ConcreteElement(具體元素):具體元素實現了accept()方法,在accept()方法中調用訪問者的訪問方法以便完成對一個元素的操作缕陕。
  • ObjectStructure(對象結構):對象結構是一個元素的集合粱锐,它用于存放元素對象,并且提供了遍歷其內部元素的方法扛邑。它可以結合組合模式來實現怜浅,也可以是一個簡單的集合對象,如一個List對象或一個Set對象蔬崩。

UML 類圖

訪問者模式 UML 類圖

訪問者模式是一種較為復雜的行為型設計模式恶座,它不是那么容易理解的,這里再描述一下訪問者模式的定義以及上面幾個角色作用沥阳。

  • 首先它是作用于某對象結構中的各元素的操作的跨琳,具體表現就是ObjectStructure(對象結構)保存了Element抽象元素中的各個ConcreteElement具體元素,并提供遍歷操作各個具體元素的方法桐罕,類圖中為accept()方法脉让。

  • 抽象元素Element中定義了accept(Visitor visitor)方法瘾晃,用于接受訪問者訪問的方法刁赦,并在具體元素類ConcreteElement中的具體方法中調用訪問者的方法同時將具體元素作為參數傳遞個訪問者。

  • 抽象訪問者中定義了訪問不同元素的接口方法抑诸,便于對象結構ObjectStructure類中方法的調用薪伏,同時具體訪問者完成訪問不同元素的具體實現代碼滚澜。

這樣就構成了訪問者模式在不改變各元素的類的前提下定義作用于這些元素的新操作

代碼改造

1.首先是抽象元素與具體元素類:

抽象父類:

// 父類嫁怀,主要存放一些公共字段
public abstract class Person {
    // 定義用于訪問者訪問的方法
    public abstract void accept(Award award);
}

老師類:

// 老師類
public class Teacher extends Person {
    // 實現訪問者訪問元素的方法
    @Override
    public void accept(Award award) {
        award.visit(this);
    }
}

學生類:

// 學生類
public class Student extends Person {
    // 實現訪問者訪問元素的方法
    @Override
    public void accept(Award award) {
        award.visit(this);
    }
}

這三個類相較于改造前的類主要是多了accept(Award award)方法设捐,其他代碼完全一樣,因此省略了重復代碼眶掌。

2.抽象訪問者類:

// 抽象訪問者挡育,定義訪問具體元素的方法
public interface Award {
    // 提供訪問老師類接口
    void visit(Teacher person);
    // 提供訪問學生類接口
    void visit(Student person);
}

3.兩個具體訪問者類:

科研獎資格判斷類:

// 科研獎資格判斷類(具體訪問者類角色)
public class ResearchAward implements Award {
    @Override
    public void visit(Teacher person) {
        int paperNums = person.getPaperNums();
        if (paperNums > 10) {
            System.out.println(person.getName() + "老師發(fā)表論文數為:" + paperNums + ",擁有評選科研獎資格");
        }
    }

    @Override
    public void visit(Student person) {
        int paperNums = person.getPaperNums();
        if (paperNums > 2) {
            System.out.println(person.getName() + "同學發(fā)表論文數為:" + paperNums + "朴爬,擁有評選科研獎資格");
        }
    }
}

成績優(yōu)秀獎資格判斷類:

// 成績優(yōu)秀獎資格判斷類(具體訪問者類)
public class ExcellentAward implements Award {
    @Override
    public void visit(Teacher person) {
        if (person.getFeedbackScore() >= 90) {
            System.out.println(person.getName() + "老師發(fā)表教學反饋分為:" + person.getFeedbackScore() + ",擁有評選成績優(yōu)秀獎資格");
        }
    }

    @Override
    public void visit(Student person) {
        if (person.getAverageScore() >= 90) {
            System.out.println(person.getName() + "同學平均成績?yōu)椋? + person.getAverageScore() + "橡淆,擁有評選成績優(yōu)秀獎資格");
        }
    }
}

4.對象結構類:

// 獎勵審批系統(tǒng)(對象結構類角色)
public class AwardCheckSystem {
    // 存放元素的容器
    private List<Person> personList = new ArrayList<>();

    // 添加元素方法
    public void addPerson(Person person) {
        personList.add(person);
    }

    // 系統(tǒng)判斷評選資格核心代碼
    public void awardCheck(Award award) {
        for (Person person : personList) {
            person.accept(award);
        }
    }
}

5.客戶端使用:

public class Main {
    public static void main(String[] args) {
        AwardCheckSystem awardCheckSystem = new AwardCheckSystem();
        awardCheckSystem.addPerson(new Teacher("張三", 9, 91));
        awardCheckSystem.addPerson(new Teacher("李四", 11, 89));
        awardCheckSystem.addPerson(new Student("王五", 1, 92));
        awardCheckSystem.addPerson(new Student("趙六", 3, 88));
        System.out.println("擁有評選科研獎資格的人有:");
        awardCheckSystem.awardCheck(new ResearchAward());
        System.out.println("----------------------------------------------");
        System.out.println("擁有評選成績優(yōu)秀獎資格的人有:");
        awardCheckSystem.awardCheck(new ExcellentAward());
    }
}
擁有評選科研獎資格的人有:
李四老師發(fā)表論文數為:11召噩,擁有評選科研獎資格
趙六同學發(fā)表論文數為:3,擁有評選科研獎資格
----------------------------------------------
擁有評選成績優(yōu)秀獎資格的人有:
張三老師發(fā)表教學反饋分為:91逸爵,擁有評選成績優(yōu)秀獎資格
王五同學平均成績?yōu)椋?2具滴,擁有評選成績優(yōu)秀獎資格

經過改造之后輸出結果和上面的一摸一樣,但獎勵審批系統(tǒng)判斷獎項評選資格的核心代碼變得非常簡潔师倔。同時如果要有其他的獎項資格判斷构韵,只需要增加一個新的具體訪問者類并在新的獎項資格判斷類中添加具體的判斷邏輯就可以了,大大提高了系統(tǒng)的可擴展性。

訪問者模式也有一個很是明顯的問題疲恢,它在添加新的訪問者的時候是很容易的凶朗,但在添加新的元素時較為麻煩。在這個獎項審批系統(tǒng)案例里面因為需求就是判斷老師和學生是否有評獎資格显拳,涉及到的元素只有老師和學生棚愤,應該也不會出現變化,但是還有可能評選其他獎項的資格杂数,所以這里用訪問者模式是很合適的宛畦。

模式應用

訪問者模式在合適的場景下使用之后,會使代碼變得更加靈活易于擴展揍移。下面通過介紹它在 Spring 中的具體應用次和,讓我們對模式的應用更加深刻。

1.首先是 pom.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>design-pattern</artifactId>
        <groupId>com.phoegel</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>vistor</artifactId>

    <properties>
        <spring.version>5.1.15.RELEASE</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>

</project>

2.簡單的定義一個實體類:

public class Person {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

3.然后是 spring 配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath:person.properties"/>

    <bean id="person" class="com.phoegel.visitor.analysis.Person" scope="prototype">
        <property name="name" value="${person.name}"/>
        <property name="age" value="${person.age}"/>
    </bean>
</beans>

4.這里使用占位符的方式初始化Person類實例那伐,因此配置一個person.properties文件:

person.name=張三
person.age=18

5.然后簡單的使用:

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person = (Person) context.getBean("person");
        System.out.println(person);
    }
}

6.使用結果:

Person{name='張三', age=18}

這里只是簡單的輸出了初始化對象的信息踏施。重點是想要說明的這里使用占位符${}的方式將配置文件person.properties的信息設置到對象字段里面,在 Spring 中是通過PropertySourcesPlaceholderConfigurer類中的processProperties()方法中完成的喧锦,而方法內部又調用了PlaceholderConfigurerSupport類中的doProcessProperties()方法读规,在doProcessProperties內部就使用到了BeanDefinitionVisitor類,這個類就代表了訪問者類燃少。通過追蹤源碼可以下面的關鍵代碼:

protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
      StringValueResolver valueResolver) {

   // 通過 BeanDefinitionVisitor 類的 visitBeanDefinition() 方法來實現訪問者模式的核心思想
   BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

   String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
   for (String curName : beanNames) {
      if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
         BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
         try {
            visitor.visitBeanDefinition(bd);
         }
         catch (Exception ex) {
            throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
         }
      }
   }
   beanFactoryToProcess.resolveAliases(valueResolver);
   beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}

其中BeanDefinitionVisitor類的visitBeanDefinition()方法如下:

public void visitBeanDefinition(BeanDefinition beanDefinition) {
   visitParentName(beanDefinition);
   visitBeanClassName(beanDefinition);
   visitFactoryBeanName(beanDefinition);
   visitFactoryMethodName(beanDefinition);
   visitScope(beanDefinition);
   if (beanDefinition.hasPropertyValues()) {
      visitPropertyValues(beanDefinition.getPropertyValues());
   }
   if (beanDefinition.hasConstructorArgumentValues()) {
      ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
      visitIndexedArgumentValues(cas.getIndexedArgumentValues());
      visitGenericArgumentValues(cas.getGenericArgumentValues());
   }
}

觀察visitBeanDefinition()方法的方法簽名束亏,可以發(fā)現BeanDefinition是一個接口,也就是訪問者模式中的抽象元素角色阵具,而它的子類有RootBeanDefinition碍遍、ChildBeanDefinitionGenericBeanDefinition等等,這些可以理解為具體的元素角色阳液。需要注意的是怕敬,這里的BeanDefinition明顯是一個實現類,也就是說在 Spring 中并沒有抽象出抽象訪問者來對具體訪問者類進行擴展帘皿,但是訪問者模式的思想在上面幾個類之間的運用得到了充分的體現东跪。

總結

主要優(yōu)點

  • 增加新的訪問操作很方便。使用訪問者模式鹰溜,增加新的訪問操作就意味著增加一個新的具體訪問者類虽填,實現簡單,無須修改源代碼曹动,符合“開閉原則”斋日。
  • 將有關元素對象的訪問行為集中到一個訪問者對象中,而不是分散在一個個的元素類中墓陈。類的職責更加清晰恶守,有利于對象結構中元素對象的復用第献,相同的對象結構可以供多個不同的訪問者訪問。
  • 讓用戶能夠在不修改現有元素類層次結構的情況下兔港,定義作用于該層次結構的操作庸毫。

主要缺點

  • 增加新的元素類很困難。在訪問者模式中押框,每增加一個新的元素類都意味著要在抽象訪問者角色中增加一個新的抽象操作岔绸,并在每一個具體訪問者類中增加相應的具體操作,這違背了“開閉原則”的要求橡伞。
  • 破壞封裝盒揉。訪問者模式要求訪問者對象訪問并調用每一個元素對象的操作,這意味著元素對象有時候必須暴露一些自己的內部操作和內部狀態(tài)兑徘,否則無法供訪問者訪問刚盈。

適用場景

  • 一個對象結構包含多個類型的對象,希望對這些對象實施一些依賴其具體類型的操作挂脑。在訪問者中針對每一種具體的類型都提供了一個訪問操作藕漱,不同類型的對象可以有不同的訪問操作。
  • 需要對一個對象結構中的對象進行很多不同的并且不相關的操作崭闲,而需要避免讓這些操作“污染”這些對象的類肋联,也不希望在增加新操作時修改這些類。訪問者模式使得我們可以將相關的訪問操作集中起來定義在訪問者類中刁俭,對象結構可以被多個不同的訪問者類所使用橄仍,將對象本身與對象的訪問操作分離。
  • 對象結構中對象對應的類很少改變牍戚,但經常需要在此對象結構上定義新的操作侮繁。

參考資料

本篇文章github代碼地址:https://github.com/Phoegel/design-pattern/tree/main/visitor
轉載請說明出處如孝,本篇博客地址:http://www.reibang.com/p/875a0a822fd1

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末宪哩,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子第晰,更是在濱河造成了極大的恐慌锁孟,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茁瘦,死亡現場離奇詭異罗岖,居然都是意外死亡,警方通過查閱死者的電腦和手機腹躁,發(fā)現死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來南蓬,“玉大人纺非,你說我怎么就攤上這事哑了。” “怎么了烧颖?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵弱左,是天一觀的道長。 經常有香客問我炕淮,道長拆火,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任涂圆,我火速辦了婚禮们镜,結果婚禮上,老公的妹妹穿的比我還像新娘润歉。我一直安慰自己模狭,他們只是感情好,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布踩衩。 她就那樣靜靜地躺著嚼鹉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪驱富。 梳的紋絲不亂的頭發(fā)上锚赤,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音褐鸥,去河邊找鬼线脚。 笑死,一個胖子當著我的面吹牛晶疼,可吹牛的內容都是我干的酒贬。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼翠霍,長吁一口氣:“原來是場噩夢啊……” “哼锭吨!你這毒婦竟也來了?” 一聲冷哼從身側響起寒匙,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤零如,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后锄弱,有當地人在樹林里發(fā)現了一具尸體考蕾,經...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年会宪,在試婚紗的時候發(fā)現自己被綠了肖卧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡掸鹅,死狀恐怖塞帐,靈堂內的尸體忽然破棺而出拦赠,到底是詐尸還是另有隱情,我是刑警寧澤葵姥,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布荷鼠,位于F島的核電站,受9級特大地震影響榔幸,放射性物質發(fā)生泄漏允乐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一削咆、第九天 我趴在偏房一處隱蔽的房頂上張望牍疏。 院中可真熱鬧,春花似錦态辛、人聲如沸麸澜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炊邦。三九已至,卻和暖如春熟史,著一層夾襖步出監(jiān)牢的瞬間馁害,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工蹂匹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碘菜,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓限寞,卻偏偏與公主長得像忍啸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子履植,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348