设计原则名称 | 作用 |
---|---|
单一职责原则 | 类的职责要单一,不能将太多的职责放在一个类中 |
开闭原则 | 软件实体对扩展是开放的,但对修改是关闭的,即在不修改一个软件实体的基础上去扩展其功能 |
里氏代换原则 | 在软件系统中,一个可以接受基类对象的地方必然可以接受一个子类对象 |
依赖倒转原则 | 要针对抽象层编程,而不要针对具体类编程(抽象不应该依赖细节,细节应该依赖抽象) |
接口隔离原则 | 使用多个专门的接口来取代一个统一的接口 |
合成复用原则 | 在系统中应该尽量多使用组合和聚合关联关系,尽量少使用甚至不使用继承关系 |
迪米特法则 | 一个软件实体对其他实体的引用越少越好,或者说如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,而是通过引入一个第三者发生间接交互 |
前四种原则——》》》单一职责、里氏代换、开闭原则、依赖倒转
我们生活中用到的智能手机和智能手表,它们有类似的接口功能,也有不同的功能,比如
一个接口中封装了太对的方法,导致phone
和Smartwatch
这两个类中必须实现一些无用的方法。
这样的接口稳定性较差,如果phone
需要增加一个方法的话,那么`Smartwatch这个实现类中也要相应的实现这个方法(当然方法体内是空的,但是必须要实现的)。
编码混乱,导致修改时难度增加(需要自己去区分开哪些是这个类中的方法,哪些是另外的一个类中的方法,这样额外增加了工作量)。
应该怎么设计?
将公共的部分抽取出来单独放在一个接口中,自己独有的行为放在相应的接口中,通过独有的这个接口去继承公共的接口,这样的话,就能很好的起到接口的隔离的作用。这个地方我只是举了这样的一个例子,公共的部分是show
,那么在实际的使用中,可能是别的相关的功能等。那样的话需要自己去对他们进行抽取。
客户端不应该依赖它不需要的接口;类间的依赖关系应该建立在最小的接口上。
注意:接口隔离和单一职责的区分
从功能上来看,接口隔离和单一职责两个原则具有一定的相似性。其实如果我们仔细想想还是有区别的。
(1)从原则约束的侧重点来说,接口隔离原则更关注的是接口依赖程度的隔离,更加关注接口的“高内聚”;而单一职责原则更加注重的是接口职责的划分。
(2)从接口的细化程度来说,单一职责原则对接口的划分更加精细,而接口隔离原则注重的是相同功能的接口的隔离。接口隔离里面的最小接口有时可以是多个单一职责的公共接口。
(3)单一职责原则更加偏向对业务的约束,接口隔离原则更加偏向设计架构的约束。这个应该好理解,职责是根据业务功能来划分的,所以单一原则更加偏向业务;而接口隔离更多是为了“高内聚”,偏向架构的设计。
接口隔离原则是为了约束接口、降低类对接口的依赖性,遵循接口隔离原则有以下 5 个优点:
#include
using namespace std;
class CommonInter {
public:
CommonInter() {}
virtual void showTime() = 0;
virtual void showWeather() = 0;
};
class phoneInter :public CommonInter
{
public:
phoneInter() {}
virtual void showTime()
{
cout << "showTime......" << endl;
}
virtual void showWeather()
{
cout << "showWeather......." << endl;
}
void listenMusic()
{
cout << "listenMusic......" << endl;
}
void watchVideo()
{
cout << "watchVideo......" << endl;
}
};
class watchInter :public CommonInter
{
public:
watchInter() {}
virtual void showTime()
{
cout << "showTime......" << endl;
}
virtual void showWeather()
{
cout << "showWeather......." << endl;
}
void checkHeartbeat()
{
cout << "checkHeartbeat......" << endl;
}
};
class phone
{
private:
phoneInter inter;
public:
void use1()
{
inter.showTime();
}
void use2()
{
inter.listenMusic();
}
};
class watch
{
private:
watchInter inter;
public:
void use1()
{
inter.showTime();
}
void use2()
{
inter.checkHeartbeat();
}
};
int main(int argc, char *argv[])
{
phone p;
p.use2();
watch w;
w.use2();
return 0;
}
也被称为最少知识原则(Least knowledge Principle,LKP)
只与你的直接朋友交谈,不跟“陌生人”说话。(Talk only to your immediate friends and not to
strangers)
明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则。
在迪米特法则中,对于一个对象,其朋友包括以下几类:
任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。
如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则要求限制软件实体之间通信的宽度和深度,正确使用迪米特法则将有以下两个优点。
但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
在运用迪米特法则时要注意以下 6 点。
#include
#include
using namespace std;
//明星
class Star
{
private:
string _name;
public:
Star(string name):_name(name) {}
string getName()
{
return _name;
}
};
//粉丝
class Fans
{
private:
string _name;
public:
Fans(string name) :_name(name) {}
string getName()
{
return _name;
}
};
//媒体公司
class Company
{
private:
string _name;
public:
Company(string name) :_name(name) {}
string getName()
{
return _name;
}
};
//经纪人
class Agent
{
private:
Star _myStar;
Fans _myFans;
Company _myCompany;
public:
Agent(Star myStar, Fans myFans, Company myCompany) :_myStar(myStar), _myFans(myFans), _myCompany(myCompany)
{ }
void setStar(Star myStar)
{
_myStar = myStar;
}
void setFans(Fans myFans)
{
_myFans = myFans;
}
void setCompany(Company myCompany)
{
_myCompany = myCompany;
}
void meeting()
{
cout << _myFans.getName() << "与明星" << _myStar.getName() << "见面了。" << endl;;
}
void business()
{
cout << _myCompany.getName() << "与明星" << _myStar.getName() << "洽淡业务。" << endl;
}
};
int main(int argc, char *argv[])
{
Star s ("KOBE");
Fans f ("KOBE_FANS");
Company c("CBA");
Agent agent(s,f,c);
agent.meeting();
agent.business();
Star s1("Lebron James");
agent.setStar(s1);
agent.business();
return 0;
}
C++类与类之间的存在的几种关系以及UML类图简单说明(依赖、关联、聚合、组合、泛化(继承)、实现)
汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多。下图是用继承关系
实现的汽车分类的类图。
从上图 可以看出用继承关系实现会产生很多子类,而且增加新的“动力源”或者增加新的“颜色”都要修改源代码,这违背了开闭原则,显然不可取。
但如果改用组合关系实现就能很好地解决以上问题,其类图如下图所示。
1、聚合关系(Aggregation)
UML:带空心菱形的实线来表示,菱形指向整体
聚合(Aggregation)关系是关联关系的一种,是强关联关系,是整体和部分之间的关系,是 has-a 的关系。
整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。
2、组合关系(Composition)
UML :组合关系用带实心菱形的实线来表示,菱形指向整体
合成复用原则又叫组合/聚合复用原则。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
1、采用组合或聚合复用时
可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能
2、继承复用
继承复用通过扩展一个已有对象的实现来得到新的功能,基类明显的捕获共同的属性和方法,而子类通过增加新的属性和方法来扩展超类的实现。继承是类型的复用。
#include
#include
#include
#include
using namespace std;
/*
合成复用原则:
继承和组合优先使用组合,避免继承带来的麻烦
人开不同的车,不必人去继承车类,而使用组合,
把车组合进人里面进行调用
*/
class AbstractCar
{
public:
virtual void run()=0;
};
class BMW : public AbstractCar
{
public:
virtual void run()
{
cout << "BMW is run."<<endl;
}
};
class Fort : public AbstractCar
{
public:
virtual void run()
{
cout << "Fort is run."<<endl;
}
};
class People
{
public:
void setCar(AbstractCar* car)
{
this->car=car;
}
void Drive()
{
car->run();
delete car;
}
private:
AbstractCar * car;
};
void test()
{
People* p=new People();
p->setCar(new BMW());
p->Drive();
p->setCar(new Fort());
p->Drive();
}
int main()
{
test();
return 0;
}
1、https://zhuanlan.zhihu.com/p/24246822
2、http://c.biancheng.net/view/1330.html