单一职责模式:在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。

典型模式:装饰模式(Decorator)、桥接模式(Bridge)。

一、装饰模式

1.动机

在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。

2.作用

使“对象功能的扩展”能够根据需要来动态地实现;同时避免“扩展功能的增多”带来的子类膨胀问题,使得任何“功能扩展变化”所导致的影响降为最低。

3.定义

动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码、减少子类个数)。

4.代码

//原有代码
//业务操作
class Stream{
public:
virtual char Read(int number)=0;
virtual void Seek(int position)=0;
virtual void Write(char data)=0;
virtual ~Stream(){}
};
//主体类
class FileStream: public Stream{
public:
virtual char Read(int number){
//读文件流
}
virtual void Seek(int position){
//定位文件流
}
virtual void Write(char data){
//写文件流
}
};
class NetworkStream :public Stream{
public:
//...
};
class MemoryStream :public Stream{
public:
//...
};
//扩展操作
class CryptoFileStream :public FileStream{
public:
virtual char Read(int number){
//额外的加密操作...
FileStream::Read(number);//读文件流
}
virtual void Seek(int position){
//额外的加密操作...
FileStream::Seek(position);//定位文件流
}
virtual void Write(byte data){
//额外的加密操作...
FileStream::Write(data);//写文件流
}
};
class CryptoNetworkStream : :public NetworkStream{
public:
//...
};
class CryptoMemoryStream : public MemoryStream{
public:
//...
};
//额外的缓冲操作...
class BufferedFileStream : public FileStream{
//...
};
class BufferedNetworkStream : public NetworkStream{
//...
};
class BufferedMemoryStream : public MemoryStream{
//...
}
//额外的加密缓存操作...
class CryptoBufferedFileStream :public FileStream{
public:
virtual char Read(int number){
//额外的加密操作...
//额外的缓冲操作...
FileStream::Read(number);//读文件流
}
virtual void Seek(int position){
//额外的加密操作...
//额外的缓冲操作...
FileStream::Seek(position);//定位文件流
}
virtual void Write(byte data){
//额外的加密操作...
//额外的缓冲操作...
FileStream::Write(data);//写文件流
}
};
void Process(){
//编译时装配
CryptoFileStream *fs1 = new CryptoFileStream();
BufferedFileStream *fs2 = new BufferedFileStream();
CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();
}
//运用装饰模式后的代码
//业务操作
class Stream{
public:
virtual char Read(int number)=0;
virtual void Seek(int position)=0;
virtual void Write(char data)=0;
virtual ~Stream(){}
};
//主体类
class FileStream: public Stream{
public:
virtual char Read(int number){
//读文件流
}
virtual void Seek(int position){
//定位文件流
}
virtual void Write(char data){
//写文件流
}
};
class NetworkStream :public Stream{
public:
//...
};
class MemoryStream :public Stream{
public:
//...
};
//扩展操作
DecoratorStream: public Stream{
protected:
Stream* stream;//...
DecoratorStream(Stream * stm):stream(stm){
}
};
class CryptoStream: public DecoratorStream {
public:
CryptoStream(Stream* stm):DecoratorStream(stm){
}
virtual char Read(int number){
//额外的加密操作...
stream->Read(number);//读文件流
}
virtual void Seek(int position){
//额外的加密操作...
stream::Seek(position);//定位文件流
}
virtual void Write(byte data){
//额外的加密操作...
stream::Write(data);//写文件流
}
};
class BufferedStream : public DecoratorStream{
Stream* stream;//...
public:
BufferedStream(Stream* stm):DecoratorStream(stm){
}
//...
};
void Process(){
//运行时装配
FileStream* s1=new FileStream();
CryptoStream* s2=new CryptoStream(s1);
BufferedStream* s3=new BufferedStream(s1);
CryptoBufferedFileStream* s4=new BufferedStream(s2);
}

5.解析

这是一个内容流程序,有读、定位(搜索)和写等功能,在业务上有文件流、网络流和内存流等,扩展出的功能包括额外加密操作和缓存操作等。

在原有代码中,以继承的方法扩展这些功能,很详细也很好理解,但存在以下问题:继承引入了静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合会导致更多子类的膨胀,如下图所示。假设一级功能有n种,二级功能有m种,则该程序一共有(1+n+X)个类。(思考X用n和m怎么表示)。

在运用装饰模式后的代码中,通过动态(组合)地给一个对象增加一些额外的职责,其结构如图所示,则该程序一共有(1+n+1+m)个类,相比之前大大减小了类的个数,且通过动态组合,将编译时依赖转化为运行时依赖。

6.结构

其中,Component(抽象类,如Stream):

1.定义一个对象接口,可以给这些对象动态地添加职责。

ConcreteComponent(具体类,如FileStream、NetworkStream)

1.定义一个对象,可以给这个对象添加一些职责

Decorator(装饰抽象类,如DecoratorStream)

1.维持一个指向Component对象的指针,并定义一个与Component接口一致的接口。

ConcreteDecorator(具体装饰类,如CryptoStream、BufferedStream)

1.向组件添加职责

7.总结

1.通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”。

2.Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。

3.Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义。

二、桥接模式

1.动机

由于某些类型固有的实现逻辑,使得它们具有两个变化的维度,乃至多个纬度的变化。

2.作用

使得该类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度,以应对这种“多维度的变化”。

3.定义

将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。

4.代码

//原有代码
class Messager{
public:
virtual void Login(string username, string password)=0;
virtual void SendMessage(string message)=0;
virtual void SendPicture(Image image)=0;
virtual void PlaySound()=0;
virtual void DrawShape()=0;
virtual void WriteText()=0;
virtual void Connect()=0;
virtual ~Messager(){}
};
//平台实现
class PCMessagerBase : public Messager{
public:
virtual void PlaySound(){
//**********
}
virtual void DrawShape(){
//**********
}
virtual void WriteText(){
//**********
}
virtual void Connect(){
//**********
}
};
class MobileMessagerBase : public Messager{
public:
//**********
};
//业务抽象
class PCMessagerLite : public PCMessagerBase {
public:
virtual void Login(string username, string password){
PCMessagerBase::Connect();
}
virtual void SendMessage(string message){
PCMessagerBase::WriteText();
}
virtual void SendPicture(Image image){
PCMessagerBase::DrawShape();
}
};
class PCMessagerPerfect : public PCMessagerBase {
public:
virtual void Login(string username, string password){
PCMessagerBase::PlaySound();
PCMessagerBase::Connect();
}
virtual void SendMessage(string message){
PCMessagerBase::PlaySound();
PCMessagerBase::WriteText();
}
virtual void SendPicture(Image image){
PCMessagerBase::PlaySound();
PCMessagerBase::DrawShape();
}
};
class MobileMessagerLite : public MobileMessagerBase {
public:
//**********
};
class MobileMessagerPerfect : public MobileMessagerBase {
public:
//**********
};
void Process(){
//编译时装配
Messager *m = new MobileMessagerPerfect();
}
//运用桥接模式后代码
class Messager{
protected:
MessagerImp* messagerImp;//...
public:
virtual void Login(string username, string password)=0;
virtual void SendMessage(string message)=0;
virtual void SendPicture(Image image)=0;
virtual ~Messager(){}
};
class MessagerImp{
public:
virtual void PlaySound()=0;
virtual void DrawShape()=0;
virtual void WriteText()=0;
virtual void Connect()=0;
virtual MessagerImp(){}
};
//平台实现 n
class PCMessagerImp : public MessagerImp{
public:
virtual void PlaySound(){
//**********
}
virtual void DrawShape(){
//**********
}
virtual void WriteText(){
//**********
}
virtual void Connect(){
//**********
}
};
class MobileMessagerImp : public MessagerImp{
public:
//**********
};
//业务抽象 m
//类的数目:1+n+m
class MessagerLite :public Messager {
public:
virtual void Login(string username, string password){
messagerImp->Connect();
}
virtual void SendMessage(string message){
messagerImp->WriteText();
}
virtual void SendPicture(Image image){
messagerImp->DrawShape();
}
};
class MessagerPerfect :public Messager {
public:
virtual void Login(string username, string password){
messagerImp->PlaySound();
messagerImp->Connect();
}
virtual void SendMessage(string message){
messagerImp->PlaySound();
messagerImp->WriteText();
}
virtual void SendPicture(Image image){
messagerImp->PlaySound();
messagerImp->DrawShape();
}
};
void Process(){
//运行时装配
MessagerImp* mImp=new PCMessagerImp();
Messager *m =new Messager(mImp);
}

 5.解析

这是一个关于在不同平台(PC和Moblie)上,实现不同业务(MessagerLite、MessagerPerfect)的程序。

原有的代码与装饰模式中原有代码类似,通过不断地继承以扩展功能,但缺点也是显而易见的,容易造成子类的庞大不灵活。

在运用桥接模式的代码中,将抽象部分(业务功能)与实现部分(平台实现)分离,使得抽象和实现可以沿着各自的维度来变化。

6.结构

其中,

1.Abstraction:定义抽象类的接口;维护一个指向Implementtor类型对象的指针;

2.RefinedAbstraction:扩充由Abstraction定义的接口;

3.Implementor:定义实现类的接口,该接口不一定要与Abstraction的接口完全一致,甚至可以完全不同;

4.ConcreteImplementor:实现Implementor接口并定义它的具体实现。

7.总结

1.Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自纬度的变化,即“子类化”它们。

2.Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差。 Bridge模式是比多继承方案更好的解决方法。

3.Bridge模式的应用一般在“两个非常强的变化维度”,有时一个类也有多于两个的变化维度,这时可以使用Bridge的扩展模式。

学习记录:《C++设计模式——李建忠主讲》4.“单一职责”模式的更多相关文章

  1. 学习记录:《C++设计模式——李建忠主讲》6.“状态变化”模式

    状态变化模式:在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时又维持高层模块的稳定.状态变化模式为这一问题提供了一种解决方案. 典型模式:状态模式(State).备忘录 ...

  2. 学习记录:《C++设计模式——李建忠主讲》1.设计模式

    1.学习目标 1)理解松耦合设计思想: 2)掌握面向对象设计原则: 3)掌握重构技法改善设计: 4)掌握GOF核心设计模式: 2.定义 每个设计模式描述了一个在我们周围不断重复发生的问题,以及该问题解 ...

  3. 学习记录:《C++设计模式——李建忠主讲》2.面向对象设计原则

    1.课程内容: 重新认识面向对象:面向对象设计原则: 2.重新认识面向对象 1)理解隔离变化:从宏观层面来看,面向对象的构建方式更能适应软件的变化,将变化所带来的影响减为最小: 2)各司其职:从微观层 ...

  4. 学习记录:《C++设计模式——李建忠主讲》3.“组件协作”模式

    “组件协作”模式:现代软件专业分工之后的第一个结果是“框架与应用程序的划分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式.典型模式:Template M ...

  5. 学习记录:《C++设计模式——李建忠主讲》5.“对象性能”模式

    对象性能模式:面向对象很好地解决了抽象地问题,但是必不可免地要付出一定地代价.对于通常情况来讲,面向对象地成本大都可以忽略不计,但某些情况,面向对象所带来地成本必须谨慎处理. 典型模式:单件模式(Si ...

  6. 学习记录:《C++设计模式——李建忠主讲》7.“领域规则”模式

    领域规则模式:在特定领域中,某些变化虽然频繁,但可以抽象为某种规则.这时候,结合特定的领域,将问题抽象为语法规则,从而给出该领域下的一般性解决方案. 典型模式:解释器模式(Interpreter). ...

  7. 工厂模式(整理自李建忠<C++设计模式>视频)

    整理自李建忠<C++设计模式>视频 一.导入:"对象创建"模式和工厂模式 工厂模式只是该模式下的一种. 二.举例说明 有这样一个场景:需要在MainForm中设计一个按 ...

  8. 设计模式---单一职责模式之装饰模式(Decorator)

    前提:"单一职责"模式 在软件组件的设计中,如果责任划分的不清晰,使用继承,得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任 典型模式(表现 ...

  9. C++设计模式 之 “单一职责”模式:Decorator、Bridge

    part 1 “单一职责”模式 在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任. 典型模式 Decorato ...

随机推荐

  1. 嗜血的硅谷与Hygge的欧洲

    听到太多抱怨工作累,加班多,996的声音.我们是否从底层来理智的分析这一过程.那些抱怨加班多.996的人有多少离开了那个让他发声的工作,去找一份轻松悠闲的工作度日? 我们是一边抱怨着工作,一边担心着离 ...

  2. 三维动画形变算法(Mixed Finite Elements)

    混合有限元方法通入引入辅助变量后可以将高阶微分问题变成一系列低阶微分问题的组合.在三维网格形变问题中,我们考虑如下泛函极值问题: 其中u: Ω0 → R3是变形体的空间坐标,上述泛函极值问题对应的欧拉 ...

  3. 【Leetcode 做题学算法周刊】第一期

    首发于微信公众号<前端成长记>,写于 2019.10.28 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 1. ...

  4. CSS布局解决方案(终结版)

    作者:无悔铭 https://segmentfault.com/a/1190000013565024 前端布局非常重要的一环就是页面框架的搭建,也是最基础的一环.在页面框架的搭建之中,又有居中布局.多 ...

  5. Spring框架学习笔记(5)——Spring Boot创建与使用

    Spring Boot可以更为方便地搭建一个Web系统,之后服务器上部署也较为方便 创建Spring boot项目 1. 使用IDEA创建项目 2. 修改groupid和artifact 3. 一路n ...

  6. django-URL反向解析Reverse(九)

    解决path中带参数的路径. reverse(viewname,urlconf=None,args=None,Kwargs=None,current_app=None) book/views.py f ...

  7. fenby C语言 P17

    for姐姐 dowhile妹妹 while for(循环变量赋初值,循环条件,循环变量自加) #include <stdio.h> int main(){ int sum=0,i; for ...

  8. 选择器, ,>,+,~

    一.后代选择器 选取指定元素的后代元素 与子元素选择器相比,后代选择器选取的不一定是直接后代(儿子),而是作用于所有后代元素(儿子.孙子.重孙…)都可以. 二.(>)子元素选择器 选取某个元素的 ...

  9. Java8系列 (五) Optional类

    概述 在Java8之前, 如果需要对一个变量做一次 null 检查, 通常会像下面这样写 T t = service1.query(); if (t != null) { K k = service2 ...

  10. Android原生PDF功能实现:PDF阅读、PDF页面跳转、PDF手势伸缩、PDF目录树、PDF预览缩略图

    1.背景 近期,公司希望实现安卓原生端的PDF功能,要求:高效.实用. 经过两天的调研.编码,实现了一个简单Demo,如上图所示. 关于安卓原生端的PDF功能实现,技术点还是很多的,为了咱们安卓开发的 ...