设计模式与代码结构
〇,设计模式简介
“设计模式”这个概念最早来源于建筑领域,在90年代后被引入软件工程领域。
软件设计模式,又称设计模式,又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
一,设计模式要素、原则与分类
软件设计模式使人们可以更加简单方便地复用成功的设计和体系结构,它通常包含以下几个基本要素:模式名称、别名、动机、问题、解决方案、效果、结构、模式角色、合作关系、实现方法、适用性、已知应用、例程、模式扩展和相关模式等。
设计模式中还有如下的设计原则:
开闭原则:对扩展开放,对修改关闭
里氏替换原则:子类可以扩展父类的功能,但不能改变父类原有的功能
依赖倒置原则:通过要面向接口的编程来降低类间的耦合性
单一职责原则:对象(类)不应该承担太多职责
接口隔离原则:将臃肿庞大的接口拆分成更小的和更具体的接口
迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话
合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
1995 年,艾瑞克·伽马(ErichGamma)、理査德·海尔姆(Richard Helm)、拉尔夫·约翰森(Ralph Johnson)、约翰·威利斯迪斯(John Vlissides)等 4 位作者合作出版了《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)一书,其中收录了 23 个设计模式,这是设计模式领域里程碑的事件。直到今天,狭义的设计模式还是本教程中所介绍的 23 种经典设计模式。
一般来说,这23种设计模式一般又分为三类:创建型模式,行为型模式和机构型模式。
创建型模式简单来说就是用来创建对象的。一共有五种:单例模式、建造者模式、工厂方法模式、抽象工厂模式、原型模式。
行为型模式主要是描述类或者对象是怎样交互和怎样分配职责的。一共有十一种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
结构型模式主要是用于处理类或者对象的组合。一共有七种:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
二,观察者模式(Facade模式)简介
1,观察者模式定义
一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
简单来说,观察者模式定义对象间的一种一个对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都得到通知并被自动更新。
2,结构与时序
外观(Facade)模式包含以下主要角色。
- 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
- 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
- 客户(Client)角色:通过一个外观角色访问各个子系统的功能。
3,代码实现
由于一时没有找到合适的代码示例,再加上熬夜看了巴萨大胜马洛卡有点兴奋,所以直接按照网上的模板实现了一个模拟足球比分推送的观察模式代码。
首先要做的就是创建观察者和主题的抽象基类。通过纯虚函数规定其派生类所需提供的接口。
1 // 观察者基类 2 class Observer_base { 3 public: 4 virtual ~Observer_base() {} 5 6 // 显示名字 7 virtual const string get_name() const = 0; 8 9 // 更新 10 virtual void update(const string & words) = 0; 11 12 // 重载==运算符,从订阅列表删除元素的时候使用 13 virtual bool operator == (const Observer_base* outer) const = 0; 14 }; 15 16 17 18 // 被观察者基类 19 class Target_base{ 20 public: 21 virtual ~Target_base() {} 22 23 // 添加订阅者 24 virtual void add_one_follower(Observer_base* outer) = 0; 25 26 // 删除订阅者 27 virtual void rm_one_follower(Observer_base* outer) = 0; 28 29 // 更新发布内容,即本类中的words变量 30 virtual void set_string(const string& words) = 0; 31 };
1 // 被观察者的一个具体类,比如某队比分推送 2 class Target_scorePush: public Target_base { 3 private: 4 // 观察者集合 5 set<Observer_base*> followers; 6 7 // 被观察者发布的内容 8 string words; 9 10 public: 11 // 添加订阅者 12 void add_one_follower(Observer_base* outer) { 13 followers.insert(outer); 14 cout << outer->get_name() << "成功订阅!" << endl; 15 } 16 17 // 删除订阅者 18 void rm_one_follower(Observer_base* outer) { 19 set<Observer_base*>::iterator it; 20 for ( it = followers.begin(); it != followers.end(); ++it) { 21 22 if (**it == outer) { 23 followers.erase(it); 24 cout << outer->get_name() << "删除成功!" << endl; 25 break; 26 } 27 28 cout << outer->get_name() <<"删除失败,预删除用户未在订阅列表中。" << endl; 29 } 30 } 31 32 // 发布新的内容 33 void set_string(const string& words) { 34 // 更新内容 35 this->words = words; 36 cout << "巴萨最新比分推送: " << this->words << endl; 37 38 // 通知订阅者,即调用订阅者的update函数 39 set<Observer_base*>::iterator it; 40 for (it = followers.begin(); it != followers.end(); ++it) { 41 (*it)->update(this->words); 42 } 43 44 } 45 }; 46 47 48 // 观察者的一个实例,比如一个球迷 49 class Observer_barcaFans :public Observer_base{ 50 private: 51 string name; 52 string news; 53 54 public: 55 // 构造函数 56 Observer_barcaFans(const string& name) 57 :name(name) {} 58 59 // 更新消息 60 void update(const string& words){ 61 this->news = words; 62 cout << "用户" << name << "你好! 巴萨的最新比分是: " << news << endl; 63 } 64 65 // 获取球迷名字 66 const string get_name() const { 67 return this->name; 68 } 69 70 // 运算符重载 71 bool operator == (const Observer_base* outer) const { 72 return outer->get_name() == this->name; 73 } 74 };
1 int main() { 2 // 创建一个比分推送 3 Target_base* scorePush_liu = new Target_scorePush(); 4 5 // 创建两个球迷 6 Observer_base* li_ming = new Observer_barcaFans("李明"); 7 Observer_base* xiao_hong = new Observer_barcaFans("小红"); 8 9 // 进行订阅 10 scorePush_liu->add_one_follower(li_ming); 11 scorePush_liu->add_one_follower(xiao_hong); 12 13 // 推送比分 14 scorePush_liu->set_string("巴萨 1 : 0 马洛卡 \n特狮助攻,格子小角度挑射破门!\n"); 15 scorePush_liu->set_string("巴萨 2 : 0 马洛卡 \n常规操作,梅西禁区外远射破门。\n"); 16 scorePush_liu->set_string("巴萨 2 : 1 马洛卡 \n呵呵。\n"); 17 scorePush_liu->set_string("巴萨 3 : 1 马洛卡 \n复制粘贴!梅西禁区外远射破门。\n"); 18 scorePush_liu->set_string("巴萨 4 : 1 马洛卡 \n德容直塞,苏神脚跟画彩虹!巧射破门。\n"); 19 scorePush_liu->set_string("巴萨 4 : 2 马洛卡 \n呵呵,还敢还手。\n"); 20 scorePush_liu->set_string("巴萨 5 : 2 马洛卡 \n苏神回做,梅西搓射洞穿人墙,手捧金球头带帽!\n"); 21 scorePush_liu->set_string("全场比赛结束,巴萨 5 : 2 马洛卡 \nps:巴尔韦德下课!\n"); 22 23 24 delete scorePush_liu; 25 delete li_ming; 26 delete xiao_hong; 27 28 return 0; 29 }
代码的实现结果如下:
4,模式分析
根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口。 外观模式也是“迪米特法则”的体现,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度。
外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。 外观模式的目的在于降低系统的复杂程度。 外观模式从很大程度上提高了客户端使用的便捷性,使得客户端无须关心子系统的工作细节,通过外观角色即可调用相关功能。
外观模式有如下优点:
- 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。
- 实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。
- 降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
- 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。
同时,外观模式还有如下缺点:
- 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
- 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
5,总结
在外观模式中,外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。
外观模式包含两个角色:外观角色是在客户端直接调用的角色,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理;在软件系统中可以同时有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能。
外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。
外观模式主要优点在于对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易,它实现了子系统与客户之间的松耦合关系,并降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程;其缺点在于不能很好地限制客户使用子系统类,而且在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
外观模式适用情况包括:要为一个复杂子系统提供一个简单接口;客户程序与多个子系统之间存在很大的依赖性;在层次化结构中,需要定义系统中每一层的入口,使得层与层之间不直接产生联系。
原文地址:https://www.cnblogs.com/imerliu/p/12004973.html