设计模式与架构01 -- 前言,UML,软件设计原则

  • 软件设计模式,又称设计模式,是一套被反复使用,多数人知晓的,经过分类编目的,代码设计经验的总结,它描述了再软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案,也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用;
  • 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性,继承性,多态性以及类的关联关系和组合关系的充分理解;
  • 设计模式的优点:
    • 可以提高程序员的思维能力,编程能力和设计能力;
    • 使程序设计更加标准化,代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期;
    • 使设计的代码可重用性高,可读性强,灵活性好,可维护性强;
  • 设计模式的分类:
    • 创建型模式:用于描述怎样创建对象,它的特点是将对象的创建与使用分离,通常有单例,原型,工厂方法,抽象工厂,建造者5种创建型模式;
    • 结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,通常有代理,适配器,桥接,装饰,外观,享元,组合7种结构型模式;
    • 行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责,通常有模版方法,策略,命令,职责链,状态,观察者,中介者,迭代器,访问者,备忘录,解释器11种行为型模式;

UML

  • UML:统一建模语言,是用来世界软件的可视化建模语言,它的特点是简单,统一,图形化,能表达软件设计中的动态与静态信息;
  • UML从目标系统的不同角度出发,定义了用例图,类图,对象图,状态图,活动图,时序图,协作图,构建图,部署图等9种图;
类图
  • 类图中包含类名,属性,方法,下面定义一个YYPerson类,用类图表示如下:
image.png
  • +表示public;
  • -表示private;
  • #表示protected;
  • 属性的完整表示方式:可见性 名称 : 类型 =缺醒值
  • 方法的完整表示方式:可见性 名称(参数列表) : 返回值类型
类与类之间关系的表示方式
  • 关联关系:是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生,师傅和徒弟,丈夫和妻子等等,关联关系有如下几种:
  • 单向关联:在UML类图中单向关联用一个带箭头的实线表示,如下图顾客对象有一个地址address成员对象;
image.png
  • 双向关联:双方各自持有对方类型的成员变量,用一个不带箭头的直线表示;
image.png
  • 自关联:用一个带有箭头且指向自身的实线表示,自己包含自己;
image.png
  • 聚合关系:是关联关系的一种,是强关联关系,是整体与部分之间的关系,是通过成员对象来实现的,其中成员对象是整体对象的部分,但是成员对象可以脱离整体对象而独立存在,例如学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在;

  • 在UML类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体

    image.png

  • 组合关系:表示整体与部分之间的关系,但它是一种更强烈的聚合关系,在组合关系中整体对象可以控制部分对象的生命周期,一旦整体对象不存在了,部分对象也将不存在,部分对象不能脱离整体对象而存在,例如头和嘴的关系,没有了头,嘴也就不存在了;

  • 在UML类图中,组合关系用实心菱形的实线来表示,菱形指向整体

    image.png

  • 依赖关系:是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时的关联,在代码中,某个类的方法通过局部变量,方法的参数或者对静态方法的调用来访问另一个类中的某些方法来完成一些职责;

  • 在UML类图中,使用带箭头的虚线来表示,箭头从使用类指向被依赖的类,例如司机驾驶汽车;

image.png
  • 继承关系:是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系;

  • 在UML类图中,用带空心三角箭头的实线来表示,箭头从子类指向父类

    image.png

  • 实现关系:是接口与实现类之间的关系,类实现了接口;

  • 在UML类图中,用带空心三角箭头的虚线来表示,箭头从实现类指向接口;

image.png

软件设计原则

  • 在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据6条原则来开发程序,从而提高软件开发效率,节约软件开发成本和维护成本;
开闭原则
  • 开闭原则:对扩展开放,对修改关闭,在程序需要进行扩展的时候,不能去修改原有的代码,实现一个热插拔的效果,想要达到这种效果,我们需要使用接口和抽象类;
  • 因为抽象灵活性好,适应性广,只要抽象合理,就可以基本保持软件架构的稳定,而软件中易变的细节可以从抽象类派生来的实现类进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类扩展就可以了;
  • 需求:实现搜狗输入法的换肤,类图设计如下所示:
image.png
  • 用Java实现上面的功能:
public abstract class AbstractSkin {
    abstract public void display();
}
public class DefaultSkin extends AbstractSkin{
    @Override
    public void display() {
        System.out.println("显示 -- 默认皮肤");
    }
}
public class RedColorSkin extends AbstractSkin{
    @Override
    public void display() {
        System.out.println("显示 -- 红色皮肤");
    }
}
public class SouGouInput {

    private AbstractSkin skin;

    //展示皮肤
    public void display(){
        skin.display();
    }

    public void setSkin(AbstractSkin skin) {
        this.skin = skin;
    }
}
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //创建输入法
        SouGouInput input = new SouGouInput();
        //创建默认皮肤
        DefaultSkin skin = new DefaultSkin();
        input.setSkin(skin);
        //输入法展示皮肤
        input.display();
    }
}
  • 再扩展一个新的皮肤GreenColorSkin继承自AbstractSkin,实现对应的抽象方法,不会修改原来类的代码,遵循了开闭原则
里氏代换原则
  • 子类可以拓展父类的功能,但不能改变父类原有的功能,换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重父类的方法,除非是抽象方法;
  • 如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大;
  • 下面实现:正方形不是长方形,类图如下:
image.png
  • 代码如下:
public class Rectangle {

    private double length;
    private double width;

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }
}
public class Square extends Rectangle{
    //重写了父类的方法
    @Override
    public void setLength(double length) {
        super.setLength(length);
        super.setWidth(length);
    }
   //重写了父类的方法
    @Override
    public void setWidth(double width) {
        super.setWidth(width);
        super.setLength(width);
    }
}
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //创建长方形
        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(10);
        rectangle.setLength(20);
        //扩宽
        resize(rectangle);
        printWidthAndLength(rectangle);

        //创建正方形
        Square square = new Square();
        square.setWidth(30);
        //扩宽 -- 出现死循环
        resize(square);
        printWidthAndLength(square);
    }

    //扩宽
    public void resize(Rectangle rectangle) {
        //当宽比长小 进行扩宽操作
        while (rectangle.getWidth() <= rectangle.getLength()) {
            rectangle.setWidth(rectangle.getWidth()+1);
        }
    }

    public void  printWidthAndLength(Rectangle rectangle) {
        System.out.println(rectangle.getWidth());
        System.out.println(rectangle.getLength());
    }
}
  • 将长方形作为参数传入resize方法中,能正常进行扩宽操作,但将正方形作为参数传入resize方法中,会出现死循环,最后导致内存溢出,出现崩溃,所以普通长方形适合上面的代码,但正方形不适合,正是由于Square重写了父类的setLengthsetWidth方法,最后导致运行崩溃,则违反了里氏代换原则;
  • 改进方案:打破Square与Rectangle之间的继承关系;
image.png
  • 代码实现:
public interface Quadrilateral {
    public double getLength();
    public double getWidth();
}
public class Rectangle implements Quadrilateral{

    private double length;
    private double width;

    public void setLength(double length) {
        this.length = length;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double getLength() {
        return length;
    }

    public double getWidth() {
        return width;
    }
}
public class Square implements Quadrilateral{

    private double side;

    public double getSide() {
        return side;
    }

    public void setSide(double side) {
        this.side = side;
    }

    @Override
    public double getLength() {
        return side;
    }

    @Override
    public double getWidth() {
        return side;
    }
}
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //创建长方形
        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(10);
        rectangle.setLength(20);
        //扩宽
        resize(rectangle);
        printWidthAndLength(rectangle);

        //resize 只能传递Rectangle类型
        //Square是独立的类 与Rectangle没有关系
    }

    //扩宽
    public void resize(Rectangle rectangle) {
        //当宽比长小 进行扩宽操作
        while (rectangle.getWidth() <= rectangle.getLength()) {
            rectangle.setWidth(rectangle.getWidth()+1);
        }
    }

    public void  printWidthAndLength(Rectangle rectangle) {
        System.out.println(rectangle.getWidth());
        System.out.println(rectangle.getLength());
    }
}
依赖倒转原则
  • 高层模块不应该依赖底层模块,两者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象,简单来说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块之间的耦合;
  • 现在来组装电脑,需要的配件有硬盘,CPU,内存条,只有这些配件都存在了,计算机才能正常运行,选择的硬盘有希捷,西数等,选择的CPU有Intel,AMD等,选择的内存条有金士顿,海盗船等等,UML类图如下:
image.png
  • 代码实现如下:
public class XiJieHardDisk {
    public void save(String data) {
        System.out.println("使用希捷硬盘存储数据:" + data);
    }

    public String getData() {
        System.out.println("使用希捷硬盘取数据:");
        return "数据";
    }
}
public class IntelCPU {
    public void run() {
        System.out.println("使用IntelCPU处理器");
    }
}
public class KingstonMemory {
    public void save() {
        System.out.println("使用金士顿内存条 存储数据");
    }
}
public class Computer {
    private XiJieHardDisk hardDisk;
    private IntelCPU cpu;
    private KingstonMemory memory;

    public void run() {
        System.out.println("运行计算机");
        String data = hardDisk.getData();
        System.out.println("从硬盘上获取数据:" + data);
        cpu.run();
        memory.save();
    }

    public XiJieHardDisk getHardDisk() {
        return hardDisk;
    }

    public void setHardDisk(XiJieHardDisk hardDisk) {
        this.hardDisk = hardDisk;
    }

    public IntelCPU getCpu() {
        return cpu;
    }

    public void setCpu(IntelCPU cpu) {
        this.cpu = cpu;
    }

    public KingstonMemory getMemory() {
        return memory;
    }

    public void setMemory(KingstonMemory memory) {
        this.memory = memory;
    }
}
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        XiJieHardDisk hardDisk = new XiJieHardDisk();
        IntelCPU cpu = new IntelCPU();
        KingstonMemory memory = new KingstonMemory();

       Computer computer = new Computer();
       computer.setHardDisk(hardDisk);
       computer.setCpu(cpu);
       computer.setMemory(memory);
       //运行计算机
       computer.run();
    }
}
  • 上面的代码,实现了组装一台电脑,但硬盘只能组装希捷,CPU只能组装IntelCpu,内存条只能组装金士顿,这样对于用户来说并不友好,要更换其他硬盘需要修改Computer类的代码,下面使用依赖倒转原则进行改进;
  • 修改Computer类,让Computer类依赖抽象(各个配件的接口),而不是依赖于各个组件具体的实现类,UML类图设计如下:
image.png
  • 代码实现如下:
public interface HardDisk {
    public void save(String data);
    public String getData();
}
public interface Cpu {
    public void run();
}
public interface Memory {
    public void save();
}
public class XiJieHardDisk implements HardDisk{
    public void save(String data) {
        System.out.println("使用希捷硬盘存储数据:" + data);
    }

    public String getData() {
        System.out.println("使用希捷硬盘取数据:");
        return "数据";
    }
}
public class IntelCPU implements Cpu{
    public void run() {
        System.out.println("使用IntelCPU处理器");
    }
}
public class KingstonMemory implements Memory{
    public void save() {
        System.out.println("使用金士顿内存条 存储数据");
    }
}
public class Computer {
    private HardDisk hardDisk;
    private Cpu cpu;
    private Memory memory;

    public void run() {
        System.out.println("运行计算机");
        String data = hardDisk.getData();
        System.out.println("从硬盘上获取数据:" + data);
        cpu.run();
        memory.save();
    }

    public HardDisk getHardDisk() {
        return hardDisk;
    }

    public void setHardDisk(HardDisk hardDisk) {
        this.hardDisk = hardDisk;
    }

    public Cpu getCpu() {
        return cpu;
    }

    public void setCpu(Cpu cpu) {
        this.cpu = cpu;
    }

    public Memory getMemory() {
        return memory;
    }

    public void setMemory(Memory memory) {
        this.memory = memory;
    }
}
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        HardDisk hardDisk = new XiJieHardDisk();
        Cpu cpu = new IntelCPU();
        Memory memory = new KingstonMemory();

       Computer computer = new Computer();
       computer.setHardDisk(hardDisk);
       computer.setCpu(cpu);
       computer.setMemory(memory);
       //运行计算
       computer.run();
    }
}
  • 现在想要更换其他硬盘例如西数硬盘,只要再定义一个类XiShuHardDisk,实现HardDisk接口接口,不用去修改Computer类的代码;
接口隔离原则
  • 客户端不应该被迫依赖于它不使用的方法,一个类对另一个类的依赖应该建立在最小的接口上;
  • 安全门案例:京东品牌的安全门,其具有防火,防盗,防水的功能,可将防火,防盗,防水的功能提取成一个接口,然后让京东品牌安全门去实现这个接口,UML类图如下:
image.png
  • 现在又有一个天猫品牌的安全门,而该安全门只有防盗,防水的功能,若让天猫品牌的安全门直接实现SafeDoor接口,那么其被迫依赖实现防火的方法,这就违背了接口隔离原则,改进方案为:接口隔离成三个不同的接口,UML类图如下:
image.png
  • 代码实现如下:
public interface AntiTheft {
    public void antiTheft();
}
public interface FireProof {
    public void fireProof();
}
public interface WaterProof {
    public void waterProof();
}
public class JDSafeDoor implements AntiTheft,FireProof,WaterProof{
    @Override
    public void antiTheft() {
        System.out.println("防盗");
    }

    @Override
    public void fireProof() {
        System.out.println("防火");
    }

    @Override
    public void waterProof() {
        System.out.println("防水");
    }
}
public class TianMaoSafeDoor implements AntiTheft,WaterProof{
    @Override
    public void antiTheft() {
        System.out.println("防盗");
    }

    @Override
    public void waterProof() {
        System.out.println("防水");
    }
}
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        JDSafeDoor jdSafeDoor = new JDSafeDoor();
        jdSafeDoor.antiTheft();
        jdSafeDoor.fireProof();
        jdSafeDoor.waterProof();

        TianMaoSafeDoor tianMaoSafeDoor = new TianMaoSafeDoor();
        tianMaoSafeDoor.antiTheft();
        tianMaoSafeDoor.waterProof();
    }
}
迪米特法则
  • 迪米特法则:又叫最少知识原则,只和你的直接朋友交谈,不跟陌生人说话,其含义是 如果两个软件实体无需直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用,其目的是降低类之间的耦合度,提高模块的相对独立性;
  • 案例:明星与经纪人的关系,明星全身心投入于艺术创作,日常事务由经纪人负责,例如粉丝见面会,与媒体公司谈合作,这里的经纪人是明星的朋友,粉丝与媒体公司是陌生人,这里可以使用迪米特法则,UML类图如下:
image.png
  • 案例代码实现:
public class Star {
    private String name;

    public Star(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
public class Fans {
    private String name;

    public Fans(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
public class Company {
    private String name;

    public Company(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
public class Agent {
    private Star star;
    private Fans fans;
    private Company company;

    public void meeting() {
        System.out.println(star.getName()+"和粉丝" + fans.getName() + "见面");
    }

    public void business() {
        System.out.println(star.getName() + "和" + company.getName() + "洽谈");
    }

    public void setStar(Star star) {
        this.star = star;
    }

    public void setFans(Fans fans) {
        this.fans = fans;
    }

    public void setCompany(Company company) {
        this.company = company;
    }
}
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Agent agent = new Agent();

        Star star = new Star("刘诗诗");
        agent.setStar(star);
        Fans fans = new Fans("SF");
        agent.setFans(fans);
        Company company = new Company("tengXun");
        agent.setCompany(company);
        //明星和粉丝见面
        agent.meeting();
        //明星和公司洽谈
        agent.business();
    }
}
合成复用原则
  • 合成复用原则:是指尽量先使用组合和聚合等关联关系来实现,其次才考虑使用继承关系来实现;
  • 类的复用分为继承服用和合成复用;
  • 继承复用简单且易实现,但存在以下缺点:
    • 继承复用破坏了类的封装性,因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为白箱复用
    • 子类与父类的耦合度高,父类实现的任何改变都会改变子类的实现发生变化,这不利于类的扩展与维护;
    • 它限制了复用的灵活性,从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化;
  • 采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,其优点又如下:
    • 它维持了类的封装性,因为成员对象的内部细节对于新对象是看不见的,所以这种复用又称为黑箱复用;
    • 对象见的耦合度低,可以在类的成员位置声明抽象;
    • 复用的灵活性高,这种复用可以在运行时动态进行,新对象可以动态的引用与成员对象类型相同的对象;
  • 案例分析:汽车分类管理程序,汽车按动力源可分为汽油汽车,电动汽车,按颜色可分为白色,黑色,红色汽车,若同时考虑这两种分类,其组合有很多,采用继承的UML类图如下:
image.png
  • 若现在再新增一个光能汽车类,那么还再会创建两个子类,白色光能与红色光能汽车类,比较繁琐;
  • 采用聚合复用的UML类图如下:
image.png
  • 新增一个光能汽车类即可;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容