博览网:C++设计模 第三周 笔记

23种设计模式

“对象性能”模式

面向对象很好的解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不计。但是某些情况,面向对象所带来的成本必须谨慎处理。

典型模式:Singleton              Flyweight

14.Singleton单件模式

模式定义

保证一个类仅有一个实例,并提供一个该实例的全局访问点。

——《设计模式》GoF

动机(Motivation)

1)在软件系统中,经常有这样一个特殊的类,必须保证它们在系统中只存在一个示例,才能确保他们的逻辑正确性、以及良好的效率。

2)如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?

3)这个应该类设计者的责任,而不是使用者的责任。

单件模式代码:

class Singleton{

private:

Singleton();

Singleton(constSingleton& other);

public:

staticSingleton* getInstance();

staticSingleton* m_instance;

};

Singleton* Singleton::m_instance=nullptr;

//线程非安全版本

Singleton* Singleton::getInstance() {

if(m_instance == nullptr) {

m_instance =newSingleton();

}

returnm_instance;

}

//线程安全版本,但锁的代价过高

Singleton* Singleton::getInstance() {

Lock lock;

if(m_instance == nullptr) {

m_instance =newSingleton();

}

returnm_instance;

}

结构:



要点总结

1)Singleton模式中的实例构造器可以设置为protected以允许子类派生。

2)Singleton模式一般不要支持拷贝构造函数和Clone接口,因为这有可能会导致多个对象实例,与Singleton模式的初衷相违背。

3)如何实现多线程环境下安全的Singleton?注意对双检查锁的正确实现。

15.享元模式FlyWeight

模式定义

运用共享技术有效地支持大量的细粒度对象

——《设计模式》GoF

动机(Motivation)

1)在软件系统中采用纯粹对象方案的问题 在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价——主要指内存需求方面的代价。

2)如何在避免大量·细粒度对象问题的同事,让外部客户程序仍然能够透明地使用面向对象的方式来进行操作?

享元模式代码:

class Font {

private:

//unique object key

string key;

//object state

//....

public:

Font(conststring& key){

//...

}

};

class FontFactory{

private:

map fontPool;

public:

Font* GetFont(conststring& key){

map::iterator item=fontPool.find(key);

if(item!=footPool.end()){

returnfontPool[key];

}

else{

Font*  font =newFont(key);

fontPool[key]= font;

returnfont;

}

}

voidclear(){

//...

}

};

结构:



要点总结

1)面向对象很好的解决了抽相性的问题,但是作为一个运行在机器中的程序实体,我们需要考虑对象的代价问题。Flyweight主要解决面向的代价问题,一般不触及面向对象的抽象性问题。

2)Flyweight采用对象共享的做法来降低系统中的对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对像状态的处理。

3)对象的数量太大,从而导致对像内存开销加大——什么样的数量才算大?这需要我们仔细根据具体应用情况进行评估,而不能凭空臆断。

“状态变化”模式

在组建构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时又维持高层模块的稳定?“状态变化”模式为这一个问题提供了一种解决方案。

典型模式:State       Memento

16.State状态模式

模式定义

允许一个对象在其内部状态改变是改变它的行为。从而使对像看起来似乎修改其行为。

——《设计模式》GoF

动机(Motivation)

1)在软件构建过程中,某些对象的状态如果改变,其行为也会随之而发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能会完全不同。

2)如何在运行时根据对象的状态来透明地更改对象的行为?而不会为对象操作和状态转化之间引入紧耦合?

状态模式代码:

class NetworkState{

public:

NetworkState* pNext;

virtual voidOperation1()=0;

virtual void Operation2()=0;

virtual void Operation3()=0;

virtual ~NetworkState(){}

};

class OpenState :public NetworkState{

static NetworkState* m_instance;

public:

static NetworkState* getInstance(){

if(m_instance == nullptr) {

m_instance =new OpenState();

}

returnm_instance;

}

void Operation1(){

//**********

pNext = CloseState::getInstance();

}

void Operation2(){

//..........

pNext = ConnectState::getInstance();

}

void Operation3(){

pNext = OpenState::getInstance();

}

};

class CloseState:public NetworkState{ }

//...

class NetworkProcessor{

NetworkState* pState;

public:

NetworkProcessor(NetworkState* pState){

this->pState = pState;

}

void Operation1(){

//...

pState->Operation1();

pState = pState->pNext;

//...

}

void Operation2(){

//...

pState->Operation2();

pState = pState->pNext;

//...

}

void Operation3(){

//...

pState->Operation3();

pState = pState->pNext;

//...

}

};

结构:



要点总结

1)State模式将所有与一个特定状态相关的行为都放入一个State的子类对象中,在对像状态切换时, 切换相应的对象;但同时维持State的接口,这样实现了具体操作与状态转换之间的解耦。

2)为不同的状态引入不同的对象使得状态转换变得更加明确,而且可以保证不会出现状态不一致的情况,因为转换是原子性的——即要么彻底转换过来,要么不转换。

3)如果State对象没有实例变量,那么各个上下文可以共享同一个State对象,从而节省对象开销。

17.Memento备忘录

模式定义

在不破坏封装性的前提下,不活一个对象的内部状态,并在该对像之外保存这个状态。这样以后就可以将该对像恢复到原想保存的状态。

——《设计模式》GoF

动机(Motivation)

1)在软件构建过程中,某些对象的状态在转会过程中,可能由于某种需求,要求程序能够回溯到对像之前处于某个点时的状态。如果使用一些公有借口来让其它对象得到对象的状态,便会暴露对象的实现细节。

2)如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性。

Memento模式代码:

class Memento

{

string state;

//..

public:

Memento(const string & s) : state(s) {}

string getState() const {returnstate; }

void setState(const string & s) { state = s; }

};

class Originator

{

string state;

//....

public:

Originator() {}

Memento createMomento() {

Memento m(state);

returnm;

}

void setMomento(constMemento & m) {

state = m.getState();

}

};

int main()

{

Originator orginator;

//捕获对象状态,存储到备忘录

Memento mem = orginator.createMomento();

//... 改变orginator状态

//从备忘录中恢复

orginator.setMomento(memento);

}

结构:



要点总结

1)备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器的状态。

2)Memento模式的核心是信息隐藏,即Originator需要向外接隐藏信息,保持其封装性。但同时又需要将其状态保持到外界(Memento)

3)由于现代语言运行时(如C#、java等)都具有相当的对象序列化支持,因此往往采用效率较高、又较容易正确实现的序列化方案来实现Memento模式。

“数据结构”模式

常常有一些组建在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大的破坏组件的复用。这时候,将这些数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问,是一种行之有效的解决方案。

典型模式:Composite         Iterator              Chain of Responsibility

18.Composite模式

模式定义

将对象组合成树形结构以表示“部分-整体”的层级结构。Compisite使得用户对单个对象和组合对象的使用具有一致性(稳定)。

——《设计模式》GoF

动机(Motivation)

1)软件在某些情况下,客户代码过多地依赖于对像容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将因其客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。

2)如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?

Composite模式代码:

#include  <iostream>

#include <list>

#include <string>

#include <algorithm>

using namespace std;

class Component

{

public:

virtual void process() = 0;

virtual ~Component(){}

};

//树节点

class Composite :public Component{

string name;

list elements;

public:

Composite (conststring & s) : name(s) {}

void add(Component* element) {

elements.push_back(element);

}

void remove(Component* element){

elements.remove(element);

}

void process(){

//1. process current node

//2. process leaf nodes

for(auto &e : elements)

e->process();//多态调用

}

};

//叶子节点

class Leaf :publicComponent{

string name;

public:

Leaf(string s) : name(s) {}

void process(){

//process current node

}

};

void Invoke(Component & c){

//...

c.process();

//...

}

int main()

{

Composite root("root");

Composite treeNode1("treeNode1");

Composite treeNode2("treeNode2");

Composite treeNode3("treeNode3");

Composite treeNode4("treeNode4");

Leaf leat1("left1");

Leaf leat2("left2");

root.add(&treeNode1);

treeNode1.add(&treeNode2);

treeNode2.add(&leaf1);

root.add(&treeNode3);

treeNode3.add(&treeNode4);

treeNode4.add(&leaf2);

process(root);

process(leaf2);

process(treeNode3);

}

结构:



要点总结

1)Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地(复用)处理对象和对象容器,无需关心处理的是单个对象还是组合的对象容器。

2)将“客户代码与复杂的对象容器结构”解耦是Composite的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对像容器的内部实现结构——发生依赖,从而更能“应对变化”。

3)Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。

19.Iterator迭代器

模式定义

提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露(隔离变化,稳定)该对象的内部表示。

——《设计模式》GoF

动机(Motivation)

1)在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明的访问其中包含的元素;同时这种“透明遍历”也为“同一种算法在多种集合对象上进行操作”提供了可能。

2)使用面向对象技术将这种遍历机制抽象为“迭代器对象”为“因对变化中的集合对象”提供了一种优雅的方式。

3)Iterator模式代码:

template

class Iterator

{

public:

virtual void first() = 0;

virtua lvoid next() = 0;

virtual bool isDone()const= 0;

virtual T& current() = 0;

};

template

class MyCollection{

public:

Iterator GetIterator(){

//...

}

};

template

class CollectionIterator :publicIterator{

MyCollection mc;

public:

CollectionIterator(constMyCollection & c): mc(c){ }

void first() override {

}

void next() override {

}

bool isDone()constoverride{

}

T& current() override{

}

};

void MyAlgorithm()

{

MyCollection mc;

Iterator iter= mc.GetIterator();

for(iter.first(); !iter.isDone(); iter.next()){

cout << iter.current() << endl;

}

}

结构:



要点总结:

1)迭代抽象:访问一个聚合对象的内容而无需暴露他的内部表示。

2)迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。

3)迭代器健壮性考虑:遍历的同时更改迭代器所在的集合结构,会导致问题。

20.Chain of Resposibility职责链

模式定义

使多个对像都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对像连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。

——《设计模式》GoF

动机(Motivation)

1)在软件构建的过程中,一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显示指定,将必不可少的带来请求发送者与接受者的耦合。

2)如何使请求的发送者不需要指定具体的接受者?让请求的接受者自己在运行时决定来处理请求,从而使两者解耦。

职责链模式代码:

#include  <iostream>

#include <string>

using names pacestd;

enum class RequestType

{

REQ_HANDLER1,

REQ_HANDLER2,

REQ_HANDLER3

};

class Reqest

{

string description;

RequestType reqType;

public:

Reqest(const string & desc, RequestType type) : description(desc), reqType(type) {}

RequestType getReqType() const {returnreqType; }

const string & getDescription() const{returndescription; }

};

class ChainHandler{

ChainHandler *nextChain;

void sendReqestToNextHandler(const Reqest & req)

{

if(nextChain != nullptr)

nextChain->handle(req);

}

protected:

virtual bool canHandleRequest(const Reqest & req) = 0;

virtua lvoid processRequest(const Reqest & req) = 0;

public:

ChainHandler() { nextChain = nullptr; }

void setNextChain(ChainHandler *next) { nextChain = next; }

void handle(constReqest & req)

{

if(canHandleRequest(req))

processRequest(req);

else

sendReqestToNextHandler(req);

}

};

class Handler1 :public ChainHandler{

protected:

bool canHandleRequest(const Reqest & req) override

{

return req.getReqType() == RequestType::REQ_HANDLER1;

}

void processRequest(cons tReqest & req) override

{

cout <<"Handler1 is handle reqest: "<< req.getDescription() << endl;

}

};

class Handler2 :public ChainHandler{

protected:

bool canHandleRequest(const Reqest & req) override

{

return req.getReqType() == RequestType::REQ_HANDLER2;

}

void processRequest(const Reqest & req) override

{

cout <<"Handler2 is handle reqest: "<< req.getDescription() << endl;

}

};

class Handler3 :public ChainHandler{

protected:

bool canHandleRequest(const Reqest & req) override

{

return req.getReqType() == RequestType::REQ_HANDLER3;

}

void processRequest(const Reqest & req) override

{

cout <<"Handler3 is handle reqest: "<< req.getDescription() << endl;

}

};

intmain(){

Handler1 h1;

Handler2 h2;

Handler3 h3;

h1.setNextChain(&h2);

h2.setNextChain(&h3);

Reqest req("process task ... ", RequestType::REQ_HANDLER3);

h1.handle(req);

return0;

}

结构:



要点总结

1)Chain of Responsibility模式的应用场合在于“一个请求可能有多个接受者,但是最后真正接受者只有一个”,这时候请求发送者与接受者的耦合可能出现“变化脆弱”的症状,职责链的目的就是将二者解耦,从而更好的应对变化。

2)应用了Chain of Responsibility模式后,对象的职责分派将更具灵活性。我们可以在运行时动态添加/修改请求的处理指责。

3)如果请求传递到职责链的末尾仍得不到处理,应该有一个合理的缺省机制。这也是每一个接受者对象的责任,而不是发出请求的对象的责任。

“行为变化”模式

在组建的构建过程中,组建行为的变化经常导致组建本身剧烈的变化。“行为变化”模式将组建的行为和组建本身进行解耦,从而主持组件的变化,实现两者之间的松耦合。

典型模式:Command         Visitor

21.Command命令模式

模式定义

将一个请求(行为)封装为对象,从而使你可用不同的请求,对客户进行参数化;对请求排队或记录请求日志以及支持可撤销的操作。

——《设计模式》GoF

动机(Motivation)

1)在软件构建构成中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合——比如需要对行为进行“记录、撤销(undo)、事务”等处理,这种无法抵御变化的紧耦合是不合适的。

2)在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

Command模式代码:

#include <iostream>

#include <vector>

#include <string>

using namespace std;

class Command

{

public:

virtua lvoid execute() = 0;

};

class ConcreteCommand1 :public Command

{

string arg;

public:

ConcreteCommand1(const string & a) : arg(a) {}

void execute() override

{

cout<<"#1 process..."<

}

};

class ConcreteCommand2 :public Command

{

string arg;

public:

ConcreteCommand2(const string & a) : arg(a) {}

void execute() override

{

cout<<"#2 process..."<

}

};

class MacroCommand :public Command

{

vector commands;

public:

void addCommand(Command *c) { commands.push_back(c); }

void execute() override

{

for(auto &c : commands)

{

c->execute();

}

}

};

int main()

{

ConcreteCommand1 command1(receiver,"Arg ###");

ConcreteCommand2 command2(receiver,"Arg $$$");

MacroCommand macro;

macro.addCommand(&command1);

macro.addCommand(&command2);

macro.execute();

}

结构



要点总结

1)Command模式的根本目的在于“行为请求者”与“行为实现者”解耦,在面向对象的语言中,常见的实现手段是“将行为抽象为对象”

2)实现Command接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。通过使用Composite模式,可以将多个“命令”封装为一个“符合命令”MacroCommand

3)Command模式与C++中的函数对像有些类似。但两者定义行为接口的规范有所区别:Command以面向对象中的“接口-实现”来定义行为接口规范,更严格,但有性能损失;C++函数对象以函数签名来定义行为接口规范,更灵活,性能能高。

22.Visitor访问器

模式定义

表示一个作用与某对像结构中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展)作用于这些元素的新操作(变化)。

——《设计模式》GoF

动机(Motivation)

1)在软件构建的过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法)。如果直接在类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。

2)如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?

Visitor模式代码:

#include <iostream>

using namespace std;

class Visitor;

class Element

{

public:

virtual void Func1() = 0;

virtua lvoid Func2(intdata)=0;

virtual void Func3(intdata)=0;

//...

virtual ~Element(){}

};

class ElementA :public Element

{

public:

void Func1() override{

//...

}

void Func2(intdata) override{

//...

}

};

class ElementB :public Element

{

public:

void Func1() override{

//***

}

void Func2(intdata) override {

//***

}

};

结构



要点总结

1)Vistor模式通过所谓的双重分发(double dispatch)来实现在不更改(不添加新的操作-编译时)Element类层次结构的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作(支持变化)。

2)所谓双重分发即Vistor模式中间包括了两个多态分发(注意其中的多态机制):第一个accept方法的多态解析;第二个为visitElementX方法的多态辨析。

3)Visitor模式最大的缺点在于扩展类层次结构(增添新的Element子类),会导致Visitor类的改变。因此Visitor模式适用于“Element类层次结构稳定,而其中的操作却进场面临频繁改动”。

“领域规则”模式

在特定领域内,某些变化虽然频繁,但可以抽象为某种规则。这时候,结合特定领域,将问题抽象为语法规则,从而给出该领域下的一般性解决方案。

典型模式

Interpreter

23.Interpreter解析器

模式定义

给定一个语言,定义它的文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子。

——《设计模式》GoF

动机(Motivation)

1)在软件构建过程中,如果某一特定领域的问题比较复杂,类似的结构不断的重复出现,如果使用普通的变成方式来实现将面临非常频繁的变化。

2)在这种情况下,将特定领域的问题表达为某种语法规则下的句子,然后构建一个解析器来解释这样的句子,从而达到解决问题的目的。

Interpret模式代码:

#include <iostream>

#include <map>

#include <stack>

using namespace std;

class Expression {

public:

virtual intinterpreter(map var)=0;

virtual ~Expression(){}

};

//变量表达式

class VarExpression:public Expression {

charkey;

public:

VarExpression(constchar& key)

{

this->key = key;

}

intinterpreter(map var) override {

returnvar[key];

}

};

//符号表达式

classS ymbolExpression :public Expression {

// 运算符左右两个参数

protected:

Expression* left;

Expression* right;

public:

SymbolExpression( Expression* left,  Expression* right):

left(left),right(right){

}

};

//加法运算

class AddExpression :public SymbolExpression {

public:

AddExpression(Expression* left, Expression* right):

SymbolExpression(left,right){

}

intinterpreter(map var) override {

returnleft->interpreter(var) + right->interpreter(var);

}

};

//减法运算

class SubExpression :public SymbolExpression {

public:

SubExpression(Expression* left, Expression* right):

SymbolExpression(left,right){

}

intinterpreter(map var) override {

returnleft->interpreter(var) - right->interpreter(var);

}

};

Expression*  analyse(string expStr) {

stack expStack;

Expression* left = nullptr;

Expression* right = nullptr;

for(inti=0; i

{

switch(expStr[i])

{

case'+':

// 加法运算

left = expStack.top();

right =newVarExpression(expStr[++i]);

expStack.push(newAddExpression(left, right));

break;

case'-':

// 减法运算

left = expStack.top();

right =newVarExpression(expStr[++i]);

expStack.push(newSubExpression(left, right));

break;

default:

// 变量表达式

expStack.push(newVarExpression(expStr[i]));

}

}

Expression* expression = expStack.top();

return expression;

}

void release(Expression* expression){

//释放表达式树的节点内存...

}

int main(intargc,constchar* argv[]) {

string expStr ="a+b-c+d-e";

map var;

var.insert(make_pair('a',5));

var.insert(make_pair('b',2));

var.insert(make_pair('c',1));

var.insert(make_pair('d',6));

var.insert(make_pair('e',10));

Expression* expression= analyse(expStr);

int result=expression->interpreter(var);

cout<

release(expression);

return0;

}

结构



要点总结

1)Interpreter模式的应用场合是Interpreter模式应用中的难点,只有满足“业务规则频繁变化,且类似的结构不断重复出现,并且容易抽象为语法规则的问题”才适合使用Interpreter模式。

2)使用Interpreter模式来表示文法规则,从而可以使用面向对象技巧来方便地“扩展”文法。

3)Interpreter模式比较适合简单的文法表示,对于复杂的文法表示,Interpreter模式会产生比较大的类层次结构,需要求助于语法分析生成器这样的标准工具。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 23种设计模式 “对象性能”模式 面向对象很好的解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况...
    孙浩_9bfd阅读 513评论 0 0
  • 23种设计模式 “对象性能”模式 面向对象很好的解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况...
    孙浩_9bfd阅读 379评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 面向对象的六大原则 单一职责原则 所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于...
    JxMY阅读 923评论 1 3