面试官问你如何进行程序设计?——设计模式之七大原则——接口隔离、合成复用、迪米特法则以及C++设计实现

七大原则

设计原则名称 作用
单一职责原则 类的职责要单一,不能将太多的职责放在一个类中
开闭原则 软件实体对扩展是开放的,但对修改是关闭的,即在不修改一个软件实体的基础上去扩展其功能
里氏代换原则 在软件系统中,一个可以接受基类对象的地方必然可以接受一个子类对象
依赖倒转原则 要针对抽象层编程,而不要针对具体类编程(抽象不应该依赖细节,细节应该依赖抽象)
接口隔离原则 使用多个专门的接口来取代一个统一的接口
合成复用原则 在系统中应该尽量多使用组合和聚合关联关系,尽量少使用甚至不使用继承关系
迪米特法则 一个软件实体对其他实体的引用越少越好,或者说如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,而是通过引入一个第三者发生间接交互

前四种原则——》》》单一职责、里氏代换、开闭原则、依赖倒转

5、接口隔离(InterfaceSegregation Principle,ISP)

5.1、背景

我们生活中用到的智能手机和智能手表,它们有类似的接口功能,也有不同的功能,比如

  • 手机(phone):看时间、看天气、听音乐、看视频
  • 智能手表(smartwatch):看时间、看天气、测心率

在这里插入图片描述

  1. 一个接口中封装了太对的方法,导致phoneSmartwatch这两个类中必须实现一些无用的方法。

  2. 这样的接口稳定性较差,如果phone需要增加一个方法的话,那么`Smartwatch这个实现类中也要相应的实现这个方法(当然方法体内是空的,但是必须要实现的)。

  3. 编码混乱,导致修改时难度增加(需要自己去区分开哪些是这个类中的方法,哪些是另外的一个类中的方法,这样额外增加了工作量)。

应该怎么设计?

在这里插入图片描述
将公共的部分抽取出来单独放在一个接口中,自己独有的行为放在相应的接口中,通过独有的这个接口去继承公共的接口,这样的话,就能很好的起到接口的隔离的作用。这个地方我只是举了这样的一个例子,公共的部分是show,那么在实际的使用中,可能是别的相关的功能等。那样的话需要自己去对他们进行抽取。

5.2、定义

客户端不应该依赖它不需要的接口;类间的依赖关系应该建立在最小的接口上。

  • 不要强迫客户使用它们不用的方法,如果强迫用户使用它们不使用的方法,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。
  • 接口属于客户,不属于它所在的类层次结构
  • 不要在一个接口里面放很多的方法,这样会显得这个类很臃肿。
    • 接口应该尽量细化,一个接口对应一个功能模块,同时接口里面的方法应该尽可能的少,使接口更加灵活轻便
    • 何为最小的接口,即能够满足项目需求的相似功能作为一个接口,这样设计主要就是为了“高内聚”

注意:接口隔离和单一职责的区分
从功能上来看,接口隔离和单一职责两个原则具有一定的相似性。其实如果我们仔细想想还是有区别的。

(1)从原则约束的侧重点来说,接口隔离原则更关注的是接口依赖程度的隔离,更加关注接口的“高内聚”;而单一职责原则更加注重的是接口职责的划分。

(2)从接口的细化程度来说,单一职责原则对接口的划分更加精细,而接口隔离原则注重的是相同功能的接口的隔离。接口隔离里面的最小接口有时可以是多个单一职责的公共接口。

(3)单一职责原则更加偏向对业务的约束,接口隔离原则更加偏向设计架构的约束。这个应该好理解,职责是根据业务功能来划分的,所以单一原则更加偏向业务;而接口隔离更多是为了“高内聚”,偏向架构的设计。

5.3、特征

接口隔离原则是为了约束接口、降低类对接口的依赖性,遵循接口隔离原则有以下 5 个优点:

  • 1、将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
  • 2、接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
  • 3、如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。
  • 4、使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
  • 5、能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代码。

5.4、应用

#include <iostream>

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

在这里插入图片描述

6、迪米特原则(Law of Demeter,LoD)

也被称为最少知识原则(Least knowledge Principle,LKP)

6.1、背景

只与你的直接朋友交谈,不跟“陌生人”说话。(Talk only to your immediate friends and not to
strangers)

明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则。
在这里插入图片描述

在迪米特法则中,对于一个对象,其朋友包括以下几类:

  • (1) 当前对象本身(this);
  • (2) 以参数形式传入到当前对象方法中的对象;
  • (3) 当前对象的成员对象;
  • (4) 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
  • (5) 当前对象所创建的对象。

任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。

6.2、定义

如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

  • 从依赖者的角度来说,只依赖应该依赖的对象。
  • 从被依赖者的角度说,只暴露应该暴露的方法。
  • 不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达
  • 那么当其中某一个模块发生修改时,就会尽量少地影响其他模块

6.3、特征

迪米特法则要求限制软件实体之间通信的宽度和深度,正确使用迪米特法则将有以下两个优点。

  • 降低了类之间的耦合度,提高了模块的相对独立性。
  • 由于亲合度降低,从而提高了类的可复用率和系统的扩展性。

但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。

6.4、应用

在运用迪米特法则时要注意以下 6 点。

  • 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
  • 在类的结构设计上,尽量降低类成员的访问权限。
  • 在类的设计上,优先考虑将一个类设置成不变类。
  • 在对其他类的引用上,将引用其他对象的次数降到最低。
  • 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
  • 谨慎使用序列化(Serializable)功能。
    • 举个例子来说,如果你使用 RMI 的方式传递一个对象 VO( Value Object),这个对象就必须使用 Serializable 接口,也就是把你的这个对象进行序列化,然后进行网络传输。突然有一天,客户端的 VO 对象修改了一个 属性的访问权限,从 private 变更为 public 了,如果服务器上没有做出响应的变更的话,就会报序列化失败。 这个应该属于项目管理范畴,一个类或接口客户端变更了,而服务端没有变更,那像话吗?!
#include <iostream>
#include<string>

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

在这里插入图片描述

7、合成复用原则(Composite Reuse Principle, CRP)

C++类与类之间的存在的几种关系以及UML类图简单说明(依赖、关联、聚合、组合、泛化(继承)、实现)

7.1、背景

汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多。下图是用继承关系实现的汽车分类的类图。
在这里插入图片描述
从上图 可以看出用继承关系实现会产生很多子类,而且增加新的“动力源”或者增加新的“颜色”都要修改源代码,这违背了开闭原则,显然不可取。

但如果改用组合关系实现就能很好地解决以上问题,其类图如下图所示。
在这里插入图片描述
1、聚合关系(Aggregation)
在这里插入图片描述
UML:带空心菱形的实线来表示,菱形指向整体

  • 聚合(Aggregation)关系是关联关系的一种,是强关联关系,是整体和部分之间的关系,是 has-a 的关系。

  • 整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。

    • 例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。

2、组合关系(Composition)
在这里插入图片描述
UML :组合关系用带实心菱形的实线来表示,菱形指向整体

  • 组合也是关联关系的一种特例,它体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合。它同样体现整体与部分间的关系,但此时整体与部分是不可分的。
  • 在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。
    • 例如,头和嘴的关系,没有了头,嘴也就不存在了。

7.2、定义

合成复用原则又叫组合/聚合复用原则。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

  • 合成/聚合复用原则是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。

7.3、特征(为什么使用合成/聚合复用,而不使用继承复用?)

1、采用组合或聚合复用时
可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能

  • 1、优点
    • 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
    • 新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。
    • 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
  • 2、缺点:
    • 就是系统中会有较多的对象需要管理。

2、继承复用

继承复用通过扩展一个已有对象的实现来得到新的功能,基类明显的捕获共同的属性和方法,而子类通过增加新的属性和方法来扩展超类的实现。继承是类型的复用。

  • 1、优点
    • (1) 新的实现较为容易,因为超类的大部分功能可以通过继承关系自动进入子类。
    • (2) 修改或扩展继承而来的实现较为容易。
  • 2、缺点
  • (1) 继承复用破坏包装,因为继承将超类的实现细节暴露给了子类。因为超类的内部细节常常对子类是透明的,因此这种复用是透明的复用,又叫“白箱”复用。
  • (2) 如果超类的实现改变了,那么子类的实现也不得不发生改变。因此,当一个基类发生了改变时,这种改变会传导到一级又一级的子类,使得设计师不得不相应的改变这些子类,以适应超类的变化。
  • (3) 从超类继承而来的实现是静态的,不可能在运行时间内发生变化,因此没有足够的灵活性。

7.4、应用

#include<iostream>
#include<vector>
#include<string>
#include<iterator>
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

猜你喜欢

转载自blog.csdn.net/JMW1407/article/details/107420740