共计 8494 个字符,预计需要花费 22 分钟才能阅读完成。
设计模式,即 Design Patterns,是指在软件设计中,被重复应用的一种代码设计教训。应用设计模式的目标是为了可重用代码,进步代码的可扩展性和可维护性。设计模式这个术语是上个世纪 90 年代由 Erich Gamma、Richard Helm、Raplh Johnson 和 Jonhn Vlissides 四个人总结提炼进去的,并且写了一本 Design Patterns 的书。
我的项目地址:https://gitee.com/baichen9187…
一,开闭准则
开闭准则的定义
开闭准则是最根底的设计准则,它领导咱们如何建设一个稳固,灵便的零碎。开闭准则定义如下:
Software entities like classes,modules and functions should be open
for extension but closed for modifications.
OOP 实践中 5 个准则之一(别离是 S O L I D,繁多职责准则、开闭准则、里氏替换、接口隔离、依赖反转)
一个软件实体如类,模块和函数应该对扩大凋谢,对批改敞开。
什么是开闭准则
开闭准则明确的通知咱们:实体实现应该对扩大凋谢,对批改敞开,其含意是说一个实体应该通过扩大来实现变动,而不是通过批改已有的代码来实现变动的。那什么是实体呢?软件实体包含以下几个局部:
我的项目或软件产品中依照肯定的逻辑规定划分的模块
形象和类
办法
开闭准则的作用
开闭准则是面向对象程序设计的终极目标,它使软件实体领有肯定的适应性和灵活性的同时具备稳定性和延续性。具体来说,其作用如下。
- 对软件测试的影响软件恪守开闭准则的话,软件测试时只须要对扩大的代码进行测试就能够了,因为原有的测试代码依然可能失常运行。
- 能够进步代码的可复用性粒度越小,被复用的可能性就越大;在面向对象的程序设计中,依据原子和形象编程能够进步代码的可复用性。
- 能够进步软件的可维护性恪守开闭准则的软件,其稳定性高和延续性强,从而易于扩大和保护。
开闭准则的长处
- 进步代码复用性
- 进步可维护性
- 进步灵活性
- 易于测试
#include <iostream>
class Number {
protected:
double pi = 3.1415926;
public:
virtual double getCircularArea(int d) = 0;
virtual double getRectangularArea(int a, int b) = 0;
};
class NumberArea : public Number {
public:
double getCircularArea(int r) {return ((double)r * r) * this->pi;
}
double getRectangularArea(int a, int b) {return (double)a * b;
}
};
int main()
{
NumberArea num;
double cricular = num.getCircularArea(1);
std::cout << cricular << std::endl;
}
二,繁多职责准则
繁多职责定义
繁多职责准则(SRP:Single responsibility principle)又称繁多性能准则,面向对象五个根本准则(SOLID)之一。它规定一个类应该只有一个发生变化的起因。该准则由罗伯特·C·马丁(Robert C. Martin)于《麻利软件开发:准则、模式与实际》一书中给出的。马丁示意此准则是基于汤姆·狄马克(Tom DeMarco)和 Meilir Page-Jones 的著述中的内聚性准则倒退出的。
所谓职责是指类变动的起因。如果一个类有多于一个的动机被扭转,那么这个类就具备多于一个的职责。而繁多职责准则就是指一个类或者模块应该有且只有一个扭转的起因。
繁多职责的作用
就一个类而言,应该仅有一个引起它变动的起因。应该只有一个职责。
每一个职责都是变动的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致软弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。例如:要实现逻辑和界面的拆散。
繁多职责的长处
繁多职责准则的长处有以下几个方面:
- 升高类的复杂性;
- 进步类的可读性;
- 进步代码的可维护性和复用性;
- 升高因变更引起的危险。
#pragma once
#include<string>
using namespace std;
class ProductFactory {
public:
virtual string product(string material) = 0;
};
class ConsumeFactory {
public:
virtual void consume(string product) = 0;
};
#include<iostream>
#include"./factroy.h"
using namespace std;
class ProductFactoryimpl :ProductFactory{
public:
string product(string material) {return material ;}
};
class ConsumeFactoryimpl : ConsumeFactory {
public :
void consume(string product) {cout << "生产了" << product << endl;}
};
int main() {
ProductFactoryimpl p;
string data = p.product("goods");
ConsumeFactoryimpl c;
c.consume(data);
return 0;
}
繁多职责的违反准则
一个接口实现多种行为办法
#pragma once
#include<string>
using namespace std;
class ConsumeFactory {
public:
virtual string product(string material) = 0;
virtual void consume(string product) = 0;
};
class ConsumeFactory :ProductFactory{
public:
string product(string material) {return material ;}
void consume(string product) {cout << "生产了" << product << endl;}
};
int main() {
ConsumeFactoryimpl c;
string data = c.product("goods");
c.consume(data);
return 0;
}
三,依赖倒置准则
依赖倒置的定义
在依赖倒置准则中的倒置指的是和个别 OO 设计的思考形式齐全相同。
面向过程的开发,下层调用上层,下层依赖于上层,当下层激烈变动时下层也要跟着变动,这就会导致模块的复用性升高而且大大提高了开发的老本。
依赖倒置准则的核心思想是 面向接口编程,依赖倒置准则是 JavaBean、EJB 和 COM 等组件设计模型背地的根本准则
高层模块不应该依赖低层模块,应该依赖形象。
形象不应该依赖细节;细节应该依赖形象。
即便实现细节一直变动,只有形象不变,客户程序就不须要变动。这大大降低了客户程序与实现细节的耦合度。
依赖倒置准则的作用
依赖倒置准则能够无效缩小类之间的耦合性,进步零碎稳定性没升高并行开发中的危险,进步代码可维护性和可读性。
依赖倒置准则的实质就是通过形象(接口)使得各个类或模块之间互相独立,互不影响,实习模块之间的松耦合
模块间的依赖通过形象产生,实现类之间不产生间接的依赖关系,其依赖关系是通过接口或抽象类产生;
- 接口或抽象类不依赖于实现类;
- 实现类依赖于接口或抽象类。
- 尽量应用多态
- 任何类不应该从具体类派生
-
尽量不重写基类办法
案例剖析
其中的生物蕴含依赖了动植物
场景问题: 类 A 间接依赖类 B,如果要将类 A 改为依赖类 C,则必须通过批改类 A 的代码来达成。这种场景下,类 A 个别是高层模块,负责简单的业务逻辑;类 B 和类 C 是低层模块,负责根本的原子操作;如果批改类 A,会给程序带来不必要的危险。
#pragma once
#include<string>
class AnimalInterface
{
public:
virtual std::string get() = 0; // 食物};
#include".\Factroy.h"
#include<string>
using std::string;
class AnimalImpl_1 : public AnimalInterface {
public:
string get() {return "草";}
};
class AnimalImpl_2 :public AnimalInterface {
public:
string get() {return "水";}
};
#include<iostream>
#include<string>
#include"abstraction.cpp"
using namespace std;
class pasture1 {
public:
void get(AnimalImpl_1 a) {vector<string> list{ "牛" ,"羊" ,"猪" ,"马"};
for (auto i : list) {cout << i << "被喂食了" << a.get() << endl;
}
}
};
class pasture2 {
public:
void get(AnimalImpl_2 a) {vector<string> list{ "牛" ,"羊" ,"猪" ,"马"};
for (auto i : list) {cout << i << "被喂食了" << a.get() << endl;
}
}
};
int main() {
pasture1 p;
p.get(AnimalImpl_1());
pasture2 p2;
p2.get(AnimalImpl_2());
return 0;
}
如果我还想加一些动物,或者做一些筛选,是不是须要批改代码
咱们依照准则批改代码,原来的形象层放弃不变,批改高层代码
#pragma once
#include<string>
class AnimalInterface
{
public:
virtual std::string get() = 0; // 食物};
#include".\Factroy.h"
#include<string>
using std::string;
class AnimalImpl_1 : public AnimalInterface {
public:
string get() {return "草";}
};
class AnimalImpl_2 :public AnimalInterface {
public:
string get() {return "水";}
};
class pasture
{
public:
void get(AnimalInterface *a){vector<string> list{ "牛" ,"羊" ,"猪" ,"马"};
for (auto i : list) {cout << i << "被喂食了" << a->get() << endl;
}
}
};
int main() {
pasture p;
AnimalInterface* ai = new AnimalImpl_1();
AnimalInterface* ai2 = new AnimalImpl_2();
p.get(ai);
p.get(ai2);
return 0;
}
四,里氏替换准则
里氏替换准则的重点在不影响原性能,而不是不笼罩原办法
里氏替换准则译自 Liskov substitution principle。Liskov 是一位计算机科学家,也就是 Barbara Liskov,麻省理工学院传授,也是美国第一个计算机科学女博士,师从图灵奖得主 John McCarthy 传授,人工智能概念的提出者。
里氏替换准则的定义
里氏替换准则在 1994 年 Barbara Liskov 和 Jeannette Wing 发表论文中的形容是:
If S is a declared subtype of T, objects of type S should behave as objects of type T are expected to behave, if they are treated as objects of type T
如果 S 是 T 的子类型,对于 S 类型的任意对象,如果将他们看作是 T 类型的对象,则对象的行为也理当与冀望的行为统一。
问题由来:有一性能 P1,由类 A 实现。现须要将性能 P1 进行扩大,扩大后的性能为 P,其中 P 由原有性能 P1 与新性能 P2 组成。新性能 P 由类 A 的子类 B 来实现,则子类 B 在实现新性能 P2 的同时,有可能会导致原有性能 P1 产生故障。
简略来说就是类 B 继承 A 时,除了新的办法外,尽量不对父类的办法进行重写,否则会对原有性能发生变化
里氏替换准则的强制性
- 子类能够实现父类的形象办法,但不能笼罩父类的非形象办法。
子类中能够减少本人特有的办法。 - 当子类的办法重载父类的办法时,办法的前置条件(即办法的形参)要比父类办法的输出参数更宽松。
- 当子类的办法实现父类的形象办法时,办法的后置条件(即办法的返回值)要比父类更严格。
解决办法有多态:新增特有办法
里氏替换准则的优缺点
长处:
- 代码奉献,减小创立工作
- 进步代码复用性
- 进步代码可扩展性
- 进步我的项目开放性
相应的毛病也有:
- 继承是入侵式的。
- 升高代码的灵活性。
-
加强了耦合性。
里氏替换准则的违反准则
class Math {
public:
virtual int sum(int a, int b) = 0;
virtual float halve(int a) = 0;
};
#include"Factroy.h"
#include<iostream>
using namespace std;
class math :Math {
public:
int sum(int a, int b) {return a + b;}
float halve(int num) {return num >> 1;}
};
class math2 :math {
private:
int add = 100;
public:
int sum(int a, int b) {return a + b + add;}
float halve(int num) {return num >> 1;}
};
int main() {
math m;
int sum = m.sum(2, 2);
float halve = m.halve(sum);
cout << "sum(2,2)" << sum << endl;
cout << "halve(4)" << halve << endl;
math2 m2;
int sum2 = m2.sum(2, 2);
float halve2 = m2.halve(sum2);
cout << "sum(2,2)" << sum2 << endl;
cout << "halve(4)" << halve2 << endl;
return 0;
}
案例剖析
class Math {
public:
virtual int sum(int a, int b) = 0;
virtual float halve(int a) = 0;
};
#include"Factroy.h"
#include<iostream>
using namespace std;
class math :Math {
public:
int sum(int a, int b) {return a + b;}
float halve(int num) {return num >> 1;}
};
class math3 : public math {
private:
int add = 100;
public:
int sum(int a, int b) {return a + b + add;}
float halve(int num) {return num >> 1;}
};
int main() {
math* math;
math3 m3;
math = &m3;
int sum3 = math->sum(2, 2);
float halve3 = math->halve(sum2);
cout << "sum(2,2)" << sum3 << endl;
cout << "halve(4)" << halve3 << endl;
return 0;
}
五,接口隔离准则
接口隔离准则的定义
接口隔离准则是对接口的应用进行束缚标准的一个准则,具体含意如下
- 客户端不应该依赖它不须要的接口,一个接口对应一个角色,不应该将不同的角色调配给同一个接口,会造成一个宏大的接口,这也导致了接口净化
- 不应该强制接口依赖与他们不须要的接口
- 类间的依赖关系应该建设在最小的接口上
接口隔离准则的长处:
- 防止接口净化,避免无奈意料的危险
- 提供类外部的逻辑的灵活性,维护性
- 提供了零碎的内聚性,升高耦合度
- 缩小代码冗余
代码实现
#pragma once
#include<string>
#include<vector>
using std::string;
using std::vector;
/* 封装了 vector*/
template<class T> class Container :public vector<T> {
};
template<class T> class Crud {
public:
virtual void insert(T t) = 0;
virtual T get(int index) = 0;
virtual void delete_(int index) = 0;
virtual int size() = 0;};
// 利用
template<class T, class V> class Apply {
public:
virtual V apply(Crud<T>* c) = 0;
};
#include <iostream>
#include"Interface.h"
using std::cout;
using std::endl;
class Applylmp : public Apply<string,string>, public Crud<string>, public Container<string> {
private:
Container<string> list;
public:
void insert(string s) {list.insert(list.end(),s);
cout << "插入了" << s << endl;
}
string get(int index) {return list.operator[](index);
}
void delete_(int index) {list.operator[](index) = "";
}
int size() {return list.size();
}
string apply() {
string s;
for (int i = 0; i < this->size(); i++) {s.append(this->get(i));
}
return s;
}
};
int main()
{
Applylmp apply;
string s[] = { "a","b","c"};
for (int i = 0; i < (sizeof(s)/ sizeof(string)); i++) {apply.insert(s[i]);
}
string data = apply.apply();
cout << data;
return 0;
}
六,迪米特准则(最小常识准则)
迪米特准则的定义
迪米特法令(Law of Demeter,简称 LOD),又称为“起码常识准则”。
迪米特法令最后是用来作为面向对象的零碎设计格调的一种法令,在 1987 年由 Ian
Holland 在美国东北大学为一个叫迪米特的我的项目设计提出的,因而叫做迪米特法令。这条法令实际上是很多驰名零碎,例如火星登陆软件系统、木星的欧罗巴卫星轨道飞船的软件系统的领导设计准则。
它的定义为:一个软件实体该当尽可能少的与其余实体产生相互作用 。这样,当一个模块批改时,就会尽量少的影响其余的模块,扩大会绝对容易。迪米特法令是对软件实体之间通信的限度,它对软件实体之间通信的宽度和深度做出了要求。迪米特的其它表述形式为:
只与你间接的敌人们通信。
不要跟“陌生人”谈话。
每一个软件单位对其余的单位都只有起码的常识,而且局限于那些与本单位密切相关的软件单位。
“敌人”的条件为:
以后对象自身(this);
被当做以后对象的办法的参数传入进来的对象;
以后对象的办法所创立或者实例化的任何对象;
以后对象的任何组件(被以后对象的实例变量援用的任何对象)。
迪米特准则的长处
- 减小了类之间的关系
- 进步了代码的弱耦合
- 进步了代码的复用性
代码实现
#include <iostream>
class Stranger {
public:
void call() {std::cout << "我是陌生人" << std::endl;}
};
class Friend {
private:
Stranger* stranger;
public:
void forward() {
std::cout << "我给你介绍一下" << std::endl;
stranger->call();}
void call() {std::cout << "我是敌人" << std::endl;}
};
class Someone {
public:
void call(Friend* f) {
std::cout << "你好!" << std::endl;
f->forward();}
};
int main()
{
Someone someone;
someone.call(new Friend());
}