写点什么

2020-06-20- 第三周学习总结

发布于: 2020 年 06 月 24 日

1 反应式编程

1.1 程序是如何崩溃的?



当服务器处理并发用户请求时,Web 容器为每一个请求创建一个线程,线程内完成全部请求操作,返回响应。这个过程由于需要争夺线程池资源、数据库连接资源等操作,故而很有可能出现等待,导致用户线程等待,最终使得请求变得很慢。此时如果再有大量的用户请求,这将导致系统崩溃。

1.2 Flower 反应式编程



flower 通过异步的方式,用户请求进来以后,不管有多少将全部处理,并将请求封装成一个 Request 对象。然后将这个 Request 对象丢到后面,ServiceA 处理结果丢该 ServiceB,处理完毕后将返回给前台。包括对数据库的连接,使用反应式编程后,发送给数据库请求后将不会一直等待,只有当数据返回后进行回调,重新给一个线程处理结果。核心是:异步执行,消息驱动


Flower 是如何实现消息驱动的?


flower 中,Sender 作为调用者,Actor 作为被调用者,这两者的调用关系并不是在同一个线程中执行的。Sender 发送消息给 Actor 引用(ActorRef),然后将消息放进 Mailbox 就执行完毕,Actor 的执行则使用另一个线程,从 Mailbox 中获取消息,处理完毕后回一个消息。


1.3 Flower 核心模块设计



Flower 分布式微服务


Flower 源码地址

https://github.com/zhihuili/flower

2 面向对象的设计模式


设计模式:创建模式/结构模式/行为模式

OOD 的原则:OCP,DIP,LSP,SRP,ISP

OOD 的目标:强内聚、低耦合的程序。

实现程序的强内聚、低耦合的目标,会运用 OOD 的原则进行实现,有了原则我们就有了许多设计模式。所有的面向对象的可复用的解决方案都是设计模式。框架则是遵循设计模式,符合 OOD 设计原则,应用程序遵循一定的约定使用该框架。

2.1 什么是设计模式?


定义:

  • 每一种模式都描述了一种问题的通用方案,这种问题在我们环境中,不停地出现。

  • 设计模式是一种可重复使用的解决方案


一个设计模式的四个部分:

  • 模式的名称:由少量的字组成的名称,有助于我们表达我们的设计

  • 待解问题:描述了何时需要运用这种模式,以及运用模式的环境(上下文)。

  • 解决方案:描述了组成设计的元素(类和对象)、它们的关系、职责以及合作。但这种解决方案是抽象的,它不代表具体的实现。

  • 结论:运用这种方案所带来的利和弊。主要是指它对系统的弹性、扩展性和可移植性的影响。

2.2 设计模式的分类


从功能分:

  • 创建模式(Creational Patterns)

  • 结构模式(Structural Patterns)

  • 行为模式(Behavioral Patterns)


从方式划分:

  • 类模式

  • 对象模式

2.3 排序问题-如何创建一个对象?



2.3.1 简单工厂


应用程序需要一个冒泡排序器,则仅需要实现 Sorter 接口,而真正的冒泡排序器则使用 SorterFactory 工厂进行创建。Client 这里符合开闭原则,使用的是策略模式。


但当需要换一种排序方式的时候,需要修改工厂类。这不符合开闭原则,怎么办呢?可以做以下改进。

2.3.2 对简单工厂的改进一


使用反射的方式,需要哪种排序器,仅需要传进类的名称进行对象实例化。


虽然解决了 Factory 的 OCP 问题,但是需要修改 Client。而且丧失了编译时的类型安全,Client 和 Factory 均类型不安全。Client 仍然“知道”Sorter 的实现是什么。限制了 Sorter 的实现只能通过“默认构造函数”创建。

2.3.3 对简单工厂的改进二


从配置文件中获取对应的实现类名,这样 Client 和 Factory 均不需要修改,需要修改的是配置文件。


这种方式满足 OCP 原则,但是缺少编译时类型安全,限制了 Sorter 的实现只能通过“默认构造函数”创建。

2.4 单例模式(Singleton)

2.4.1 为什么要使用单例模式?


Singleton 模式保证产生单一实例,就是说一个类只产生一个实例。使用 Singleton 有两个原因:

  • 只有一个实例,可以减少实例频繁创建和销毁带来的资源消耗

  • 是当多个用户使用这个实例的时候,便于进行统一控制(比如打印机对象)


两种模式:预加载和懒加载。


实现单例模式的几种方式


饿汉式单例:一上来就加载,造成内存浪费。

/** * 饿汉式单例 */public class HungrySingleton {    private static HungrySingleton instance = new HungrySingleton();    private HungrySingleton(){}    public static HungrySingleton getInstance() {        return instance;    }}
复制代码


懒汉式单例:等到用的时候再加载

(1)简单判断单例,多线程无效

/** * 懒汉式单例 - 等到用的时候再加载,简单判断单例,多线程无效 */public class LazySingleton {    private static LazySingleton instance;    private LazySingleton(){}    public static LazySingleton getInstance() {        if(instance == null) {            instance = new LazySingleton();        }        return instance;    }}
复制代码


(2)加锁(synchronized)双重检测锁模式判断单例,指令重排会有问题

/** * 懒汉式单例 - 加锁(synchronized)双重检测锁模式判断单例,指令重排会有问题 */public class LazySingleton1 {    private static LazySingleton1 instance;    private LazySingleton1(){}    public static synchronized LazySingleton1 getInstance() {        if(instance == null) {            instance = new LazySingleton1();        }        return instance;    }}
复制代码


(3)双重检测锁 + volatile,解决指令重排问题。

/** * 懒汉式单例 - 双重校验锁(DCL, Double-Checked Locking),线程安全 */public class LazySingleton2 {    private volatile static LazySingleton2 instance;    private LazySingleton2(){}    public static LazySingleton2 getInstance() {        if(instance == null) {            synchronized(LazySingleton2.class) {                if(instance == null) {                    instance = new LazySingleton2();                }            }        }        return instance;    }}
复制代码


(4)使用静态内部类实现,反射会破坏以上单例模式

/** * 懒汉式单例 - 静态内部类,线程安全 */public class LazySingleton3 {
private static class SingletonHolder { private static final LazySingleton3 INSTANCE = new LazySingleton3(); }
private LazySingleton3(){} public static final LazySingleton3 getInstance() { return SingletonHolder.INSTANCE; }}
复制代码


(5)枚举类实现单例

单例模式的最佳方案

使用枚举解决反射破坏单例的问题。因为 Enum 枚举类(有参构造)不能允许使用反射机制。具体做法是在单例类中新建一个内部枚举类,在内部枚举类中实现该单例的实例化以及静态获取方法,并将该获取实例的静态方法暴露给单例类。

/** * 懒汉式单例 - 枚举类实现 */public enum LazySingleton4 {    INSTANCE;    public void doSomeThing() {        System.out.println("doSomeThing");    }}
复制代码


2.5 适配器模式(Adapter)


适配器就是把某个对象已经提供的方法,适配成另外一种接口对象,以供调用者使用。


最好使用对象的适配器,因为类的适配器违反了里氏替换原则,因为 SortableList 不仅是为了使用 ArrayList 的方法才继承的,ArrayList 也没有任何抽象。最好的方式是将其作为一个成员变量,即使用对象的适配器。


Flower 是 NIO 非阻塞线程。


Slf4j/log4j 等开发工具遵循一定的开发规范,这种思想便是依赖倒置。

3 Junit 中的设计模式

3.1 实现一个单元测试的步骤


创建测试类,从 TestCase 派生

(1)初始化:setUp()

(2)清除环境:tearDown()

(3)书写测试方法:testXxx()

3.2 Junit 单元测试的执行


策略模式有三种角色:应用程序策略接口策略实现,该模式使用策略接口进行开发,运行期间是执行的策略实现。上图有两个地方实现了策略模式:


(1)当前时序图中 TestCase 是一个策略接口抽象类,XyzTests 实现了这个抽象类,是策略实现,Eclipse 则是应用程序。

(2)另一个隐藏的策略模式是,Eclipse 定义了 Plugin 接口规范,Junit 实现了这个接口规范,作为一个 Eclipse 的插件。加载插件后,菜单里可以显示,输出可以有图形化显示,应用程序可以以 plugin 的形式调用它。其中应用程序是 Eclipse IDE,策略接口是 Plugin 接口,策略实现则是实现了这个插件接口的 Junit 框架。


整个依赖倒置原则就是策略模式,针对接口进行编程,具体的实现在运行期间加载进来。所有框架都是策略模式,比如 Spring/Tomcat 等。

4 模板方法模式(Template Method)


模板方法模式是扩展功能的最基本模式之一,它是一种“类的行为模式”。它是通过“继承”的方法来实现扩展,基类负责算法的轮廓和骨架,子类负责树算法的具体实现。基于“继承”的模板方法比“组合”更容易实现,在很多情况下,可以适当使用这种模式。

4.1 模板方法的形式


抽象方法(abstract):父类定义抽象方法,强制子类实现该步骤。

具体方法:父类定义具体的方法,子类不需要覆盖,但也可以覆盖。如果想明确告诉子类“不要覆盖它”,最好标明 final。

钩子方法:比如 setUp(),父类是空的实现,子类选择性覆盖。

4.2 Java Servlet 中的模板方法


模板方法在 GenericServlet 中的 service 方法。


servlet 接口中的 destroy 方法是接口,tomcat 中实现了这个方法,并且提供一个模板方法执行 init->service->destroy


策略模式通常与模板方法模式相结合使用。

5 策略模式(Strategy)


策略模式是扩展功能的另一种最基本的模式,它是一种“对象的行为模式”,通过“组合”的方法来实现扩展。


5.1 什么时候使用策略模式?

系统需要在多种算法中选择一种

重构系统时将条件语句转换成对于策略的多态性调用。

策略模式的优点(对比模板方法)

  • 将使用策略的人与策略的具体实现分离

  • 策略对象可以自由组合


策略模式可能存在的问题:

  • 策略模式仅仅封装了“算法的具体实现”,方便添加和替换算法。但它并不关心何时使用何种算法,这个必须由客户端来决定。


5.2 策略模式与模板方法的结合



应用程序去调用的并不是一个纯粹的接口,而是调用了一个模板方法,在模板方法中已经定义了通用的操作。而我们再实现模板方法的时候,按照模板方法暴露出来的方法做一定的处理。

6 组合模式(Composite)


组合模式,是一种“对象的结构模式”。适用于处理树形结构。


6.1 组合模式的应用


文件系统/AWT 控件


分析 Junit 背后的设计模式,设计模式是如何实现的,带来的价值是什么

7 装饰器模式(Decorator)


7.1 什么是装饰器模式


装饰模式是大家都实现一个接口,装饰者和被装饰者是通过构造函数传递的。

自己叫装饰者,传入的叫被装饰者。


装饰者模式可以互相装饰,所以使用接口的方式进行实现装饰者和被装饰者。

AnyThing t = new Moon(new Dream(new You(null)));t.exe();	// 输出:明月装饰了梦装饰了你AnyThing t = new Dream(new Moon(new You(null)));t.exe();	// 输出:梦装饰了明月装饰了你
复制代码


7.2 装饰器与其他模式的区别


装饰器与模板方法、策略模式的比较

  • 装饰器保持对象的功能不变,扩展其外围的功能

  • 模板方法和策略模式则保持算法的框架不变,而扩展其内部的实现


装饰器和继承的比较

  • 都可以用来扩展对象的功能

  • 但装饰器是动态的,继承是静态的

  • 装饰器可以任意组合,但这也使装饰器更复杂,有可能会组合出荒谬的结果


装饰器模式与代理模式的区别

对装饰器模式来说,装饰者(decorator)和被装饰者(decoratee)都实现同一个 接口。对代理模式来说,代理类(proxy class)和真实处理的类(real class)都实现同一个接口。他们之间的边界确实比较模糊,两者都是对类的方法进行扩展,具体区别如下:

1、装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了而已;代理模式强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。

2、装饰模式是以对客户端透明的方式扩展对象的功能,是继承方案的一个替代方案;代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用;

3、装饰模式是为装饰的对象增强功能;而代理模式对代理的对象施加控制,但不对对象本身的功能进行增强;

使用代理模式,代理和真实对象之间的的关系通常在编译时就已经确定了,而装饰者能够在运行时递归地被构造。

7.3 装饰器的应用


Java Servlet 的应用

  • HttpServletRequest/HttpServletRequestWrapper

  • HttpServeltResponse/HttpServletResponseWrapper


同步化装饰器

  • Collections.synchronized(list)

  • 取代原先的 Vector、HashTable 等同步类


Java I/O 类库简介

  • 核心:流,即数据的有序排列,将数据从源送达目的地

  • 流的种类

  • 流的对称性

8 Spring 中的设计模式


  • 依赖注入 DI 与控制反转 IoC

  • Spring 中的单例模式

  • Spring MVC 模式

9 Sql 语法解析为 Hive 语法

源码地址

https://github.com/zhihuili/project-panthera-ase/tree/master/ql/src/java/org/apache/hadoop/hive/ql/parse/sql


用户头像

还未添加个人签名 2020.05.11 加入

还未添加个人简介

评论

发布
暂无评论
2020-06-20-第三周学习总结