设计模式(一)(设计模式分类、重构关键技法、重新认识面相对象、八大设计原则)

设计模式分类

从目的来看:

  • 创建型模式——如何创建对象
  • 结构型模式——如何实现类或对象的组合
  • 行为型模式——类或对象怎样交互以及怎样分配职责

从范围来看:

  • 类模式处理类与子类的静态关系——偏重于继承方案
  • 对象模式处理对象间的动态关系——偏重于组合方案

从封装变化角度对模式分类:

组件协作模式 对象性能 数据结构 单一职责
       
接口隔离 对象创建 行为变化 状态变化
       
领域问题      
       

重构关键技法

  • 静态绑定->动态绑定
  • 早绑定->晚绑定
  • 继承->组合
  • 编译时依赖->运行时依赖
  • 紧耦合->松耦合

设计模式的终极目标——高内聚低耦合(高内聚:一个模块中的功能越少越好)


重新认识面相对象

从底层思维理解:

封装、继承、多态

从抽象思维理解:

1、面向对象可以隔离变化——将变化带来的影响减为最小

2、各司其责——面向对象更强调各个类的“责任”

3、对象是什么

  • 从语言层面——封装了代码和数据
  • 从规格层面——对象是一系列可悲使用的公共接口
  • 从概念层面——对象是某种拥有责任的抽象

八大设计原则

单一职责原则

  • 一个类应该仅有一个引起他变化的原因
  • 变化的方向隐含着类的责任

类的职责单一,对外只提供一种功能,而引起类变化的原因都应该只有一个。

观察下面类,两个方法的返回值相同,后期接手该代码的人可能会因此将main函数中的函数调用全都改成cs.working()

class Clothes{
public:
    void working(){
        cout<<"working"<<endl;
    }

    void shopping(){
        cout<<"working"<<endl;
    }
};

int main(){
    Clothes cs;
    cs.working();
    cs.shopping();
}

这就是不遵循单一职责原则带来的弊端。改进方式是涉及两个类,一个用于实现working,一个实现shopping

开闭原则

  • 对扩展开放,对更改封闭
  • 类模块应该是可扩展的,但是不可修改

类的改动是通过增加代码进行的,而不是修改源代码。

写一个类,如果写成如下形式,那么想增加其他功能就需要修改类的代码,一旦因为修改出现错误,会导致整个类无法使用

可以通过抽象类,对于每个子类实现一种功能

 

里氏替换原则

  • 子类能够替换他们的基类(is a)
  • 继承表达类型抽象

任何抽象类出现的地方都可以用他的实现类进行替换,实际就是虚拟机制,语言级别实现面相对象功能。

依赖倒置原则

依赖于抽象(接口),不要依赖集体的实现(类),也就是针对接口编程。

代码实现

//抽象层
class Car{
public:
    virtual void run()=0;
};

class Driver{
public:
    virtual void driver()=0;
};

//实现层
calss Benz:public Car{
public:
    virtual void run(){
        cout<<"benz run..."<<endl;
    }
};

class BMW:public Car{
public:
    virtual void run(){
        cout<<"BMW run..."<<endl;
    }
};

class Zhang3:public Driver{
public:
    Zhang3(Car *car){
        this->car=car;
    }
    virtual void drive(){
        cout<<"Zhang3 run..."<<endl
    }

private:
    Car *car;
};

//业务逻辑实现,只依赖抽象层
int main(){
    Car *benz=new Benz;
    Driver *zhang3=new zhang3(benz);

    zhang3->drive();
}

总结:

  • 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
  • 抽象不应该依赖于实现细节,实现细节应该依赖于抽象

观察下列代码,分解的设计方法。这种设计如果有需求变动,会改动很多原本的代码。

类之间的结构如下(mainForm依赖于line和rect类,这里的变化就是line和rect,由于mainform依赖于变化,导致mainform也变得不稳定了):

class Point {
public:
	int x;
	int y;
};

class Line {
public:
	Point start;
	Point end;

	Line(const Point& start, const Point& end) {
		this->start = start;
		this->end = end;
	}
};

class Rect {
public:
	Point leftUp;
	int width;
	int height;

	Rect(const Point& leftUp, int width, int height) {
		this->leftUp = leftUp;
		this->width = width;
		this->height = height;
	}
};


class MainForm : public Form {
private:
	Point p1;
	Point p2;

    //直接存储的是line对象和rect对象,因为此时我们不需要多态性。
    //对比下面的那一块的代码因为需要多态性,所以存储的是指针
	vector<Line> lineVector;
	vector<Rect> rectVector;
	//改变
	vector<Circle> circleVector;

public:
	MainForm(){
		//...
	}
protected:

	virtual void OnMouseDown(const MouseEventArgs& e);
	virtual void OnMouseUp(const MouseEventArgs& e);
	virtual void OnPaint(const PaintEventArgs& e);
};

抽象的设计方法——所有的图形类都继承他,抽象有利于统一处理

补充:析构函数设置成virtual,是为了正确的调用子类的析构函数

类之间的结构如下(mainForm依赖于shape类,line和rect也依赖于shape。这里的变化就是line和rect,我们通过抽象类shape进行隔离变化。下图中的mainform和shape是稳定的):

class Shape {
public:
	virtual void Draw(const Graphics& g) = 0;
	virtual ~Shape() { }
};

class Point {
public:
	int x;
	int y;
};

class Line : public Shape {
public:
	Point start;
	Point end;

	Line(const Point& start, const Point& end) {
		this->start = start;
		this->end = end;
	}

	//实现自己的Draw,负责画自己
	virtual void Draw(const Graphics& g) {
		g.DrawLine(Pens.Red,
			start.x, start.y, end.x, end.y);
	}
};

class Rect : public Shape {
public:
	Point leftUp;
	int width;
	int height;

	Rect(const Point& leftUp, int width, int height) {
		this->leftUp = leftUp;
		this->width = width;
		this->height = height;
	}

	//实现自己的Draw,负责画自己
	virtual void Draw(const Graphics& g) {
		g.DrawRectangle(Pens.Red,
			leftUp, width, height);
	}
};

class MainForm : public Form {
private:
	Point p1;
	Point p2;

	//针对所有形状。存放指针是为了实现多态性
	vector<Shape*> shapeVector;

public:
	MainForm(){
		//...
	}
protected:

	virtual void OnMouseDown(const MouseEventArgs& e);
	virtual void OnMouseUp(const MouseEventArgs& e);
	virtual void OnPaint(const PaintEventArgs& e);
};

接口隔离原则

  • 不应该强迫客户程序依赖他们不用的方法(因为一旦产生依赖,就需要接口保持稳定)
  • 接口应该小而完备

不应该强迫用户依赖他们不需要的接口方法。一个接口应该只提供一种对外功能,不应该把所有的操作都封装到一个接口中去。

合成复用原则

对象组合——即class A里面封装了class B,class B可以是对象指针,也可以是对象。

  • 类继承通常为“”白箱复用“,对象组合通常为“黑箱复用””。
  • 继承在某种程度上破坏了封装性,子类父类的耦合度高
  • 对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。

如果使用继承,会导致父类的任何变换都可能影响到子类的行为。如果使用对象组合,就降低了这种依赖关系。对于继承和组合,优先使用组合。

迪米特法则

一个对象应当对其他对象尽可能少的了解,从而降低对象之间的耦合,提高体统的可维护性。例如在一个程序中,各个模块之间互相调用时,通常会提供一个统一的接口来实现。这样其他模块不需要了解一个模块内部的实现细节(黑盒原理)

封装变化点

使用封装来创建对象之间的分界层,让设计者可以在一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。

猜你喜欢

转载自blog.csdn.net/qq_29996285/article/details/88376625