设计模式理解与总结

设计模式描述的是类与相互通讯的对象之间的关系,包括角色,责任等。

目录

uml图中的一些解释:

创建模式

工厂模式

抽象工厂模式

单例模式

建造者模式

原型模式

行为型模式

责任链模式

命令模式

解释器模式

迭代器模式

中介者模式

备忘录模式

观察者模式

状态模式

策略模式

模板模式

空对象模式

访问者模式

结构型模式

适配器模式

桥接模式

过滤器模式

组合模式

装饰器模式

外观模式

享元模式

代理模式

(待补充.....) 


本文是参考 菜鸟教程/设计模式 所做的个人总结,这里仅总结对设计模式的理解,没有太多的代码。

uml图中的一些解释:

-------泛化,可以用来表示继承关系

-----实现,实现接口

-----依赖,方法参数需要传入另一个类的对象,就表示依赖这个类

-----关联,表示类与类之间的联接,它使一个类知道另一个类的属性和方法,这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的。

-----聚合,强的关联关系。聚合是整体和个体之间的关系,即has-a的关系

-----组合,关联关系的一种特例。组合是一种整体与部分的关系,即contains-a的关系

 

设计原则: 

1.依赖倒置(DIP)高程模块不应该依赖于低层,二者都应该依赖u于抽象。抽象不应该依赖于细节,实现细节应该依赖于抽象。

(简单来说,使用抽象为中间项,调用 ----》抽象---》实现。实现细节继承于抽象,抽象改变,实现跟着改变,而不是实现细节拉着抽象改变)

2.开发封闭原则:对扩展开发,对更改封闭。类模块应该扩展,不可修改。

3.单一职责原则(srp):一个类应该仅有一个I一年期变化的原因。变化的方向隐含着类的责任。

4.里氏替换原则(LSP):子类必须能够替换对应的基类(IS-A,就是public继承)

5.接口隔离原则(ISP): 不应该强迫客户程序依赖他们不用的方法,接口应该小而完备

6.优先使用对象组合,而不是继承:类继承为“白箱复用”,组合为“黑箱复用”。相对来说继承的父子类耦合性高,组合低

7.封装变化点:(感觉就是说封装)

8.针对接口编程,而不是针对实现编程:不将变量类型生命为某个具体类,二是声明为接口(感觉和第一差不多一个意思)

设计模式之间的关系

 

创建模式

这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

  • 工厂模式(Factory Pattern)
  • 抽象工厂模式(Abstract Factory Pattern)
  • 单例模式(Singleton Pattern)
  • 建造者模式(Builder Pattern)
  • 原型模式(Prototype Pattern)

工厂模式

使用一个类(工厂)来选择某些类(产品)里面的其中一个创建

好处是:以后只需要记住工厂和产品的关键字就行了,不需要记住具体的类

   优点是符合开闭原则,支持扩展产品类,不支持修改。需要添加新产品直接添加新的产品类和对应的新的工厂类就行了(一个工厂对应一个产品)

  对比直接new的优势,当产品很简单,一个new就能搞定的话,直接new有优势,但是,当产品很复杂,由多个类构成的话,工厂模式的优势就体现出来了,如果直接new,每使用这个复杂类都要new好多个类

  参考工厂模式对比直接new的优点

补充代码:

#include <iostream>
#include <string>

class project //抽象产品
{
public:
	virtual void create() = 0;
};


class kileC51 : public project //具体产品
{
public:
	virtual void create()
	{
		std::cout << "c51" << std::endl;
	}
};

class kileArm : public project//具体产品
{
public:
	virtual void create()
	{
		std::cout << "arm" << std::endl;
	}
};

class factory //抽象工厂
{
public:
	virtual project *create() = 0;
	//virtual project *create2() = 0;
};

class kileC51Factory : public factory//具体工厂
{
public:
	project *create()
	{
		return new kileC51();
	}

};
class kileArmFactory : public factory//具体工厂
{
public:
	project *create()
	{
		return new kileArm();
	}
};

int main()
{
	project *c51 = 0;
	project *arm = 0;
	factory *c51F = 0;
	factory *armF = 0;

	c51F = new kileC51Factory;
	armF = new kileArmFactory;

	c51 = c51F->create();
	c51->create();

	arm = armF->create();
	arm->create();
}

抽象工厂模式

在工厂的基础上整合多个工厂。(多了一个整合的工厂基类,本质是多个工厂模式)

这种整合,在实现的时候需要在抽象工厂上定义具体的产品种类,一旦这么做了,后期需要修改具体的产品种类时将会是一个巨大的工程,所以需要一开始就严格定义好接口的new产品的种类

对比工厂方法模式,优点是减少工厂类(工厂方法模式是一对一,抽象工厂是一对多,如下代码,4个具体的产品,工厂方法需要4个工厂,而抽象工厂方法仅需要2个),缺点是不一定遵循开闭原则。

补充代码

#include <iostream>
#include <string>

class project //抽象产品
{
public:
	virtual void create() = 0;
};


class kileC51 : public project //具体产品
{
public:
	virtual void create()
	{
		std::cout << "c51" << std::endl;
	}
};

class kileArm : public project//具体产品
{
public:
	virtual void create()
	{
		std::cout << "arm" << std::endl;
	}
};
class iar1 : public project//具体产品
{
public:
	virtual void create()
	{
		std::cout << "iar1" << std::endl;
	}
};
class iar2 : public project//具体产品
{
public:
	virtual void create()
	{
		std::cout << "iar2" << std::endl;
	}
};

class factory //抽象工厂
{
public:
	//这里要定死种类的,一旦变更会非常麻烦,继承的所有子类都要修改
	virtual project *create() = 0;
	virtual project *create2() = 0;
};

class Factory1 : public factory//具体工厂
{
public:
	project *create()
	{
		return new kileC51();
	}
	project *create2()
	{
		return new iar1();
	}

};
class Factory2 : public factory//具体工厂
{
public:
	project *create()
	{
		return new kileArm();
	}
	project *create2()
	{
		return new iar2();
	}
};

单例模式

把构造函数私有化,使其不能被创建,不能继承,只有唯一一个

用于解决一个全局使用的类频繁地创建与销毁,减少开销

 单例模式有很多种创建方法,常用的是饿汉式:

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

建造者模式

使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。

建造者模式是把一个复杂的类细分为一个个小类的组合(可能是同一层关系,也可能是上下级关系),例如

一个肯德基的套餐可能有汉堡、可乐等等,其他套餐也是这种基本类型的其他组合方式

这些套餐就是复杂的类。

 具体流程:点一个套餐------->套餐分配员分(MealBuilder)配套餐--------->把包装好的菜放到一个集合(Meal)里-------->上包装(Packing)(new)--------->做好菜(new)

原型模式

实际上感觉和c++的拷贝构造一样,实现上是要在类内写一个成员new一个this(this)

java的实现依赖于clone()函数(被复制的类的成员函数)复制一个现有对象代替new

 

拷贝构造与原型模式区别:

相同点:原型模式和拷贝构造函数都是要产生对象的复制品。

不同点:原型模式实现的是一个clone接口,注意是接口,也就是基于多态的clone虚函数。也就是说原型模式能够通过基类指针来复制派生类对象。拷贝构造函数完不成这样的任务。

原型模式的核心是克隆,构造函数只是克隆的一个办法而已。
 

行为型模式

这些设计模式特别关注对象之间的通信

  • 责任链模式(Chain of Responsibility Pattern)
  • 命令模式(Command Pattern)
  • 解释器模式(Interpreter Pattern)
  • 迭代器模式(Iterator Pattern)
  • 中介者模式(Mediator Pattern)
  • 备忘录模式(Memento Pattern)
  • 观察者模式(Observer Pattern)
  • 状态模式(State Pattern)
  • 空对象模式(Null Object Pattern)
  • 策略模式(Strategy Pattern)
  • 模板模式(Template Pattern)
  • 访问者模式(Visitor Pattern)

责任链模式

使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系

注:这里的请求、命令正是可以和命令模式进行结合的地方

做法:每个消息都会有一个附带一个处理等级,达不到处理等级回向下传递,怎么传递?每个接收者对象都会有一个设置下一个处理请求对象setNextLogger的成员函数,该函数:

public void setNextLogger(Logger nextLogger){
      this.nextLogger = nextLogger;
   }

//使用时会设置传递对象
public class ChainPatternDemo {
   
   private static AbstractLogger getChainOfLoggers(){
 
      AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
      AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
      AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
 
      errorLogger.setNextLogger(fileLogger);
      fileLogger.setNextLogger(consoleLogger);
 
      return errorLogger;  
   }

会设置达不到处理等级时传递的对象 。

命令模式

把命令(请求)封装到一个类,然后具体的子类对象通过输入的形参执行者绑定执行者,把请求者和执行者通过命令类解耦

这样做的好处是使执行者能够对应多个请求者,通过命令类

例如:一个开关对象的请求者可以命令电灯对象的执行者是否开关

解释器模式

对于一些固定文法构建一个解释句子的解释器,当一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题

虽然使用解释器模式的实例不是很多,但对于满足以上特点,且对运行效率要求不是很高的应用实例,如果用解释器模式来实现,其效果是非常好的

常用于语法树,编译器,符号解析,正则表达等

解释器模式是一种类行为型模式,其主要优点如下。

  1. 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
  2. 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。


解释器模式的主要缺点如下。

  1. 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
  2. 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
  3. 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。

 

迭代器模式

迭代器模式的目的是提供一种方法顺序访问聚合对象中的元素,而不暴露其内部表示

例如c++中的stl迭代器

一个做法是在需要迭代器的类中写一个创建(new)迭代器对象的成员方法,每次类内的数据++一次就判断一次迭代器真假,迭代器超过数据长度为假。上面是一个描述顺序结构的简单迭代器写法,以前在c++primer上看到的实现迭代器的做法。

总之,迭代器支持的类需要在类内关联这个迭代器对象,如上uml图。

中介者模式

 定义一个中介者对象, 封装一系列对象的交互关系, 使得各对象不必显示的相互引用, 从而使其耦合松散, 而且可以独立的改变它们的交互

//ChatRoom.java
import java.util.Date;
 
public class ChatRoom {
   public static void showMessage(User user, String message){
      System.out.println(new Date().toString()
         + " [" + user.getName() +"] : " + message);
   }
}

//创建 user 类。

//User.java
public class User {
   private String name;
 
   public String getName() {
      return name;
   }
 
   public void setName(String name) {
      this.name = name;
   }
 
   public User(String name){
      this.name  = name;
   }
 
   public void sendMessage(String message){
      ChatRoom.showMessage(this,message); //这里就是使用中介者处理交互信息(显示通信内容)
   }
}

//使用 User 对象来显示他们之间的通信。

//MediatorPatternDemo.java
public class MediatorPatternDemo {
   public static void main(String[] args) {
      User robert = new User("Robert");
      User john = new User("John");
 
      robert.sendMessage("Hi! John!");
      john.sendMessage("Hello! Robert!");
   }
}

//执行程序,输出结果:

//Thu Jan 31 16:05:46 IST 2013 [Robert] : Hi! John!
//Thu Jan 31 16:05:46 IST 2013 [John] : Hello! Robert!

备忘录模式

描述:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。

备忘录模式随处可见,浏览器退回,windows的ctrl+z,棋牌游戏的悔棋等等。

做法是依赖原发器,备忘录,负责人3个角色,备忘录角色负责保存原发器的状态(一般来说是保存原发器类的成员变量),负责人负责管理备忘录(添加删除原发器某一时刻的状态,什么时候启用备忘录等需要备忘录的操作)。

例如:原发器(棋手)下棋时备忘录记录(调用备忘录记录,或者说new一个备忘录类记录这时棋子的状态)这手棋前后的角色,坐标,悔棋时负责人拿出备忘录记录(添加到备忘录数组里的)的状态恢复棋盘。

参考文章

观察者模式

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

做法是再被订阅者中维护一个订阅表,属性更新时告诉订阅表中的对象更新(调用订阅表中的对象的函数),

订阅者使用被订阅者的订阅函数把自己加到订阅表里

 

public class ObserverPatternDemo {
   public static void main(String[] args) {
      Subject subject = new Subject(); //创建被订阅者
 
      new HexaObserver(subject);  //创建观察者并使用构造函数调用subject的订阅方法来订阅被观察者
      new OctalObserver(subject);
      new BinaryObserver(subject);
 
      System.out.println("First state change: 15");   
      subject.setState(15);     //修改属性,会调用订阅表里的观察者对象的成员方法告诉订阅者有修改
      System.out.println("Second state change: 10");  
      subject.setState(10);
   }
}

 

状态模式

实际上状态模式和if...else的功能时一样的,判断不同的条件执行不同的代码,不过如果有很多条件时使用if...else代码会非常的臃肿,难以解读,并且难以维护,例如有1到100的100个条件(状态)时你就要写100个if...else,而且每个分支都会有几十行代码甚至几百行代码时(实际可能远不止如此)阅读起来就会非常的可怕,这时使用状态模式相对而言能够简单些(如果当代码两到一定层次时,我觉得没差多少)
 

做法: 创建2个角色,状态接口state(可能有很多子类状态),不同状态的不同输出类context,实际状态对象做2件事情,1.给context设置当前状态,2.在该状态时要做什么。context要做的很简单,输出某状态下的动作,所有输出在context中(他会有一个成员方法,比如getstate()返回一个状态对象,然后动作都是getstate().xxx)。

策略模式

策略模式和状态模式感觉差不多的,不过状态模式多一个设置状态的工作,而策略模式是可以直接使用的。他们都是可以在if住够多且复杂的时候代替if使代码容易阅读维护。(判断使用哪个策略可以工厂模式)

public class StrategyPatternDemo {
   public static void main(String[] args) {        //判断使用什么策略交由其他了完成,例如工厂类
      Context context = new Context(new OperationAdd());    //设置了对应的策略
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));  //使用策略
 
      context = new Context(new OperationSubstract());      
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationMultiply());    
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}

 

模板模式

思路:将一些特定步骤的具体实现、延迟到子类。使得可以在不改变算法流程的情况下,通过不同的子类、来实现“定制”流程中的特定的步骤

感觉和普通的设计基类子类没有什么大区别。就是要把所有的方法,变量全部在基类中定下来,公共的方法写在基类中,不一样的方法写成接口,在所有子类中实现 

 

空对象模式

既是把对象当作NULL来使用,当有些操作不规范是返回空对象事很好的方法

做法是把实现接口的其中一个子类的成员变量设为空值的标志,如整形设为0,字符串设为this is NULL等

例如:当工厂类的判断创建类的字符串入参输入不规范时返回一个空对象(有点像swich的default)

 

 

访问者模式

 

结构型模式

这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

  • 适配器模式(Adapter Pattern)
  • 桥接模式(Bridge Pattern)
  • 过滤器模式(Filter、Criteria Pattern)
  • 组合模式(Composite Pattern)
  • 装饰器模式(Decorator Pattern)
  • 外观模式(Facade Pattern)
  • 享元模式(Flyweight Pattern)
  • 代理模式(Proxy Pattern)

适配器模式

当要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。

例如:一个MP3播放器本来只能播放mp3格式的文件,现在要扩展可以播放MP4和vlc文件。

 

MedioaAdapter适配器想要实现播放mp4格式,也要在类里new一个扩展的格式类(适配器继承或依赖已有的对象,实现想要的目标接口)。这样比直接在原MP3播放器类new新的扩展类好在哪里? 

假如有很多扩展格式的话就不要一个一个的加入,只要加入到适配器对象,再调用适配器就解决了,把扩展和原来的类解耦,方便以后扩展。


适配器模式的优点:
  更好的复用性
  系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。

  更好的扩展性
  在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。

适配器模式的缺点:
  过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

桥接模式

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化,因为在在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活,所以把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。

例如:设计一个能画形状和形状颜色的draw类,有2种方法:

1.继承,一个抽象形状类有多个具体形状类继承,每个具体形状类又有具体颜色类继承(17个类)

2.一个类控制形状,一个类控制颜色,最后组合(颜色组合图形或者图形组合颜色都可以),这个就是桥接(9个类)

参考文章

 

过滤器模式

和其名字一样,使用一个类,根据另一个需要过滤的类的条件进行过滤归类划分。

关键实现:实现一个存有被过滤的类的数组的接口,由具体的子类根据条件决定那些类能够保存到该对象的数组里(除了被过滤类的条件,还会有些组合类,比如和,或这些,符合和的条件的被过滤类才能够保存,或也一样)。

总之,和创建多个被过滤类数组,,使用if判断什么类加到什么数组的功能一样。

优点:简单,解耦,使用方便。(事实上不是很明白他的好处)

例如:人类对象,有姓名,性别,婚配等属性,我们可以设计一个过滤器对象对其条件(男人,女人,是否婚配和组合条件等)进行过滤。

 

组合模式

类对象里有分部分整体的,能够像树一样一层一层向下,层次分明,有主次关系的对象,合适使用组合模式

例如:

关键实现:在类内创建一个自身的数组,层层向下

装饰器模式

注意:装饰器对象也和被装饰对象一样,继承同一个接口 使用同一个抽象方法   draw();

装饰器与适配器都有一个别名叫做 包装模式(Wrapper),它们看似都是起到包装一个类或对象的作用,但是使用它们的目的很不一一样。适配器模式的意义是要将一个接口转变成另一个接口,它的目的是通过改变接口来达到重复使用的目的。
而装饰器模式不是要改变被装饰对象的接口,而是恰恰要保持原有的接口,但是增强原有对象的功能,或者改变原有对象的处理方式而提升性能。所以这两个模式设计的目的是不同的
 

外观模式

外观模式是一个很简单的模式,主要的内容就是把系统多数的复杂的东西全部都集合起来,给客户暴露一个访问的接口,来隐藏系统的复杂性。

优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性, 4、在层次化结构中,可以使用外观模式定义系统中每一层的入口。

缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

关键实现:在外观类中new其他类的对象,写一个成员方法,里面调用其他对象的方法。(一个外观类整和其他类的各种方法)

 

享元模式

主要用于减少创建对象的数量,以减少内存占用和提高性能,

何时使用: 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。

关键实现:创建对象,保存到数组里(一般是哈希容器)用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。

代理模式

不直接访问(对象在远程上,或者涉及到安全问题等),通过第三方对象进行访问并对其进行控制。

例如:windows的快捷键访问,linux的软连接,代售点买车票等等

代理模式按照职责划分可以分为;1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

关键实现:由代理类内部new需要访问的对象,通过成员方法对该对象进行访问(可能会做什么处理)。(代理模式有很多实现方法,这是其中之一)。

(待补充.....) 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值