解读23种设计模式:C++版(待续)

1、背景:

1994年,软件设计领域的四位大师(GoF,“四人帮”,又称 Gang of Four,即 Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides)通过论著《Design Patterns: Elements of Reusable Object-Oriented Software》阐述了设计模式领域的开创性成果,其在这本书中总结了23个经典的设计模式。

20多年过去了,软件行业迅猛发展,越来越多的新模式不断诞生并得以应用,但作为设计模式的根基,深刻理解和学习23种设计模式仍是非常有必要的,本文就择其重点来重温这23个经典的设计模式,并结合C++语言本身给出一些实例。

设计模式中文版封面.jpg

 

2、分类:

书中将23种设计模分成以下三类:

创建型模式(Creational Pattern):

关注类和对象的创建过程。

对类的实例化过程进行了抽象,将模块中对象的创建和对象的使用分离。

使用者只对接口编程,掩盖实现细节,使整个系统的设计更加符合单一职责原则。

结构型模式(Structural Pattern):

关注类和对象的组合过程,从而形成更加强大的结构。

又可以细分成类结构型模式和对象结构型模式:

  • 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。

  • 对象结构型模式关心类与对象的组合,通过关联关系使得在一 个类中定义另一个类的实例对象,然后通过该对象调用其方法。 在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。

行为型模式(Behavioral Pattern):

关注类和对象的交互过程,重点在相互作用上。

 

3、分类:

23种设计模式都是针对于具体场景提出的,是为了解决具体的问题,所以无论是在学习还是在具体使用时,最好能够熟记一两个使用场景,这样用起来才能更加融会贯通。

所有的设计模式都是为了代码服务的,切忌为了使用而使用的过度设计,也切忌纠结于使用具体哪一种设计模式而束手束脚,只要能够使代码结构清晰,满足“高内聚,低耦合,高扩展”的设计风格,都是好的设计。

下面,我们根据使用场景的不同,将这23类分成不同的部分,因为有些设计模式的场景太过单一,难以应用到其他场景,比如备忘录,命令等。而有些设计模式比较简单易懂,比如工厂,观察者,迭代器,我们也不做过多讲解,下文只重点关注其中的部分设计模式,其他的部分请读者自行查阅。

创建型结构型行为型
常用不常用常用不常用常用不常用

单例(Singleton )

 

工厂(Factory)

 

抽象工厂(Abstract Factory)

 

建造者(Builder)

原型(Prototype )

代理(Proxy )

 

桥接(Bridge)

 

装饰者(Decorator )

 

适配器(Adapter )

外观(Facade )

 

组合(Composite )

 

享元(Flyweight)

 

观察者 (Observer )

 

模板(Template )

 

策略(Strategy )

 

职责链(Chain of Responsibility)

 

迭代器(Iterator )

 

状态(State)

 

访问者(Visitor )

 

备忘录(Memento )

 

命令(Command )

 

解释器(Interpreter )

 

中介(Mediator )

 

4、创建型模式(Creational Pattern):

顾名思义,创建型模式(Creational Pattern)就是着眼于如何创建对象的模式

创建型模式对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。

4_1、单例模式:

单例模式大概是大家最熟悉模式了,c++中经典的写法是Meyers Singleton,在此就不赘述具体实现了。

我在此要强调的是,要谨慎的使用单例模式。

因为在面向对象中使用单例和在面向过程中使用全局变量是一样的,也就意味着所有使用Singleton的客户端和其是紧耦合的,这样可能导致的结果是:

  • 失去了多态特性,比如难以使用Mock Object来完成单元测试。
  • 如果单例类要更改使用方式,那么所有的依赖类都需要更改。

所以,本文建议在工程中,只把那些工具类声明为单例,比如计算时间的类或者字符串操作的类等,而对于实体类,推荐使用依赖注入(Dependency Injection)来代替单例实现。

现以调用日志库为例:

使用单例模式实现的Logger类如图所示:

#include <string_view>
class Logger {
	public:
		static Logger& getInstance() {
			static Logger theLogger { };
			return theLogger;
		}

		void writeInfoEntry(std::string_view entry) {
		// ...
		}
		void writeWarnEntry(std::string_view entry) {
		// ...
		}
		void writeErrorEntry(std::string_view entry) {
		// ...
		}

}

使用方可以在任意的地方调用单例,例如:

#include "Logger.h"

class CustomerRepository {
	public:
	//...
	Customer findCustomerById(const Identifier& customerId) {
		Logger::getInstance().writeInfoEntry("Starting to search for a customer specified by a given unique identifier...");
		// ...
	}
	// ...
};

试想,如果代码中大量充斥着这样的调用,无疑会使代码结构变得混乱,

下面是使用依赖注入改造的一个实例:

我们引入一个抽象类(接口),并使client和实例logger都依赖于该接口:

#include <memory>
#include <string_view>
class LoggingFacility {
	public:
		virtual ~LoggingFacility() = default;
		virtual void writeInfoEntry(std::string_view entry) = 0;
		virtual void writeWarnEntry(std::string_view entry) = 0;
		virtual void writeErrorEntry(std::string_view entry) = 0;
};
using Logger = std::shared_ptr<LoggingFacility>;



#include "LoggingFacility.h"
#include <iostream>

class StandardOutputLogger : public LoggingFacility {
	public:
		virtual void writeInfoEntry(std::string_view entry) override {
			std::cout << "[INFO] " << entry << std::endl;
		}
		virtual void writeWarnEntry(std::string_view entry) override {
			std::cout << "[WARNING] " << entry << std::endl;
		}
		virtual void writeErrorEntry(std::string_view entry) override {
			std::cout << "[ERROR] " << entry << std::endl;
		}
};



#include "Customer.h"
#include "LoggingFacility.h"

class CustomerRepository {
public:
	CustomerRepository() = delete;
    
    CustomerRepository(const Logger& loggingService):logger {loggingService} {}

    Customer findCustomerById(const Identifier& customerId) {
	    logger->writeInfoEntry("Starting to search for a customer specified by a given unique identifier...");
    }
private:
    Logger logger;
};

我们可以按照这种模式创建所有的client和logger实体。

依赖注入中,需要有一个基础结构组件负责初始化对象创建和关联,即所谓的汇编器(Assembler)或者叫注入器(Injector),该组件通常在程序启动时操作,并处理整个软件系统的构建。

我们在Assembler中可以这样构建日志类:

Logger logger = std::make_shared<StandardOutputLogger>();
CustomerRepository customerRepository { logger };

这种依赖注入称为构造函数注入,如果我们在程序运行时需要替换logger,则可以在Customer中实现setter方法,动态替换Logger,这种注入方式称为setter注入。

如此重构之后,再进行单元测试时就会简单很多,我们只需要实现相应接口,并将Mock Object在Assembler中替换即可。

 

4_2、建造者模式和工厂模式的区别:

工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。

举个例子,顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、生菜等,我们通过建造者模式根据用户选择的不同配料来制作披萨。

下面我们举一个使用Builder来精简多参数构造的case:


public class ResourcePoolConfig {
  private String name;
  private int maxTotal;
  private int maxIdle;
  private int minIdle;

  private ResourcePoolConfig(Builder builder) {
    this.name = builder.name;
    this.maxTotal = builder.maxTotal;
    this.maxIdle = builder.maxIdle;
    this.minIdle = builder.minIdle;
  }
 

  public static class Builder {
    private static final int DEFAULT_MAX_TOTAL = 8;
    private static final int DEFAULT_MAX_IDLE = 8;
    private static final int DEFAULT_MIN_IDLE = 0;

    private String name;
    private int maxTotal = DEFAULT_MAX_TOTAL;
    private int maxIdle = DEFAULT_MAX_IDLE;
    private int minIdle = DEFAULT_MIN_IDLE;

    public ResourcePoolConfig build() {
      // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
      if (StringUtils.isBlank(name)) {
        throw new IllegalArgumentException("...");
      }
      if (maxIdle > maxTotal) {
        throw new IllegalArgumentException("...");
      }
      if (minIdle > maxTotal || minIdle > maxIdle) {
        throw new IllegalArgumentException("...");
      }

      return new ResourcePoolConfig(this);
    }

    public Builder setName(String name) {
      if (StringUtils.isBlank(name)) {
        throw new IllegalArgumentException("...");
      }
      this.name = name;
      return this;
    }

    public Builder setMaxTotal(int maxTotal) {
      if (maxTotal <= 0) {
        throw new IllegalArgumentException("...");
      }
      this.maxTotal = maxTotal;
      return this;
    }

    public Builder setMaxIdle(int maxIdle) {
      if (maxIdle < 0) {
        throw new IllegalArgumentException("...");
      }
      this.maxIdle = maxIdle;
      return this;
    }

    public Builder setMinIdle(int minIdle) {
      if (minIdle < 0) {
        throw new IllegalArgumentException("...");
      }
      this.minIdle = minIdle;
      return this;
    }
  }
}

// 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
ResourcePoolConfig config = new ResourcePoolConfig.Builder()
        .setName("dbconnectionpool")
        .setMaxTotal(16)
        .setMaxIdle(10)
        .setMinIdle(12)
        .build();

这种在接口中返回this的实现称为Fluent interface,在类中接口一般需要连续调用时可以使用。

 

 

5、结构型模式(Structural Pattern):

5_1、代理模式

为其他对象提供代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,或者需要对另一个对象增加控制,比如说:要访问的对象在远程的机器上。或者有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

在不改变原始类(或叫被代理类)的情况下,通过引入代理类来给原始类附加功能。一般情况下,我们让代理类和原始类实现同样的接口。但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的。在这种情况下,我们可以通过让代理类继承原始类的方法来实现代理模式。

RPC就是一种代理。

下面举一个例子来说明:


public interface IUserController {
  UserVo login(String telephone, String password);
  UserVo register(String telephone, String password);
}

public class UserController implements IUserController {
  //...省略其他属性和方法...

  @Override
  public UserVo login(String telephone, String password) {
    //...省略login逻辑...
    //...返回UserVo数据...
  }

  @Override
  public UserVo register(String telephone, String password) {
    //...省略register逻辑...
    //...返回UserVo数据...
  }
}

public class UserControllerProxy implements IUserController {
  private MetricsCollector metricsCollector;
  private UserController userController;

  public UserControllerProxy(UserController userController) {
    this.userController = userController;
    this.metricsCollector = new MetricsCollector();
  }

  @Override
  public UserVo login(String telephone, String password) {
    long startTimestamp = System.currentTimeMillis();

    // 委托
    UserVo userVo = userController.login(telephone, password);

    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
    RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);

    return userVo;
  }

  @Override
  public UserVo register(String telephone, String password) {
    long startTimestamp = System.currentTimeMillis();

    UserVo userVo = userController.register(telephone, password);

    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
    RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);

    return userVo;
  }
}

//UserControllerProxy使用举例
//因为原始类和代理类实现相同的接口,是基于接口而非实现编程
//将UserController类对象替换为UserControllerProxy类对象,不需要改动太多代码
IUserController userController = new UserControllerProxy(new UserController());

 

5_2、桥接模式

GOF提出,桥接模式的用意是"将抽象化与实现化解耦,使得二者可以独立地变化"。

存在于多个实体中的共同的概念性联系,就是抽象化。作为一个过程,抽象化就是忽略一些信息,从而把不同的实体当做同样的实体对待,抽象化给出的具体实现,就是实现化,而解耦,就是降低各个部分之间的耦合,在有两个或两个以上的变化纬度的类设计中用组合代替继承,就是桥接模式的一个很好实现。

下面是一个具体例子:

#include <iostream>
 
//操作系统
class OS
{
public:
	virtual void InstallOS_Imp() = 0;
};
class WindowOS : public OS
{
public:
	void InstallOS_Imp() 
	{ 
		std::cout << "安装Window操作系统" << std::endl; 
	}
};
class LinuxOS : public OS
{
public:
	void InstallOS_Imp() 
	{ 
		std::cout << "安装Linux操作系统" << std::endl; 
	}
};
class MacOS : public OS
{
public:
	void InstallOS_Imp() 
	{ 
		std::cout << "安装Mac操作系统" << std::endl; 
	}
};
//计算机
class Computer
{
public:
	virtual void InstallOS(OS* os) = 0;
};
 
class AppleComputer : public Computer
{
public:
	void InstallOS(OS* os) 
	{ 
		std::cout << "在苹果电脑上";
		os->InstallOS_Imp(); 
	}
};
class HPComputer : public Computer
{
public:
	void InstallOS(OS* os) 
	{ 
		std::cout << "在惠普电脑上";
		os->InstallOS_Imp(); 
	}
};
 
int main()
{
	OS* os1 = new WindowOS();
	OS* os2 = new MacOS();
	
	Computer* computer1 = new AppleComputer();
	computer1->InstallOS(os1);
	computer1->InstallOS(os2);
 
	OS* os3 = new LinuxOS();
	Computer* computer2 = new HPComputer();
	computer2->InstallOS(os3);
 
	delete os1;
	delete os2;
	delete os3;
    
	return 0;
}

 

 

5_3、装饰者模式

装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

特点:

1、 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。

2、装饰对象包含一个真实对象的引用(reference)

3、装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。

4、装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。

装饰者模式和代理模式的区别:

1、代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功能。

2、代理还可以用于不能直接调用原始类的情况,而装饰器更多用于增加新功能。

 

 

5_4、适配器模式

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁,其将一个类的接口转换成客户希望的另外一个接口。

其原理并不复杂,大家可以想象一下各种手机转接口,适配器模式实现的就是该功能,我们主要来看一下适配器模式的应用场景。

1、封装依赖库的接口设计

假设我们依赖的外部系统在接口设计方面和我们本身的接口风格不一致,我们ke对外可以统提供的接口进行二次封装,抽象出更好的接口设计。


public class CD { //这个类来自外部库,我们无权修改它的代码
  //...
  public static void staticFunction1() { //... }
  
  public void uglyNamingFunction2() { //... }

  public void tooManyParamsFunction3(int paramA, int paramB, ...) { //... }
  
   public void lowPerformanceFunction4() { //... }
}

// 使用适配器模式进行重构
public class ITarget {
  void function1();
  void function2();
  void fucntion3(ParamsWrapperDefinition paramsWrapper);
  void function4();
  //...
}

public class CDAdaptor extends CD implements ITarget {
  //...
  public void function1() {
     super.staticFunction1();
  }
  
  public void function2() {
    super.uglyNamingFucntion2();
  }
  
  public void function3(ParamsWrapperDefinition paramsWrapper) {
     super.tooManyParamsFunction3(paramsWrapper.getParamA(), ...);
  }
  
  public void function4() {
    //...reimplement it...
  }
}

2、统一多个类的接口设计

某个功能的实现依赖多个外部系统(或者说类)。通过适配器模式,将它们的接口适配为统一的接口定义。


public class ASensitiveWordsFilter { // A敏感词过滤系统提供的接口
  //text是原始文本,函数输出用***替换敏感词之后的文本
  public String filterSexyWords(String text) {
    // ...
  }
  
  public String filterPoliticalWords(String text) {
    // ...
  } 
}

public class BSensitiveWordsFilter  { // B敏感词过滤系统提供的接口
  public String filter(String text) {
    //...
  }
}

public class CSensitiveWordsFilter { // C敏感词过滤系统提供的接口
  public String filter(String text, String mask) {
    //...
  }
}

// 未使用适配器模式之前的代码:代码的可测试性、扩展性不好
public class RiskManagement {
  private ASensitiveWordsFilter aFilter = new ASensitiveWordsFilter();
  private BSensitiveWordsFilter bFilter = new BSensitiveWordsFilter();
  private CSensitiveWordsFilter cFilter = new CSensitiveWordsFilter();
  
  public String filterSensitiveWords(String text) {
    String maskedText = aFilter.filterSexyWords(text);
    maskedText = aFilter.filterPoliticalWords(maskedText);
    maskedText = bFilter.filter(maskedText);
    maskedText = cFilter.filter(maskedText, "***");
    return maskedText;
  }
}

// 使用适配器模式进行改造
public interface ISensitiveWordsFilter { // 统一接口定义
  String filter(String text);
}

public class ASensitiveWordsFilterAdaptor implements ISensitiveWordsFilter {
  private ASensitiveWordsFilter aFilter;
  public String filter(String text) {
    String maskedText = aFilter.filterSexyWords(text);
    maskedText = aFilter.filterPoliticalWords(maskedText);
    return maskedText;
  }
}
//...省略BSensitiveWordsFilterAdaptor、CSensitiveWordsFilterAdaptor...

// 扩展性更好,更加符合开闭原则,如果添加一个新的敏感词过滤系统,
// 这个类完全不需要改动;而且基于接口而非实现编程,代码的可测试性更好。
public class RiskManagement { 
  private List<ISensitiveWordsFilter> filters = new ArrayList<>();
 
  public void addSensitiveWordsFilter(ISensitiveWordsFilter filter) {
    filters.add(filter);
  }
  
  public String filterSensitiveWords(String text) {
    String maskedText = text;
    for (ISensitiveWordsFilter filter : filters) {
      maskedText = filter.filter(maskedText);
    }
    return maskedText;
  }
}

 

3. 替换依赖的外部系统

当我们把项目中依赖的一个外部系统替换为另一个外部系统的时候,利用适配器模式,可以减少对代码的改动。


// 外部系统A
public interface IA {
  //...
  void fa();
}
public class A implements IA {
  //...
  public void fa() { //... }
}
// 在我们的项目中,外部系统A的使用示例
public class Demo {
  private IA a;
  public Demo(IA a) {
    this.a = a;
  }
  //...
}
Demo d = new Demo(new A());

// 将外部系统A替换成外部系统B
public class BAdaptor implemnts IA {
  private B b;
  public BAdaptor(B b) {
    this.b= b;
  }
  public void fa() {
    //...
    b.fb();
  }
}
// 借助BAdaptor,Demo的代码中,调用IA接口的地方都无需改动,
// 只需要将BAdaptor如下注入到Demo即可。
Demo d = new Demo(new BAdaptor(new B()));

 

4、兼容老版本接口

 

代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。

尽管代码结构相似,但这 4 种设计模式的用意完全不同,也就是说要解决的问题、应用场景不同,这也是它们的主要区别。

代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

 

 

6、行为型模式(Behavioral Pattern):

行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。

6_1、模板模式

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

在Effective C++的item 35中,有一个使用NVI(Non-Virtual Interface )来实现模板模式的case:

你正在制作一个视频游戏,你正在为游戏中的人物设计一个类继承体系。你的游戏处在农耕时代,人类很容易受伤或者说健康度降低。因此你决定为其提供一个成员函数,healthValue,返回一个整型值来表明一个人物的健康度。因为不同的人物会用不同的方式来计算健康度,将healthValue声明为虚函数看上去是一个比较明显的设计方式:

class GameCharacter {
public:

virtual int healthValue() const; // return character’s health rating;

...                                               // derived classes may redefine this

};

为了考虑扩展兼容性,我们现在采用NVI来重新定义这个类

class GameCharacter {
public:
    int healthValue() const // derived classes do not redefine
    { 

    ...                                       // do “before” stuff — see below

    int retVal = doHealthValue(); // do the real work

    ...                                       // do “after” stuff — see below

    return retVal;

    }
    ...
private:
    virtual int doHealthValue() const // derived classes may redefine this
    {
    ... // default algorithm for calculating
    } // character’s health
};

NVI用法的一个优点可以从代码注释中看出来,也就是“do before stuff”和“do after stuff”。这些注释指出了在做真正工作的虚函数之前或之后保证要被执行的代码。这意味着这个包装函数在一个虚函数被调用之前,确保了合适的上下文的创建,在这个函数调用结束后,确保了上下文被清除。举个例子,“before”工作可以包括lock a mutex,记录log,验证类变量或者检查函数先验条件是否满足要求,等等。”after”工作可能包含unlocking a mutex,验证函数的后验条件是否满足要求,重新验证类变量等等。如果你让客户直接调用虚函数,那么没有什么好的方法来做到这些。

如果实现含有特定功能的子类,那么只需重写private方法即可

 

 

6_2、策略模式

在 GoF 的《设计模式》一书中,它是这样定义的:

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端。

这个模式比较通俗易懂,下面举一个例子来展示策略模式是如何避免if-else分支判断的


public class OrderService {
  public double discount(Order order) {
    double discount = 0.0;
    OrderType type = order.getType();
    if (type.equals(OrderType.NORMAL)) { // 普通订单
      //...省略折扣计算算法代码
    } else if (type.equals(OrderType.GROUPON)) { // 团购订单
      //...省略折扣计算算法代码
    } else if (type.equals(OrderType.PROMOTION)) { // 促销订单
      //...省略折扣计算算法代码
    }
    return discount;
  }
}

我们保留一个策略工厂,来保存所有的订单算法:


// 策略的定义
public interface DiscountStrategy {
  double calDiscount(Order order);
}
// 省略NormalDiscountStrategy、GrouponDiscountStrategy、PromotionDiscountStrategy类代码...

// 策略的创建
public class DiscountStrategyFactory {
  private static final Map<OrderType, DiscountStrategy> strategies = new HashMap<>();

  static {
    strategies.put(OrderType.NORMAL, new NormalDiscountStrategy());
    strategies.put(OrderType.GROUPON, new GrouponDiscountStrategy());
    strategies.put(OrderType.PROMOTION, new PromotionDiscountStrategy());
  }

  public static DiscountStrategy getDiscountStrategy(OrderType type) {
    return strategies.get(type);
  }
}

// 策略的使用
public class OrderService {
  public double discount(Order order) {
    OrderType type = order.getType();
    DiscountStrategy discountStrategy = DiscountStrategyFactory.getDiscountStrategy(type);
    return discountStrategy.calDiscount(order);
  }
}

在使用时,就可以根据不同的type取出不同的策略算法来使用了。

 

 

6_3、职责链模式

在 GoF 的《设计模式》中,它是这么定义的:

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it

将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。

#include <iostream>
using namespace std;

// Handle类,处理请求的接口:
class Handle {
protected:
    Handle* successor;
public:
    void SetSuccessor(Handle* s) {
        successor = s;
    }
    virtual void HandleRequest(int request) {
        successor->HandleRequest(request);
    }
};

// ConcreteHandler1 & ConcreteHandler2,具体处理类,处理其负责的请求,如果不能处理就将请求转发给它的后继者:
class ConcreteHandler1 : public Handle {
public:
    void HandleRequest(int request) {
        if (request == 1) cout << "HandleRequest1:" << request << endl;
        else if (successor != NULL) successor->HandleRequest(request);
    }
};

class ConcreteHandler2 : public Handle {
public:
    void HandleRequest(int request) {
        if (request == 2) cout << "HandleRequest2:" << request << endl;
        else if (successor != NULL) successor->HandleRequest(request);
    }
};

// 客户端代码实现:
int main() {
    int request1 = 1;
    int request2 = 2;

    Handle* h1 = new ConcreteHandler1();
    Handle* h2 = new ConcreteHandler2();

    h1->SetSuccessor(h2);   // 设置链
h2->SetSuccessor(h1);

    h1->HandleRequest(request2);  // HandleRequest2:2
    h2->HandleRequest(request1);  // HandleRequest1:1
    delete h1;
    delete h2;
    return 0;
}

其应用场景如下:

  • 当客户提交一个请求时,请求时沿链传递直至有一个ConcreteHandler对象负责处理它。
  • 接受者和发送者都没有对方的明确信息,且链中的对象自己也不知道链的结构,结果是职责链可简化对象的相互连接,它们仅需保持一个指向其后继者的引用,而不需要保持所有的候选接受者的引用,这就大大降低了耦合度。
  • 在客户端设置链结构,增强了给对象指派职责的灵活性

当然,在实现职责链时,可以不拦截请求,而继续向后转发,比如我们实现一个多个条件的过滤器,就可以应用职责链模式。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页