精华内容
下载资源
问答
  • 2016-03-12 19:15:42

     

    一:概要模式

    1:简介

    概要设计模式更接近简单的MR应用,因为基于键将数据分组是MR范型的核心功能,所有的键将被分组汇入reducer中

    本章涉及的概要模式有数值概要(numerical summarization),倒排索引(inverted index),计数器计数(counting with counter)

     

    2:概要设计模式包含

     

          2.1:关于Combiner和paritioner

           combiner:reducer之前调用reducer函数,对数据进行聚合,极大的减少通过网络传输到reducer端的key/value数量,适用的条件是你可以任意的改变值的顺序,并且可以随意的将计算进行分组,同时需要注意的是一个combiner函数只对一个map函数有作用

             partitioner:许多概要模式通过定制partitioner函数实现更优的将键值对分发到n个reducer中,着这样的需求场景会比较少,但如果任务的执行时间要求很高,数据量非常大,且存在数据倾斜的情况,定制partitioner将是非常有效的解决方案

            源码分析请点击            编程实例请点击

    2.2:数值概要模式

            2.2.1:数值概要模式:计算数据聚合统计的一般性模式
            2.2.2:数值概要应用的场景需要满足以下亮点:
                      1:要处理的数据是数值数据或者计数
                       2:数据可以按照某些特定的字段分组
            2.2.3:适用场景:

                       1:单词计数 (可以使用combiner)
                       2:最大值/最小值/计数  (可以使用combiner)
                       3:平均值  (可以使用combiner,但必须做相应的处理,即迂回算法,举例如下)
                             给定用户的评论列表,按天计算每小时的评论长度
                             Map:context.write(1,tuple(1,1小时的平均长度))
                             reducer:
                                       处理:sum += tulpe.gethour * tuple.getavrg
                                                  count += tuple.gethour
                                       输出:
                                            key=1
                                           value=sum/count
                       4:中位数/标准差

    2.3:倒排索引概要

     

              适用场景:通常用在需要快速搜索查询响应的场景,可以对一个查询结果进行预处理并存储在一个数据库中

             倒排索引实战1         倒排索引实战2

    2.4:计数器计数 

            已知应用

            统计记录数:简单的对指定时间段的记录数进行统计是很常见的,统计小数量级的唯一实例计数
            汇总:用来执行对数据的某些字段进行汇总

                    

    二:过滤模式

    1:简介

           过滤模式也可以被认为是一种搜索形式,如果你对找出所有具备特定信息的记录感兴趣,就可以过滤掉不匹配搜索条件的其他记录,与大多数基础模式类似,过滤作为一种抽象模式为其他模式服务,过滤简单的对某一条记录进行评估,并基于某个条件作出判断,以确定当前这条记录是保留还是丢弃

     2:适用场景

           2.1: 过滤, 使用过滤的唯一必要条件是数据可以被解析成记录,并可以通过非常特定的准则来确定它们是否需要保留,不需要reducer函数
                    近距离观察数据:准备一个特定的子集,子集中的记录有某些共同属性或者具备某些有趣的特性,需要进一步深入的分析。 
                    跟踪某个事件的线索:从一个较大数据集中抽取一个连续事件作为线索来做案例研究。
                    分布式grep:通过一个正则表达式匹配每一行,输出满足条件的行
                    数据清理:数据有时是畸形的,不完整的 或者是格式错误的,过滤可以用于验证每一条数据是否满足记录,将不满足的数据删除
                   简单随机抽样:可以使用随机返回True or False的评估函数做过滤,可以通过调小true返回的概率实现对结果集合大小的控制
                   移除低分值数据:将不满足某个特定阀值的记录过滤出去    
           2.2:布隆过滤, 对每一条记录,抽取其中一个特征,如果抽取的特性是布隆过滤中所表示的值的集合成员,则保留记录  
                   移除大多数不受监视的值:最直接的使用案例是清楚不感兴趣的值
                   对成本很高的集合成员资格检查做数据的预先过滤:
           2.3:Top10,不管输入数据的大小是多少,你都可以精确的知道输出的结果的记录数
                   异类分析:
                   选取感兴趣的数据:
                   引人注目的指标面板:
           2.4:去重,过滤掉数据集中的相似数据,找出唯一的集合
                   数据去重:  代码举例
                   抽取重复值:
                   规避内连接的数据膨胀:
     

    三:数据组织模式

    1:分层结构模式

        分层模式是从数据中创造出不同于原结构的新纪录
        适用场景:数据源被外部链接,数据是结构化的并且是基于行的
         <MultipleInputs类:用于指定多个Mapper任务进行不同格式文件的输入>

    2:分区和分箱模式

         分区:将记录进行分类(即分片,分区或者分箱),但他并不关心记录的顺序,目地是将数据集中相似的记录分成不同的,更小的数据集,在该模式下数据是通过自定义Map的分区器进行分区的。
         分箱:是在不考虑记录顺序的情况下对记录进行分类,目的是将数据集中每条记录归档到一个或者多个举例
         两者的不同之处在于分箱是在Map阶段对数据进行拆分,其好处是减少reduce的工作量,通常使资源分布更有效,缺点是每个mapper将为每个可能输出的箱子创建文件,对后续的分析十分不利

    3:全排序和混排模式

         全排序:关注的是数据从记录到记录的顺序,目的是能够按照指定的键进行并行排序。适用的范围是排序的键必须具有可比性只有这样数据才能被排序
         混排序:关注记录在数据集中的顺序,目的是将一个给定的记录完全随机化

    4:数据生成模式

    四:连接模式

    SQL连接模式包括内连接和外连接

    eg:A表  B表
     内连接:只连接两个表中都用的外键连接(eg 以ID作为连接键,只连接有相同ID)
     外连接:
    1:做外连接
          以用户ID为外键的A+B做外连接   以A表为基准,A表数据全部显示,B表中不在A表中的ID显示为null
    2:右外连接
         和做外连接相反
    3:全外连接
          左外连接和右外连接的合并,有相同ID 的显示,没有相同ID的显示为NULL

         反连接:全外连接减去内连接的结果

    1:reduce端连接:

     相当其他连接模式来讲用时最长,但是也是实现简单并且支持所有不同类型的操作
     适用场景:
    1:多个大数据需要按一个外键做链接操作,如果除了一个数据集以外,其他所有的数据集都可以放入内存,可以尝试使用复制连接
            2:你需要灵活的执行任意类型的连接操作
         等效的SQL:Select user.id,user.location,comment.uprotes
                              from user
                              [inner | left | right] join comments
                              on user.id=comments.userid
            优化方案:可以使用布隆过滤器执行reduce端的连接

    2:复制连接:

         是一种特殊类型的连接操作,是在一个打的数据集和许多小的数据集之间通过MAP端执行的连接的操作,该模式完全消除了混排数据到reduce的需求
          适用场景:
               1:要执行的连接类型是由内连接或者左外连接,且大的输入数据集在连接操作符的“左边”时
               2:除一个大的数据集外,所有的数据集都可以存入每个Map任务的内存中
               性能分析:因为不需要reduce,因此在所有连接模式是最快的一种,代价是数据量,数据要能完全的储存在JVM中,这极大的受限于你愿意为每个Map和reduce分配多少内存

     3:组合连接:

          是一种非常特殊的连接操作,他可以在map端对许多非常大的格式化输入做连接,需要预先组织好的或者是使用特定的方式预处理过的,即在使用这个类型的连接操作之前,必须按照外键对数据集进行排序个分区,并以一种非常特殊的方式读入数据集

          Hadoop通过CompositeInputFormat来支持组合连接方式
          仅适用于内连接和全外连,每一个mapper的输入都需要按照指定的方式做分区和排序,对于每一个输入数据集都要分成相同数目的分区,此外,对应于某个特定的外链所做的所有记录必须处于同一分区中
           通常情况下这发生在几个作业的输出有相同数量的reducer和相同的外键,并且输出文件是不可拆分的即不大于一个hdfs文件快的大小或是gzip压缩的
          适用场景:
              1:需要执行的是内连接或者全外连接
              2:所有的数据集都足够大
              3:所有的数据集都可以用相同的外键当mapper的输入键读取
              4:所有的数据集有相同的数据的分区
              5:数据集不会经常改变
              6:每一个分区都是按照外键排序的,并且所有的外键都出现在关联分区的每个数据集中

    4:笛卡尔积:

    是一种有效的将多个输入源的灭一个记录跟所有其他记录配对的方式
    适用场景:
          1:需要分析各个记录的所有配对之间的关系
          2:没有其他方法可以解决这个问题
          3:对执行时间没有限制
                等效的SQL:SELECT * FROM t1,t2
                等效的PIG:CROSS a,b;

    五:元模式

    关于模式的模式

    1:作业链

    针对MapReduce处理小的文件时,优化的办法是可以在作业中始终执行CombineFileInputFormat加载间歇性的输出,
    在进入mapper处理之前,CombineFileInputFormat会将小的块组合在一起形成较大的输入split

    当执行做个作业的作业链时,可以使用job.submit方法代替job.waitForCompletion()来并行的启动多个作业,
    调用submit方法后会立即返回至当前线程,而作业在后台运行,该方法允许一次执行多个任务, 
    job.isComplete()是检查一个作业是否完成的非阻塞方法,该方法可以通过不断轮询的方式判断所有作业是否完成


    如果检测到一个依赖的作业失败了,此时你应该退出整个作业链,而不是试图让他继续


    示例:
    (1)基本作业
    (2)并行作业链
    (3)关于Shelll脚本
    (4)关于JobControl

     

    2:链折叠

    链折叠是应用于MapReduce作业链的一种优化方法,基本上他是一个经验法则,即每一条记录都可以提交至多个mapper或者一个reducer,然后再交给一个mapper
    这种合并处理能够减少很多读取文件和传输数据的时间,
    作业链的这种结构使得这种方法是可行的,因为map阶段是完全无法共享的,因此map并不关心数据的组织形式和或者数据有没有分组,在构建大的作业链时,通过
    将作业链折叠,使得map阶段合并起来带来很大的性能提升
    链折叠的主要优点是减少mapreduce管道中移动的数据量


    作业链中有许多模式,可以通过下面介绍的这些方法来查找和确认哪些可以折叠
    (1)看看作业链的map阶段,如果多个map阶段是相邻的,将他们合并到一个阶段
    (2)如果作业链是以map阶段结束,将这个阶段移动到前一个reducer里边,他除去了写临时数据的IO操作,然后在reduce中执行只有map的作业,这同一也能减少任务启动的开销
    (3)注意,作业链的第一个map阶段无法 从下一个优化中获益,尽可能的在减少数据量(如过滤)的操作和增加数据量(如丰富)的操作之间拆分每个map阶段(合并或者其他)

    注意:(1)合并阶段需要大量的内存,例如将5个复制连接合并在一起可能不是一个好的选择,因为他将可能超过任务可用的总内存,在这些情况下,最好将这些操作分开
    (2)不管一个作业是不是作业链,都要尽早尽可能的去过滤掉更多的数据,mr作业开销最大的部分通常都是管道推送数据:加载数据,混排/排序阶段,以及存储数据



    实现折叠链有两种主要方法:
    (1)手动裁剪然后将代码粘贴在一起
    (2)使用特殊类ChainMapper和ChainReducer(特殊类介绍参考: http://www.iteye.com/topic/1134144

     

    3:作业归并

    和作业链折叠一样,作业归并是另一种减少MR管道IO管道的优化方法,通过作业归并可以使得加载同一份数据的两个不相关作业共享MR管道,作业归并最主要的优点是数据只需要加载和解析一次。
    先决条件是:两个作业必须有相同的中间键和输出格式,因为他们将共享管道,因而需要使用相同的数据类型,如果这的确是一个问题的话,可以使用序列化或者多态,但会增加复制度

    作业归并步骤如下:
    (1)将两个mapper代码放在一起
    (2)在mapper中生成键和值时,需要用标签加以标记,以区别map源
    (3)在reducer中,在解析出标签后使用if语句切换到相应的reducer代码中去执行
    (4)使用multipleOutputs将作业的输出分来
     

    六:输入输出模式

    自定义输入与输出

     

    在hadoop自定义输入和输出

    Hadoop允许用户修改从磁盘加载数据的方式,修改方式有两种:

             1:配置如何根据HDFS的块生成连续的输入分块,配置记录在map阶段如何实现。

                       为此将要用到的两个类即,RecordReader和InputFormat

             2:hadoop也允许用户通过类似的方式修改数据的存储形式

                    通过OutputFormat和RecordWriter实现

     

    1:生成数据

    这个模式下是只有Map的

     (1)InputFormat凭空创建split

     (2)RecordReader读入虚的split并根据他生成随机记录

     (3)某些情况下,能够在split中赋予一些信息,告诉recordreader生成什么

     (4)通常情况下,IdentityMap仅仅将读入的数据原样输出

    2:外部源输出

             外部源输出详细描述:在作业提交之前,OutputFormat将验证作业配置中指定的输出规范。RecordReader负责将所有的键值对写入外部源

             性能分析:必须小心数据接收者能否处理并行连接。有1000个任务将数据写入到单个SQL数据库中,者=这工作起来并不好,为避免这种情况你可能不得不让每个reducer多处理一些数据以减少写入到数据接收者的并行度,如果数据接收者支持并行写入,那么这未必是个问题。

    3:外部源输入

     在MapReduce中数据是以并行的方式加载而不是以串行的方式,为了能够大规模的读取数据,源需要有定义良好的边界

     

     MR实现该模式的瓶颈将是数据源或网络,数据源对于多连接可能不具很好的扩展性,同时给定的数据源可能与MR集群的网络不在同一个网络环境下

    4:分区裁剪

    分区裁剪模式将通过配置决定框架如何选取输入split以及如何基于文件名过滤加载到MR作业的文件

    描述:分区裁剪模式是在InputFormat中实现的,其中getsplit方法是我们需要特别注意的,因为他确定了要创建的输入split,进而确定map任务的个数,  RecordReader的实现依赖于数据是如何存储的


    搜索与推荐Wiki

    扫一扫 关注微信公众号!号主 专注于搜索和推荐系统,尝试使用算法去更好的服务于用户,包括但不局限于机器学习,深度学习,强化学习,自然语言理解,知识图谱,还不定时分享技术,资料,思考等文章!


                                 【技术服务】,详情点击查看:https://mp.weixin.qq.com/s/PtX9ukKRBmazAWARprGIAg 


    外包服务

    更多相关内容
  • 23 种设计模式详解(全23种)

    万次阅读 多人点赞 2019-06-09 00:21:59
    设计模式的分类 ...行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。 A、创建...

    设计模式的分类

    总体来说设计模式分为三大类:

    创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

    结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

    行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

     

    A、创建模式(5种)

    工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

    1 工厂模式

    1.1 简单工厂模式

    定义:定义了一个创建对象的类,由这个类来封装实例化对象的行为。

    举例:(我们举一个pizza工厂的例子)

    pizza工厂一共生产三种类型的pizza:chesse,pepper,greak。通过工厂类(SimplePizzaFactory)实例化这三种类型的对象。类图如下:

     

    工厂类的代码:

    public class SimplePizzaFactory {
           public Pizza CreatePizza(String ordertype) {
                  Pizza pizza = null;
                  if (ordertype.equals("cheese")) {
                         pizza = new CheesePizza();
                  } else if (ordertype.equals("greek")) {
                         pizza = new GreekPizza();
                  } else if (ordertype.equals("pepper")) {
                         pizza = new PepperPizza();
                  }
                  return pizza;
           }
    }
    

    简单工厂存在的问题与解决方法: 简单工厂模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了开闭原则,所以,从设计角度考虑,有一定的问题,如何解决?我们可以定义一个创建对象的抽象方法并创建多个不同的工厂类实现该抽象方法,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。这种方法也就是我们接下来要说的工厂方法模式。

    1.2 工厂方法模式

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

    举例:(我们依然举pizza工厂的例子,不过这个例子中,pizza产地有两个:伦敦和纽约)。添加了一个新的产地,如果用简单工厂模式的的话,我们要去修改工厂代码,并且会增加一堆的if else语句。而工厂方法模式克服了简单工厂要修改代码的缺点,它会直接创建两个工厂,纽约工厂和伦敦工厂。类图如下:

     

    OrderPizza中有个抽象的方法:

    abstract Pizza createPizza();

    两个工厂类继承OrderPizza并实现抽象方法:

    public class LDOrderPizza extends OrderPizza {
           Pizza createPizza(String ordertype) {
                  Pizza pizza = null;
                  if (ordertype.equals("cheese")) {
                         pizza = new LDCheesePizza();
                  } else if (ordertype.equals("pepper")) {
                         pizza = new LDPepperPizza();
                  }
                  return pizza;
           }
    }
    public class NYOrderPizza extends OrderPizza {
    
    	Pizza createPizza(String ordertype) {
    		Pizza pizza = null;
    
    		if (ordertype.equals("cheese")) {
    			pizza = new NYCheesePizza();
    		} else if (ordertype.equals("pepper")) {
    			pizza = new NYPepperPizza();
    		}
    		return pizza;
    
    	}
    
    }

    、通过不同的工厂会得到不同的实例化的对象,PizzaStroe的代码如下:

    public class PizzaStroe {
           public static void main(String[] args) {
                  OrderPizza mOrderPizza;
                  mOrderPizza = new NYOrderPizza();
           }
    }

    解决了简单工厂模式的问题:增加一个新的pizza产地(北京),只要增加一个BJOrderPizza类:

    public class BJOrderPizza extends OrderPizza {
           Pizza createPizza(String ordertype) {
                  Pizza pizza = null;
                  if (ordertype.equals("cheese")) {
                         pizza = new LDCheesePizza();
                  } else if (ordertype.equals("pepper")) {
                         pizza = new LDPepperPizza();
                  }
                  return pizza;
           }
    }

    其实这个模式的好处就是,如果你现在想增加一个功能,只需做一个实现类就OK了,无需去改动现成的代码。这样做,拓展性较好!

    工厂方法存在的问题与解决方法:客户端需要创建类的具体的实例。简单来说就是用户要订纽约工厂的披萨,他必须去纽约工厂,想订伦敦工厂的披萨,必须去伦敦工厂。 当伦敦工厂和纽约工厂发生变化了,用户也要跟着变化,这无疑就增加了用户的操作复杂性。为了解决这一问题,我们可以把工厂类抽象为接口,用户只需要去找默认的工厂提出自己的需求(传入参数),便能得到自己想要产品,而不用根据产品去寻找不同的工厂,方便用户操作。这也就是我们接下来要说的抽象工厂模式。

    1.3 抽象工厂模式

     定义:定义了一个接口用于创建相关或有依赖关系的对象族,而无需明确指定具体类。

    举例:(我们依然举pizza工厂的例子,pizza工厂有两个:纽约工厂和伦敦工厂)。类图如下:

    工厂的接口:

    public interface AbsFactory {
           Pizza CreatePizza(String ordertype) ;
    }

    工厂的实现:

    public class LDFactory implements AbsFactory {
           @Override
           public Pizza CreatePizza(String ordertype) {
                  Pizza pizza = null;
                  if ("cheese".equals(ordertype)) {
                         pizza = new LDCheesePizza();
                  } else if ("pepper".equals(ordertype)) {
                         pizza = new LDPepperPizza();
                  }
                  return pizza;
           }
    }

    PizzaStroe的代码如下:

    public class PizzaStroe {
           public static void main(String[] args) {
                  OrderPizza mOrderPizza;
                  mOrderPizza = new OrderPizza("London");
           }
    }

    解决了工厂方法模式的问题:在抽象工厂中PizzaStroe中只需要传入参数就可以实例化对象。

    1.4 工厂模式适用的场合

    大量的产品需要创建,并且这些产品具有共同的接口 。

    1.5  三种工厂模式的使用选择

    简单工厂 : 用来生产同一等级结构中的任意产品。(不支持拓展增加产品)

    工厂方法 :用来生产同一等级结构中的固定产品。(支持拓展增加产品)   

    抽象工厂 :用来生产不同产品族的全部产品。(支持拓展增加产品;支持增加产品族)  

    简单工厂的适用场合:只有伦敦工厂(只有这一个等级),并且这个工厂只生产三种类型的pizza:chesse,pepper,greak(固定产品)。

    工厂方法的适用场合:现在不光有伦敦工厂,还增设了纽约工厂(仍然是同一等级结构,但是支持了产品的拓展),这两个工厂依然只生产三种类型的pizza:chesse,pepper,greak(固定产品)。

    抽象工厂的适用场合:不光增设了纽约工厂(仍然是同一等级结构,但是支持了产品的拓展),这两个工厂还增加了一种新的类型的pizza:chinese pizza(增加产品族)。

    所以说抽象工厂就像工厂,而工厂方法则像是工厂的一种产品生产线。因此,我们可以用抽象工厂模式创建工厂,而用工厂方法模式创建生产线。比如,我们可以使用抽象工厂模式创建伦敦工厂和纽约工厂,使用工厂方法实现cheese pizza和greak pizza的生产。类图如下:     

    总结一下三种模式:

    简单工厂模式就是建立一个实例化对象的类,在该类中对多个对象实例化。工厂方法模式是定义了一个创建对象的抽象方法,由子类决定要实例化的类。这样做的好处是再有新的类型的对象需要实例化只要增加子类即可。抽象工厂模式定义了一个接口用于创建对象族,而无需明确指定具体类。抽象工厂也是把对象的实例化交给了子类,即支持拓展。同时提供给客户端接口,避免了用户直接操作子类工厂。

     

    2 单例模式

    定义:确保一个类最多只有一个实例,并提供一个全局访问点

    单例模式可以分为两种:预加载和懒加载

    2.1 预加载

    顾名思义,就是预先加载。再进一步解释就是还没有使用该单例对象,但是,该单例对象就已经被加载到内存了。

    public class PreloadSingleton {
           
           public static PreloadSingleton instance = new PreloadSingleton();
       
           //其他的类无法实例化单例类的对象
           private PreloadSingleton() {
           };
           
           public static PreloadSingleton getInstance() {
                  return instance;
           }
    }

    很明显,没有使用该单例对象,该对象就被加载到了内存,会造成内存的浪费。

    2.2 懒加载

    为了避免内存的浪费,我们可以采用懒加载,即用到该单例对象的时候再创建。

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

    2.3 单例模式和线程安全

    (1)预加载只有一条语句return instance,这显然可以保证线程安全。但是,我们知道预加载会造成内存的浪费。

    (2)懒加载不浪费内存,但是无法保证线程的安全。首先,if判断以及其内存执行代码是非原子性的。其次,new Singleton()无法保证执行的顺序性。

    不满足原子性或者顺序性,线程肯定是不安全的,这是基本的常识,不再赘述。我主要讲一下为什么new Singleton()无法保证顺序性。我们知道创建一个对象分三步:

    memory=allocate();//1:初始化内存空间
    
    ctorInstance(memory);//2:初始化对象
    
    instance=memory();//3:设置instance指向刚分配的内存地址

    jvm为了提高程序执行性能,会对没有依赖关系的代码进行重排序,上面2和3行代码可能被重新排序。我们用两个线程来说明线程是不安全的。线程A和线程B都创建对象。其中,A2和A3的重排序,将导致线程B在B1处判断出instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访问到一个还未初始化的对象(线程不安全)。

    2.4 保证懒加载的线程安全

    我们首先想到的就是使用synchronized关键字。synchronized加载getInstace()函数上确实保证了线程的安全。但是,如果要经常的调用getInstance()方法,不管有没有初始化实例,都会唤醒和阻塞线程。为了避免线程的上下文切换消耗大量时间,如果对象已经实例化了,我们没有必要再使用synchronized加锁,直接返回对象。

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

    我们把sychronized加在if(instance==null)判断语句里面,保证instance未实例化的时候才加锁

    public class Singleton {
           private static Singleton instance = null;
           private Singleton() {
           };
           public static synchronized Singleton getInstance() {
                  if (instance == null) {
                         synchronized (Singleton.class) {
                               if (instance == null) {
                                      instance = new Singleton();
                               }
                         }
                  }
                  return instance;
           }
    }

    我们经过2.3的讨论知道new一个对象的代码是无法保证顺序性的,因此,我们需要使用另一个关键字volatile保证对象实例化过程的顺序性。

    public class Singleton {
           private static volatile Singleton instance = null;
           private Singleton() {
           };
           public static synchronized Singleton getInstance() {
                  if (instance == null) {
                         synchronized (instance) {
                               if (instance == null) {
                                      instance = new Singleton();
                               }
                         }
                  }
                  return instance;
           }
    }

    到此,我们就保证了懒加载的线程安全。

     

    3 生成器模式

    定义:封装一个复杂对象构造过程,并允许按步骤构造。

    定义解释: 我们可以将生成器模式理解为,假设我们有一个对象需要建立,这个对象是由多个组件(Component)组合而成,每个组件的建立都比较复杂,但运用组件来建立所需的对象非常简单,所以我们就可以将构建复杂组件的步骤与运用组件构建对象分离,使用builder模式可以建立。

    3.1 模式的结构和代码示例

    生成器模式结构中包括四种角色:

    (1)产品(Product):具体生产器要构造的复杂对象;

    (2)抽象生成器(Bulider):抽象生成器是一个接口,该接口除了为创建一个Product对象的各个组件定义了若干个方法之外,还要定义返回Product对象的方法(定义构造步骤);

    (3)具体生产器(ConcreteBuilder):实现Builder接口的类,具体生成器将实现Builder接口所定义的方法(生产各个组件);

    (4)指挥者(Director):指挥者是一个类,该类需要含有Builder接口声明的变量。指挥者的职责是负责向用户提供具体生成器,即指挥者将请求具体生成器类来构造用户所需要的Product对象,如果所请求的具体生成器成功地构造出Product对象,指挥者就可以让该具体生产器返回所构造的Product对象。(按照步骤组装部件,并返回Product

     

    举例(我们如果构建生成一台电脑,那么我们可能需要这么几个步骤(1)需要一个主机(2)需要一个显示器(3)需要一个键盘(4)需要一个鼠标)

    虽然我们具体在构建一台主机的时候,每个对象的实际步骤是不一样的,比如,有的对象构建了i7cpu的主机,有的对象构建了i5cpu的主机,有的对象构建了普通键盘,有的对象构建了机械键盘等。但不管怎样,你总是需要经过一个步骤就是构建一台主机,一台键盘。对于这个例子,我们就可以使用生成器模式来生成一台电脑,他需要通过多个步骤来生成。类图如下:

    ComputerBuilder类定义构造步骤:

    public abstract class ComputerBuilder {
       
        protected Computer computer;
       
        public Computer getComputer() {
            return computer;
        }
       
        public void buildComputer() {
            computer = new Computer();
            System.out.println("生成了一台电脑!!!");
        }
        public abstract void buildMaster();
        public abstract void buildScreen();
        public abstract void buildKeyboard();
        public abstract void buildMouse();
        public abstract void buildAudio();
    }

    HPComputerBuilder定义各个组件:

    public class HPComputerBuilder extends ComputerBuilder {
        @Override
        public void buildMaster() {
            // TODO Auto-generated method stub
            computer.setMaster("i7,16g,512SSD,1060");
            System.out.println("(i7,16g,512SSD,1060)的惠普主机");
        }
        @Override
        public void buildScreen() {
            // TODO Auto-generated method stub
            computer.setScreen("1080p");
            System.out.println("(1080p)的惠普显示屏");
        }
        @Override
        public void buildKeyboard() {
            // TODO Auto-generated method stub
            computer.setKeyboard("cherry 青轴机械键盘");
            System.out.println("(cherry 青轴机械键盘)的键盘");
        }
        @Override
        public void buildMouse() {
            // TODO Auto-generated method stub
            computer.setMouse("MI 鼠标");
            System.out.println("(MI 鼠标)的鼠标");
        }
        @Override
        public void buildAudio() {
            // TODO Auto-generated method stub
            computer.setAudio("飞利浦 音响");
            System.out.println("(飞利浦 音响)的音响");
        }
    }

    Director类对组件进行组装并生成产品

    public class Director {
       
        private ComputerBuilder computerBuilder;
        public void setComputerBuilder(ComputerBuilder computerBuilder) {
            this.computerBuilder = computerBuilder;
        }
       
        public Computer getComputer() {
            return computerBuilder.getComputer();
        }
       
        public void constructComputer() {
            computerBuilder.buildComputer();
            computerBuilder.buildMaster();
            computerBuilder.buildScreen();
            computerBuilder.buildKeyboard();
            computerBuilder.buildMouse();
            computerBuilder.buildAudio();
        }
    }

    3.2 生成器模式的优缺点

    优点

    • 将一个对象分解为各个组件

    • 将对象组件的构造封装起来

    • 可以控制整个对象的生成过程

    缺点

    • 对不同类型的对象需要实现不同的具体构造器的类,这可能回答大大增加类的数量

    3.3 生成器模式与工厂模式的不同

    生成器模式构建对象的时候,对象通常构建的过程中需要多个步骤,就像我们例子中的先有主机,再有显示屏,再有鼠标等等,生成器模式的作用就是将这些复杂的构建过程封装起来。工厂模式构建对象的时候通常就只有一个步骤,调用一个工厂方法就可以生成一个对象。

     

    4 原型模式

    定义:通过复制现有实例来创建新的实例,无需知道相应类的信息。

    简单地理解,其实就是当需要创建一个指定的对象时,我们刚好有一个这样的对象,但是又不能直接使用,我会clone一个一毛一样的新对象来使用;基本上这就是原型模式。关键字:Clone

    4.1 深拷贝和浅拷贝

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

    深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。clone明显是深复制,clone出来的对象是是不能去影响原型对象的

    4.2 原型模式的结构和代码示例

    Client:使用者

    Prototype:接口(抽象类),声明具备clone能力,例如java中得Cloneable接口

    ConcretePrototype:具体的原型类

    可以看出设计模式还是比较简单的,重点在于Prototype接口和Prototype接口的实现类ConcretePrototype。原型模式的具体实现:一个原型类,只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法。

    public class Prototype implements Cloneable {  
         public Object clone() throws CloneNotSupportedException {  
             Prototype proto = (Prototype) super.clone();  
             return proto;  
         }  
    }  

    举例(银行发送大量邮件,使用clone和不使用clone的时间对比):我们模拟创建一个对象需要耗费比较长的时间,因此,在构造函数中我们让当前线程sleep一会

    public Mail(EventTemplate et) {
                  this.tail = et.geteventContent();
                  this.subject = et.geteventSubject();
                  try {
                         Thread.sleep(1000);
                  } catch (InterruptedException e) {
                         // TODO Auto-generated catch block
                         e.printStackTrace();
                  }
           }

    不使用clone,发送十个邮件

    public static void main(String[] args) {
                  int i = 0;
                  int MAX_COUNT = 10;
                  EventTemplate et = new EventTemplate("9月份信用卡账单", "国庆抽奖活动...");
                  long start = System.currentTimeMillis();
                  while (i < MAX_COUNT) {
                         // 以下是每封邮件不同的地方
                         Mail mail = new Mail(et);
                         mail.setContent(getRandString(5) + ",先生(女士):你的信用卡账单..." + mail.getTail());
                         mail.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");
                         // 然后发送邮件
                         sendMail(mail);
                         i++;
                  }
                  long end = System.currentTimeMillis();
                  System.out.println("用时:" + (end - start));
           }

     

    用时:10001

    使用clone,发送十个邮件

        public static void main(String[] args) {
                  int i = 0;
                  int MAX_COUNT = 10;
                  EventTemplate et = new EventTemplate("9月份信用卡账单", "国庆抽奖活动...");
                  long start=System.currentTimeMillis();
                  Mail mail = new Mail(et);         
                  while (i < MAX_COUNT) {
                         Mail cloneMail = mail.clone();
                         mail.setContent(getRandString(5) + ",先生(女士):你的信用卡账单..."
                                      + mail.getTail());
                         mail.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");
                         sendMail(cloneMail);
                         i++;
                  }
                  long end=System.currentTimeMillis();
                  System.out.println("用时:"+(end-start));
           }

    用时:1001

    4.3 总结

    原型模式的本质就是clone,可以解决构建复杂对象的资源消耗问题,能再某些场景中提升构建对象的效率;还有一个重要的用途就是保护性拷贝,可以通过返回一个拷贝对象的形式,实现只读的限制。

     

    B、结构模式(7种)

    适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

     

    5 适配器模式

    定义: 适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。

    主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。

    5.1 类适配器模式

    通过多重继承目标接口和被适配者类方式来实现适配

    举例(将USB接口转为VGA接口),类图如下:

     

    USBImpl的代码:

    public class USBImpl implements USB{
           @Override
           public void showPPT() {
                  // TODO Auto-generated method stub
                  System.out.println("PPT内容演示");
           }
    }

    AdatperUSB2VGA 首先继承USBImpl获取USB的功能,其次,实现VGA接口,表示该类的类型为VGA。

    public class AdapterUSB2VGA extends USBImpl implements VGA {
           @Override
           public void projection() {
                  super.showPPT();
           }
    }

    Projector将USB映射为VGA,只有VGA接口才可以连接上投影仪进行投影

    public class Projector<T> {
           public void projection(T t) {
                  if (t instanceof VGA) {
                         System.out.println("开始投影");
                         VGA v = new VGAImpl();
                         v = (VGA) t;
                         v.projection();
                  } else {
                         System.out.println("接口不匹配,无法投影");
                  }
           }
    }

    test代码

           @Test
           public void test2(){
                  //通过适配器创建一个VGA对象,这个适配器实际是使用的是USB的showPPT()方法
                  VGA a=new AdapterUSB2VGA();
                  //进行投影
                  Projector p1=new Projector();
                  p1.projection(a);
           } 

    5.2 对象适配器模式

    对象适配器和类适配器使用了不同的方法实现适配,对象适配器使用组合,类适配器使用继承。

    举例(将USB接口转为VGA接口),类图如下:

     

    public class AdapterUSB2VGA implements VGA {
           USB u = new USBImpl();
           @Override
           public void projection() {
                  u.showPPT();
           }
    }

    实现VGA接口,表示适配器类是VGA类型的,适配器方法中直接使用USB对象。

    5.3 接口适配器模式

    当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。

    举例(将USB接口转为VGA接口,VGA中的b()和c()不会被实现),类图如下:

    AdapterUSB2VGA抽象类

    public abstract class AdapterUSB2VGA implements VGA {
           USB u = new USBImpl();
           @Override
           public void projection() {
                  u.showPPT();
           }
           @Override
           public void b() {
           };
           @Override
           public void c() {
           };
    }

    AdapterUSB2VGA实现,不用去实现b()和c()方法。

    public class AdapterUSB2VGAImpl extends AdapterUSB2VGA {
           public void projection() {
                  super.projection();
           }
    }

    5.4 总结

    总结一下三种适配器模式的应用场景:

    类适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。

    对象适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。

    接口适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。

    命名规则:

    我个人理解,三种命名方式,是根据 src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。 

    类适配器,以类给到,在Adapter里,就是将src当做类,继承, 

    对象适配器,以对象给到,在Adapter里,将src作为一个对象,持有。 

    接口适配器,以接口给到,在Adapter里,将src作为一个接口,实现。

    使用选择:

    根据合成复用原则,组合大于继承。因此,类的适配器模式应该少用。

     

    6 装饰者模式

    定义:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性。

    6.1 装饰者模式结构图与代码示例

    1.Component(被装饰对象的基类)

       定义一个对象接口,可以给这些对象动态地添加职责。

    2.ConcreteComponent(具体被装饰对象)

       定义一个对象,可以给这个对象添加一些职责。

    3.Decorator(装饰者抽象类)

       维持一个指向Component实例的引用,并定义一个与Component接口一致的接口。

    4.ConcreteDecorator(具体装饰者)

       具体的装饰对象,给内部持有的具体被装饰对象,增加具体的职责。

    被装饰对象和修饰者继承自同一个超类

    举例(咖啡馆订单项目:1)、咖啡种类:Espresso、ShortBlack、LongBlack、Decaf2)、调料(装饰者):Milk、Soy、Chocolate),类图如下:

     

    被装饰的对象和装饰者都继承自同一个超类

    public abstract class Drink {
           public String description="";
           private float price=0f;;
           
           
           public void setDescription(String description)
           {
                  this.description=description;
           }
           
           public String getDescription()
           {
                  return description+"-"+this.getPrice();
           }
           public float getPrice()
           {
                  return price;
           }
           public void setPrice(float price)
           {
                  this.price=price;
           }
           public abstract float cost();
           
    }

    被装饰的对象,不用去改造。原来怎么样写,现在还是怎么写。

    public  class Coffee extends Drink {
           @Override
           public float cost() {
                  // TODO Auto-generated method stub
                  return super.getPrice();
           }
           
    }

    coffee类的实现

    public class Decaf extends Coffee {
           public Decaf()
           {
                  super.setDescription("Decaf");
                  super.setPrice(3.0f);
           }
    }

    装饰者

    装饰者不仅要考虑自身,还要考虑被它修饰的对象,它是在被修饰的对象上继续添加修饰。例如,咖啡里面加牛奶,再加巧克力。加糖后价格为coffee+milk。再加牛奶价格为coffee+milk+chocolate。

    public class Decorator extends Drink {
           private Drink Obj;
           public Decorator(Drink Obj) {
                  this.Obj = Obj;
           };
           @Override
           public float cost() {
                  // TODO Auto-generated method stub
                  return super.getPrice() + Obj.cost();
           }
           @Override
           public String getDescription() {
                  return super.description + "-" + super.getPrice() + "&&" + Obj.getDescription();
           }
    }

    装饰者实例化(加牛奶)。这里面要对被修饰的对象进行实例化。

    public class Milk extends Decorator {
           public Milk(Drink Obj) {          
                  super(Obj);
                  // TODO Auto-generated constructor stub
                  super.setDescription("Milk");
                  super.setPrice(2.0f);
           }
    }

    coffee店:初始化一个被修饰对象,修饰者实例需要对被修改者实例化,才能对具体的被修饰者进行修饰。

    public class CoffeeBar {
           public static void main(String[] args) {
                  Drink order;
                  order = new Decaf();
                  System.out.println("order1 price:" + order.cost());
                  System.out.println("order1 desc:" + order.getDescription());
                  System.out.println("****************");
                  order = new LongBlack();
                  order = new Milk(order);
                  order = new Chocolate(order);
                  order = new Chocolate(order);
                  System.out.println("order2 price:" + order.cost());
                  System.out.println("order2 desc:" + order.getDescription());
           }
    }

    6.2 总结

    装饰者和被装饰者之间必须是一样的类型,也就是要有共同的超类。在这里应用继承并不是实现方法的复制,而是实现类型的匹配。因为装饰者和被装饰者是同一个类型,因此装饰者可以取代被装饰者,这样就使被装饰者拥有了装饰者独有的行为。根据装饰者模式的理念,我们可以在任何时候,实现新的装饰者增加新的行为。如果是用继承,每当需要增加新的行为时,就要修改原程序了。

     

     

    7 代理模式

    定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

    举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。用图表示如下:

    7.1 为什么要用代理模式?

    中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。

    开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

    代理模式分为三类:1. 静态代理 2. 动态代理 3. CGLIB代理

    7.2 静态代理

    举例(买房),类图如下:

    第一步:创建服务类接口

    public interface BuyHouse {
        void buyHosue();
    }

    第二步:实现服务接口

    public class BuyHouseImpl implements BuyHouse {
           @Override
           public void buyHosue() {
                  System.out.println("我要买房");
           }
    }

    第三步:创建代理类

    public class BuyHouseProxy implements BuyHouse {
           private BuyHouse buyHouse;
           public BuyHouseProxy(final BuyHouse buyHouse) {
                  this.buyHouse = buyHouse;
           }
           @Override
           public void buyHosue() {
                  System.out.println("买房前准备");
                  buyHouse.buyHosue();
                  System.out.println("买房后装修");
           }
    }

    总结:

    优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。

    缺点: 代理对象与目标对象要实现相同的接口,我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。 

    7.3 动态代理

    动态代理有以下特点:

    1.代理对象,不需要实现接口

    2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)

    代理类不用再实现接口了。但是,要求被代理对象必须有接口。

    动态代理实现:

    Java.lang.reflect.Proxy类可以直接生成一个代理对象

    • Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成一个代理对象

      • 参数1:ClassLoader loader 代理对象的类加载器 一般使用被代理对象的类加载器

      • 参数2:Class<?>[] interfaces 代理对象的要实现的接口 一般使用的被代理对象实现的接口

      • 参数3:InvocationHandler h (接口)执行处理类

    • InvocationHandler中的invoke(Object proxy, Method method, Object[] args)方法:调用代理类的任何方法,此方法都会执行

      • 参数3.1:代理对象(慎用)

      • 参数3.2:当前执行的方法

      • 参数3.3:当前执行的方法运行时传递过来的参数

    第一步:编写动态处理器

    public class DynamicProxyHandler implements InvocationHandler {
           private Object object;
           public DynamicProxyHandler(final Object object) {
                  this.object = object;
           }
           @Override
           public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                  System.out.println("买房前准备");
                  Object result = method.invoke(object, args);
                  System.out.println("买房后装修");
                  return result;
           }
    }

    第二步:编写测试类

    public class DynamicProxyTest {
        public static void main(String[] args) {
            BuyHouse buyHouse = new BuyHouseImpl();
            BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new
                    Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse));
            proxyBuyHouse.buyHosue();
        }
    }

    动态代理总结:虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏(我们要使用被代理的对象的接口),因为它的设计注定了这个遗憾。

    7.4 CGLIB代理

    CGLIB 原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

    CGLIB 底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

    CGLIB缺点:对于final方法,无法进行代理。

    CGLIB的实现步骤:

    第一步:建立拦截器

    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    
            System.out.println("买房前准备");
    
            Object result = methodProxy.invoke(object, args);
    
            System.out.println("买房后装修");
    
            return result;
    
        }

    参数:Object为由CGLib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。

    返回:从代理实例的方法调用返回的值。

    其中,proxy.invokeSuper(obj,arg) 调用代理类实例上的proxy方法的父类方法(即实体类TargetObject中对应的方法)

    第二步: 生成动态代理类

    public class CglibProxy implements MethodInterceptor {
        private Object target;
        public Object getInstance(final Object target) {
            this.target = target;
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(this.target.getClass());
            enhancer.setCallback(this);
            return enhancer.create();
        }
        public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            System.out.println("买房前准备");
            Object result = methodProxy.invoke(object, args);
            System.out.println("买房后装修");
            return result;
        }
    }

    这里Enhancer类是CGLib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展,以后会经常看到它。

    首先将被代理类TargetObject设置成父类,然后设置拦截器TargetInterceptor,最后执行enhancer.create()动态生成一个代理类,并从Object强制转型成父类型TargetObject。

    第三步:测试

    public class CglibProxyTest {
        public static void main(String[] args){
            BuyHouse buyHouse = new BuyHouseImpl();
            CglibProxy cglibProxy = new CglibProxy();
            BuyHouseImpl buyHouseCglibProxy = (BuyHouseImpl) cglibProxy.getInstance(buyHouse);
            buyHouseCglibProxy.buyHosue();
        }
    }

    CGLIB代理总结: CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

     

    8 外观模式

    定义: 隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口。

    8.1 模式结构和代码示例

    简单来说,该模式就是把一些复杂的流程封装成一个接口供给外部用户更简单的使用。这个模式中,设计到3个角色。

      1).门面角色:外观模式的核心。它被客户角色调用,它熟悉子系统的功能。内部根据客户角色的需求预定了几种功能的组合。(客户调用,同时自身调用子系统功能

      2).子系统角色:实现了子系统的功能。它对客户角色和Facade时未知的。它内部可以有系统内的相互交互,也可以由供外界调用的接口。(实现具体功能)

      3).客户角色:通过调用Facede来完成要实现的功能(调用门面角色)。

    举例(每个Computer都有CPU、Memory、Disk。在Computer开启和关闭的时候,相应的部件也会开启和关闭),类图如下:

     

    首先是子系统类:

    public class CPU {
    
    	public void start() {
    		System.out.println("cpu is start...");
    	}
    
    	public void shutDown() {
    		System.out.println("CPU is shutDown...");
    	}
    }
    
    public class Disk {
    	public void start() {
    		System.out.println("Disk is start...");
    	}
    
    	public void shutDown() {
    		System.out.println("Disk is shutDown...");
    	}
    }
    
    public class Memory {
    	public void start() {
    		System.out.println("Memory is start...");
    	}
    
    	public void shutDown() {
    		System.out.println("Memory is shutDown...");
    	}
    }

    然后是,门面类Facade

    public class Computer {
    
    	private CPU cpu;
    	private Memory memory;
    	private Disk disk;
    
    	public Computer() {
    		cpu = new CPU();
    		memory = new Memory();
    		disk = new Disk();
    	}
    
    	public void start() {
    		System.out.println("Computer start begin");
    		cpu.start();
    		disk.start();
    		memory.start();
    		System.out.println("Computer start end");
    	}
    
    	public void shutDown() {
    		System.out.println("Computer shutDown begin");
    		cpu.shutDown();
    		disk.shutDown();
    		memory.shutDown();
    		System.out.println("Computer shutDown end...");
    	}
    }

    最后为,客户角色

    public class Client {
    
    	public static void main(String[] args) {
    		Computer computer = new Computer();
    		computer.start();
    		System.out.println("=================");
    		computer.shutDown();
    	}
    
    }

    8.2 优点

      - 松散耦合

      使得客户端和子系统之间解耦,让子系统内部的模块功能更容易扩展和维护;

      - 简单易用

      客户端根本不需要知道子系统内部的实现,或者根本不需要知道子系统内部的构成,它只需要跟Facade类交互即可。

      - 更好的划分访问层次

        有些方法是对系统外的,有些方法是系统内部相互交互的使用的。子系统把那些暴露给外部的功能集中到门面中,这样就可以实现客户端的使用,很好的隐藏了子系统内部的细节。

     

    9 桥接模式

    定义: 将抽象部分与它的实现部分分离,使它们都可以独立地变化。

    9.1 案例

    看下图手机与手机软件的类图

    增加一款新的手机软件,需要在所有手机品牌类下添加对应的手机软件类,当手机软件种类较多时,将导致类的个数急剧膨胀,难以维护

    手机和手机中的软件是什么关系?

    手机中的软件从本质上来说并不是一种手机,手机软件运行在手机中,是一种包含与被包含关系,而不是一种父与子或者说一般与特殊的关系,通过继承手机类实现手机软件类的设计是违反一般规律的。

    如果Oppo手机实现了wifi功能,继承它的Oppo应用商城也会继承wifi功能,并且Oppo手机类的任何变动,都会影响其子类

    换一种解决思路

    从类图上看起来更像是手机软件类图,涉及到手机本身相关的功能,比如说:wifi功能,放到哪个类中实现呢?放到OppoAppStore中实现显然是不合适的

    引起整个结构变化的元素有两个,一个是手机品牌,一个是手机软件,所以我们将这两个点抽出来,分别进行封装

    9.2 桥接模式结构和代码示例

    类图:

    实现:

    public interface Software {
    	public void run();
    
    }
    public class AppStore implements Software {
    	 
        @Override
        public void run() {
            System.out.println("run app store");
        }
    }
    public class Camera implements Software {
    	 
        @Override
        public void run() {
            System.out.println("run camera");
        }
    }

    抽象:

    public abstract class Phone {
    
    	protected Software software;
    
    	public void setSoftware(Software software) {
    		this.software = software;
    	}
    
    	public abstract void run();
    
    }
    public class Oppo extends Phone {
    	 
        @Override
        public void run() {
            software.run();
        }
    }
    public class Vivo extends Phone {
    	 
        @Override
        public void run() {
            software.run();
        }
    }

    对比最初的设计,将抽象部分(手机)与它的实现部分(手机软件类)分离,将实现部分抽象成单独的类,使它们都可以独立地变化。整个类图看起来像一座桥,所以称为桥接模式

    继承是一种强耦合关系,子类的实现与它的父类有非常紧密的依赖关系,父类的任何变化 都会导致子类发生变化,因此继承或者说强耦合关系严重影响了类的灵活性,并最终限制了可复用性

    从桥接模式的设计上我们可以看出聚合是一种比继承要弱的关联关系,手机类和软件类都可独立的进行变化,不会互相影响

    9.3 适用场景

    桥接模式通常适用于以下场景。

    1. 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。

    2. 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。

    3. 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。

    9.4 优缺点

    优点:

    (1)在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则”,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少了子类的个数。

    (2)桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”。

    缺点:

    桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。

     

    10 组合模式

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

    意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

    主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

    何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

    如何解决:树枝和叶子实现统一接口,树枝内部组合该接口。

    关键代码:树枝内部组合该接口,并且含有内部属性 List,里面放 Component。

    组合模式的主要优点有:

    1. 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;

    2. 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

    其主要缺点是:

    1. 设计较复杂,客户端需要花更多时间理清类之间的层次关系;

    2. 不容易限制容器中的构件;

    3. 不容易用继承的方法来增加构件的新功能;

    10.1 模式结构和代码示例

    • 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。

    • 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。

    • 树枝构件(Composite)角色:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法

    举例(访问一颗树),类图如下:

    1 组件

    public interface Component {
        public void add(Component c);
        public void remove(Component c);
        public Component getChild(int i);
        public void operation();
    
    }
    

    2 叶子

    public class Leaf implements Component{
        
    	private String name;
    	
    	
    	public Leaf(String name) {
    		this.name = name;
    	}
    
    	@Override
    	public void add(Component c) {}
    
    	@Override
    	public void remove(Component c) {}
    
    	@Override
    	public Component getChild(int i) {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	@Override
    	public void operation() {
    		// TODO Auto-generated method stub
    		 System.out.println("树叶"+name+":被访问!"); 
    	}
    
    }
    

    3 树枝

    public class Composite implements Component {
    
    	private ArrayList<Component> children = new ArrayList<Component>();
    
    	public void add(Component c) {
    		children.add(c);
    	}
    
    	public void remove(Component c) {
    		children.remove(c);
    	}
    
    	public Component getChild(int i) {
    		return children.get(i);
    	}
    
    	public void operation() {
    		for (Object obj : children) {
    			((Component) obj).operation();
    		}
    	}
    }

     

    11 享元模式

    定义:通过共享的方式高效的支持大量细粒度的对象。

    主要解决:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

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

    如何解决:用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。

    关键代码:用 HashMap 存储这些对象。

    应用实例: 1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。

    优点:大大减少对象的创建,降低系统的内存,使效率提高。

    缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

    简单来说,我们抽取出一个对象的外部状态(不能共享)和内部状态(可以共享)。然后根据外部状态的决定是否创建内部状态对象。内部状态对象是通过哈希表保存的,当外部状态相同的时候,不再重复的创建内部状态对象,从而减少要创建对象的数量。

    11.1 享元模式的结构图和代码示例

    1、Flyweight (享元抽象类):一般是接口或者抽象类,定义了享元类的公共方法。这些方法可以分享内部状态的数据,也可以调用这些方法修改外部状态。

    2、ConcreteFlyweight(具体享元类):具体享元类实现了抽象享元类的方法,为享元对象开辟了内存空间来保存享元对象的内部数据,同时可以通过和单例模式结合只创建一个享元对象。

    3、FlyweightFactory(享元工厂类):享元工厂类创建并且管理享元类,享元工厂类针对享元类来进行编程,通过提供一个享元池来进行享元对象的管理。一般享元池设计成键值对,或者其他的存储结构来存储。当客户端进行享元对象的请求时,如果享元池中有对应的享元对象则直接返回对应的对象,否则工厂类创建对应的享元对象并保存到享元池。

    举例(JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面)。类图如下:

    (1)创建享元对象接口

    public interface IFlyweight {
        void print();
    }

    (2)创建具体享元对象

    public class Flyweight implements IFlyweight {
        private String id;
        public Flyweight(String id){
            this.id = id;
        }
        @Override
        public void print() {
            System.out.println("Flyweight.id = " + getId() + " ...");
        }
        public String getId() {
            return id;
        }
    }

    (3)创建工厂,这里要特别注意,为了避免享元对象被重复创建,我们使用HashMap中的key值保证其唯一。

    public class FlyweightFactory {
        private Map<String, IFlyweight> flyweightMap = new HashMap();
        public IFlyweight getFlyweight(String str){
            IFlyweight flyweight = flyweightMap.get(str);
            if(flyweight == null){
                flyweight = new Flyweight(str);
                flyweightMap.put(str, flyweight);
            }
            return  flyweight;
        }
        public int getFlyweightMapSize(){
            return flyweightMap.size();
        }
    }

    (4)测试,我们创建三个字符串,但是只会产生两个享元对象

    public class MainTest {
    	public static void main(String[] args) {
            FlyweightFactory flyweightFactory = new FlyweightFactory();
            IFlyweight flyweight1 = flyweightFactory.getFlyweight("A");
            IFlyweight flyweight2 = flyweightFactory.getFlyweight("B");
            IFlyweight flyweight3 = flyweightFactory.getFlyweight("A");
            flyweight1.print();
            flyweight2.print();
            flyweight3.print();
            System.out.println(flyweightFactory.getFlyweightMapSize());
        }
    
    }
    

     

    C、关系模式(11种)

    先来张图,看看这11中模式的关系:

    第一类:通过父类与子类的关系进行实现。

    第二类:两个类之间。

    第三类:类的状态。

    第四类:通过中间类

     

    12 策略模式

    定义: 策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。

    意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。

    主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。

    何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。

    如何解决:将这些算法封装成一个一个的类,任意地替换。

    关键代码:实现同一个接口。

    优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。

    缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。

    12.1 策略模式结构和示例代码

     

    抽象策略角色: 这个是一个抽象的角色,通常情况下使用接口或者抽象类去实现。对比来说,就是我们的Comparator接口。

    具体策略角色: 包装了具体的算法和行为。对比来说,就是实现了Comparator接口的实现一组实现类。 

    环境角色: 内部会持有一个抽象角色的引用,给客户端调用。

    举例如下( 实现一个加减的功能),类图如下:

    1、定义抽象策略角色

    public interface Strategy {
    
    	public int calc(int num1,int num2);
    }
    

    2、定义具体策略角色

    public class AddStrategy implements Strategy {
    
    	@Override
    	public int calc(int num1, int num2) {
    		// TODO Auto-generated method stub
    		return num1 + num2;
    	}
    
    }
    public class SubstractStrategy implements Strategy {
    
    	@Override
    	public int calc(int num1, int num2) {
    		// TODO Auto-generated method stub
    		return num1 - num2;
    	}
    
    }

    3、环境角色

    public class Environment {
    	private Strategy strategy;
    
    	public Environment(Strategy strategy) {
    		this.strategy = strategy;
    	}
    
    	public int calculate(int a, int b) {
    		return strategy.calc(a, b);
    	}
    
    }

    4、测试

    public class MainTest {
    	public static void main(String[] args) {
    		
    		Environment environment=new Environment(new AddStrategy());
    		int result=environment.calculate(20, 5);
    		System.out.println(result);
    		
    		Environment environment1=new Environment(new SubstractStrategy());
    		int result1=environment1.calculate(20, 5);
    		System.out.println(result1);
    	}
    
    }
    

     

    13 模板模式

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

    通俗点的理解就是 :完成一件事情,有固定的数个步骤,但是每个步骤根据对象的不同,而实现细节不同;就可以在父类中定义一个完成该事情的总方法,按照完成事件需要的步骤去调用其每个步骤的实现方法。每个步骤的具体实现,由子类完成。

    13.1 模式结构和代码示例

     

    抽象父类(AbstractClass):实现了模板方法,定义了算法的骨架。

    具体类(ConcreteClass):实现抽象类中的抽象方法,即不同的对象的具体实现细节。

    举例( 我们做菜可以分为三个步骤 (1)备料 (2)具体做菜 (3)盛菜端给客人享用,这三部就是算法的骨架 ;然而做不同菜需要的料,做的方法,以及如何盛装给客人享用都是不同的这个就是不同的实现细节。)。类图如下:

    a. 先来写一个抽象的做菜父类: 

    public abstract class Dish {    
        /**
         * 具体的整个过程
         */
        protected void dodish(){
            this.preparation();
            this.doing();
            this.carriedDishes();
        }
        /**
         * 备料
         */
        public abstract void preparation();
        /**
         * 做菜
         */
        public abstract void doing();
        /**
         * 上菜
         */
        public abstract void carriedDishes ();
    }

    b. 下来做两个番茄炒蛋(EggsWithTomato)和红烧肉(Bouilli)实现父类中的抽象方法

    public class EggsWithTomato extends Dish {
    
    	@Override
    	public void preparation() {
    		System.out.println("洗并切西红柿,打鸡蛋。");
    	}
    
    	@Override
    	public void doing() {
    		System.out.println("鸡蛋倒入锅里,然后倒入西红柿一起炒。");
    	}
    
    	@Override
    	public void carriedDishes() {
    		System.out.println("将炒好的西红寺鸡蛋装入碟子里,端给客人吃。");
    	}
    
    }
    public class Bouilli extends Dish{
    
        @Override
        public void preparation() {
            System.out.println("切猪肉和土豆。");
        }
    
        @Override
        public void doing() {
            System.out.println("将切好的猪肉倒入锅中炒一会然后倒入土豆连炒带炖。");
        }
    
        @Override
        public void carriedDishes() {
            System.out.println("将做好的红烧肉盛进碗里端给客人吃。");
        }
    
    }

    c. 在测试类中我们来做菜:

    public class MainTest {
    	public static void main(String[] args) {
    		Dish eggsWithTomato = new EggsWithTomato();
    		eggsWithTomato.dodish();
    
    		System.out.println("-----------------------------");
    
    		Dish bouilli = new Bouilli();
    		bouilli.dodish();
    	}
    
    }

    13.2  模板模式的优点和缺点

    优点:

     (1)具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法整体结构。

     (2)代码复用的基本技术,在数据库设计中尤为重要。

     (3)存在一种反向的控制结构,通过一个父类调用其子类的操作,通过子类对父类进行扩展增加新的行为,符合“开闭原则”。

    缺点:

        每个不同的实现都需要定义一个子类,会导致类的个数增加,系统更加庞大。

     

    14 观察者模式

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

    主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

    何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

    如何解决:使用面向对象技术,可以将这种依赖关系弱化。

    关键代码:在抽象类里有一个 ArrayList 存放观察者们。

    优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。

    缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

    14.1 模式结构图和代码示例

    • 抽象被观察者角色:也就是一个抽象主题,它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。

    • 抽象观察者角色:为所有的具体观察者定义一个接口,在得到主题通知时更新自己。

    • 具体被观察者角色:也就是一个具体的主题,在集体主题的内部状态改变时,所有登记过的观察者发出通知。

    • 具体观察者角色:实现抽象观察者角色所需要的更新接口,一边使本身的状态与制图的状态相协调。

    举例(有一个微信公众号服务,不定时发布一些消息,关注公众号就可以收到推送消息,取消关注就收不到推送消息。)类图如下:

     

    1、定义一个抽象被观察者接口

    public interface Subject {
    	
    	  public void registerObserver(Observer o);
    	  public void removeObserver(Observer o);
    	  public void notifyObserver();
    
    }
    

    2、定义一个抽象观察者接口

    public interface Observer {
    	
    	public void update(String message);
    
    }
    

    3、定义被观察者,实现了Observerable接口,对Observerable接口的三个方法进行了具体实现,同时有一个List集合,用以保存注册的观察者,等需要通知观察者时,遍历该集合即可。

    public class WechatServer implements Subject {
    
    	private List<Observer> list;
    	private String message;
    
    	public WechatServer() {
    		list = new ArrayList<Observer>();
    	}
    
    	@Override
    	public void registerObserver(Observer o) {
    		// TODO Auto-generated method stub
    		list.add(o);
    	}
    
    	@Override
    	public void removeObserver(Observer o) {
    		// TODO Auto-generated method stub
    		if (!list.isEmpty()) {
    			list.remove(o);
    		}
    	}
    
    	@Override
    	public void notifyObserver() {
    		// TODO Auto-generated method stub
    		for (Observer o : list) {
    			o.update(message);
    		}
    	}
    
    	public void setInfomation(String s) {
    		this.message = s;
    		System.out.println("微信服务更新消息: " + s);
    		// 消息更新,通知所有观察者
    		notifyObserver();
    	}
    
    }

    4、定义具体观察者,微信公众号的具体观察者为用户User

    public class User implements Observer {
    
    	private String name;
    	private String message;
    
    	public User(String name) {
    		this.name = name;
    	}
    
    	@Override
    	public void update(String message) {
    		this.message = message;
    		read();
    	}
    
    	public void read() {
    		System.out.println(name + " 收到推送消息: " + message);
    	}
    
    }

    5、编写一个测试类

    public class MainTest {
    	
    	 public static void main(String[] args) {
    		 
    	        WechatServer server = new WechatServer();
    	        
    	        Observer userZhang = new User("ZhangSan");
    	        Observer userLi = new User("LiSi");
    	        Observer userWang = new User("WangWu");
    	        
    	        server.registerObserver(userZhang);
    	        server.registerObserver(userLi);
    	        server.registerObserver(userWang);
    	        server.setInfomation("PHP是世界上最好用的语言!");
    	        
    	        System.out.println("----------------------------------------------");
    	        server.removeObserver(userZhang);
    	        server.setInfomation("JAVA是世界上最好用的语言!");
    	        
    	    }
    
    }
    

     

    15 迭代器模式

    定义:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。

    简单来说,不同种类的对象可能需要不同的遍历方式,我们对每一种类型的对象配一个迭代器,最后多个迭代器合成一个。

    主要解决:不同的方式来遍历整个整合对象。

    何时使用:遍历一个聚合对象。

    如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。

    关键代码:定义接口:hasNext, next。

    应用实例:JAVA 中的 iterator。

    优点: 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。

    缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

    15.1 模式结构和代码示例

    (1)迭代器角色(Iterator):定义遍历元素所需要的方法,一般来说会有这么三个方法:取得下一个元素的方法next(),判断是否遍历结束的方法hasNext()),移出当前对象的方法remove(),

    (2)具体迭代器角色(Concrete Iterator):实现迭代器接口中定义的方法,完成集合的迭代。

    (3)容器角色(Aggregate):  一般是一个接口,提供一个iterator()方法,例如java中的Collection接口,List接口,Set接口等

    (4)具体容器角色(ConcreteAggregate):就是抽象容器的具体实现类,比如List接口的有序列表实现ArrayList,List接口的链表实现LinkList,Set接口的哈希列表的实现HashSet等。

    举例(咖啡厅和中餐厅合并,他们两个餐厅的菜单一个是数组保存的,一个是ArrayList保存的。遍历方式不一样,使用迭代器聚合访问,只需要一种方式)

    1 迭代器接口

    public interface Iterator {
    	
    	public boolean hasNext();
    	public Object next();
    	
    }
    

    2 咖啡店菜单和咖啡店菜单遍历器

    public class CakeHouseMenu {
    	private ArrayList<MenuItem> menuItems;
    	
    	
    	public CakeHouseMenu() {
    		menuItems = new ArrayList<MenuItem>();
    		
    		addItem("KFC Cake Breakfast","boiled eggs&toast&cabbage",true,3.99f);
    		addItem("MDL Cake Breakfast","fried eggs&toast",false,3.59f);
    		addItem("Stawberry Cake","fresh stawberry",true,3.29f);
    		addItem("Regular Cake Breakfast","toast&sausage",true,2.59f);
    	}
    
    	private void addItem(String name, String description, boolean vegetable,
    			float price) {
    		MenuItem menuItem = new MenuItem(name, description, vegetable, price);
    		menuItems.add(menuItem);
    	}
    	
    
    	
    	public Iterator getIterator()
    	{
    		return new CakeHouseIterator() ;
    	}
    	
    	class CakeHouseIterator implements  Iterator
    	 {		
    		private int position=0;
    		public CakeHouseIterator()
    		{
    			  position=0;
    		}
    		
    		 	@Override
    			public boolean hasNext() {
    			// TODO Auto-generated method stub
    			if(position<menuItems.size())
    			{
    				return true;
    			}
    			
    			return false;
    		}
    
    		@Override
    		public Object next() {
    			// TODO Auto-generated method stub
    			MenuItem menuItem =menuItems.get(position);
    			position++;
    			return menuItem;
    		}};
    	//鍏朵粬鍔熻兘浠g爜
    	
    }

    3 中餐厅菜单和中餐厅菜单遍历器

    public class DinerMenu {
    	private final static int Max_Items = 5;
    	private int numberOfItems = 0;
    	private MenuItem[] menuItems;
    
    	public DinerMenu() {
    		menuItems = new MenuItem[Max_Items];
    		addItem("vegetable Blt", "bacon&lettuce&tomato&cabbage", true, 3.58f);
    		addItem("Blt", "bacon&lettuce&tomato", false, 3.00f);
    		addItem("bean soup", "bean&potato salad", true, 3.28f);
    		addItem("hotdog", "onions&cheese&bread", false, 3.05f);
    
    	}
    
    	private void addItem(String name, String description, boolean vegetable,
    			float price) {
    		MenuItem menuItem = new MenuItem(name, description, vegetable, price);
    		if (numberOfItems >= Max_Items) {
    			System.err.println("sorry,menu is full!can not add another item");
    		} else {
    			menuItems[numberOfItems] = menuItem;
    			numberOfItems++;
    		}
    
    	}
    
    	public Iterator getIterator() {
    		return new DinerIterator();
    	}
    
    	class DinerIterator implements Iterator {
    		private int position;
    
    		public DinerIterator() {
    			position = 0;
    		}
    
    		@Override
    		public boolean hasNext() {
    			// TODO Auto-generated method stub
    			if (position < numberOfItems) {
    				return true;
    			}
    			
    			return false;
    		}
    
    		@Override
    		public Object next() {
    			// TODO Auto-generated method stub
    			MenuItem menuItem = menuItems[position];
    			position++;
    			return menuItem;
    		}
    	};
    }
    

    4 女服务员

    public class Waitress {
    	private ArrayList<Iterator> iterators = new ArrayList<Iterator>();
    
    	public Waitress() {
    
    	}
    
    	public void addIterator(Iterator iterator) {
    		iterators.add(iterator);
    
    	}
    
    	public void printMenu() {
    		Iterator iterator;
    		MenuItem menuItem;
    		for (int i = 0, len = iterators.size(); i < len; i++) {
    			iterator = iterators.get(i);
    
    			while (iterator.hasNext()) {
    				menuItem = (MenuItem) iterator.next();
    				System.out
    						.println(menuItem.getName() + "***" + menuItem.getPrice() + "***" + menuItem.getDescription());
    
    			}
    
    		}
    
    	}
    
    	public void printBreakfastMenu() {
    
    	}
    
    	public void printLunchMenu() {
    
    	}
    
    	public void printVegetableMenu() {
    
    	}
    }
    

    16 责任链模式

    定义:如果有多个对象有机会处理请求,责任链可使请求的发送者和接受者解耦,请求沿着责任链传递,直到有一个对象处理了它为止。

    主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

    何时使用:在处理消息的时候以过滤很多道。

    如何解决:拦截的类都实现统一接口。

    关键代码:Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。

    16.1 模式的结构和代码示例

    1. 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。

    2. 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。

    3. 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

    举例(购买请求决策,价格不同要由不同的级别决定:组长、部长、副部、总裁)。类图如下:

    1 决策者抽象类,包含对请求处理的函数,同时还包含指定下一个决策者的函数

    public abstract class Approver {
    	 Approver successor;
    	 String Name;
    	public Approver(String Name)
    	{
    		this.Name=Name;
    	}
    	public abstract void ProcessRequest( PurchaseRequest request);
    	public void SetSuccessor(Approver successor) {
    		// TODO Auto-generated method stub
    		this.successor=successor;
    	}
    }
    

    2 客户端以及请求

    public class PurchaseRequest {
    	private int Type = 0;
    	private int Number = 0;
    	private float Price = 0;
    	private int ID = 0;
    
    	public PurchaseRequest(int Type, int Number, float Price) {
    		this.Type = Type;
    		this.Number = Number;
    		this.Price = Price;
    	}
    
    	public int GetType() {
    		return Type;
    	}
    
    	public float GetSum() {
    		return Number * Price;
    	}
    
    	public int GetID() {
    		return (int) (Math.random() * 1000);
    	}
    }
    public class Client {
    
    	public Client() {
    
    	}
    
    	public PurchaseRequest sendRequst(int Type, int Number, float Price) {
    		return new PurchaseRequest(Type, Number, Price);
    	}
    
    }
    

    3 组长、部长。。。继承决策者抽象类

    public class GroupApprover extends Approver {
    
    	public GroupApprover(String Name) {
    		super(Name + " GroupLeader");
    		// TODO Auto-generated constructor stub
    
    	}
    
    	@Override
    	public void ProcessRequest(PurchaseRequest request) {
    		// TODO Auto-generated method stub
    
    		if (request.GetSum() < 5000) {
    			System.out.println("**This request " + request.GetID() + " will be handled by " + this.Name + " **");
    		} else {
    			successor.ProcessRequest(request);
    		}
    	}
    
    }
    public class DepartmentApprover extends Approver {
    
    	public DepartmentApprover(String Name) {
    		super(Name + " DepartmentLeader");
    
    	}
    
    	@Override
    	public void ProcessRequest(PurchaseRequest request) {
    		// TODO Auto-generated method stub
    
    		if ((5000 <= request.GetSum()) && (request.GetSum() < 10000)) {
    			System.out.println("**This request " + request.GetID()
    					+ " will be handled by " + this.Name + " **");
    		} else {
    			successor.ProcessRequest(request);
    		}
    
    	}
    
    }

    4测试

    public class MainTest {
    
    	public static void main(String[] args) {
    
    		Client mClient = new Client();
    		Approver GroupLeader = new GroupApprover("Tom");
    		Approver DepartmentLeader = new DepartmentApprover("Jerry");
    		Approver VicePresident = new VicePresidentApprover("Kate");
    		Approver President = new PresidentApprover("Bush");
    
    		GroupLeader.SetSuccessor(VicePresident);
    		DepartmentLeader.SetSuccessor(President);
    		VicePresident.SetSuccessor(DepartmentLeader);
    		President.SetSuccessor(GroupLeader);
    
    		GroupLeader.ProcessRequest(mClient.sendRequst(1, 10000, 40));
    
    	}
    
    }

     

    17 命令模式

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

    意图:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

    主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

    何时使用:在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

    如何解决:通过调用者调用接受者执行命令,顺序:调用者→接受者→命令。

    17.1模式结构和代码示例

    1. 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
    2. 具体命令角色(Concrete    Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
    3. 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
    4. 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。

    代码举例(开灯和关灯),类图如下:

    1 命令抽象类

    public interface Command {
    	
    	public void excute();
    	public void undo();
    
    }
    

    2 具体命令对象

    public class TurnOffLight implements Command {
    
    	private Light light;
    
    	public TurnOffLight(Light light) {
    		this.light = light;
    	}
    
    	@Override
    	public void excute() {
    		// TODO Auto-generated method stub
    		light.Off();
    	}
    
    	@Override
    	public void undo() {
    		// TODO Auto-generated method stub
    		light.On();
    	}
    
    }

    3 实现者

    public class Light {
    
    	String loc = "";
    
    	public Light(String loc) {
    		this.loc = loc;
    	}
    
    	public void On() {
    
    		System.out.println(loc + " On");
    	}
    
    	public void Off() {
    
    		System.out.println(loc + " Off");
    	}
    
    }

    4 请求者

    public class Contral{
    
    	public void CommandExcute(Command command) {
    		// TODO Auto-generated method stub
    		command.excute();
    	}
    
    	public void CommandUndo(Command command) {
    		// TODO Auto-generated method stub
    		command.undo();
    	}
    
    }
    

     

    18 状态模式

    定义: 在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。

    简单理解,一个拥有状态的context对象,在不同的状态下,其行为会发生改变。

    意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

    主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。

    何时使用:代码中包含大量与对象状态有关的条件语句。

    如何解决:将各种具体的状态类抽象出来。

    关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if...else 等条件选择语句。

    优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

    缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

    18.1 模式结构和代码示例

     

    • State抽象状态角色

      接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换。

    • ConcreteState具体状态角色

      具体状态主要有两个职责:一是处理本状态下的事情,二是从本状态如何过渡到其他状态。

    • Context环境角色

      定义客户端需要的接口,并且负责具体状态的切换。

    举例(人物在地点A向地点B移动,在地点B向地点A移动)。类图如下:

    1 state接口

    public interface State {
    	public void stop();
    	public void move();
    
    }
    

    2 状态实例

    public class PlaceA implements State {
    
    	private Player context;
    
    	public PlaceA(Player context) {
    		this.context = context;
    	}
    
    	@Override
    	public void move() {
    		System.out.println("处于地点A,开始向B移动");
    		System.out.println("--------");
    		context.setDirection("AB");
    		context.setState(context.onMove);
    
    	}
    
    	@Override
    	public void stop() {
    		// TODO Auto-generated method stub
    		System.out.println("正处在地点A,不用停止移动");
    		System.out.println("--------");
    	}
    
    }
    

    3 context(player)拥有状态的对象

    public class Player {
    
    	State placeA;
    	State placeB;
    	State onMove;
    	private State state;
    	private String direction;
    
    	public Player() {
    		direction = "AB";
    		placeA = new PlaceA(this);
    		placeB = new PlaceB(this);
    		onMove = new OnMove(this);
    		this.state = placeA;
    	}
    
    	public void move() {
    		System.out.println("指令:开始移动");
    		state.move();
    	}
    
    	public void stop() {
    		System.out.println("指令:停止移动");
    		state.stop();
    	}
    
    	public State getState() {
    		return state;
    	}
    
    	public void setState(State state) {
    		this.state = state;
    	}
    
    	public void setDirection(String direction) {
    		this.direction = direction;
    	}
    
    	public String getDirection() {
    		return direction;
    	}
    
    }

     

    19 备忘录模式

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

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

    • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。

    • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。

    • 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

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

    19.1 模式结构图和代码示例

    1. 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。

    2. 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。

    3. 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

     

    举例(发起者通过备忘录存储信息和获取信息),类图如下:

     

    1 备忘录接口

    public interface MementoIF {
    
    }
    

    2 备忘录

    public class Memento implements MementoIF{
    	
    	private String state;
    
    	public Memento(String state) {
    		this.state = state;
    	}
    	
    	public String getState(){
    		return state;
    	}
    	
    
    }
    

    3 发起者

    public class Originator {
    
    	private String state;
    
    	public String getState() {
    		return state;
    	}
    
    	public void setState(String state) {
    		this.state = state;
    	}
    
    	public Memento saveToMemento() {
    		return new Memento(state);
    	}
    
    	public String getStateFromMemento(MementoIF memento) {
    		return ((Memento) memento).getState();
    	}
    
    }

    4 管理者

    public class CareTaker {
    	
    	private List<MementoIF> mementoList = new ArrayList<MementoIF>();
    
    	public void add(MementoIF memento) {
    		mementoList.add(memento);
    	}
    
    	public MementoIF get(int index) {
    		return mementoList.get(index);
    	}
    
    }
    

     

    20 访问者模式

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

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

    1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。

    2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。

    3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。

    4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

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

    1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。

    2. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。

    3. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

    20.1 模式结构和代码示例

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

    1. 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。

    2. 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。

    3. 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。

    4. 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。

    5. 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

    1 抽象访问者

    public interface Visitor {
    
    	abstract public void Visit(Element element);
    }
    

    2 具体访问者

    public class CompensationVisitor implements Visitor {
    
    	@Override
    	public void Visit(Element element) {
    		// TODO Auto-generated method stub
    		Employee employee = ((Employee) element);
    
    		System.out.println(
    				employee.getName() + "'s Compensation is " + (employee.getDegree() * employee.getVacationDays() * 10));
    	}
    
    }
    

    3 抽象元素

    public interface Element {
    	abstract public void Accept(Visitor visitor);
    
    }
    

    4 具体元素

    public class CompensationVisitor implements Visitor {
    
    	@Override
    	public void Visit(Element element) {
    		// TODO Auto-generated method stub
    		Employee employee = ((Employee) element);
    
    		System.out.println(
    				employee.getName() + "'s Compensation is " + (employee.getDegree() * employee.getVacationDays() * 10));
    	}
    
    }
    

    5 对象结构

    public class ObjectStructure {
    	private HashMap<String, Employee> employees;
    
    	public ObjectStructure() {
    		employees = new HashMap();
    	}
    
    	public void Attach(Employee employee) {
    		employees.put(employee.getName(), employee);
    	}
    
    	public void Detach(Employee employee) {
    		employees.remove(employee);
    	}
    
    	public Employee getEmployee(String name) {
    		return employees.get(name);
    	}
    
    	public void Accept(Visitor visitor) {
    		for (Employee e : employees.values()) {
    			e.Accept(visitor);
    		}
    	}
    
    }

     

    21 中介者模式

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

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

    1. 降低了对象之间的耦合性,使得对象易于独立地被复用。

    2. 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。

    其主要缺点是:当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。

    21.1 模式结构和代码示例

    1. 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。

    2. 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。

    3. 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。

    4. 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

    举例(通过中介卖方),类图如下:

    1 抽象中介者

    public interface Mediator {
    
    	void register(Colleague colleague); // 客户注册
    
    	void relay(String from, String to,String ad); // 转发
    
    }
    

    2 具体中介者

    public class ConcreteMediator implements Mediator {
    
    	private List<Colleague> colleagues = new ArrayList<Colleague>();
    
    	@Override
    	public void register(Colleague colleague) {
    		// TODO Auto-generated method stub
    		if (!colleagues.contains(colleague)) {
    			colleagues.add(colleague);
    			colleague.setMedium(this);
    		}
    	}
    
    	@Override
    	public void relay(String from, String to, String ad) {
    		// TODO Auto-generated method stub
    		for (Colleague cl : colleagues) {
    
    			String name = cl.getName();
    			if (name.equals(to)) {
    				cl.receive(from, ad);
    			}
    
    		}
    
    	}
    
    }

    3 抽象同事类

    public abstract class Colleague {
    
    	protected Mediator mediator;
    	protected String name;
    
    	public Colleague(String name) {
    		this.name = name;
    	}
    
    	public void setMedium(Mediator mediator) {
    
    		this.mediator = mediator;
    
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public abstract void Send(String to, String ad);
    
    	public abstract void receive(String from, String ad);
    
    }

    4 具体同事类

    public class Buyer extends Colleague {
    
    	public Buyer(String name) {
    
    		super(name);
    
    	}
    
    	@Override
    	public void Send(String to, String ad) {
    		// TODO Auto-generated method stub
    		mediator.relay(name, to, ad);
    	}
    
    	@Override
    	public void receive(String from, String ad) {
    		// TODO Auto-generated method stub
    		System.out.println(name + "接收到来自" + from + "的消息:" + ad);
    	}
    
    }

     

    展开全文
  • 导读: Material Design & iOS 13 黑暗模式总结探索 暗黑模式苹果开发文档 ...苹果官方强烈建议适配暗黑模式(Dark Mode)此功能也是为了开发者能慢慢将应用适配暗黑模式,所以想通过此功能不进行适配暗黑...

    导读:
    Material Design & iOS 13 黑暗模式总结探索

    暗黑模式苹果开发文档

    如何不进行系统切换样式的适配

    注意

        同一工程内多个Assets文件在打包后,就会生成一个Assets.car 文件,所以要保证Assets内资源文件的名字不能相同。
        苹果官方强烈建议适配暗黑模式(Dark Mode)此功能也是为了开发者能慢慢将应用适配暗黑模式,所以想通过此功能不进行适配暗黑模式,预计将会被拒。
    

    暗黑模式的优点:

    • 省电
    • 沉浸式效果明显
      深色背景的优势是可以突出与我们主要交互操作的内容,弱化其他辅助元素并降低屏幕整体的亮度减少视觉压力。

    全局关闭暗黑模式

    在Info.plist 文件中,添加UIUserInterfaceStyle key 名字为 User Interface Style 值为String, 将UIUserInterfaceStyle key 的值设置为 Light

    单个界面不遵循暗黑模式

    UIViewController与UIView 都新增一个属性 overrideUserInterfaceStyle
    overrideUserInterfaceStyle 设置为对应的模式,则强制限制该元素与其子元素以设置的模式进行展示,不跟随系统模式改变进行改变

    • 设置 ViewController 的该属性, 将会影响视图控制器的视图和子视图控制器采用该样式
    • 设置 View 的该属性, 将会影响视图及其所有子视图采用该样式
    • 设置 Window 的该属性, 将会影响窗口中的所有内容都采用样式,包括根视图控制器和在该窗口中显示内容的所有演示控制器(UIPresentationController)

    如何在代码里进行适配颜色(UIColor)

    + (UIColor *)colorWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    - (UIColor *)initWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    
    
    

    e.g.

    [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trait) {
        if (trait.userInterfaceStyle == UIUserInterfaceStyleDark) {
            return UIColorRGB(0x000000);
        } else {
            return UIColorRGB(0xFFFFFF);
        }
     }];
    

    系统调用更新方法,自定义重绘视图

    当用户更改外观时,系统会通知所有window与View需要更新样式,在此过程中iOS会触发以下方法

    UIView
    traitCollectionDidChange(_:)
    layoutSubviews()
    draw(_:)
    updateConstraints()
    tintColorDidChange()
    
    UIViewController
    traitCollectionDidChange(_:)
    updateViewConstraints()
    viewWillLayoutSubviews()
    viewDidLayoutSubviews()
    
    UIPresentationController
    traitCollectionDidChange(_:)
    containerViewWillLayoutSubviews()
    containerViewDidLayoutSubviews()
    

    适配Dark Mode

    • 颜色适配
    • 图片适配

    1 颜色适配

    • iOS13系统动态颜色

    iOS13 之前 UIColor只能表示一种颜色,而从 iOS13 开始UIColor是一个动态的颜色,在LightModeDark Mode可以分别设置不同的颜色。
    iOS13系统提供了一些动态颜色

    @property (class, nonatomic, readonly) UIColor *systemBrownColor        API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    @property (class, nonatomic, readonly) UIColor *systemIndigoColor       API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    @property (class, nonatomic, readonly) UIColor *systemGray2Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *systemGray3Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *systemGray4Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *systemGray5Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *systemGray6Color        API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *labelColor              API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    @property (class, nonatomic, readonly) UIColor *secondaryLabelColor     API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    @property (class, nonatomic, readonly) UIColor *tertiaryLabelColor      API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    @property (class, nonatomic, readonly) UIColor *quaternaryLabelColor    API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    @property (class, nonatomic, readonly) UIColor *linkColor               API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    @property (class, nonatomic, readonly) UIColor *placeholderTextColor    API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    @property (class, nonatomic, readonly) UIColor *separatorColor          API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    @property (class, nonatomic, readonly) UIColor *opaqueSeparatorColor    API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    @property (class, nonatomic, readonly) UIColor *systemBackgroundColor                   API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *secondarySystemBackgroundColor          API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *tertiarySystemBackgroundColor           API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *systemGroupedBackgroundColor            API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *secondarySystemGroupedBackgroundColor   API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *tertiarySystemGroupedBackgroundColor    API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *systemFillColor                         API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *secondarySystemFillColor                API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *tertiarySystemFillColor                 API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    @property (class, nonatomic, readonly) UIColor *quaternarySystemFillColor               API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
    
    
    
    

    ① 实例

    [self.view setBackgroundColor:[UIColor systemBackgroundColor]];
    [self.titleLabel setTextColor:[UIColor labelColor]];
    [self.detailLabel setTextColor:[UIColor placeholderTextColor]];
    

    效果展示
    在这里插入图片描述
    用法和iOS13之前的一样,使用系统提供的这些动态颜色,不需要其他的适配操作

    • 自定义动态UIColor
      在实际开发过程,系统提供的这些颜色还远远不够,因此我们需要创建更多的动态颜色

    初始化动态UIColor方法

    iOS13 UIColor增加了两个初始化方法,使用以下方法可以创建动态UIColor
    注:一个是类方法,一个是实例方法

    + (UIColor *)colorWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    - (UIColor *)initWithDynamicProvider:(UIColor * (^)(UITraitCollection *))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
    
    
    
    • 这两个方法要求传一个block进去
    • 当系统在LightModeDarkMode之间相互切换时就会触发此回调
    • 这个block会返回一个UITraitCollection
    • 我们需要使用其属性userInterfaceStyle,它是一个枚举类型,会告诉我们当前是LightMode还是DarkMode
    typedef NS_ENUM(NSInteger, UIUserInterfaceStyle) {
        UIUserInterfaceStyleUnspecified,
        UIUserInterfaceStyleLight,
        UIUserInterfaceStyleDark,
    } API_AVAILABLE(tvos(10.0)) API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos);
    

    ② 实例

    UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {
            if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
                return [UIColor redColor];
            }
            else {
                return [UIColor greenColor];
            }
        }];
        
     [self.bgView setBackgroundColor:dyColor];
    
    

    效果展示
    在这里插入图片描述

    2 图片适配

    • 打开Assets.xcassets
    • 新建一个Image set
      在这里插入图片描述
    • 打开右侧工具栏,点击最后一栏,找到Appearances,选择Any,Dark
      在这里插入图片描述
    • 将两种模式下不同的图片资源都拖进去
      在这里插入图片描述
    • 使用该图片
    [_logoImage setImage:[UIImage imageNamed:@"icon_logo"]];
    

    在这里插入图片描述
    大功告成,完成了颜色和图片的Dark Mode适配。

    获取当前模式(Light or Dark)

    有时候我们需要知道当前处于什么模式,并根据不同的模式执行不同的操作 iOS13中CGColor依然只能表示单一的颜色通过调用UITraitCollection.currentTraitCollection.userInterfaceStyle获取当前模式

    if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
            [self.titleLabel setText:@"DarkMode"];
        }
        else {
            [self.titleLabel setText:@"LightMode"];
        }
        
    

    3. 其他

    1.监听模式切换

    有时我们需要监听系统模式的变化,并作出响应
    那么我们就需要在需要监听的viewController中,重写下列函数

    // 注意:参数为变化前的traitCollection
    - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection;
    
    // 判断两个UITraitCollection对象是否不同
    - (BOOL)hasDifferentColorAppearanceComparedToTraitCollection:(UITraitCollection *)traitCollection;
    
    

    ① 示例

    - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
        [super traitCollectionDidChange:previousTraitCollection];
        // trait发生了改变
        if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
        // 执行操作
        }
        }
        
    

    2.CGColor适配

    我们知道iOS13后,UIColor能够表示动态颜色,但是CGColor依然只能表示一种颜色,那么对于CALayer等对象如何适配暗黑模式呢?当然是利用上一节提到的监听模式切换的方法啦。

    ① 方式一:resolvedColor

    // 通过当前traitCollection得到对应UIColor
    // 将UIColor转换为CGColor
    - (UIColor *)resolvedColorWithTraitCollection:(UITraitCollection *)traitCollection;
    

    实例

    - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
        [super traitCollectionDidChange:previousTraitCollection];
        
        UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {
            if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
                return [UIColor redColor];
            }
            else {
                return [UIColor greenColor];
            }
        }];
        UIColor *resolvedColor = [dyColor resolvedColorWithTraitCollection:previousTraitCollection];
        layer.backgroundColor = resolvedColor.CGColor;
        
    

    ② 方式二:performAsCurrent

    // 使用当前trainCollection调用此方法
    - (void)performAsCurrentTraitCollection:(void (^)(void))actions;
    

    示例

    - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
        [super traitCollectionDidChange:previousTraitCollection];
        
        UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {
            if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
                return [UIColor redColor];
            }
            else {
                return [UIColor greenColor];
            }
        }];
        [self.traitCollection performAsCurrentTraitCollection:^{
            layer.backgroundColor = dyColor.CGColor;
        }];
        
    }
    
    

    方式三:最简单的方法
    直接设置为一个动态UIColor的CGColor即可

    - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
        [super traitCollectionDidChange:previousTraitCollection];
        UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {
            if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
                return [UIColor redColor];
            }
            else {
                return [UIColor greenColor];
            }
        }];
            layer.backgroundColor = dyColor.CGColor;
    }
    
    
    

    ⚠️!!! 设置layer颜色都是在traitCollectionDidChange中,意味着如果没有发生模式切换,layer将会没有颜色,需要设置一个基本颜色

    3.模式切换时打印log

    • 在Xcode菜单栏Product->Scheme->Edit Scheme
    • 选择Run->Arguments->Arguments Passed On Launch
    • 添加以下命令即可
      UITraitCollectionChangeLoggingEnabled YES
      在这里插入图片描述

    4.强行设置App模式

    当系统设置为Light Mode时,对某些App的个别页面希望一直显示Dark Mode下的样式,这个时候就需要强行设置当前ViewController的模式了

    // 设置当前view或viewCongtroller的模式
    @property(nonatomic) UIUserInterfaceStyle overrideUserInterfaceStyle;
    

    示例

    // 设置为Dark Mode即可
    [self setOverrideUserInterfaceStyle:UIUserInterfaceStyleDark];
    

    ⚠️ 注意!!!

    • 当我们强行设置当前viewControllerDark Mode后,这个viewController下的view都是Dark Mode
    • 由这个ViewController present出的ViewController不会受到影响,依然跟随系统的模式
    • 要想一键设置App下所有的ViewController都是Dark Mode,请直接在Window上执行overrideUserInterfaceStyle
    • window.rootViewController强行设置Dark Mode也不会影响后续present出的ViewController的模式

    5.NSAttributedString优化

    对于UILabel、UITextField、UITextView,在设置NSAttributedString时也要考虑适配Dark Mode,否则在切换模式时会与背景色融合,造成不好的体验

    推荐的做法

    // 添加一个NSForegroundColorAttributeName属性
    NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:16],NSForegroundColorAttributeName:[UIColor labelColor]};
    NSAttributedString *str = [[NSAttributedString alloc] initWithString:@"富文本文案" attributes:dic];
    
    
    

    总结

    总的来说,iOS13主要有以下变化:
    1.支持 Dark Mode
    2.UIColor变为动态颜色
    3.更新StatusBar样式
    4.更新UIActivityIndicatorView样式

    参考文档

    iOS开发如何适配暗黑模式(Dark Mode)
    iOS13 暗黑模式(Dark Mode)适配之OC版

    展开全文
  • 常用的几种设计模式详解

    千次阅读 2021-04-09 15:01:27
    设计模式的概述 设计模式分类 创建型模式 特点是将对象的创建与使用分离(解耦),有 单例、原型、工厂方法、抽象工厂、建造者等5种。 结构型模式 用于描述如何将类或对象按某种布局组成更大的结构,代理、...

    设计模式的概述

    设计模式分类

    • 创建型模式

      特点是将对象的创建与使用分离(解耦),有 单例、原型、工厂方法、抽象工厂、建造者等5种。

    • 结构型模式

      用于描述如何将类或对象按某种布局组成更大的结构,代理、适配器、桥接、装饰、享元、组合等7种。

    • 行为型模式

      用于描述类或对象之间相互协作共同完成 单个对象无法完成的任务,模板方法、策略命令、职责链、状态观察者、中介者、迭代器、访问者、备忘录、解释器等11种。

    UML

    包含了用例图、类图、对象图、状态图、活动图、时序图、协作图、构建图、部署图等9种。

    类图概述

    类图显示了模型的静态结构

    类的作用

    • 简化了人们对系统的理解

    • 是系统编码和测试的重要模型

    类图表示法

    类的表示方式

    在这里插入图片描述

    类与类之间关系表示方式

    关联关系

    用于表示一类与另一类对象之间的联系,如老师和学生

    • 1.单项关联

      在这里插入图片描述

    • 2.双向关联

    在这里插入图片描述

    • 3.自关联

      在这里插入图片描述

    聚合关系

    聚合关系是关联关系的一种,是强关联关系,是整体和部分之间的关系。

    聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。

    在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。下图所示是大学和教师的关系图:

    在这里插入图片描述

    组合关系

    组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。

    在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,没有了头,嘴也就不存在了。

    在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。下图所示是头和嘴的关系图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TxZN80TZ-1617951667348)(img/image-20191229173455149.png)]

    依赖关系

    依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。

    就是一个类里面有另一个类作参数

    在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示是司机和汽车的关系图,司机驾驶汽车:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vosJgQmb-1617951667348)(img/image-20191229173518926.png)]

    继承关系

    继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。

    在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。例如,Student 类和 Teacher 类都是 Person 类的子类,其类图如下图所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yJ4jmANX-1617951667349)(img/image-20191229173539838.png)]

    实现关系

    实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。

    在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如,汽车和船实现了交通工具,其类图如图 9 所示。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bEtOfEmv-1617951667349)(img/image-20191229173554296.png)]

    软件设计原则

    开闭原则

    对扩展开放,对修改关闭。在不修改原有的代码,实现一个热插拔的效果。简言之,是为了更好的扩展。我们可以使用接口和抽象类

    因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。

    里氏代换原则

    里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法

    如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。

    依赖倒转原则

    高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程(使用接口),这样就降低了客户与实现模块间的耦合。

    接口隔离原则

    客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上(接口的方法尽量拆分)。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OTncFwKD-1617951667349)(img/接口隔离.png)]

    迪米特法则

    迪米特法则又叫最少知识原则。

    只和你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。

    其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

    迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。

    合成复用原则

    合成复用原则是指:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

    通常类的复用分为继承复用和合成复用两种。

    采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:

    1. 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
    2. 对象间的耦合度低。可以在类的成员位置声明抽象。
    3. 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。

    如下,将继承复用改为聚合复用

    在这里插入图片描述

    修改后

    创建者模式

    创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。

    这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。

    可分为 单例模式、工厂方法模式、抽象工程模式、原型模式、建造者模式。

    单例模式

    单例模式的结构

    单例模式的实现

    1. 饿汉式

    2. 懒汉式

    3. 懒汉式-双重检查锁

      /**
       * 双重检查方式
       */
      public class Singleton { 
      
          //私有构造方法
          private Singleton() {}
      
          private static volatile Singleton instance;
      
         //对外提供静态方法获取该对象
          public static Singleton getInstance() {
      		//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
              if(instance == null) {
                  synchronized (Singleton.class) {
                      //抢到锁之后再次判断是否为null
                      if(instance == null) {
                          instance = new Singleton();
                      }
                  }
              }
              return instance;
          }
      }
      
    4. 枚举方式

      /**
       * 枚举方式
       */
      public enum Singleton {
          INSTANCE;
      }
      

    存在问题

    • 序列化反序列化破坏
    • 通过反射通过单例模式

    工厂模式

    概述

    ​ 在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严 重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。

    如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦

    工厂模式分三种,简单工厂模式,工厂方法模式,抽象工厂模式。

    简单工厂模式

    简单工厂不是模式,是一种编程习惯。

    • 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
    • 具体产品 :实现或者继承抽象产品的子类
    • 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。

    优缺点

    优点:

    封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。

    缺点:

    增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。

    扩展-静态工厂

    在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是

    23种设计模式中的。代码如下:

    public class SimpleCoffeeFactory {
        public static Coffee createCoffee(String type) {
            //声明Coffee类型的变量,根据不同类型创建不同的coffee子类对象
            Coffee coffee = null;
            if("american".equals(type)) {
                coffee = new AmericanCoffee();
            } else if("latte".equals(type)) {
                coffee = new LatteCoffee();
            } else {
                throw new RuntimeException("对不起,您所点的咖啡没有");
            }
    
            return coffee;
        }
    }
    

    工厂方法模式

    概念

    定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延

    迟到其工厂的子类。

    结构

    • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂

    方法来创建产品。

    • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法(重写抽象抽象工厂方法),完成具体产品的创建。

    • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。

    • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同

    具体工厂之间一一对应。

    实现

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YrFeCxGw-1617951667350)(img/工厂方法模式.png)]

    优缺点

    优点:

    用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;

    在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,

    满足开闭原则;

    缺点:

    每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

    抽象工厂模式

    前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。

    这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。

    本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示横轴是产品等级,也就是同一类产品;纵轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QNRaLyeM-1617951667350)(img/产品组产品等级.png)]

    概念

    抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

    结构

    • 抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品

    • 具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建

    • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多

      个抽象产品。

    • 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。

    实现

    现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。其中拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级,都是甜品;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是都属于美式风味)。所以他们是一个二维结构,分别表示产品等级,和产品组。这个案例可以使用抽象工厂模式实现,类图如下:

    如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。

    优缺点

    优点:

    当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

    缺点:

    当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。

    使用场景

    • 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。

    • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。

    • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。

    如:输入法换皮肤,一整套一起换。生成不同操作系统的程序。

    模式扩展

    简单工厂+配置文件解除耦合

    可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合(spring框架就是用的这个)。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。

    第一步:定义配置文件

    american=com.itheima.pattern.factory.config_factory.AmericanCoffee latte=com.itheima.pattern.factory.config_factory.LatteCoffee
    

    第二步:改进工厂类

    JDK源码解析-Coleection.iterator方法

    我们看通过类图看看结构:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGuLlJX1-1617951667351)(img/iterator方法.png)]

    Collection接口是抽象工厂类,ArrayList是具体的工厂类;Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。

    另:

    1,DateForamt类中的getInstance()方法使用的是工厂模式;
    
    2,Calendar类中的getInstance()方法使用的是工厂模式;
    

    原型模式

    概述

    用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

    结构

    原型模式包含如下角色:

    • 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。

    • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。

    • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8zhDipvn-1617951667351)(img/原型模式.png)]

    实现

    原型模式的克隆分为浅克隆和深克隆。

    浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

    深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

    Java中的Object类中提供了 clone() 方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。

    使用场景

    • 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
    • 性能和安全要求比较高。

    扩展-深克隆

    使用深克隆,可以使用对象流方法。

    建造者模式

    概述

    将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。

    • 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
    • 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
    • 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

    结构

    建造者(Builder)模式包含如下角色:

    • 抽象建造者类(AbstractBuilder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。

    • 具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例(实现原材料生产)。

    • 产品类(Product):要创建的复杂对象(原材料)。

    • 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建(组装原材料,构成整体)。

    类图如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JKqtGWxM-1617951667352)(img/建造者模式.png)]

    实例

    创建共享单车

    生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。

    这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体的建造者;Director是指挥者。类图如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UYnui6Yy-1617951667352)(img/建造者模式1.png)]

    优缺点

    优点:

    • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
    • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
    • 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
    • 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。

    缺点:

    造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

    使用场景

    建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。

    • 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
    • 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

    创建者模式对比

    工厂方法模式VS建造者模式

    工厂方法模式注重的是整体对象的创建方式建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。

    我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。

    抽象工厂模式VS建造者模式

    抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。

    建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

    如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

    结构型模式

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

    • 代理模式
    • 适配器模式
    • 装饰者模式
    • 桥接模式
    • 外观模式
    • 组合模式
    • 享元模式

    代理模式

    概述

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

    Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。

    结构

    代理(Proxy)模式分为三种角色:

    • 抽象主题(Subject)类(买票接口): 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
    • 具体主题(Real Subject)类(具体卖票): 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
    • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

    静态代理

    【例】火车站卖票

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IayrLQ4E-1617951667353)(img/静态代理.png)]

    JDK动态代理

    jdk动态代理结构和静态代理一样。不同的是静态只有一个proxyPoint代理点(类似于一个代理人),而静态代理将代理点升级为ProxyFactory(类似于一个代理公司),具体代理流程在ProxyFactory里面实现。

    使用了动态代理,我们思考下面问题:

    ProxyFactory是代理类吗?

    ProxyFactory不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。

    从上面的类中,我们可以看到以下几个信息:

    • 代理类($Proxy0)实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口
    • 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。

    动态代理的执行流程是什么样?

    执行流程如下:

    1. 在测试类中通过代理对象调用sell()方法
    2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
    3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
    4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法
    

    CGLIB动态代理

    CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

    CGLIB是第三方提供的包,所以需要引入jar包的坐标:

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>2.2.2</version>
    </dependency>
    

    三种代理的对比

    • jdk代理和CGLIB代理

      使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。

      在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用 CGLIB代理

    • 动态代理和静态代理

      动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)(集中处理,如在invoke()或intercept()中实现)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

      如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。

    优缺点

    优点:

    • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
    • 代理对象可以扩展目标对象的功能,功能增强
    • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

    缺点:

    • 增加了系统的复杂度;

    使用场景

    • 远程(Remote)代理

      本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节(这个就是RPC思想)。

    • 防火墙(Firewall)代理

      当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器(VPN)。

    • 保护(Protect or Access)代理

      控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

    适配器模式

    定义:

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

    ​ 适配器模式分为类适配器模式和对象适配器模式(常用),前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

    结构

    适配器模式(Adapter)包含以下主要角色:

    • 目标(Target)接口(SD卡):当前系统业务所期待的接口,它可以是抽象类或接口。
    • 适配者(Adaptee)类(TF卡):它是被访问和适配的现存组件库中的组件接口。
    • 适配器(Adapter)类(TF卡转SD卡):它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

    类适配器模式

    实现方式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。

    【例】读卡器

    现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2sT23us4-1617951667353)(img/适配器模式.png)]

    类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。

    对象适配器模式

    实现方式:对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。

    例】读卡器

    我们使用对象适配器模式将读卡器的案例进行改写。类图如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ka3eFwDy-1617951667354)(img/对象适配器模式.png)]

    应用场景

    • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致
    • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同

    装饰者模式

    定义

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

    结构

    装饰(Decorator)模式中的角色:

    • 抽象构件(对应案例的FastFood)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
    • 具体构件(炒饭、炒面)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
    • 抽象装饰(装饰者Garnish)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
    • 具体装饰(鸡蛋、培根)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

    案例

    我们使用装饰者模式对快餐店案例进行改进,体会装饰者模式的精髓。

    类图如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IusYzJAi-1617951667354)(img/装饰者模式.png)]

    这里主要写一下Garnish抽象类,它是继承自FastFood,且聚集了FastFood类

    //配料类
    public abstract class Garnish extends FastFood {
    
        private FastFood fastFood;
    
        public FastFood getFastFood() {
            return fastFood;
        }
    
        public void setFastFood(FastFood fastFood) {
            this.fastFood = fastFood;
        }
    
        public Garnish(FastFood fastFood, float price, String desc) {
            super(price,desc);
            this.fastFood = fastFood;
        }
    }
    

    优点

    • 饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
    • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

    使用场景

    • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。

      不能采用继承的情况主要有两类:

      • 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
      • 第二类是因为类定义不能继承(如final类)
    • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

    • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。

    小结:

    ​ BufferedWriter使用装饰者模式对Writer子实现类进行了增强,添加了缓冲区,提高了写数据的效率。

    代理和装饰者的区别

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

    • 相同点:
      • 都要实现与目标类相同的业务接口
      • 在两个类中都要声明目标对象
      • 都可以在不修改目标类的前提下增强目标方法
    • 不同点:
      • 目的不同
        装饰者是为了增强目标对象
        静态代理是为了保护和隐藏目标对象
      • 获取目标对象构建的地方不同
        装饰者是由外界传递进来,可以通过构造方法传递
        静态代理是在代理类内部创建,以此来隐藏目标对象

    桥接模式

    定义:

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

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

    • 抽象化(Abstraction)角色 :定义抽象类,并包含一个对实现化对象的引用。
    • 扩展抽象化(Refined Abstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
    • 实现化(Implementor)角色 :定义实现化角色的接口,供扩展抽象化角色调用。
    • 具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。

    案例

    【例】视频播放器

    需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个维度,适合使用桥接模式。

    类图如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M1Klo1c3-1617951667355)(img/桥接模式.png)]

    优点:

    • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。

      如:如果现在还有一种视频文件类型wmv,我们只需要再定义一个类实现VideoFile接口即可,其他类不需要发生变化。

    • 实现细节对客户透明

    使用场景

    • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
    • 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
    • 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。

    行为型模式

    行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。

    行为型模式分为:

    • 模板方法模式
    • 策略模式
    • 命令模式
    • 职责链模式
    • 状态模式
    • 观察者模式
    • 中介者模式
    • 迭代器模式
    • 访问者模式
    • 备忘录模式
    • 解释器模式

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

    模板方法模式

    在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。

    定义

    定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。(父类中抽象声明,子类重新定义或重写)

    结构

    模板方法(Template Method)模式包含以下主要角色:

    • 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。

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

      • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:

        • 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。

        • 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。

        • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型

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

    实现案例

    例】炒菜

    炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gqgqu1Er-1617951667355)(img/模板方法模式.png)]

    优缺点

    优点:

    • 提高代码复用性

    • 实现了反向控制

    缺点:

    • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
    • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

    使用场景

    • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
    • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制(钩子函数)

    JDK源码应用

    InputStream类就使用了模板方法模式。在InputStream类中定义了多个 read() 方法。

    策略模式

    概述

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

    结构

    • 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口
    • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
    • 环境(Context)类:持有一个策略类的引用,最终给客户端调用

    案例实现

    【例】促销活动

    一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l11DwO7E-1617951667356)(img/策略模式.png)]

    优缺点

    1,优点:

    • 策略类之间可以自由切换

    • 易于扩展

    • 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。

    2,缺点:

    • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
    • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

    使用场景

    • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
    • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句(可替换大量if else语句)。
    • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
    • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构
    • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

    JDK源码应用

    Comparator 中的策略模式。在Arrays类中有一个 sort() 方法。

    命令模式

    概述

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

    结构

    • 抽象命令类(Command): 定义命令的接口,声明执行的方法。
    • 具体命令(Concrete Command):具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
    • 实现者/接收者(厨师)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
    • 调用者/请求者(服务员)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。

    案例实现

    将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当。

    服务员: 就是调用者角色,由她来发起命令。

    资深大厨: 就是接收者角色,真正命令执行的对象。

    订单: 命令中包含订单。

    类图如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oj33tUTZ-1617951667356)(img/命令模式.png)]

    优缺点

    1,优点:

    • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
    • 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
    • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
    • 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复

    2,缺点:

    • 使用命令模式可能会导致某些系统有过多的具体命令类。
    • 系统结构更加复杂。

    使用场景

    • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
    • 系统需要在不同的时间指定请求、将请求排队和执行请求。
    • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

    JDK源码应用

    Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法

    责任链模式

    概述

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

    结构

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

    • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
    • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
    • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

    案例实现

    现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天还需要总经理同意才行。

    类图如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7DgrqG1n-1617951667356)(img/责任链模式.png)]

    优缺点

    1,优点:

    • 降低了对象之间的耦合度

    • 增强了系统的可扩展性

    • 增强了给对象指派职责的灵活性当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。

    • 责任链简化了对象之间的连接

    • 责任分担

    2,缺点:

    • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
    • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
    • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

    JDK源码应用

    在javaWeb应用开发中,FilterChain是职责链(过滤器)模式的典型应用

    Spring自定义框架

    spring回顾

    spring使用结构有数据访问层(dao层),到业务逻辑层(service层),到控制层(controller层),到配置文件。我们可以看出:

    • userService对象是从applicationContext容器对象获取到的,也就是userService对象交由spring进行管理。
    • 上面结果可以看到调用了UserDao对象中的add方法,也就是说UserDao子实现类对象也交由spring管理了。
    • UserService中的userDao变量我们并没有进行赋值,但是可以正常使用,说明spring已经将UserDao对象赋值给了userDao变量。

    上面三点体现了Spring框架的IOC(Inversion of Control)和DI(Dependency Injection, DI)

    spring核心功能

    Spring大约有20个模块,由1300多个不同的文件构成。这些模块可以分为:

    核心容器、AOP和设备支持、数据访问与集成、Web组件、通信报文和集成测试等,下面是 Spring 框架的总体架构图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gYDfAq7M-1617951667357)(img/image-20200429111324770.png)]

    核心容器由 beans、core、context 和 expression(Spring Expression Language,SpEL)4个模块组成。

    • spring-beans和spring-core模块是Spring框架的核心模块,包含了控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)。BeanFactory使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。BeanFactory属于延时加载,也就是说在实例化容器对象后并不会自动实例化Bean,只有当Bean被使用时,BeanFactory才会对该 Bean 进行实例化与依赖关系的装配。
    • spring-context模块构架于核心模块之上,扩展了BeanFactory,为它添加了Bean生命周期控制、框架事件体系及资源加载透明化等功能。此外,该模块还提供了许多企业级支持,如邮件访问、远程访问、任务调度等,ApplicationContext 是该模块的核心接口,它的超类是 BeanFactory。与BeanFactory不同,ApplicationContext实例化后(非延时加载)会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。
    • spring-context-support模块是对Spring IoC容器及IoC子容器的扩展支持。
    • spring-context-indexer模块是Spring的类管理组件和Classpath扫描组件。
    • spring-expression 模块是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也可以方便地调用对象方法,以及操作数组、集合等。它的语法类似于传统EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。EL的特性是基于Spring产品的需求而设计的,可以非常方便地同Spring IoC进行交互

    bean的概述

    Spring 就是面向 Bean 的编程(BOP,Bean Oriented Programming),Bean 在 Spring 中处于核心地位。Bean对于Spring的意义就像Object对于OOP的意义一样,Spring中没有Bean也就没有Spring存在的意义。Spring IoC容器通过配置文件或者注解的方式来管理bean对象之间的依赖关系。

    spring中bean用于对一个类进行封装。如下面的配置:

    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
    

    为什么Bean如此重要呢?

    • spring 将bean对象交由一个叫IOC容器进行管理。
    • bean对象之间的依赖关系在配置文件中体现,并由spring完成。

    spring ioc相关接口分析

    BeanFactory解析

    Spring中Bean的创建是典型的工厂模式,这一系列的Bean工厂(简单工厂+配置文件),即IoC容器,为开发者管理对象之间的依赖关系提供了很多便利和基础服务,在Spring中有许多IoC容器的实现供用户选择,其相互关系如下图所示。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-csxPIRk6-1617951667357)(img/image-20200429185050396.png)]

    ​ 其中,BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范,BeanFactory有三个重要的子接口:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是从类图中我们可以发现最终的默认实现类是DefaultListableBeanFactory,它实现了所有的接口

    那么为何要定义这么多层次的接口呢?

    每个接口都有它的使用场合,主要是为了区分在Spring内部操作过程中对象的传递和转化,对对象的数据访问所做的限制。例如,

    • ListableBeanFactory接口表示这些Bean可列表化(通过列表存储)
    • HierarchicalBeanFactory表示这些Bean 是有继承关系的,也就是每个 Bean 可能有父 Bean
    • AutowireCapableBeanFactory 接口定义Bean的自动装配规则。

    这三个接口共同定义了Bean的集合、Bean之间的关系及Bean行为。最基本的IoC容器接口是BeanFactory,来看一下它的源码:

    public interface BeanFactory {
    
    	String FACTORY_BEAN_PREFIX = "&";
    
    	//根据bean的名称获取IOC容器中的的bean对象
    	Object getBean(String name) throws BeansException;
    	//根据bean的名称获取IOC容器中的的bean对象,并指定获取到的bean对象的类型,这样我们使用时就不需要进行类型强转了
    	<T> T getBean(String name, Class<T> requiredType) throws BeansException;
    	Object getBean(String name, Object... args) throws BeansException;
    	<T> T getBean(Class<T> requiredType) throws BeansException;
    	<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
    	
    	<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
    	<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
    
    	//判断容器中是否包含指定名称的bean对象
    	boolean containsBean(String name);
    	//根据bean的名称判断是否是单例
    	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    	boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
    	boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
    	@Nullable
    	Class<?> getType(String name) throws NoSuchBeanDefinitionException;
    	String[] getAliases(String name);
    }
    

    在BeanFactory里只对IoC容器的基本行为做了定义,根本不关心你的Bean是如何定义及怎样加载的。正如我们只关心能从工厂里得到什么产品,不关心工厂是怎么生产这些产品的。

    BeanFactory有一个很重要的子接口,就是ApplicationContext接口,该接口主要来规范容器中的bean对象是非延时加载,即在创建容器对象的时候就对象bean进行初始化,并存储到一个容器中。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RiJ7KXmg-1617951667358)(img/image-20200430220155371.png)]

    要知道工厂是如何产生对象的,我们需要看具体的IoC容器实现,Spring提供了许多IoC容器实现,比如:

    • ClasspathXmlApplicationContext : 根据类路径加载xml配置文件,并创建IOC容器对象。
    • FileSystemXmlApplicationContext :根据系统路径加载xml配置文件,并创建IOC容器对象。
    • AnnotationConfigApplicationContext :加载注解类配置,并创建IOC容器。

    BeanDefinition解析

    Spring IoC容器管理我们定义的各种Bean对象及其相互关系,而Bean对象在Spring实现中是以BeanDefinition来描述的(封装配置文件的对象),如下面配置文件

    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
    
    bean标签还有很多属性:
    	scope、init-method、destory-method等。
    

    其继承体系如下图所示。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dNeq9x3h-1617951667358)(img/image-20200429204239868.png)]

    看看BeanDefinitionReader接口定义的功能来理解它具体的作用:

    public interface BeanDefinitionReader {
    
    	//获取BeanDefinitionRegistry注册器对象
    	BeanDefinitionRegistry getRegistry();
    
    	@Nullable
    	ResourceLoader getResourceLoader();
    
    	@Nullable
    	ClassLoader getBeanClassLoader();
    
    	BeanNameGenerator getBeanNameGenerator();
    
    	/*
    		下面的loadBeanDefinitions都是加载bean定义,从指定的资源中
    	*/
    	int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
    	int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
    	int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
    	int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
    }
    

    BeanDefinitionReader解析

    Bean的解析过程非常复杂,功能被分得很细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化。Bean的解析主要就是对Spring配置文件的解析。这个解析过程主要通过BeanDefinitionReader来完成对Bean的解析,看看Spring中BeanDefinitionReader的类结构图,如下图所示。

    看看BeanDefinitionReader接口定义的功能来理解它具体的作用:

    public interface BeanDefinitionReader {
    
    	//获取BeanDefinitionRegistry注册器对象
    	BeanDefinitionRegistry getRegistry();
    
    	@Nullable
    	ResourceLoader getResourceLoader();
    
    	@Nullable
    	ClassLoader getBeanClassLoader();
    
    	BeanNameGenerator getBeanNameGenerator();
    
    	/*
    		下面的loadBeanDefinitions都是加载bean定义,从指定的资源中
    	*/
    	int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
    	int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
    	int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
    	int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
    }
    

    BeanDefinitionRegistry解析

    BeanDefinitionReader用来解析bean定义,并封装BeanDefinition对象,而我们定义的配置文件中定义了很多bean标签,所以就有一个问题,解析的BeanDefinition对象存储到哪儿?答案就是BeanDefinition的注册中心,而该注册中心顶层接口就是BeanDefinitionRegistry。

    public interface BeanDefinitionRegistry extends AliasRegistry {
    
    	//往注册表中注册bean
    	void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
    			throws BeanDefinitionStoreException;
    
    	//从注册表中删除指定名称的bean
    	void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
    
    	//获取注册表中指定名称的bean
    	BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
        
    	//判断注册表中是否已经注册了指定名称的bean
    	boolean containsBeanDefinition(String beanName);
        
    	//获取注册表中所有的bean的名称
    	String[] getBeanDefinitionNames();
        
    	int getBeanDefinitionCount();
    	boolean isBeanNameInUse(String beanName);
    }
    

    继承结构图如下:

    从上面类图可以看到BeanDefinitionRegistry接口的子实现类主要有以下几个:

    • DefaultListableBeanFactory

      在该类中定义了如下代码,就是用来注册bean

      private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
      
    • SimpleBeanDefinitionRegistry

      在该类中定义了如下代码,就是用来注册bean

      private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);
      

    SpringIOC总结

    使用到的设计模式

    • 工厂模式。这个使用工厂模式 + 配置文件的方式。
    • 单例模式。Spring IOC管理的bean对象都是单例的,此处的单例不是通过构造器进行单例的控制的,而是spring框架对每一个bean只创建了一个对象。
    • 模板方法模式。AbstractApplicationContext类中的finishBeanInitialization()方法调用了子类的getBean()方法,因为getBean()的实现和环境息息相关。
    • 迭代器模式。对于MutablePropertyValues类定义使用到了迭代器模式,因为此类存储并管理PropertyValue对象,也属于一个容器,所以给该容器提供一个遍历方式。

    spring框架其实使用到了很多设计模式,如AOP使用到了代理模式,选择JDK代理或者CGLIB代理使用到了策略模式,还有适配器模式,装饰者模式,观察者模式等。

    说明

    此笔记是我学习黑马程序员课程做的笔记,方便以后查阅。

    展开全文
  • 匹配当前众多的超级平台,中兴平台等,无不符合平台战略的商业模式。 平台战略的商业模式的核心:最大程度促进平台内多方“玩家”的互动 ,让一方带动另一方的增长(良性循环机制:通过此平台交流 的各方也会促进对方...
  • 32位ARM的七种工作模式

    千次阅读 2019-05-24 15:33:19
    而当CPU处于用户模式(Linux处于用户态)时,Linux从用户态切换到内核态(ARM从用户模式切换到其他特权模式)只有在系统调用和 中断(中断进入IRQ模式,也可以操作内核) 两种情况下发生,一般程序一开始都是运行于...
  • } 静态:长度固定 动态:不够存放可以加空间(搬家) /* 任务名任务:1_2 动态顺序存储线性表的基本实现 */ #include #include #include #define LIST_INIT_SIZE 100 #define LISTINCREMENT 10 #define Status int...
  • ARM处理器工作模式

    千次阅读 2016-03-01 16:02:13
    1、用户模式(usr):正常的程序执行状态 2、快速中断模式(fiq): 3、中断模式(irq): 4、管理模式(svc):操作系统使用的保护模式 5、系统模式(sys):运行具有特权的操作系统任务 6、数据访问终止模式(abt...
  • 模式和保护模式

    万次阅读 多人点赞 2016-06-13 16:21:59
    80386开始,CPU有三种工作方式:实模式,保护模式和虚拟8086模式。只有在刚刚启动的时候是real-mode,等到操作系统运行起来以后就切换到protected-mode。实模式只能访问地址在1M以下的内存称为常规内存,我们把地址...
  • Java设计模式

    万次阅读 多人点赞 2019-09-03 23:20:31
    Java设计模式 1.工厂模式 工厂模式一般分为简单工厂、工厂、抽象工厂3钟情况,属于创建型设计模式。 2.生成器模式 3.观察者模式 4.桥接模式 5.代理模式 6.状态模式 7.访问者模式 ...
  • 什么是软件开发模式

    万次阅读 2019-04-09 00:05:43
    软件开发模式简介 1. 边做边改模型(Build-and-Fix Model)  好吧,其实现在许多产品实际都是使用的“边做边改”模型来开发的,特别是很多小公司产品周期压缩的太短。在这种模型中,既没有规格说明,也没有经过...
  • x86 实模式与保护模式

    千次阅读 2016-07-26 16:25:47
    0386开始,CPU有三种工作方式:实模式,保护模式和虚拟8086模式。只有在刚刚启动的时候是real-mode,等到操作系统运行起来以后就切换到protected-mode。实模式只能访问地址在1M以下的内存称为常规内存,我们把地址在...
  • 大小等 Cd:用于切换到目标目录 Mkdir:用于简历当前目录的目录 rm-r 删除的文件或者目录,需确认 rm –rf同上但无需确认 cp:复制文件 若复制目录则必须加上-r 数据库MySQL 一.索引 1.B树,B+树,以及两者的区别 B...
  • C#设计模式

    千次阅读 2018-12-15 21:15:44
    C#设计模式(1)——单例模式(Singleton Pattern) C#设计模式(2)——工厂方法模式(Factory Pattern) C#设计模式(3)——抽象工厂模式(Abstract Pattern) C#设计模式(4)——建造者模式(Builder Pattern) C#...
  • 四、ESP32单片机wifi的AP与STA模式使用

    千次阅读 多人点赞 2020-10-20 09:07:28
    在ESP32的 WIFI 中有两种模式是我们今天需要认识的,我们平时的wifi也是一样,他们就是我们今天的主题,AP模式与STA模式 > AP模式,指的无线接入点,创建一个无线网络的模式,家里的路由器就是最好的例子,通俗易懂...
  • Win7/Win10移动用户文件夹(C:\Users)移到非系统盘(如D:)

    万次阅读 多人点赞 2019-06-02 14:53:08
    Windows的用户文件夹默认所在位置是系统盘(通常是C盘)下的“\Users”目录之内。该文件夹中保存着所有的用户个人数据,比如你保存在“桌面”...用户文件夹处于系统盘的坏处在于,如若系统盘一旦坏掉,就可能连带用...
  • 分布式计算模式:Stream

    万次阅读 2021-12-24 20:20:31
    分布式计算模式:Stream前言什么是 Stream?Stream 工作原理Storm 的工作原理知识扩展:流计算和批量计算的区别是什么?总结 前言 分布式计算模式中的 MapReduce 模式的核心思想是,将大任务拆分成多个小任务,...
  • 用户模式:大部分情况下,程序是运行在用户模式下,此时某些被保护的寄存器是不能访问;非用户模式:除了用户模式之外的模式;也被称作特权模式。特权模式下,可以访问寄存器和一下片内资源。异常模式:处理用户模式...
  • 震惊!小猪的设计模式初涉总结!纯干货~

    万次阅读 多人点赞 2017-05-06 11:31:02
    小猪的设计模式初涉总结!纯干货~标签: 知识点总结描述性文字 今年一月初有了离职的念头后,就盘算着把设计模式给过一遍,索性就 开了一个新的系列:《如何让孩子爱上设计模式》,在编写过程中经历了 旧项目重构...
  • 大数据架构和模式

    万次阅读 2016-03-14 12:10:41
    实现预测分析战略的电信提供商可通过分析用户的呼叫模式来管理和预测流失。 市场营销:情绪分析 Web 和社交数据 营销部门使用 Twitter 源来执行情绪分析,以便确定用户对公司及其产品或服务的评价,尤其是在一个新...
  • 联想应用用户手册(QT)V3.9电子版 目录 服务分区特殊说明 联想教育应用方案注意事项 第1章 联想教育应用方案功能简介 功能特色 总体安装说明 第2章 联想教育应用的首次部署 安装前的准备 第一次布署的简要...
  • 抖音:运营与商业模式分析

    千次阅读 2020-10-23 10:30:58
    本文将简单分析抖音的运营与商业模式。 01抖音产品简介 1.产生背景 随着移动互联网的发展,移动端的使用习惯是碎片化使用,短视频作为新型的方式区别于传统的文字、图片、语音方式。通过动态视觉和立体听觉更加...
  • Android夜间模式的实现方案

    万次阅读 多人点赞 2017-07-27 23:45:44
    对于一款阅读类的软件,夜间模式是不可缺少的。最初看到这个需求时候觉得无从下手,没有一点头绪。后来通过查阅资料发现Android官方在Support Library 23.2.0中已经加入了夜间主题。也就是只需要通过更换主题便可...
  • 用户故事拆分是敏捷实施的入门实践——没有用户故事拆分,就没有真正意义上的迭代,也就没法做到敏捷所倡导的快速反馈、快速学习和快速价值交付。INVEST 原则常常被看做是用户故事拆分的基本要求[1]。但是,不少敏捷...
  • ² SaaS模式的提出虽然时间比较长,但市场还处于萌芽阶段;同业竞争不大,业务发展有巨大空间; ² 最新互联网技术的发展,使得该业务以及可延展业务的想象空间非常大; 表16-1产业活动单位的行业分布 单位数(万个)...
  • 因为处于 no 模式下的 flushAppendOnlyFile 调用无须执行同步操作, 所以该模式下的 AOF 文件写入速度总是最快的, 不过因为这种模式会在系统缓存中积累一段时间的写入数据, 所以该模式的单次同步时长通常是三...
  • 改变 Windows 用户文件夹默认位置

    万次阅读 2019-01-18 23:23:42
    Windows7的用户文件夹默认所在位置是系统盘(通常是C盘)下的“\Users”目录之内。该文件夹中储存着所有的用户生成文件,比如你保存在“桌面”上的文件(实际上是保存在C:\Users\YourUserName\Desktop目录之中)...
  • Android M新特性Doze and App Standby模式详解 深入android6.0 设备 idle状态 Android M 的 Doze 模式下第三方推送服务还能用吗?一、Optimizing for Doze and App Standby 从Android6.0开始,Android提供了两种省
  • 理解 Web 3 —— 用户控制的互联网

    千次阅读 2019-05-14 09:47:46
    这就是为什么多年以来广告始终是互联网的商业模式,因为广告是唯一可以有效存储并转移数十亿用户状态的业务。再强调一遍,广告从本质上来说无可厚非。不过这里的问题存在于三个方面: 第三方中介会促成每一笔广告...
  • CVAT 用户指南

    万次阅读 多人点赞 2020-08-02 17:46:57
    CVAT 用户指南 用户指南 入门 授权 管理面板 创建标注任务 基本配置 高级配置 模型 搜索 标注工具的界面 基本导航 形状类型(基础) 形状模式(基础) 轨迹模式(基础) 属性标注模式(基础) 下载标注 同步存储库...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 132,936
精华内容 53,174
关键字:

当前处于子用户模式