精华内容
下载资源
问答
  • Java常见设计模式总结

    万次阅读 多人点赞 2021-09-18 17:18:54
    设计模式是一套经过反复使用的代码设计经验,目的是为了重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式于己于人于系统都是多赢的,它使得代码编写真正工程化,它是软件工程的基石,如同大厦的一块块...

     一、设计模式总述:

    1、什么是设计模式:

            设计模式是一套经过反复使用的代码设计经验,目的是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 设计模式于己于人于系统都是多赢的,它使得代码编写真正工程化,它是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。总体来说,设计模式分为三大类:

    • 创建型模式:共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
    • 结构型模式:共7种:适配器模式、装饰器模式、代理模式、桥接模式、外观模式、组合模式、享元模式
    • 行为型模式:共11种:策略模式、模板方法模式、观察者模式、责任链模式、访问者模式、中介者模式、迭代器模式、命令模式、状态模式、备忘录模式、解释器模式

    其实还有两类:并发型模式和线程池模式,用一个图片来整体描述一下:

    2、设计模式的六大原则:

    (1)开闭原则 (Open Close Principle) :

            开闭原则指的是对扩展开放,对修改关闭。在对程序进行扩展的时候,不能去修改原有的代码,想要达到这样的效果,我们就需要使用接口或者抽象类

    (2)依赖倒转原则 (Dependence Inversion Principle):

            依赖倒置原则是开闭原则的基础,指的是针对接口编程,依赖于抽象而不依赖于具体

    (3)里氏替换原则 (Liskov Substitution Principle) :

            里氏替换原则是继承与复用的基石,只有当子类可以替换掉基类,且系统的功能不受影响时,基类才能被复用,而子类也能够在基础类上增加新的行为。所以里氏替换原则指的是任何基类可以出现的地方,子类一定可以出现。

            里氏替换原则是对 “开闭原则” 的补充,实现 “开闭原则” 的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范。

    (4)接口隔离原则 (Interface Segregation Principle):

            使用多个隔离的接口,比使用单个接口要好,降低接口之间的耦合度与依赖,方便升级和维护方便

    (5)迪米特原则 (Demeter Principle):

            迪米特原则,也叫最少知道原则,指的是一个类应当尽量减少与其他实体进行相互作用,使得系统功能模块相对独立,降低耦合关系。该原则的初衷是降低类的耦合,虽然可以避免与非直接的类通信,但是要通信,就必然会通过一个“中介”来发生关系,过分的使用迪米特原则,会产生大量的中介和传递类,导致系统复杂度变大,所以采用迪米特法则时要反复权衡,既要做到结构清晰,又要高内聚低耦合。

    (6)合成复用原则 (Composite Reuse Principle):

            尽量使用组合/聚合的方式,而不是使用继承。

    二、Java的23种设计模式:

            接下来我们详细介绍Java中23种设计模式的概念,应用场景等情况,并结合他们的特点及设计模式的原则进行分析

    1、创建型-工厂方法模式:

    工厂方法模式分为三种:

    (1)简单工厂模式:

    建立一个工厂类,并定义一个接口对实现了同一接口的产品类进行创建。首先看下关系图:

    (2)工厂方法模式:

    工厂方法模式是对简单工厂模式的改进,简单工厂的缺陷在于不符合“开闭原则”,每次添加新产品类就需要修改工厂类,不利于系统的扩展维护。而工厂方法将工厂抽象化,并定义一个创建对象的接口。每增加新产品,只需增加该产品以及对应的具体实现工厂类,由具体工厂类决定要实例化的产品是哪个,将对象的创建与实例化延迟到子类,这样工厂的设计就符合“开闭原则”了,扩展时不必去修改原来的代码。UML关系图如下:

     (3)静态工厂方法模式:

    静态工厂模式是将工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

    工厂方法模式详情文章:Java设计模式之创建型:工厂模式详解(简单工厂+工厂方法+抽象工厂)

    2、创建型-抽象工厂模式:

            抽象工厂模式主要用于创建相关对象的家族。当一个产品族中需要被设计在一起工作时,通过抽象工厂模式,能够保证客户端始终只使用同一个产品族中的对象;并且通过隔离具体类的生成,使得客户端不需要明确指定具体生成类;所有的具体工厂都实现了抽象工厂中定义的公共接口,因此只需要改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。

            但该模式的缺点在于添加新的行为时比较麻烦,如果需要添加一个新产品族对象时,需要更改接口及其下所有子类,这必然会带来很大的麻烦。

            UML结构图如下:

    抽象工厂模式详情:Java设计模式之创建型:工厂模式详解(简单工厂+工厂方法+抽象工厂)

    3、创建型-建造者模式:

             建造者模式将复杂产品的创建步骤分解在在不同的方法中,使得创建过程更加清晰,从而更精确控制复杂对象的产生过程;通过隔离复杂对象的构建与使用,也就是将产品的创建与产品本身分离开来,使得同样的构建过程可以创建不同的对象;并且每个具体建造者都相互独立,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。UML结构图如下:

     建造者模式详情:Java设计模式之创建型:建造者模式

    4、创建型-单例模式:

            单例模式可以确保系统中某个类只有一个实例,该类自行实例化并向整个系统提供这个实例的公共访问点,除了该公共访问点,不能通过其他途径访问该实例。单例模式的优点在于:

    • 系统中只存在一个共用的实例对象,无需频繁创建和销毁对象,节约了系统资源,提高系统的性能
    • 可以严格控制客户怎么样以及何时访问单例对象。

    单例模式的写法有好几种,主要有三种:懒汉式单例、饿汉式单例、登记式单例。

    单例模式详情:Java设计模式之创建型:单例模式

    5、创建型-原型模式:

            原型模式也是用于对象的创建,通过将一个对象作为原型,对其进行复制克隆,产生一个与源对象类似的新对象。UML类图如下:

     在 Java 中,原型模式的核心是就是原型类 Prototype,Prototype 类需要具备以下两个条件:

    • 实现 Cloneable 接口:
    • 重写 Object 类中的 clone() 方法,用于返回对象的拷贝;

    Object 类中的 clone() 方法默认是浅拷贝,如果想要深拷贝对象,则需要在 clone() 方法中自定义自己的复制逻辑。

    • 浅复制:将一个对象复制后,基本数据类型的变量会重新创建,而引用类型指向的还是原对象所指向的内存地址。
    • 深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。

            使用原型模式进行创建对象不仅简化对象的创建步骤,还比 new 方式创建对象的性能要好的多,因为 Object 类的 clone() 方法是一个本地方法,直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显;

    原型模式详情:Java设计模式之创建型:原型模式

            

            上面我们介绍了5种创建型模式,下面我们就开始介绍下7种结构型模式:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。其中对象的适配器模式是各种模式的起源,如下图:

    6、结构型-适配器模式:

            适配器模式主要用于将一个类或者接口转化成客户端希望的格式,使得原本不兼容的类可以在一起工作,将目标类和适配者类解耦;同时也符合“开闭原则”,可以在不修改原代码的基础上增加新的适配器类;将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性,但是缺点在于更换适配器的实现过程比较复杂。

            所以,适配器模式比较适合以下场景:

    • (1)系统需要使用现有的类,而这些类的接口不符合系统的接口。
    • (2)使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。

    下面有个非常形象的例子很好地说明了什么是适配器模式:

    适配器模式的主要实现有三种:类的适配器模式、对象的适配器模式、接口的适配器模式。三者的使用场景如下:

    • 类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
    • 对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。
    • 接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。

    适配器模式详情:Java设计模式之结构型:适配器模式

    7、结构型-装饰器模式:

            装饰器模式可以动态给对象添加一些额外的职责从而实现功能的拓展,在运行时选择不同的装饰器,从而实现不同的行为;比使用继承更加灵活,通过对不同的装饰类进行排列组合,创造出很多不同行为,得到功能更为强大的对象;符合“开闭原则”,被装饰类与装饰类独立变化,用户可以根据需要增加新的装饰类和被装饰类,在使用时再对其进行组合,原有代码无须改变。装饰器模式的UML结构图如下:

            但是装饰器模式也存在缺点,首先会产生很多的小对象,增加了系统的复杂性,第二是排错比较困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

    装饰器模式详情:Java设计模式之结构型:装饰器模式

    8、结构型-代理模式:

            代理模式的设计动机是通过代理对象来访问真实对象,通过建立一个对象代理类,由代理对象控制原对象的引用,从而实现对真实对象的操作。在代理模式中,代理对象主要起到一个中介的作用,用于协调与连接调用者(即客户端)和被调用者(即目标对象),在一定程度上降低了系统的耦合度,同时也保护了目标对象。但缺点是在调用者与被调用者之间增加了代理对象,可能会造成请求的处理速度变慢。UML结构图如下:

    代理模式详情:Java设计模式之结构型:代理模式

    9、结构型-桥接模式:

            桥接模式将系统的抽象部分与实现部分分离解耦,使他们可以独立的变化。为了达到让抽象部分和实现部分独立变化的目的,桥接模式使用组合关系来代替继承关系,抽象部分拥有实现部分的接口对象,从而能够通过这个接口对象来调用具体实现部分的功能。也就是说,桥接模式中的桥接是一个单方向的关系,只能够抽象部分去使用实现部分的对象,而不能反过来。 

            桥接模式符合“开闭原则”,提高了系统的可拓展性,在两个变化维度中任意扩展一个维度,都不需要修改原来的系统;并且实现细节对客户不透明,可以隐藏实现细节。但是由于聚合关系建立在抽象层,要求开发者针对抽象进行编程,这增加系统的理解和设计难度。桥接模式的UML结构图如下:

            就像在Java中我们使用 JDBC 连接数据库时,在各个数据库之间进行切换,基本不需要动太多的代码,原因就是使用了桥接模式,JDBC 提供统一接口,每个数据库提供各自的实现,然后由桥接类创建一个连接数据库的驱动,使用某一个数据库的时候只需要切换一下就行。JDBC 的结构图如下:

             在 JDBC 中,桥接模式的实现化角色 (Implementor) 为的 Driver 接口,具体实现化 (Concrete Implementor) 角色对应 MysqlDriver、OracleDriver 和 MariadbDriver,扩展抽象化 (Refined Abstraction) 角色对应 DriverManager,不具有抽象化 (Abstraction) 角色作为扩展抽象化角色的父类。

    桥接模式详情:Java设计模式之结构型:桥接模式

    10、结构型-外观模式:

            外观模式通过对客户端提供一个统一的接口,用于访问子系统中的一群接口。使用外观模式有以下几点好处:

    (1)更加易用:使得子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟外观类交互就可以了;

    (2)松散耦合:将客户端与子系统解耦,让子系统内部的模块能更容易扩展和维护。

    (3)更好的划分访问层次:通过合理使用 Facade,可以更好地划分访问的层次,有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到门面中,这样既方便客户端使用,也很好地隐藏了内部的细节。

            但是如果外观模式对子系统类做太多的限制则减少了可变性和灵活性,所以外观模式适用于为复杂子系统提供一个简单接口,提高系统的易用性场景 以及 引入外观模式将子系统与客户端进行解耦,提高子系统的独立性和可移植性。

            外观模式的UML结构图如下:

    外观模式详情: Java设计模式之结构型:外观模式

    11、结构型-组合模式:

            组合模式将叶子对象和容器对象进行递归组合,形成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性,能够像处理叶子对象一样来处理组合对象,无需进行区分,从而使用户程序能够与复杂元素的内部结构进行解耦。

            组合模式最关键的地方是叶子对象和组合对象实现了相同的抽象构建类,它既可表示叶子对象,也可表示容器对象,客户仅仅需要针对这个抽象构建类进行编程,这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。组合模式的UML结构图如下:

    组合模式详情: Java设计模式之结构型:组合模式

    12、结构型-享元模式:

            享元模式通过共享技术有效地支持细粒度、状态变化小的对象复用,当系统中存在有多个相同的对象,那么只共享一份,不必每个都去实例化一个对象,极大地减少系统中对象的数量,从而节省资源。

            享元模式的核心是享元工厂类,享元工厂类维护了一个对象存储池,当客户端需要对象时,首先从享元池中获取,如果享元池中存在对象实例则直接返回,如果享元池中不存在,则创建一个新的享元对象实例返回给用户,并在享元池中保存该新增对象,这点有些单例的意思。

            工厂类通常会使用集合类型来保存对象,如 HashMap、Hashtable、Vector 等等,在 Java 中,数据库连接池、线程池等都是用享元模式的应用。

            享元模式的UML结构图如下:

             Java 中,String 类型就是使用享元模式,String 对象是 final 类型,对象一旦创建就不可改变。而 Java 的字符串常量都是存在字符串常量池中的,JVM 会确保一个字符串常量在常量池中只有一个拷贝。

            而且提到共享池,我们也很容易联想到 Java 里面的JDBC连接池,通过连接池的管理,实现了数据库连接的共享,不需要每一次都重新创建连接,节省了数据库重新创建的开销,提升了系统的性能!

    享元模式详情:Java设计模式之结构型:享元模式

            前面我们介绍了7种结构型设计模式,接下来我们介绍一下11种行为型设计模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。先来张图,看看这11中模式的关系:

     13、行为型-策略模式:

            将类中经常改变或者可能改变的部分提取为作为一个抽象策略接口类,然后在类中包含这个对象的实例,这样类实例在运行时就可以随意调用实现了这个接口的类的行为。

            比如定义一系列的算法,把每一个算法封装起来,并且使它们可相互替换,使得算法可独立于使用它的客户而变化,这就是策略模式。UML结构图如下:

            策略模式的优点在于可以动态改变对象的行为;但缺点是会产生很多策略类,并且策略模式的决定权在用户,系统只是提供不同算法的实现,所以客户端必须知道所有的策略类,并自行决定使用哪一个策略类; 

            策略模式适用用于以下几种场景:

    • (1)应用程序需要实现特定的功能服务,而该程序有多种实现方式使用,所以需要动态地在几种算法中选择一种
    • (2)一个类定义了多种行为算法,并且这些行为在类的操作中以多个条件语句的形式出现,就可以将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

    策略模式详情:Java设计模式之行为型:策略模式

    14、行为型-模板方法:

            模板方法是基于继承实现的,在抽象父类中声明一个模板方法,并在模板方法中定义算法的执行步骤(即算法骨架)。在模板方法模式中,可以将子类共性的部分放在父类中实现,而特性的部分延迟到子类中实现,只需将特性部分在父类中声明成抽象方法即可,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤,不同的子类可以以不同的方式来实现这些逻辑。

            模板方法模式的优点在于符合“开闭原则”,也能够实现代码复用,将不变的行为转移到父类,去除子类中的重复代码。但是缺点是不同的实现都需要定义一个子类,导致类的个数的增加使得系统更加庞大,设计更加抽象。模板方法模式的UML图如下:

    模板方法详情:Java设计模式之行为型:模板方法模式

    15、行为型-责任链模式:

            职责链可以将请求的处理者组织成一条链,并将请求沿着链传递,如果某个处理者能够处理请求则处理,否则将该请求交由上级处理。客户端只需将请求发送到职责链上,无须关注请求的处理细节,通过职责链将请求的发送者和处理者解耦了,这也是职责链的设计动机。        

           职责链模式可以简化对象间的相互连接,因为客户端和处理者都没有对方明确的信息,同时处理者也不知道职责链中的结构,处理者只需保存一个指向后续者的引用,而不需要保存所有候选者的引用。

            另外职责链模式增加了系统的灵活性,我们可以任意增加或更改处理者,甚至更改处理者的顺序,不过有可能会导致一个请求无论如何也得不到处理,因为它可能被放置在链末端。

    所以责任链模式有以下几个优点:

    • (1)降低耦合度,将请求的发送者和接收者解耦。反映在代码上就是不需要在类中写很多丑陋的 if….else 语句,如果用了职责链,相当于我们面对一个黑箱,只需将请求递交给其中一个处理者,然后让黑箱内部去负责传递就可以了。
    • (2)简化了对象,使得对象不需要链的结构。
    • (3)增加系统的灵活性,通过改变链内的成员或者调动他们的次序,允许动态地新增或者删除处理者
    • (4)增加新的请求处理类很方便。

    但是责任链模式也存在一些缺点:

    • (1)不能保证请求一定被成功处理
    • (2)系统性能将受到一定影响,并且可能会造成循环调用。
    • (3)可能不容易观察运行时的特征,而且在进行代码调试时不太方便,有碍于除错。

            责任链模式的UML结构图如下:

    责任链模式详情:Java设计模式之行为型:责任链模式

    16、行为型-观察者模式:

            观察者模式又称为 发布-订阅模式,定义了对象之间一对多依赖关系,当目标对象(被观察者)的状态发生改变时,它的所有依赖者(观察者)都会收到通知。一个观察目标可以对应多个观察者,而这些观察者之间没有相互联系,所以能够根据需要增加和删除观察者,使得系统更易于扩展,符合开闭原则;并且观察者模式让目标对象和观察者松耦合,虽然彼此不清楚对方的细节,但依然可以交互,目标对象只知道一个具体的观察者列表,但并不认识任何一个具体的观察者,它只知道他们都有一个共同的接口。

            但观察者模式的缺点在于如果存在很多个被观察者的话,那么将需要花费一定时间通知所有的观察者,如果观察者与被观察者之间存在循环依赖的话,那么可能导致系统崩溃,并且观察者模式没有相应的机制让观察者知道被观察对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。观察者模式的UML结构图如下:

     观察者模式详情:Java设计模式之行为型:观察者模式

    17、行为型-访问者模式:

            访问者模式就是一种分离对象数据结构与行为 (基于数据结构的操作) 的方法,通过这种分离,达到为一个被访问者动态添加新的操作而无需做其它修改的效果,使得添加作用于这些数据结构的新操作变得简单,并且不需要改变各数据结构,为不同类型的数据结构提供多种访问操作方式,这样是访问者模式的设计动机。

            除了使新增访问操作变得更加简单,也能够在不修改现有类的层次结构下,定义该类层次结构的操作,并将有关元素对象的访问行为集中到一个访问者对象中,而不是分散搞一个个的元素类中。

           但访问者模式的缺点在于让增加新的元素类变得困难,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,违背了“开闭原则”的要求;

            所以访问者模式适用于对象结构中很少改变,但经常需要在此对象结构上定义新的操作的系统,使得算法操作的增加变得简单;或者需要对一个对象结构中进行很多不同并且不相关的操作,并且需要避免让这些操作污染这些对象,也不希望在增加新操作时修改这些类的场景。

            访问者模式的UML结构图如下:

            从上面的 UML 结构图中我们可以看出,访问者模式主要分为两个层次结构,一个是访问者层次结构,提供了抽象访问者和具体访问者,主要用于声明一些操作;一个是元素层次结构,提供了抽象元素和具体元素,主要用于声明 accept 操作;而对象结构 ObjectStructure 作为两者的桥梁,存储了不同类型的对象,以便不同的访问者来访问,相同访问者可以以不同的方式访问不同的元素,所以在访问者模式中增加新的访问者无需修改现有代码,可扩展行强。

            在访问者模式使用了双分派技术,所谓双分派技术就是在选择方法的时候,不仅仅要根据消息接收者的运行时区别,还要根据参数的运行时区别。在访问者模式中,客户端将具体状态当做参数传递给具体访问者,这里完成第一次分派,然后具体访问者作为参数的“具体状态”中的方法,同时也将自己this作为参数传递进去,这里就完成了第二次分派。双分派意味着得到的执行操作决定于请求的种类和接受者的类型。

     访问者模式详情:Java设计模式之行为型:访问者模式

    18、行为型-中介者模式:

             中介者模式通过中介者对象来封装一系列的对象交互,将对象间复杂的关系网状结构变成结构简单的以中介者为核心的星形结构,对象间一对多的关联转变为一对一的关联,简化对象间的关系,便于理解;各个对象之间的关系被解耦,每个对象不再和它关联的对象直接发生相互作用,而是通过中介者对象来与关联的对象进行通讯,使得对象可以相对独立地使用,提高了对象的可复用和系统的可扩展性。

            在中介者模式中,中介者类处于核心地位,它封装了系统中所有对象类之间的关系,除了简化对象间的关系,还可以对对象间的交互进行进一步的控制。中介者模式的UML结构图如下:

            但是,中介者对象封装了对象之间的关联关系,导致中介者对象变得比较庞大复杂,所承担的责任也比较多,维护起来也比较困难,它需要知道每个对象和他们之间的交互细节,如果它出问题,将会导致整个系统都会出问题。

    中介者模式详情:Java设计模式之行为型:中介者模式

    19、行为型-命令模式:

            命令模式的本质是将请求封装成对象,将发出命令与执行命令的责任分开,命令的发送者和接收者完全解耦,发送者只需知道如何发送命令,不需要关心命令是如何实现的,甚至是否执行成功都不需要理会。命令模式的关键在于引入了抽象命令接口,发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。

            使用命令模式的优势在于降低了系统的耦合度,而且新命令可以很方便添加到系统中,也容易设计一个组合命令。但缺点在于会导致某些系统有过多的具体命令类,因为针对每一个命令都需要设计一个具体命令类。

            命令模式的UML结构图如下:

    命令模式详情: Java设计模式之行为型:命令模式

    20、行为型-状态模式:

            状态模式,就是允许对象在内部状态发生改变时改变它的行为,对象看起来就好像修改了它的类,也就是说以状态为原子来改变它的行为,而不是通过行为来改变状态。

            当对象的行为取决于它的属性时,我们称这些属性为状态,那该对象就称为状态对象。对于状态对象而言,它的行为依赖于它的状态,比如要预订房间,只有当该房间空闲时才能预订,想入住该房间也只有当你预订了该房间或者该房间为空闲时。对于这样的一个对象,当它的外部事件产生互动的时候,其内部状态就会发生变化,从而使得他的行为也随之发生变化。

            状态模式的UML结构图如下:

     从上面的UML结构图我们可以看出状态模式的优点在于:

    (1)封装了转换规则,允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块

    (2)将所有与状态有关的行为放到一个类中,可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 

    但是状态模式的缺点在于:

    (1)需要在枚举状态之前需要确定状态种类

    (2)会导致增加系统类和对象的个数。

    (3)对 “开闭原则” 的支持并不友好,新增状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。

    所以状态模式适用于:代码中包含大量与对象状态有关的条件语句,以及对象的行为依赖于它的状态,并且可以根据它的状态改变而改变它的相关行为。

    状态模式详情:Java设计模式之行为型:状态模式

    21、行为型-备忘录模式:

            备忘录模式提供了一种恢复状态的机制,在不破坏封装的前提下,捕获对象的某个时刻内部状态,并保存在该对象之外,保证该对象能够恢复到某个历史状态;备忘录模式将保存的细节封装在备忘录中,除了创建它的创建者之外其他对象都不能访问它,并且实现了即使要改变保存的细节也不影响客户端。但是备忘录模式都是多状态和多备份的,会早用较多的内存,消耗资源。备忘录模式的额UML结构图如下:

             备忘录模式的核心就是备忘录 Memento,在备忘录中存储的就是原发器 Originator 的部分或者所有的状态信息,而这些状态信息是不能够被其他对象所访问的,也就是说我们是不能使用备忘录之外的对象来存储这些状态信息,如果暴漏了内部状态信息就违反了封装的原则,故备忘录除了原发器外其他对象都不可以访问。所以为了实现备忘录模式的封装,我们需要对备忘录的访问做些控制:

    (1)对原发器:可以访问备忘录里的所有信息。

    (2)对负责人 caretaker:不可以访问备忘录里面的数据,但是他可以保存备忘录并且可以将备忘录传递给其他对象。

    (3)其他对象:不可访问也不可以保存,它只负责接收从负责人那里传递过来的备忘录同时恢复原发器的状态。

    备忘录模式详情:Java设计模式之行为型:备忘录模式

    22、行为型-迭代器模式:

            迭代器模式提供一种访问集合中的各个元素,而不暴露其内部表示的方法。将在元素之间游走的职责交给迭代器,而不是集合对象,从而简化集合容器的实现,让集合容器专注于在它所应该专注的事情上,更加符合单一职责原则,避免在集合容器的抽象接口层中充斥着各种不同的遍历操作。迭代器模式的UML结构图如下:

    迭代器模式详情:Java设计模式之行为型:迭代器模式

    23、行为型-解释器模式:

            解释器模式,就是定义语言的文法,并建立一个解释器来解释该语言中的句子,通过构建解释器,解决某一频繁发生的特定类型问题实例。

            解释器模式描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发的编译器中,它描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。    

            解释器模式中除了能够使用文法规则来定义一个语言,还能通过使用抽象语法树来更加直观表示、更好地地表示一个语言的构成,每一颗抽象语法树对应一个语言实例。抽象语法树描述了如何构成一个复杂的句子,通过对抽象语法树的分析,可以识别出语言中的终结符和非终结符类。 在解释器模式中由于每一种终结符表达式、非终结符表达式都会有一个具体的实例与之相对应,所以系统的扩展性比较好。

            解释器模式的UML如下:

     解释器模式详情:Java设计模式之行为型:解释器模式


    相关推荐阅读:

    Spring常见面试题总结

    SpringMVC常见面试题总结

    Mybatis常见面试题总结

    MySQL常见面试题总结

    Redis常见面试题总结

    RabbitMQ消息队列常见面试题总结

    ElasticSearch搜索引擎常见面试题总结

    计算机网络常见面试题总结

    操作系统常见面试题总结

    Java基础、集合、多线程常见面试题总结

    Java虚拟机常见面试题总结

    Java常见设计模式总结

    海量数据处理的方法总结


    参考文章:

    Java之美[从菜鸟到高手演变]之设计模式

    Java之美[从菜鸟到高手演变]之设计模式二

    Java之美[从菜鸟到高手演变]之设计模式三

    Java之美[从菜鸟到高手演变]之设计模式四

    展开全文
  • 前端开发中常用的几种设计模式

    万次阅读 多人点赞 2021-08-17 15:05:54
    设计模式是对软件设计开发过程中反复出现的某类问题的通用解决方案。设计模式更多的是指导思想和方法论,而不是现成的代码,当然每种设计模式都有每种语言中的具体实现方式。学习设计模式更多的是理解各种模式的内在...

    设计模式概览

    设计模式是对软件设计开发过程中反复出现的某类问题的通用解决方案。设计模式更多的是指导思想和方法论,而不是现成的代码,当然每种设计模式都有每种语言中的具体实现方式。学习设计模式更多的是理解各种模式的内在思想和解决的问题,毕竟这是前人无数经验总结成的最佳实践,而代码实现则是对加深理解的辅助。

    设计模式可以分为三大类:

    1. 结构型模式(Structural Patterns): 通过识别系统中组件间的简单关系来简化系统的设计。
    2. 创建型模式(Creational Patterns): 处理对象的创建,根据实际情况使用合适的方式创建对象。常规的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。
    3. 行为型模式(Behavioral Patterns):用于识别对象之间常见的交互模式并加以实现,如此,增加了这些交互的灵活性。

     23种设计模式概览

    上述中一共有23种设计模式,但我们作为前端开发人员,需要了解的大概有以下10种。

    前端需要了解的设计模式(10种)

    创建型模式

    故名思意,这些模式都是用来创建实例对象的。

    1. 工厂模式

    我们从简单的开始。 简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。 

    工厂模式

    上图为例,我们构造一个简单的汽车工厂来生产汽车:

    // 汽车构造函数
    function SuzukiCar(color) {
      this.color = color;
      this.brand = 'Suzuki';
    }
    
    // 汽车构造函数
    function HondaCar(color) {
      this.color = color;
      this.brand = 'Honda';
    }
    
    // 汽车构造函数
    function BMWCar(color) {
      this.color = color;
      this.brand = 'BMW';
    }
    
    // 汽车品牌枚举
    const BRANDS = {
      suzuki: 1,
      honda: 2,
      bmw: 3
    }
    
    /**
     * 汽车工厂
     */
    function CarFactory() {
      this.create = (brand, color)=> {
        switch (brand) {
          case BRANDS.suzuki:
            return new SuzukiCar(color);
          case BRANDS.honda:
            return new HondaCar(color);
          case BRANDS.bmw:
            return new BMWCar(color);
          default:
            break;
        }
      }
    }

    使用一下我们的工厂:

    const carFactory = new CarFactory();
    const cars = [];
    
    cars.push(carFactory.create(BRANDS.suzuki, 'brown'));
    cars.push(carFactory.create(BRANDS.honda, 'grey'));
    cars.push(carFactory.create(BRANDS.bmw, 'red'));
    
    function sayHello() {
      console.log(`Hello, I am a ${this.color} ${this.brand} car`);
    }
    
    for (const car of cars) {
      sayHello.call(car);
    }

    输出结果:

    Hello, I am a brown Suzuki car
    Hello, I am a grey Honda car
    Hello, I am a red BMW car

    使用工厂模式之后,不再需要重复引入一个个构造函数,只需要引入工厂对象就可以方便的创建各类对象。

    2. 单例模式

    首先我们需要理解什么是单例?

    单:指的是一个。
    例:指的是创建的实例。
    单例:指的是创建的总是同一个实例。也就是使用类创建的实例始终是相同的。

    先看下面的一段代码:

    class Person{
      constructor(){}
    }
    
    let p1 = new Person();
    let p2 = new Person();
    
    console.log(p1===p2) //false

    上面这段代码,定义了一个Person类,通过这个类创建了两个实例,我们可以看到最终这两个实例是不相等的。也就是说,通过同一个类得到的实例不是同一个(这本就是理所应当),但是如果我们想始终得到的是同一个实例,那么这就是单例模式。那么下面就该介绍如何实现单例模式了

    想要实现单例模式,我们需要注意两点:

    1. 需要使用return。使用new的时候如果没有手动设置return,那么会默认返回this。但是,我们这里要使得每次返回的实例相同,也就是需要手动控制创建的对象,因此这里需要使用return
    2.  我们需要每次return的是同一个对象。也就是说实际上在第一次实例的时候,需要把这个实例保存起来。再下一个实例的时候,直接return这个保存的实例。因此,这里需要用到闭包了
    const Person = (function(){
      let instance = null;
      return class{
          constructor(){
            if(!instance){
             //第一次创建实例,那么需要把实例保存
              instance = this;
            }else{
              return instance;
          }
      }
      }
    })()
    let p3 = new Person();
    let p4 = new Person();
    console.log(p3===p4)  //true

    从上面的代码中,我们可以看到在闭包中,使用instance变量来保存创建的实例,每次返回的都是第一次创建的实例。这样的话就实现了无论创建多少次,创建的都是同一个实例,这就是单例模式。

    3. 原型模式

    通俗点讲就是创建一个共享的原型,并通过拷贝这些原型创建新的对象。

    在我看来,其实原型模式就是指定新创建对象的模型,更通俗一点来说就是我想要新创建的对象的原型是我指定的对象。

    最简单的原型模式的实现就是通过Object.create()。Object.create(),会使用现有的对象来提供新创建的对象的__proto__。例如下方代码:

    let person = {
      name:'hello',
      age:24
    }
    
    let anotherPerson = Object.create(person);
    console.log(anotherPerson.__proto__)  //{name: "hello", age: 24}
    
    anotherPerson.name = 'world';  //可以修改属性
    anotherPerson.job = 'teacher';

    另外,如果我们想要自己实现原型模式,而不是使用封装好的Object.create()函数,那么可以使用原型继承来实现

    function F(){}
    
    F.prototype.g = function(){}
     
    //G类继承F类
     
    function G(){
      F.call(this);
    }
     
    //原型继承
    function Fn(){};
    Fn.prototype = F.prototype;
    G.prototype = new Fn();
     
    G.prototype.constructor = G;

    原型模式就是创建一个指定原型的对象。如果我们需要重复创建某个对象,那么就可以使用原型模式来实现。


    结构型模式

    1. 装饰器模式

    装饰器模式:为对象添加新功能,不改变其原有的结构和功能。

    适配器模式是原有的不能用了,要重新封装接口。装饰器模式是原有的还能用,但是需要新增一些东西来完善这个功能。

    比如手机壳,手机本身的功能不受影响,手机壳就是手机的装饰器模式。

    装饰器模式

    class Circle {
        draw() {
            console.log('画一个圆形');
        }
    }
    
    class Decorator {
        constructor(circle) {
            this.circle = circle;
        }
        draw() {
            this.circle.draw();
            this.setRedBorder(circle);
        }
        setRedBorder(circle) {
            console.log('设置红色边框')
        }
    }
    
    // 测试
    let circle = new Circle();
    
    let client = new Decorator(circle);
    client.draw();

    输出结果:

    画一个圆形
    设置红色边框

    如今都2021了,es7也应用广泛,我们在es7中这么写(ES7装饰器):

    1、安装 yarn add babel-plugin-transform-decorators-legacy

    2、新建.babelrc文件,进行下面的配置

    {
        "presets": ["es2015", "latest"],
        "plugins": ["transform-decorators-legacy"]
    }

     3、上代码

    @testDec
    class Demo {
        // ...
    }
    
    function testDec(target) {
        target.isDec = true
    }
    
    console.log(Demo.isDec)
    
    //输出true

    打印出来了true,说明@testDec这个装饰器已经成功了,函数是个装饰器,用@testDec给Demo装饰了一遍。这个target其实就是class Demo,然后给她加一个isDec。

    拆解后就是下面的内容:

    // 装饰器原理
    @decorator
    class A {}
    
    // 等同于
    class A {}
    A = decorator(A) || A;

    装饰器参数的形式 

    @testDec(false)
    
    class Demo {
    }
    
    function testDec(isDec) {
        return function (target) {
            target.isDec = isDec
        }
    }
    
    console.log(Demo.isDec);

    验证是否是一个真正的装饰器模式需要验证以下几点:

    1.将现有对戏那个和装饰器进行分离,两者独立存在
    2.符合开放封闭原则
    

    2. 适配器模式

    适配器模式:旧接口格式和使用者不兼容,中间加一个适配转换接口。

    比如国外的插座跟国内的插座不一样,我们需要买个转换器去兼容。

    适配器模式

     上代码:

    class Adaptee {
        specificRequest() {
            return '德国标准的插头';
        }
    }
    
    class Target {
        constructor() {
            this.adaptee = new Adaptee();
        }
        request() {
            let info = this.adaptee.specificRequest();
            return `${info} -> 转换器 -> 中国标准的插头`
        }
    }
    
    // 测试
    let client = new Target();
    client.request();

     结果:

    德国标准的插头 -> 转换器 -> 中国标准的插头

     场景上可封装旧接口:

    // 自己封装的ajax,使用方式如下:
    ajax({
        url: '/getData',
        type: 'Post',
        dataType: 'json',
        data: {
            id: '123'
        }
    }).done(function(){
    
    })
    // 但因为历史原因,代码中全都是:
    // $.ajax({...})

    这个时候需要一个适配器

    // 做一层适配器
    var $ = {
        ajax: function (options) {
            return ajax(options)
        }
    }

    3. 代理模式

    代理模式:使用者无权访问目标对象,中间加代理,通过代理做授权和控制。

    明星经纪人:比如有个演出,要请明星,要先联系经纪人。

    或者理解为:为一个对象提供一个代用品或者占位符,以便控制对它的访问。例如图片懒加载、中介等。代理模式

    /**
     * pre:代理模式
     * 小明追求A,B是A的好朋友,小明比不知道A什么时候心情好,不好意思直接将花交给A,
     * 于是小明将花交给B,再由B交给A.
     */
    
    // 花的类 
    class Flower{
        constructor(name){
            this.name = name 
        }
    }
    
    // 小明拥有sendFlower的方法
    let Xioaming = {
        sendFlower(target){
            var flower = new Flower("玫瑰花")
            target.receive(flower)
        }
    }
    // B对象中拥有接受花的方法,同时接收到花之后,监听A的心情,并且传入A心情好的时候函数
    let B = {
        receive(flower){
            this.flower =flower
            A.listenMood(()=>{
                A.receive(this.flower)
            })
        }
    
    }
    // A接收到花之后输出花的名字
    let A = {
        receive(flower){
            console.log(`A收到了${flower.name} `)
            // A收到了玫瑰花 
        },
        listenMood(func){
            setTimeout(func,1000)
        }
    }
    Xioaming.sendFlower(B)

    虚拟代理用于图片的预加载

    图片很大,页面加载时会空白,体验不好,所以我们需要个占位符,来短暂替代这个图片,等图片加载好了放上去。

    let myImage = (function(){
        let img = new Image
        document.body.appendChild(img)
        return {
            setSrc:(src)=>{
                img.src = src
            }
        }
    })()
    let imgProxy =(function(){
        let imgProxy = new Image
        // 这个地方我使用了setTimeout来增强演示效果,否则本地加载太快,根本看不到。
        imgProxy.onload=function(){
            setTimeout(()=>{
                myImage.setSrc(this.src)
            },2000)
        }
        
        return (src)=>{
            myImage.setSrc("../../img/bgimg.jpeg")
            imgProxy.src=src
        }
    })()
    
    imgProxy("../../img/background-cover.jpg")

    ES6 Proxy 

    其实在ES6中,已经有了Proxy,这个内置的函数。我们来用一个例子来演示一下他的用法。这是一个明星代理的问题。

    let star={
        name : "张XX",
        age:25,
        phone : "1300001111"
    }
    let agent = new Proxy(star,
        {
            get:function(target,key){
                if(key === "phone"){
                    return  "18839552597"
                }else if(key === "name"){
                    return "张XX"
                }else if(key === "price"){
                    return "12W"
                }else if(key === "customPrice"){
                    return target.customPrice
                }
            },
            set:function(target,key,value){
                if(key === "customPrice"){
                    if(value < "10"){
                        console.log("太低了!!!")
                        return false
                    }else{
                        target[key] = value
                        return true
                    }
                }
            }
        }
    )
    
    console.log(agent.name)
    console.log(agent.price)
    console.log(agent.phone)
    console.log(agent.age)
    agent.customPrice = "12"
    console.log(agent)
    console.log(agent.customPrice)

    设计原则验证

    代理类和目标类分离,隔离开目标类和使用者

    符合开放封闭原则


    行为型模式

    1. 策略模式

    策略模式是一种简单却常用的设计模式,它的应用场景非常广泛。我们先了解下策略模式的概念,再通过代码示例来更清晰的认识它。

    策略模式由两部分构成:一部分是封装不同策略的策略组,另一部分是 Context。通过组合和委托来让 Context 拥有执行策略的能力,从而实现可复用、可扩展和可维护,并且避免大量复制粘贴的工作。

    策略模式

    策略模式的典型应用场景是表单校验中,对于校验规则的封装。接下来我们就通过一个简单的例子具体了解一下:

    /**
     * 登录控制器
     */
    function LoginController() {
      this.strategy = undefined;
      this.setStrategy = function (strategy) {
        this.strategy = strategy;
        this.login = this.strategy.login;
      }
    }
    
    /**
     * 用户名、密码登录策略
     */
    function LocalStragegy() {
      this.login = ({ username, password }) => {
        console.log(username, password);
        // authenticating with username and password... 
      }
    }
    
    /**
     * 手机号、验证码登录策略
     */
    function PhoneStragety() {
      this.login = ({ phone, verifyCode }) => {
        console.log(phone, verifyCode);
        // authenticating with hone and verifyCode... 
      }
    }
    
    /**
     * 第三方社交登录策略
     */
    function SocialStragety() {
      this.login = ({ id, secret }) => {
        console.log(id, secret);
        // authenticating with id and secret... 
      }
    }
    
    const loginController = new LoginController();
    
    // 调用用户名、密码登录接口,使用LocalStrategy
    app.use('/login/local', function (req, res) {
      loginController.setStrategy(new LocalStragegy());
      loginController.login(req.body);
    });
    
    // 调用手机、验证码登录接口,使用PhoneStrategy
    app.use('/login/phone', function (req, res) {
      loginController.setStrategy(new PhoneStragety());
      loginController.login(req.body);
    });
    
    // 调用社交登录接口,使用SocialStrategy
    app.use('/login/social', function (req, res) {
      loginController.setStrategy(new SocialStragety());
      loginController.login(req.body);
    });

    从以上示例可以得出使用策略模式有以下优势:

    1. 方便在运行时切换算法和策略
    2. 代码更简洁,避免使用大量的条件判断
    3. 关注分离,每个strategy类控制自己的算法逻辑,strategy和其使用者之间也相互独立 

    2. 观察者模式

    观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一或一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。典型代表vue/react等。

    使用观察者模式的好处:

    1. 支持简单的广播通信,自动通知所有已经订阅过的对象。
    2. 目标对象与观察者存在的是动态关联,增加了灵活性。
    3. 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

    观察者模式当然给元素绑定事件的addEventListener()也是一种:

    target.addEventListener(type, listener [, options]);

    Target就是被观察对象Subject,listener就是观察者Observer。

    观察者模式中Subject对象一般需要实现以下API:

    • subscribe(): 接收一个观察者observer对象,使其订阅自己
    • unsubscribe(): 接收一个观察者observer对象,使其取消订阅自己
    • fire(): 触发事件,通知到所有观察者

    用JavaScript手动实现观察者模式:

    // 被观察者
    function Subject() {
      this.observers = [];
    }
    
    Subject.prototype = {
      // 订阅
      subscribe: function (observer) {
        this.observers.push(observer);
      },
      // 取消订阅
      unsubscribe: function (observerToRemove) {
        this.observers = this.observers.filter(observer => {
          return observer !== observerToRemove;
        })
      },
      // 事件触发
      fire: function () {
        this.observers.forEach(observer => {
          observer.call();
        });
      }
    }

    验证一下订阅是否成功: 

    const subject = new Subject();
    
    function observer1() {
      console.log('Observer 1 Firing!');
    }
    
    
    function observer2() {
      console.log('Observer 2 Firing!');
    }
    
    subject.subscribe(observer1);
    subject.subscribe(observer2);
    subject.fire();

    输出:

    Observer 1 Firing! 
    Observer 2 Firing!

    验证一下取消订阅是否成功:

    subject.unsubscribe(observer2);
    subject.fire();

    输出:

    Observer 1 Firing!

    3. 迭代器模式

    ES6中的迭代器 Iterator 相信大家都不陌生,迭代器用于遍历容器(集合)并访问容器中的元素,而且无论容器的数据结构是什么(Array、Set、Map等),迭代器的接口都应该是一样的,都需要遵循 迭代器协议

    迭代器模式解决了以下问题:

    1. 提供一致的遍历各种数据结构的方式,而不用了解数据的内部结构
    2. 提供遍历容器(集合)的能力而无需改变容器的接口

    迭代器模式

    一个迭代器通常需要实现以下接口:

    • hasNext():判断迭代是否结束,返回Boolean
    • next():查找并返回下一个元素

    为Javascript的数组实现一个迭代器可以这么写:

    const item = [1, 'red', false, 3.14];
    
    function Iterator(items) {
      this.items = items;
      this.index = 0;
    }
    
    Iterator.prototype = {
      hasNext: function () {
        return this.index < this.items.length;
      },
      next: function () {
        return this.items[this.index++];
      }
    }

     验证一下迭代器:

    const iterator = new Iterator(item);
    
    while(iterator.hasNext()){
      console.log(iterator.next());
    }

    输出:

    1, red, false, 3.14

    ES6提供了更简单的迭代循环语法 for...of,使用该语法的前提是操作对象需要实现 可迭代协议(The iterable protocol),简单说就是该对象有个Key为 Symbol.iterator 的方法,该方法返回一个iterator对象。

    比如我们实现一个 Range 类用于在某个数字区间进行迭代:

    function Range(start, end) {
      return {
        [Symbol.iterator]: function () {
          return {
            next() {
              if (start < end) {
                return { value: start++, done: false };
              }
              return { done: true, value: end };
            }
          }
        }
      }
    }

    验证:

    for (num of Range(1, 5)) {
      console.log(num);
    }

    结果:

    1, 2, 3, 4

    4. 状态模式

    状态模式:一个对象有状态变化,每次状态变化都会触发一个逻辑,不能总是用if...else来控制。

    状态模式

     比如红绿灯:

    // 状态(红灯,绿灯 黄灯)
    class State {
        constructor(color) {
            this.color = color;
        }
        // 设置状态
        handle(context) {
            console.log(`turn to ${this.color} light`);
            context.setState(this)
        }
    }
    
    // 主体
    class Context {
        constructor() {
            this.state = null;
        }
        // 获取状态
        getState() {
            return this.state;
        }
        setState(state) {
            this.state = state;
        }
    }
    
    // 测试
    let context = new Context();
    let green = new State('green');
    let yellow = new State('yellow');
    let red = new State('red');
    
    // 绿灯亮了
    green.handle(context);
    console.log(context.getState())
    
    // 黄灯亮了
    yellow.handle(context);
    console.log(context.getState())
    
    // 红灯亮了
    red.handle(context);
    console.log(context.getState())
    

    设计原则验证

    将状态对象和主体对象分离,状态的变化逻辑单独处理

    符合开放封闭原则

    展开全文
  • 已经转做Android半个月了 设计模式还不怎么了解 从今天起就从最简单的单例模式开始吧! ###说得不对的还请各位来battle (粗体是重点,对象 和 实例 是一个意思) ...显而易见,在移动端开发过程中 有一些实例化起来比

    已经转做Android半个月了 设计模式还不怎么了解 从今天起就从最简单的单例模式开始吧!

    ###说得不对的还请各位来battle (粗体是重点,对象实例 是一个意思)

    单例模式singleton

    顾名思义单例模式就是只有一个实例

    如:需要班长统计学生信息的时候(需求) 不用每次找班长的时候都进行选拔(进行实例化) 在整个大学期间(程序运行期间)只选拔一次(实例化一次)就够了

    确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

    优点

    显而易见,在移动端开发过程中 有一些实例化起来比较消耗内存的类 索性我们就不回收了 减少gc 保持程序在运行时始终有一个该类的对象在内存中 这种方式可以大大提高性能
    当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存

    实现方式

    1.必须防止外部可以调用构造函数进行实例化,私有化构造方法(不对外开放)

    2.对外只提供一个获取本类实例的静态方法(一个全局访问点)

    3.确保一个类只有一个实例对象

    单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。

    结构图UML

    主要角色
    1.可以自行实例化的单例类SingLeton
    2.调用它的访问类Client
    在这里插入图片描述

    实现

    通常有两种实现方式
    1.饿汉式(先填饱肚子再说)

    /**
     * @program: TestSinglet
     * @description: 饿汉式
     * @author: tkx
     * @create: 2021-01-16 14:11
     **/
    public class Singleton {
        private static final Singleton instance = new Singleton();
    
        private Singleton() {
    
        }
    
        public static Singleton getInstance() {
            return instance;
        }
    }
    

    在类创建时同时创建好一个静态对象来提供使用 所以是线程安全的
    2.懒汉式(你啥时候喊我,我啥时候动弹)

    /**
     * @program: TestSinglet
     * @description: 懒汉式
     * @author: tkx
     * @create: 2021-01-16 14:21
     **/
    public class Singleton {
        //保证 instance 在所有线程中同步
        private static volatile Singleton instance = null;
    
        private Singleton() {
        }    //private 避免类在外部被实例化
    
        public static synchronized Singleton getInstance() {
            //getInstance 方法前加同步
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    如果编写的是多线程程序,则不要删除上例代码中的关键字 volatile 和 synchronized,否则将存在线程非安全的问题。如果不删除这两个关键字就能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。

    volatile:

    被volatile关键字修饰的变量,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

    其他实现方式

    • 双重检查锁模式(在需要创建对象的时候再进行同步锁操作 (懒汉中的懒汉))
    • 枚举模式(枚举实例在任何情况都是一个单例且是线程安全的)
    • 静态内部类实现的单例(单例模式推荐的实现方式 详情请查阅:JVM类加载机制(类加载过程和类加载器))

    最后 我们应用一下单例模式。管理我们的Activity,下面这个可以作为一个工具类

    /**
     * @program: TestSinglet
     * @description: manager
     * @author: tkx
     * @create: 2021-01-16 14:30
     **/
    public class ActivityManager {
    
    	private static volatile ActivityManager instance;
    	private Stack<Activity> mActivityStack = new Stack<Activity>();
    	
    	private ActivityManager(){
    		
    	}
    	
    	public static ActivityManager getInstance(){
    		if (instance == null) {
    		synchronized (ActivityManager.class) {
    			if (instance == null) {
    				instance = new ActivityManager();
    			}
    		}
    		return instance;
    	}
    	
    	public void addActicity(Activity act){
    		mActivityStack.push(act);
    	}
    	
    	public void removeActivity(Activity act){
    		mActivityStack.remove(act);
    	}
    	
    	public void killMyProcess(){
    		int nCount = mActivityStack.size();
    		for (int i = nCount - 1; i >= 0; i--) {
            	Activity activity = mActivityStack.get(i);
            	activity.finish();
            }
    		
    		mActivityStack.clear();
    		android.os.Process.killProcess(android.os.Process.myPid());
    	}
    }
    
    展开全文
  • 通常,一个设计模式描述了一个被证明可行的方案。...部分常见的Java设计模式有以下10种:1、抽象工厂模式(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体...

    通常,一个设计模式描述了一个被证明可行的方案。这些方案非常广泛,是具有完整定义的最常用的行式。普通模式有4个基本要素:模式名称(pattern name)、问题(problem)、解决方案(solution)、效果(consequences)。

    部分常见的Java设计模式有以下10种:

    1、抽象工厂模式(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

    2、适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口或类不兼容而不能一起工作的类可以一起工作。

    3、桥梁模式(Bridge):将抽象部分与它的实现部分分离,达内培训使它们都可以独立地变化。

    4、建造模式(Builder):将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。

    5、责任链模式(Chain of Responsibility):为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。

    6、命令模式(Command):将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。

    7、合成模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。

    8、装饰模式(Decorator):动态地给一个对象添加一些额外的职责。就扩展功能而言,它能生成子类的方式更为灵活。

    9、门面模式(Facade):为子系统中的一组接口提供一个一致的界面,门面模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

    10、工厂方法(Factory Method):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method 使一个类的实例化延迟到其子类。

    展开全文
  • Java常见设计模式面试题及答案

    千次阅读 2021-03-29 06:12:47
    设计模式是什么?你是否在代码中使用过?2. JDK 中常用的设计模式有哪些?3.单例模式是什么?请用 Java 写出线程安全的单例模式4.在 Java 中,什么叫观察者模式(observer design pattern)?5.使用工厂模式有哪些...
  • 想知道如何设计大型企业级的系统吗?在开始主要的代码开发之前,我们必须选择一种合适的体系架构,它将为我们提供所需的功能和质量属性。因此,在将它们应用到我们的设计之前,应该先了解不同的体系结构...
  • 23种设计模式常见面试题) 1.什么是设计模式? 答: 设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的...
  • Java开发项目常见BUG

    千次阅读 2021-01-14 17:34:18
    而在这个模式下,我们使用分组查询时,出现在select字段后面的只能是group by后面的分组字段,或使用聚合函数包裹着的字段。 bug Error querying database. Cause: java.sql.SQLException: Data truncated for ...
  • 1.java基础 2.多线程 3.sql优化,mysql数据库优化 4.redis集群,redis键的使用,redis持久化;redis雪崩,击穿,穿透概念以及解决方案 ...12.分布式,RPC,分布式事务(CAP概念以及常见的中间件或者组件使用的
  • C语言常见开发工具的安装和配置

    千次阅读 2021-08-10 01:25:00
    工欲善其事,必先利其器,进行C语言编程之前,需要有一个称手的开发工具。本文就面向新接触编程的朋友,介绍了几种常见C语言开发工具,希望可以帮助到大家。
  • 软件开发经典流程图 在这里插入图片描述 一、瀑布模型 模型图 定义:瀑布模型(Waterfall Model)是将软件生存周期的各项活动规定为按固定顺序而连接的若干阶段工作,形如瀑布流水,最终得到软件产品。 地位:这是一...
  • 4 网站架构模式

    千次阅读 2021-05-07 16:50:40
    分层是企业应用系统中最常见的一种架构模式,将系统在横向维度上切分成几个部 分,每个部分负责一部分相对比较单一的职责,然后通过上层对下层的依赖和调用组成 一个完整的系统。 分层结构在计算机世界中无处不在,...
  • Web开发常见问题汇总

    万次阅读 2021-11-09 12:28:41
    singleton(单例模式):全局有且仅有一个实例 prototype(原型模式):每次获取bean的时候会有一个新的实例 request:针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效 session:...
  • 编辑 | 宋慧 供稿 | Mendix 头图 | 付费下载于视觉中国 如今,低代码对很多人来讲可能已经不再是一个...基于与数百家企业客户的合作经验,Mendix认为低代码平台非常适合开发以下四类常见用例: · 创新应用 · 客户交.
  • Go常见并发模式

    千次阅读 2021-01-14 10:36:28
    多并发控制神器:Context二、常见并发模式Go语言实现1.for select 循环模式无限循环(监控狗)有限循环(for range select )2.select timeout 模式3.流水线模式(Pipeline)4.扇出和扇入模式5.未来模式(Futures) 一、...
  • 设计模式是对软件设计开发过程中反复出现的某类问题的通用解决方案。设计模式更多的是指导思想和方法论,而不是现成的代码,当然每种设计模式都有每种语言中的具体实现方式。学习设计模式更多的是理解各种模式的内在...
  • python后端开发面试常见问题 (持续更新)

    万次阅读 多人点赞 2021-03-14 15:21:18
    python后端开发面试常见问题   大家好,我叫亓官劼(qí guān jié ),在GitHub和CSDN中记录学习的点滴历程,时光荏苒,未来可期,一起加油~~ 本篇文章将在GitHub和CSDN上持续更新,主要是Python后端开发的...
  • C++常用的11种设计模式

    千次阅读 2021-03-08 01:42:15
    这里写目录标题工厂模式单例模式(懒汉式、饿汉式)适配器模式外观模式代理模式包装模式(装饰器模式)桥接模式模板方法模式策略模式观察者模式责任链模式 工厂模式 定义:将工厂变成一个抽象类,在里面定义一个纯虚...
  • 赚外快—常见编程接单的网站集合(20余个)

    万次阅读 多人点赞 2021-02-07 15:07:48
    海量资源池包括:网站设计、网站开发、手机应用开发、移动应用开发、安卓应用开发、苹果应用开发、微信应用开发、Java技术、C#技术、Web前端开发、IT人力外包、IT人力外派、IT人力短期招聘、技术合伙人、通用软件...
  • java开发常见的问题及解决办法1、 索引越界异常...这个是java开发中最常见的问题之一,碰到空指针异常,首先需要定位是哪一句出现了异常,然后再判断是那个对象出现了异常,常用debug模式进行调试3、类型转换异常,j...
  • •CS(Client/Server,客服机/服务器)结构,C/S结构在技术上很成熟,它的主要特点是交互性强、具有安全的存取模式、网络通信量低、响应速度快、利于处理大量数据。因为客户端要负责绝大多数的业务逻辑和UI展示,又...
  • 我们大多数人的使用和开发区块链的目标来说,并不是要真的自己重新创建一套区块链,只是希望基于现有的区块链底层或技术框架去开发自己的应用。因此,对于类似加密算法、 P2P技术、共识算法等我们只需要有个基本了解...
  • Java开发常见专业术语

    千次阅读 2021-05-10 10:00:23
    例如,许多人在设计网站时使用IDE(如HomeSite、DreamWeaver等),因为很多项任务会自动生成。 29. .exe EXE File英文全名executable file ,译作可执行文件,可移植可执行 (PE) 文件格式的文件,它可以加载到内存中...
  • 常见开发框架介绍

    千次阅读 2021-08-18 09:43:00
    常见开发框架  一、Spring  Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。  A.控制反转(IOC)是什么呢?  IOC:控制反转也叫依赖注入。利用了工厂模式将对象交给容器管理,你只需要在spring...
  • PM,RD,FE,UE,UI,QA等开发常见部门简称

    千次阅读 2021-01-07 19:44:30
    计算机技术和互联网的发展,使技术创新形态正在发生转变,以用户为中心、以人为本越来越得到重视,用户体验也因此被称做创新2.0模式的精髓。 另外还有有个组合叫法:UED(产品交互设计师,用户体验师)。 UI 用户...
  • 设计模式其实并不神秘,今天carson将带你了解一切关于设计模式的知识。 Carson带你学设计模式系列文章 这是一份全面 & 详细的设计模式学习指南 Carson带你学设计模式:单例模式(Singleton) Carson带你学设计...
  • 设计模式 - Prototype 原型模式

    千次阅读 多人点赞 2021-05-17 23:35:46
    前言 在设计模式的系列文章中,我们前面已经写了工厂模式、单列模式、建造者模式,在针对创建型模式中,今天想跟大家分享的是原型模式 其实原型模式在我们的代码中是很常见的,但是又容易被我们所忽视的一种模式,...
  • 阿里P6+面试:介绍下观察者模式

    千次阅读 多人点赞 2021-06-03 00:03:19
    今天阿丙就分享一下实际开发中比较常见的这种模式 大纲 定义 什么是观察者模式?他的目的是什么? 当一个对象的状态发生改变时,已经登记的其他对象能够观察到这一改变从而作出自己相对应的改变。通过这种方式来...
  • nvue是属于 weex 编写范畴,作用是增强uniapp对原生渲染支持nvue的组件和API写法与vue页面一致,其内置组件还比vue...nvue开发与vue开发常见区别基于原生引擎的渲染,虽然还是前端技术栈,但和web开发肯定是有区别

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 480,113
精华内容 192,045
关键字:

常见的开发动态网站的模式