1. 程式人生 > 實用技巧 >設計模式2--設計原則

設計模式2--設計原則

設計模式--設計原則

重新認識面向物件

  • 理解隔離變化

    • 從巨集觀層面來看,面向物件的構建方式更能適應軟體的變化,能將變化所帶來的影響減為最小
  • 各司其職

    • 從微觀層面來看,面向物件的方式更強調各個類的“責任”

    • 由於需求變化導致的新增型別不應該影響原來型別的實現—— 是所謂各負其責

  • 物件是什麼?

    • 從語言實現層面來看,物件封裝了程式碼和資料。

    • 從規格層面講,物件是一系列可被使用的公共介面。

    • 從概念層面講,物件是某種擁有責任的抽象。

面向物件的8大設計原則

1:依賴倒置原則(DIP)

  • 高層模組(穩定)不應該依賴於低層模組(變化),二者都應該依賴於抽象(穩定) 。
  • 抽象(穩定)不應該依賴於實現細節(變化) ,實現細節應該依賴於抽象(穩定)。

2:開放封閉原則(OCP)

  • 對擴充套件開放,對更改封閉。
  • 類模組應該是可擴充套件的,但是不可修改。

3:單一職責原則(SRP)

  • 一個類應該僅有一個引起它變化的原因。
  • 變化的方向隱含著類的責任。

4:Liskov 替換原則(LSP)

  • 子類必須能夠替換它們的基類(IS-A)。
  • 繼承表達型別抽象。

5:介面隔離原則(ISP)

  • 不應該強迫客戶程式依賴它們不用的方法。
  • 介面應該小而完備。

6:優先使用物件組合,而不是類繼承

  • 類繼承通常為“白箱複用”,物件組合通常為“黑箱複用”。
  • 繼承在某種程度上破壞了封裝性,子類父類耦合度高。
  • 而物件組合則只要求被組合的物件具有良好定義的介面,耦合度低。

7:封裝變化點

  • 使用封裝來建立物件之間的分界層,讓設計者可以在分界層的 一側進行修改,而不會對另一側產生不良的影響,從而實現層次間的鬆耦合。

8:針對介面程式設計,而不是針對實現程式設計

  • 不將變數型別宣告為某個特定的具體類,而是宣告為某個介面。

  • 客戶程式無需獲知物件的具體型別,只需要知道物件所具有的 介面。

  • 減少系統中各部分的依賴關係,從而實現“高內聚、鬆耦合”的型別設計方案。

示例程式碼

不好的程式碼設計

MainForm1.cpp

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

	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);
};


void MainForm::OnMouseDown(const MouseEventArgs& e){
	p1.x = e.X;
	p1.y = e.Y;

	//...
	Form::OnMouseDown(e);
}

void MainForm::OnMouseUp(const MouseEventArgs& e){
	p2.x = e.X;
	p2.y = e.Y;

	if (rdoLine.Checked){
		Line line(p1, p2);
		lineVector.push_back(line);
	}
	else if (rdoRect.Checked){
		int width = abs(p2.x - p1.x);
		int height = abs(p2.y - p1.y);
		Rect rect(p1, width, height);
		rectVector.push_back(rect);
	}
	//改變
	else if (...){
		//...
		circleVector.push_back(circle);
	}

	//...
	this->Refresh();

	Form::OnMouseUp(e);
}

void MainForm::OnPaint(const PaintEventArgs& e){

	//針對直線
	for (int i = 0; i < lineVector.size(); i++){
		e.Graphics.DrawLine(Pens.Red,
			lineVector[i].start.x, 
			lineVector[i].start.y,
			lineVector[i].end.x,
			lineVector[i].end.y);
	}

	//針對矩形
	for (int i = 0; i < rectVector.size(); i++){
		e.Graphics.DrawRectangle(Pens.Red,
			rectVector[i].leftUp,
			rectVector[i].width,
			rectVector[i].height);
	}

	//改變
	//針對圓形
	for (int i = 0; i < circleVector.size(); i++){
		e.Graphics.DrawCircle(Pens.Red,
			circleVector[i]);
	}

	//...
	Form::OnPaint(e);
}

Shape1.h

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 Circle{

};
該設計的缺陷
  • 當加入需要畫園的需求的時候,需要大面積更改原始碼,主要違背封閉原則。

優化程式碼結構

MainForm2.cpp

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);
};


void MainForm::OnMouseDown(const MouseEventArgs& e){
	p1.x = e.X;
	p1.y = e.Y;

	//...
	Form::OnMouseDown(e);
}

void MainForm::OnMouseUp(const MouseEventArgs& e){
	p2.x = e.X;
	p2.y = e.Y;

	if (rdoLine.Checked){
		shapeVector.push_back(new Line(p1,p2));
	}
	else if (rdoRect.Checked){
		int width = abs(p2.x - p1.x);
		int height = abs(p2.y - p1.y);
		shapeVector.push_back(new Rect(p1, width, height));
	}
	//改變
	else if (...){
		//...
		shapeVector.push_back(circle);
	}

	//...
	this->Refresh();

	Form::OnMouseUp(e);
}

void MainForm::OnPaint(const PaintEventArgs& e){

	//針對所有形狀
	for (int i = 0; i < shapeVector.size(); i++){

		shapeVector[i]->Draw(e.Graphics); //多型呼叫,各負其責
	}

	//...
	Form::OnPaint(e);
}

Shape2.h

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 Circle : public Shape{
public:
	//實現自己的Draw,負責畫自己
	virtual void Draw(const Graphics& g){
		g.DrawCircle(Pens.Red,
			...);
	}

};
優勢分析
  • 通過新增一個基類Shape並在中新增畫這個動作的純虛擬函式virtual void Draw(const Graphics& g)=0;,之後所有的形狀都繼承該基類Shape各自實現自己對應的畫的動作,在MainForm::OnPaint(const PaintEventArgs& e)函式中進行迴圈多型呼叫shapeVector[i]->Draw(e.Graphics);
  • 該設計的基類Shape是個穩定的抽象的介面,之後所有的子類都依賴該基類,滿足依賴倒置原則,封閉原則,等。