设计模式镇魂帆张

对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1A2

注意事项

定义:客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上

示例:

类A通过接口Interface1依赖类B

类C通过接口Interface1依赖类D

如果接口Interface1对于类A和类C来说不是最小接口

那么类B和类D必须去实现他们不需要的方法。

按隔离原则应当这样处理:将接口Interface1拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则

什么叫依赖关系的传递?就是说类A依赖类B,但类A内部不直接new类B,通过依赖关系传递,让类A能依赖到类B

1)低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好

3)继承时遵循里氏替换原则

1)继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。

2)继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障

3)问题提出:在编程中,如何正确的使用继承?=>里氏替换原则

1)里氏替换原则(LiskovSubstitutionPrinciple)在1988年,由麻省理工学院的一位姓里的女士提出的

2)如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P中所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。

3)在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法

4)里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题。

3)当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

4)编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。

1)一个对象应该对其他对象保持最少的了解

2)类与类关系越密切,耦合度越大

3)迪米特法则(DemeterPrinciple)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息

4)迪米特法则还有个更简单的定义:只与直接的朋友通信

5)直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

尽量使用合成/聚合的方式,而不是使用继承,因为继承的耦合性更高

1)UML-----UnifiedmodelinglanguageUML(统一建模语言),是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果

2)UML本身是一套符号的规定,就像数学符号和化学符号一样,这些符号用于描述软件模型中的各个元素和他们之间的关系,比如类、接口、实现、泛化、依赖、组合、聚合等

3)使用UML来建模,常用的工具有RationalRose,也可以使用一些插件来建模

常用关系:

1)用例图(usecase)

2)静态结构图:类图、对象图、包图、组件图、部署图

3)动态行为图:交互图(时序图与协作图)、状态图、活动图

说明:

1)类图是描述类与类之间的关系的,是UML图中最核心的

只要是在类中用到了对方,那么他们之间就存在依赖关系。如果没有对方,连编绎都通过不了。

publicclassPersonServiceBean{ privatePersonDaopersonDao;//类 publicvoidsave(Personperson){} publicIDCardgetIDCard(Integerpersonid){returnnull;} publicvoidmodify(){ Departmentdepartment=newDepartment();}}publicclassPersonDao{}publicclassIDCard{}publicclassPerson{}publicclassDepartment{}小结:

1)类中用到了对方

2)如果是类的成员属性

3)如果是方法的返回类型

4)如果是方法接收的参数类型

5)如果是方法的成员变量

泛化关系实际上就是继承关系,他是依赖关系的特例

publicabstractclassDaoSupport{ publicvoidsave(Objectentity){ } publicvoiddelete(Objectid){ }}publicclassPersonServiceBeanextendsDaosupport{}类图-实现关系(Implementation)实现关系实际上就是A类实现B类,他是依赖关系的特例

publicinterfacePersonService{ publicvoiddelete(Intergerid);}publicclassPersonServiceBeanimplementsPersonService{ publicvoiddelete(Intergerid){}}类图-关联关系(Association)关联关系一般是成员属性的一种依赖

单向一对一关系:

publicclassPerson{ privateIDCardcard;}publicclassIDCard{}双向一对一关系:

publicclassPerson{ privateIDCardcard;}publicclassIDCard{ privatePersonperson}类图-聚合关系(Aggregation)聚合关系(Aggregation)表示的是整体和部分的关系,整体与部分可以分开。聚合关系是关联关系的特例,所以他具有关联的导航性与多重性。如:一台电脑由键盘(keyboard)、显示器(monitor)、鼠标等组成;组成电脑的各个配件是可以从电脑上分离出来的:

publicclassComputer{ Mousemouse; Monitormonitor: publicvoidsetMouse(Mousemouse){ this.mouse=mouse; } publicvoidsetMonitor(Monitormonitor){ this.monitor=monitor; }}如果鼠标、显示器和电脑是不可以分离的,则升级为组合关系

组合关系:也是整体与部分的关系,但是整体与部分不可以分开。再看一个案例:在程序中我们定义实体:Person与IDCard、Head,那么Head和Person就是组合,IDCard和Person就是聚合。

publicclassPerson{ privateIDCardcard; privateHeadhead=newHead();)publicclassIDCard{}publicclassHead{}但是如果在程序中Person实体中定义了对IDCard进行级联删除,即删除Person时,连同IDCard一起删除,那么IDCard和Person就是组合关系了

Demodemo=newDemo();1.分配内存空间(堆中)

2.初始化该内存空间

3.把该空间的地址赋值给demo变量(可能在栈里,也可能在堆中,因为对象可能是Integer或者String这样的常量池),让demo变量指向该空间

类加载过程:

1、加载二进制数据到内存中,生成对应的Class数据结构

2、连接:a.验证,b.准备(给类的静态成员变量赋默认值),c.解析

3、初始化:给类的静态变量赋初值

总共23种

站在对象创建角度

站在整个系统设计角度

站在方法角度

单例模式注意事项和细节说明:

1)单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能

2)当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new()

单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

实现单例模式有8种方法:

1)饿汉式(静态常量)

2)饿汉式(静态代码块)

3)懒汉式(线程不安全)

4)懒汉式(线程安全,同步方法)

5)懒汉式(线程安全,同步代码块)

6)双重检查

7)静态内部类

8)枚举

步骤如下:

1)构造器私有化(防止new)

2)类的内部创建对象

3)向外暴露一个静态的公共方法。

//饿汉式(静态变量)classSingleton{//1.构造器私有化,外部不能newprivateSingleton(){}//2.本类内部创建对象实例privatefinalstaticSingletoninstance=newSingleton();//3.提供一个公有的静态方法,返回实例对象publicstaticSingletongetInstance(){returninstance;}}优缺点:

1)优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

2)缺点:在类装载的时候就完成实例化,没有达到LazyLoading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费

3)这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazyloading的效果

4)结论:这种单例模式可用,但可能造成内存浪费

只有在真正主动使用对应的类时,才会触发初始化如(当前类是启动类即main函数所在类,直接进行new操作,访问静态属性、访问静态方法,用反射访问类,初始化一个类的子类等)

类加载的初始化阶段就完成了实例的初始化。本质上就是借助于jvm类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)

通过反射的方式可以让饿汉式的单例模型产生多例:

饿汉式(静态代码块)//饿汉式(静态变量)classSingleton{//1.构造器私有化,外部不能newprivateSingleton(){}//2.本类内部创建对象实例privatestaticSingletoninstance;static{instance=newSingleton();}//3.提供一个公有的静态方法,返回实例对象publicstaticSingletongetInstance(){returninstance;}}优缺点:

1)这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

2)结论:这种单例模式可用,但是可能造成内存浪费

classSingleton{privatestaticSingletoninstance;privateSingleton(){}//提供一个静态的公有方法,当使用到该方法时,才去创建instance//即懒汉式publicstaticSingletongetInstance(){if(instance==null){instance=newSingleton();}returninstance;}}优缺点说明:

1)起到了LazyLoading的效果,但是只能在单线程下使用。

2)如果在多线程下,一个线程进入了if(singleton==null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式

3)结论:在实际开发中,不要使用这种方式

classSingleton{privatestaticSingletoninstance;privateSingleton(){}//提供一个静态的公有方法,当使用到该方法时,才去创建instance//即懒汉式publicstaticsynchronizedSingletongetInstance(){if(instance==null){instance=newSingleton();}returninstance;}}优缺点说明:

1)解决了线程不安全问题

2)效率太低了,每个线程在想获得类的实例时候,执行getlnstance方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低

3)结论:在实际开发中,不推荐使用这种方式

classSingleton{privatestaticSingletoninstance;privateSingleton(){}//提供一个静态的公有方法,当使用到该方法时,才去创建instance//即懒汉式publicstaticSingletongetInstance(){if(instance==null){synchronized(Singleton.class){instance=newSingleton();}}returninstance;}}优缺点说明:

1)这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的的代码块

2)但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if(singleton==null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例

3)结论:在实际开发中,不能使用这种方式

classSingleton{//被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象。volatile的作用是让下面1、2、3步骤按顺序执行//为什么会出现脏读呢?//因为instance=newSingleton();这句话可以被拆分成3句话//1.分配内存//2.初始化对象//3.变量instance指向刚分配的内存地址//因为CPU进行了优化,2、3不一定顺序执行,当先执行了3后,某个线程认为已经存在了该对象,但实际上该对象还未初始化,使用该对象就会出错privatestaticvolatileSingletoninstance;privateSingleton(){}//提供一个静态的公有方法,当使用到该方法时,才去创建instance//即懒汉式publicstaticSingletongetInstance(){if(instance==null){synchronized(Singleton.class){if(instance==null)instance=newSingleton();}}returninstance;}}优缺点说明:

本质是从懒汉模式上进化来的

静态内部类的特点

publicclassDemo7{publicstaticvoidmain(String[]args){System.out.println("静态内部类,线程安全,效率也高,推荐");Singletoninstance=Singleton.getInstance();Singletoninstance2=Singleton.getInstance();System.out.println(instance==instance2);//trueSystem.out.println("instance.hashCode="+instance.hashCode());System.out.println("instance2.hashCode="+instance2.hashCode());//探究一个问题,执行下面这句话的时候,"Singleton类初始化了"会不会输出?"SingletonInstance类初始化了"会不会输出System.out.println(Singleton.name);}}classSingleton{staticStringname="aaa";static{System.out.println("Singleton类初始化了");}privateSingleton(){}privatestaticclassSingletonInstance{static{System.out.println("SingletonInstance类初始化了");}privatestaticfinalSingletonINSTANCE=newSingleton();}publicstaticSingletongetInstance(){returnSingletonInstance.INSTANCE;}}优缺点说明:

1)这种方式采用了类装载的机制来保证初始化实例时只有一个线程

2)静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getlnstance方法,才会装载Singletoninstance类,从而完成Singleton的实例化

3)类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

4)优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

5)结论:推荐使用.

本质是从静态模式中进化来的

enumSingleton{INSTANCE;publicvoidmethod(){System.out.println("这是方法");}}优缺点说明:

1)这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

2)这种方式是EffectiveJava作者JoshBloch提倡的方式

3)结论:推荐使用

publicclassRuntime{privatestaticRuntimecurrentRuntime=newRuntime();/***ReturnstheruntimeobjectassociatedwiththecurrentJavaapplication.*MostofthemethodsofclassRuntimeareinstance*methodsandmustbeinvokedwithrespecttothecurrentruntimeobject.**@returntheRuntimeobjectassociatedwiththecurrent*Javaapplication.*/publicstaticRuntimegetRuntime(){returncurrentRuntime;}/**Don'tletanyoneelseinstantiatethisclass*/privateRuntime(){}优缺点优点:

1)在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例

缺点:

1)在分布式系统中,存在多个JVM,就会有多个实例,违反了单例原则

2)因为是单例,所以假如该单例是有状态的,会导致多个应用该单例的线程出问题,因为单例模式必须得应用在无状态的场景下,比如作为工具类

看一个披萨的项目:要便于披萨种类的扩展,要便于维护

1)披萨的种类很多(比如GreekPizz、CheesePizz等)

3)完成披萨店订购功能。

传统方式类图:

传统方式优缺点:

1)优点是比较好理解简单易操作。

2)缺点是违反了设计模式的ocp原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码.

3)比如我们这时要新增加一个Pizza的种类(Pepper披萨),我们需要做如下修改.1.新增一个Pepper类2.修改OrderPizza类代码

4)改进的思路分析分析:修改代码可以接受,但是如果我们在其它的地方也有创建Pizza的代码,就意味着,也需要修改,而创建Pizza的代码,往往有多处。思路,把创建Pizza对象封装到一个类中,这样我们有新的Pizza种类时,只需要修改该类就可,其它有创建到Pizza对象的代码就不需要修改了简单工厂模式

1)简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式

2)简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)

3)在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式.

上面披萨店订购方案改进类图:

优点:

1、工厂bai类含有必要的判断du逻辑,可以决zhi定在什么时候dao创建哪一个zhuan产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;

2、简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。

3、客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。

4、通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。

缺点:

1、由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。

2、使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度。

3、系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。

4、简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

5、违反开闭原则

和简单工厂模式的案例需求一模一样

定义了一个创建对象的抽象方法,由子类决定要实例化的类,工厂方法模式将对象的实例化推迟到子类。

优点:

工厂模式是简单工厂模式的进一步抽象和推广。它遵循了“开放—封闭”原则。

1.工厂方法把简单工厂的内部逻辑判断转移到了客户端里来执行;每增加一产品就要增加一个产品工厂的类,增加了额外的开发量。

2.工厂和产品是一对一的关系,工厂只能生产某一个大类的产品,按照披萨的例子,工厂模式只能生产披萨

3.违反开闭原则

和工厂模式案例一样,但是新增了一个大类热狗

2)抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。

3)从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。

4)将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。

5)本质上是工厂模式的扩展,当抽象工厂模式只生产一类产品,那么就退化到工厂模式

可以生产不同类别的产品

和工厂模式一样,违反了开闭原则

1)工厂模式的意义:将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。

2)三种工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)

3)设计模式的依赖抽象原则:

创建对象实例时,不要直接new类:而是把这个new类的动作放在一个工厂的方法中并返回。有的书上说,变量不要直接持有具体类的引用。

不要让类继承具体类,而是继承抽象类或者是实现interface(接口)

不要覆盖基类中已经实现的方法。

克隆羊问题:现在有一只羊tom,姓名为:tom,年龄为1,颜色为:白色,请编写程序创建和tom羊属性完全相同的10只羊。

传统方式解决方案类图:

传统的方式的优缺点:

1)优点是比较好理解,简单易操作。

2)库创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低

3)总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活

4)改进的思路分析

思路,Java中Object类是所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现done的Java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力=>原型模式

1)原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象

2)原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节

3)工作原理是:通过将一个原型对象传给那个要发起创建的对象,这个要发起创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象done()

//beans.xml//bean的scope是原型模式,意味着创建bean实例时,每次创建都会产生一个新的实例 ApplicationContextapplicationContext=newClassPathXmlApplicationContext("beans.xml");Objectsuzy1=applicationContext.getBean("suzy");Objectsuzy2=applicationContext.getBean("suzy");System.out.println(suzy1==suzy2);//false其他例子:ArrayList(浅拷贝)

浅拷贝基本介绍:

1)对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。

2)对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值

3)前面我们克隆羊就是浅拷贝

4)浅拷贝是使用默认的done。方法来实现sheep=(Sheep)supen.clone();

深拷贝基本介绍:

1)复制对象的所有基本数据类型的成员变量值

2)为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所弓用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝

深拷贝的实现方式:

1)重写done方法来实现深拷贝

2)通过对象序列化实现深拷贝(不推荐,因为非常费CPU,需要解析流)

深拷贝-重写clone()

@DatapublicclassSheepimplementsCloneable{//Cloneable接口没有方法,只是一个标记接口,假如没有实现该接口,调用clone()会报错Stringname;intage;Stringcolor;Sheepfriend;publicSheep(Stringname,intage,Stringcolor,Sheepfriend){this.name=name;this.age=age;this.color=color;this.friend=friend;}publicSheep(Sheepsheep)throwsCloneNotSupportedException{this.name=sheep.name;this.age=sheep.age;this.color=sheep.color;if(sheep.friend!=null)this.friend=(Sheep)sheep.friend.clone();}@OverridepublicStringtoString(){return"Sheep{"+"name='"+name+'\''+",age="+age+",color='"+color+'\''+'}';}@OverrideprotectedObjectclone()throwsCloneNotSupportedException{returnnewSheep(this);}}深拷贝-序列化实现:

publicclassCloneUtils{@SuppressWarnings("unchecked")publicstaticTclone(Tobject){//Serializable接口也是个标记接口TcloneObj=null;try{ByteArrayOutputStreamout=newByteArrayOutputStream();ObjectOutputStreamobs=newObjectOutputStream(out);obs.writeObject(object);obs.close();ByteArrayInputStreamios=newByteArrayInputStream(out.toByteArray());ObjectInputStreamois=newObjectInputStream(ios);cloneObj=(T)ois.readObject();}catch(Exceptione){e.printStackTrace();}returncloneObj;}}建造者模式(生成器模式)案例:盖房项目需求

1)房子的内部属性(该属性是个对象)有很多,有地基、墙、屋顶等等,

2)建造房子是有顺序的,得先打桩、再砌墙、最后封顶

3)最后通过复杂的建设,创建出一个房子对象

传统方式解决该问题的思路:通过构造函数的方式创建房子对象

传统方式解决盖房需求问题的优缺点分析:

1)优点:比较好理解,简单易操作。

2)缺点:设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展性和维护性不好,比方说这个时候房子要带院子,有地下车库,就要改构造函数;也就是说,这种设计方案,把产品(即:房子)和创建产品的过程(即:建房子流程)封装在一起,耦合性增强了。

3)解决方案;将产品和产品建造过程解耦=>建造者模式

1)建造者模式(BuilderPattern)又叫生成器模式,是一种对象构建模式。

为了构建一个复杂对象,将复杂对象的每个对象属性的建造过程抽象出来(抽象类别),最终按照一定的顺序构建出这个复杂对象2)建造者模式是一步一步创建复杂的对象的属性对象,从而最终构建出该复杂都西昂,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

建造者模式的四个角色:

1)Product(产品角色):一个具体的产品对象。

2)Builder(抽象建造者):创建一个Product对象的各个部件指定的接口。

3)ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件。

4)Director(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程(先创建哪一属性,再创建哪一属性)。

建造者模式原理类图:

需要创建复杂对象的时候,该复杂对象有很多复杂属性,有些属性是必须的,有些属性是不必须的,还可能属性间有依赖关系

结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。

由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。

结构型模式分为以下7种:

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

1.简单的比喻就是电源适配器,我们的手机是直流电,家用电是交流电,插头就是一个适配器,把交流电转换成手机能用的直流电

2.适配器模式的特点是不修改原有的接口或类

适配器模式原理类图1:

适配器模式实现方式有对象模式(组合或聚合)和类模式(继承)

推荐使用对象模式,类模式把原始接口或类的方法暴露给客户端,违反了迪米特原则(最少知道原则)

有人耳聋听不到,通过适配器中转

publicclassDemo1{/***充当客户端角色*@paramargs*/publicstaticvoidmain(String[]args){Translatortranslator=newAdapter(newSpeaker());System.out.println(translator.translate());}}/***原有的类或接口*/classSpeaker{publicStringspeak(){return"ChinaNo.l";}}/***适配器接口*/interfaceTranslator{Stringtranslate();}/***具体的适配器*/classAdapterimplementsTranslator{privateSpeakerspeaker;publicAdapter(Speakerspeaker){this.speaker=speaker;}publicStringtranslate(){Stringresult=speaker.speak();//...理解、手语话returnresult;}}JDK&Spring源码中的应用JDK:java.util.Arrays#asList()java.util.Collections#list()Spring:org.springframework.context.event.GenericApplicationListenerAdapter桥接模式基本介绍出现背景:

在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于Photoshop这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m种形状和n种颜色的图形就有m×n种,不但对应的子类很多,而且扩展困难。

当然,这样的例子还有很多,如不同颜色和字体的文字、不同品牌和功率的汽车、不同性别和职业的男女、支持不同平台和不同文件格式的媒体播放器等。如果用桥接模式就能很好地解决这些问题。

定义:

将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。

原理类图:

桥接(Bridge)模式包含以下主要角色。

原理演示代码:

packagebridge;publicclassBridgeTest{publicstaticvoidmain(String[]args){Implementorimple=newConcreteImplementorA();Abstractionabs=newRefinedAbstraction(imple);abs.Operation();}}//实现化角色interfaceImplementor{publicvoidOperationImpl();}//具体实现化角色classConcreteImplementorAimplementsImplementor{publicvoidOperationImpl(){System.out.println("具体实现化(ConcreteImplementor)角色被访问");}}//抽象化角色abstractclassAbstraction{protectedImplementorimple;protectedAbstraction(Implementorimple){this.imple=imple;}publicabstractvoidOperation();}//扩展抽象化角色classRefinedAbstractionextendsAbstraction{protectedRefinedAbstraction(Implementorimple){super(imple);}publicvoidOperation(){System.out.println("扩展抽象化(RefinedAbstraction)角色被访问");imple.OperationImpl();}}应用场景:

桥接模式的一个常见使用场景就是替换继承。我们知道,继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的实现代码复用(封装)的功能,但这也是继承的一大缺点。

很多时候,我们分不清该使用继承还是组合/聚合或其他方式等,其实可以从现实语义进行思考。因为软件最终还是提供给现实生活中的人使用的,是服务于人类社会的,软件是具备现实场景的。当我们从纯代码角度无法看清问题时,现实角度可能会提供更加开阔的思路。

女士买包:女士皮包有很多种,可以按用途分、按皮质分、按品牌分、按颜色分、按大小分等,存在多个维度的变化,所以采用桥接模式来实现女士皮包的选购比较合适。

本实例按用途分可选钱包(Wallet)和挎包(HandBag),按颜色分可选黄色(Yellow)和红色(Red)。可以按两个维度定义为颜色类和包类。

publicclassDemo2{publicstaticvoidmain(String[]args){//女士买个红色的挎包Redred=newRed();HandBaghandBag=newHandBag();handBag.setColor(red);System.out.println("买了一个"+handBag.getName());}}//实现化角色:颜色interfaceColor{StringgetColor();}//具体实现化角色:黄色classYellowimplementsColor{publicStringgetColor(){return"yellow";}}//具体实现化角色:红色classRedimplementsColor{publicStringgetColor(){return"red";}}//抽象化角色:包abstractclassBag{protectedColorcolor;publicvoidsetColor(Colorcolor){this.color=color;}publicabstractStringgetName();}//扩展抽象化角色:挎包classHandBagextendsBag{publicStringgetName(){returncolor.getColor()+"HandBag";}}//扩展抽象化角色:钱包classWalletextendsBag{publicStringgetName(){returncolor.getColor()+"Wallet";}}装饰器模式基本介绍定义:

指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式

优点有:

缺点是:装饰器模式会增加许多子类,过度使用会增加程序得复杂性。

通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰器模式的目标。

装饰器模式主要包含以下角色:

装饰器模式原理类图:

孩子穿衣服,只能穿内衣内裤,爸爸帮忙穿裤子,妈妈帮忙穿上衣

publicclassDemo2{publicstaticvoidmain(String[]args){Dressdress=newMom(newDad(newChild()));dress.dress();dress=newDad(newMom(newChild()));dress.dress();}}/***穿衣服接口,抽象构件角色*/interfaceDress{voiddress();}/***孩子类,会自己穿衣服,但只穿了内衣内裤,具体构件角色*/classChildimplementsDress{publicvoiddress(){System.out.println("孩子穿了内衣和内裤");}}/***穿其他衣服的类,抽象装饰*/abstractclassDecoratorimplementsDress{privateDressdress;publicDecorator(Dressdress){this.dress=dress;}publicvoiddress(){dress.dress();}}/***爸爸类,帮忙穿裤子,具体装饰角色*/classDadextendsDecorator{publicDad(Dressdress){super(dress);}publicvoiddress(){super.dress();System.out.println("爸爸帮忙穿裤子");}}/***妈妈类,帮忙穿上衣,具体装饰角色*/classMomextendsDecorator{publicMom(Dressdress){super(dress);}publicvoiddress(){super.dress();System.out.println("妈妈帮忙穿上衣");}}JDK源码中的应用装饰器模式通常在以下几种情况使用:

装饰器模式在Java语言中的最著名的应用莫过于JavaI/O标准库的设计了。例如,InputStream的子类FilterInputStream,OutputStream的子类FilterOutputStream,Reader的子类BufferedReader以及FilterReader,还有Writer的子类BufferedWriter、FilterWriter以及PrintWriter等,它们都是抽象装饰类。

下面代码是为FileReader增加缓冲区而采用的装饰类BufferedReader的例子:

BufferedReaderin=newBufferedReader(newFileReader("filename.txt"));Strings=in.readLine();组合模式基本介绍定义:

有时又叫作整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式。

组合模式包含以下主要角色。

组合模式分为透明式的组合模式和安全式的组合模式。

透明方式

透明方式类图:

安全方式

在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。

安全方式类图:

人口统计:全国有33个省组成,每个省有多个市组成,每个市自己计算人口

软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。

外观模式的定义与特点

外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

在日常编码工作中,我们都在有意无意的大量使用外观模式。只要是高层模块需要调度多个子系统(2个以上的类对象),我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。

外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点。

外观(Facade)模式的主要缺点如下。

在外观模式中,当增加或移除子系统时需要修改外观类,这违背了“开闭原则”。如果引入抽象外观类,则在一定程度上解决了该问题

外观模式原理类图:

外观(Facade)模式包含以下主要角色。

1.tomcat里的RequestFacade享元模式基本介绍在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。

例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,局域网中的路由器、交换机和集线器,教室里的桌子和凳子等。这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。

享元(Flyweight)模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

其主要缺点是:

享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。

比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。

享元模式的本质是缓存共享对象,降低内存消耗。

享元模式的主要角色有如下。

享元模式原理类图:

森林里种树,因为森林里的树有很多,所以不可能每个树都是一个独立的对象,要不然内存爆炸了,所以运用享元模式,树的坐标做为外部数据

代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

代理模式的主要优点有:

那么如何解决以上提到的缺点呢?答案是可以使用动态代理方式

代理模式原理类图:

代理模式的主要角色如下。

根据代理的创建时期,代理模式分为静态代理和动态代理。

当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。

前面分析了代理模式的结构与特点,现在来分析以下的应用场景。

在前面介绍的代理模式中,代理类中包含了对真实主题的引用,这种方式存在两个缺点。

动态代理用的是反射吗?

房产中介,二手房购房者不能直接联系到房东,通过中介找房,中介就可以在中间哄抬价格

publicclassDemo2{publicstaticvoidmain(String[]args){HouseProxyhouseProxy=newHouseProxy();System.out.println("买房人收到报价:"+houseProxy.offer());}}//出价接口,抽象主题interfaceOffer{intoffer();}//房子的主人classHouseHolderimplementsOffer{publicintoffer(){return50;}}classHouseProxyimplementsOffer{privateHouseHolderhouseHolder;publicHouseProxy(){this.houseHolder=newHouseHolder();}publicintoffer(){System.out.println("房子房主出价是:"+houseHolder.offer()+",这个我们不会告知买房人");System.out.println("我们告诉买房人的价格是翻一番:"+houseHolder.offer()*2);returnhouseHolder.offer()*2;}}行为型模式行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。

以上11种行为型模式,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式

例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。

模板方法(TemplateMethod)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。

该模式的主要优点如下。

该模式的主要缺点如下。

模板方法模式包含以下主要角色。

1)抽象类/抽象模板(AbstractClass)

抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。

①模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

②基本方法:是整个算法中的一个步骤,包含以下几种类型。

2)具体子类/具体实现(ConcreteClass)

具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

模板方法模式通常适用于以下场景。

1.典型案例:Servlet,我们都会重写doGet或doPost等方法,Servlet种的模板方法是services()方法策略模式基本介绍在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等,超市促销可以釆用打折、送商品、送积分等方法。

在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。

如果使用多重条件转移语句实现(即硬编码),不但使条件语句变得很复杂,而且增加、删除或更换算法要修改原代码,不易维护,违背开闭原则。如果采用策略模式就能很好解决该问题。

策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

策略模式的主要优点如下。

其主要缺点如下。

策略模式的主要角色如下。

策略模式在很多地方用到,如JavaSE中的容器布局管理就是一个典型的实例,JavaSE中的每个容器都存在多种布局供用户选择。在程序设计中,通常在以下几种情况中使用策略模式较多。

在一个使用策略模式的系统中,当存在的策略很多时,客户端管理所有策略算法将变得很复杂,如果在环境类中使用策略工厂模式来管理这些策略类将大大减少客户端的工作复杂度

List的Comparable类,进行不同的排序命令模式基本介绍在软件开发系统中,“方法的请求者”与“方法的实现者”之间经常存在紧密的耦合关系,这不利于软件功能的扩展与维护。例如,想对方法进行“撤销、重做、记录”等处理都很不方便,因此“如何将方法的请求者与实现者解耦?”变得很重要,命令模式就能很好地解决这个问题。

在现实生活中,命令模式的例子也很多。比如看电视时,我们只需要轻轻一按遥控器就能完成频道的切换,这就是命令模式,将换台请求和换台处理完全解耦了。电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。

再比如,我们去餐厅吃饭,菜单不是等到客人来了之后才定制的,而是已经预先配置好的。这样,客人来了就只需要点菜,而不是任由客人临时定制。餐厅提供的菜单就相当于把请求和处理进行了解耦,这就是命令模式的体现。

命令(Command)模式的定义如下:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

命令模式的主要优点如下。

其缺点是:

命令模式包含以下主要角色。

当系统的某项操作具备命令语义,且命令实现不稳定(变化)时,可以通过命令模式解耦请求与实现。使用抽象命令接口使请求方的代码架构稳定,封装接收方具体命令的实现细节。接收方与抽象命令呈现弱耦合(内部方法无需一致),具备良好的扩展性。

命令模式通常适用于以下场景。

命令模式的扩展

在软件开发中,有时将命令模式与前面学的组合模式联合使用,这就构成了宏命令模式,也叫组合命令模式。宏命令包含了一组命令,它充当了具体命令与调用者的双重角色,执行它时将递归调用它所包含的所有命令

当然,命令模式还可以同备忘录(Memento)模式组合使用,这样就变成了可撤销的命令模式

责任链(ChainofResponsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

注意:责任链模式也叫职责链模式。

在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。

责任链模式是一种对象行为型模式,其主要优点如下。

职责链模式主要包含以下角色。

责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。

应用场景

责任链模式通常在以下几种情况使用。

模式的扩展

职责链模式存在以下两种情况。

责任链模式和建造者模式合用,请求对象可以用建造者模式创建

在软件开发过程中,应用程序中的部分对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态就会发生改变,从而使其行为也发生改变。如人都有高兴和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。

对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用if-else或switch-case语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的if-else语句,这违背了“开闭原则”,不利于程序的扩展。

状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

状态模式是一种对象行为型模式,其主要优点如下。

状态模式的主要缺点如下。

通常在以下情况下可以考虑使用状态模式。

状态模式的扩展

在有些情况下,可能有多个环境对象需要共享一组状态,这时需要引入享元模式,将这些具体状态对象放在集合中供程序共享

在软件世界也是这样,例如,Excel中的数据与折线图、饼状图、柱状图之间的关系;MVC模式中的模型与视图的关系;事件模型中的事件源与事件处理者。所有这些,如果用观察者模式来实现就非常方便。

观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

观察者模式是一种对象行为型模式,其主要优点如下。

它的主要缺点如下。

观察者模式的主要角色如下。

在软件系统中,当系统一方行为依赖另一方行为的变动时,可使用观察者模式松耦合联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。

通过前面的分析与应用实例可知观察者模式适合以下几种情形。

Observable类是抽象目标类,它有一个Vector向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的3个方法。

Observer接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用voidupdate(Observableo,Objectarg)方法,进行相应的工作。

出现背景

中介者(Mediator)模式的定义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。

中介者模式是一种对象行为型模式,其主要优点如下。

其主要缺点是:中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。

原理类图:

中介者模式包含以下主要角色。

模式扩展

在实际开发中,通常采用以下两种方法来简化中介者模式,使开发变得更简单。

在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如“数据结构”中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了“开闭原则”。

既然将遍历方法封装在聚合类中不可取,那么聚合类中不提供遍历方法,将遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点:

迭代器模式在生活中应用的比较广泛,比如:物流系统中的传送带,不管传送的是什么物品,都会被打包成一个个箱子,并且有一个统一的二维码。这样我们不需要关心箱子里是什么,在分发时只需要一个个检查发送的目的地即可。再比如,我们平时乘坐交通工具,都是统一刷卡或者刷脸进站,而不需要关心是男性还是女性、是残疾人还是正常人等信息。

迭代器(Iterator)模式的定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

迭代器模式是一种对象行为型模式,其主要优点如下。

其主要缺点是:增加了类的个数,这在一定程度上增加了系统的复杂性。

在日常开发中,我们几乎不会自己写迭代器。除非需要定制一个自己实现的数据结构对应的迭代器,否则,开源框架提供的API完全够用。

原理类图

迭代器模式主要包含以下角色。

迭代器模式通常在以下几种情况使用。

由于聚合与迭代器的关系非常密切,所以大多数语言在实现聚合类时都提供了迭代器类,因此大数情况下使用语言中已有的聚合类的迭代器就已经够了。

迭代器模式常常与组合模式结合起来使用,在对组合模式中的容器构件进行访问时,经常将迭代器潜藏在组合模式的容器构成类中。

在现实生活中,有些集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药。

这样的例子还有很多,例如,电影或电视剧中的人物角色,不同的观众对他们的评价也不同;还有顾客在商场购物时放在“购物车”中的商品,顾客主要关心所选商品的性价比,而收银员关心的是商品的价格和数量。

这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便。访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。

访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。

访问者(Visitor)模式是一种对象行为型模式,其主要优点如下。

访问者(Visitor)模式的主要缺点如下。

访问者模式包含以下主要角色。

当系统中存在类型数量稳定(固定)的一类数据结构时,可以使用访问者模式方便地实现对该类型所有数据结构的不同操作,而又不会对数据产生任何副作用(脏数据)。

简而言之,就是当对集合中的不同类型数据(类型数量稳定)进行多种操作时,使用访问者模式。

通常在以下情况可以考虑使用访问者(Visitor)模式。

访问者(Visitor)模式是使用频率较高的一种设计模式,它常常同以下两种设计模式联用。

(1)与“迭代器模式”联用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。如【例1】中的对象结构是用List实现的,它通过List对象的Iterator()方法获取迭代器。如果对象结构中的聚合类没有提供迭代器,也可以用迭代器模式自定义一个。

(2)访问者(Visitor)模式同“组合模式”联用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式

每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。

其实很多应用软件都提供了这项功能,如Word、记事本、Photoshop、Eclipse等软件在编辑时按Ctrl+Z组合键时能撤销当前操作,使文档恢复到之前的状态;还有在IE中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。

备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。

备忘录(Memento)模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。

备忘录模式是一种对象行为型模式,其主要优点如下。

其主要缺点是:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

备忘录模式的主要角色如下。

由于JDK、Spring、Mybatis中很少有备忘录模式,所以该设计模式不做典型应用源码分析。

SpringWebflow中DefaultMessageContext类实现了StateManageableMessageContext接口,查看其源码可以发现其主要逻辑就相当于给Message备份。

在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。

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

解释器(Interpreter)模式的定义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。

这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。例如,汉语中的句子有很多,“我是中国人”是其中的一个句子,可以用一棵语法树来直观地描述语言中的句子。

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

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

文法是用于描述语言的语法结构的形式规则。没有规矩不成方圆,例如,有些人认为完美爱情的准则是“相互吸引、感情专一、任何一方都没有恋爱经历”,虽然最后一条准则较苛刻,但任何事情都要有规则,语言也一样,不管它是机器语言还是自然语言,都有它自己的文法规则。例如,中文中的“句子”的文法如下。

〈句子〉::=〈主语〉〈谓语〉〈宾语〉〈主语〉::=〈代词〉|〈名词〉〈谓语〉::=〈动词〉〈宾语〉::=〈代词〉|〈名词〉〈代词〉你|我|他〈名词〉7大学生I筱霞I英语〈动词〉::=是|学习注:这里的符号“::=”表示“定义为”的意思,用“〈”和“〉”括住的是非终结符,没有括住的是终结符。

句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。例如,上述文法可以推出“我是大学生”,所以它是句子。

语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次。

句子“我是大学生”的语法树:

解释器模式的结构与组合模式相似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式。解释器模式的结构与组合模式相似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式。

解释器模式包含以下主要角色。

注意:解释器模式在实际的软件开发中使用比较少,因为它会引起效率、性能以及维护等问题。如果碰到对表达式的解释,在Java中可以用Expression4J或Jep等来设计。

在项目开发中,如果要对数据表达式进行分析与计算,无须再用解释器模式进行设计了,Java提供了以下强大的数学公式解析器:Expression4J、MESP(MathExpressionStringParser)和Jep等,它们可以解释一些复杂的文法,功能强大,使用简单。

现在以Jep为例来介绍该工具包的使用方法。Jep是Javaexpressionparser的简称,即Java表达式分析器,它是一个用来转换和计算数学表达式的Java库。通过这个程序库,用户可以以字符串的形式输入一个任意的公式,然后快速地计算出其结果。而且Jep支持用户自定义变量、常量和函数,它包括许多常用的数学函数和常量。

使用前先下载Jep压缩包,解压后,将jep-x.x.x.jar文件移到选择的目录中,在Eclipse的“Java构建路径”对话框的“库”选项卡中选择“添加外部JAR(X)...”,将该Jep包添加项目中后即可使用其中的类库。

THE END
1.房地产中介招聘招聘房地产中介人才房地产中介招聘专场进行时 10万起 全国学历不限经验不限 汇聚众多行业名企 领导好发展空间大五险一金 本期新增2606个职位 二手房业务员 【耀州区】 3-7k 经验不限中专/中技 西安智汇创想网络科技有限公司 互联网1-49人 张女士 总监 客户经理 【南京】 https://www.liepin.com/career/fangdchanzhongjie/
2.房屋中介承担哪些责任李冰子律师精选解答房屋中介承担的法律责任如下:(1)出于故意而提供了不实的信息作出了违法的行为,应当承担民事或者行政责任,甚至刑事责任;(2)因委托人提供的信息有误或有欺诈内容,由中介机构承担损失,先行向买受人赔偿损失;(3)因中介公司的职员过错给买受人造成损失,承担赔偿责任。房屋中介的职责有:1、提供房屋信息,房屋中介需要提供https://m.64365.com/tuwen/aacfkat/
3.《我的真朋友》涉及法律问题交流(完结)(我的真朋友)剧评《我的真朋友》已经有部分片花出来了,从片花中可以看出本剧剧情还是有部分聚焦在了房子问题上,男女主演也均是房屋中介销售人员,基于现实生活中有关房屋的法律纠纷越来越多,虽然不知道本剧可以有多深入的探讨,但想必无论如何也绕不过去相关问题,所以下面想和大家交流一下剧中的相关法律问题。如有错误,请第一时间指正https://movie.douban.com/review/10186367
4.坚朗五金:首次公开发行股票招股说明书股票频道人于取得收益之日起10个工作日内将违反承诺所得收益汇至发行人指定账户。 另外,发行人的控股股东、实际控制人白宝鲲已承诺,在任何情形下,其均 不会越权干预发行人的经营管理活动,不会侵占发行人的利益;其将切实履行实 际控制人的义务,忠实、勤勉地履行职责,维护发行人和全体股东的合法权益。 (七)原股东公开发售老https://stock.stockstar.com/notice/JC2016031700000276_89.shtml
5.房产中介岗位职责(工作内容,是做什么的)房产中介置业顾问 岗位职责来自 福安市宏泰房产经纪有限公司 一、岗位职责: 1、为客户提供一手房和二手房买卖服务 2、房屋租赁 二、岗位要求: 1、团队精神 2、勤奋好学 三、薪资待遇: 1、行业内高提成 2、 更新于 2024-12-01 更多 房产中介 岗位职责来自 奇林房产 工作内容: 1、负责客户接https://www.jobui.com/gangwei/fangchanzhongjie/
6.您好,请问房屋出租中介的职责只是负责帮忙找房源么?后期所有事情您好,请问房屋出租中介的职责只是负责帮忙找房源么?后期所有事情都不管了么?另外中介的费用怎么收取是正 立即咨询 谢律师1分钟前解答了房产纠纷问题 朱律师1分钟前解答了房产纠纷问题 张律师2分钟前解答了房产纠纷问题 张律师3分钟前解答了房产纠纷问题 谢律师1分钟前解答了房产纠纷问题 朱律师1分钟前解答了房产纠纷问https://m.findlaw.cn/yushu/wenda_23296503.html