設計模式2--設計原則
阿新 • • 發佈:2020-08-05
設計模式--設計原則
重新認識面向物件
-
理解隔離變化
- 從巨集觀層面來看,面向物件的構建方式更能適應軟體的變化,能將變化所帶來的影響減為最小
-
各司其職
-
從微觀層面來看,面向物件的方式更強調各個類的“責任”
-
由於需求變化導致的新增型別不應該影響原來型別的實現—— 是所謂各負其責
-
-
物件是什麼?
-
從語言實現層面來看,物件封裝了程式碼和資料。
-
從規格層面講,物件是一系列可被使用的公共介面。
-
從概念層面講,物件是某種擁有責任的抽象。
-
面向物件的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
是個穩定的抽象的介面,之後所有的子類都依賴該基類,滿足依賴倒置原則,封閉原則,等。