代理模式
代理模式在不改变原始类 (或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
为了将框架代码和业务代码解耦,代理模式就派上用场了。代理类 UserControllerProxy 和原始类 UserController 实现相同的接口 IUserController。UserController 类只负责业 务功能。代理类 UserControllerProxy 负责在业务代码执行前后附加其他逻辑代码,并通 过委托的方式调用原始类来执行业务代码。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253public interface IUserController { UserVo login(String telephone, String password); UserVo register(String telephone, String password);}// 这里只写业务逻辑public cl ...
原型模式
原型模式的原理与应用如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同), 在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新 对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式 (Prototype Design Pattern),简称原型模式。
什么是对象成本创建比较大如果对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值),或者需 要从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取,这种情况下,我们就可以利 用原型模式,从其他已有对象中直接拷贝得到,而不用每次在创建新对象的时候,都重复执 行这些耗时的操作。
原型模式的实现方式:深拷贝和浅拷贝散列表索引中,每个结点存储的 key 是搜索关 键词,value 是 SearchWord 对象的内存地址。SearchWord 对象本身存储在散列表之外 的内存空间中。
浅拷贝和深拷贝的区别在于,浅拷贝只会复制图中的索引(散列表),不会复制数据 (SearchWord 对象)本身。相反,深拷贝不仅仅会复制索引,还会复制数据本身。浅拷 ...
工厂模式(下)
这篇文章主要涉及的内容是依赖注入框架,或者叫依赖注入容器,简称DI容器。
工厂模式和 DI 容器的区别DI 容器底层最基本的设计思路就是基于工厂模式的。DI 容器相当于一个大的工厂 类,负责在程序启动的时候,根据配置(要创建哪些类对象,每个类对象的创建需要依赖哪 些其他类对象)事先创建好对象。当应用程序需要使用某个类对象的时候,直接从容器中获取即可。
一个工厂类只负责某个对象或者某一组相关类的创建,而DI容器负责的是整个应用中所有类对象的创建。
DI 容器的核心功能一般有三个:配置解析、对象创建和对象生命周期管理。
配置解析工厂类要创建哪个对象都是写死的,属于事先定义好的,但是DI容器并不知道应用会创建哪个对象,与不可能全部写死。
我们将需要由 DI 容器来创建的类对象和创建类对象的必要信息(使用哪个构造函数以及对 应的构造函数参数都是什么等等),放到配置文件中。容器读取配置文件,根据配置文件提供的信息来创建对象。
DI容器通过解析这些配置文件,来得知具体创建哪些对象。
对象创建在 DI 容器中,如果我们给每个类都对应创建一个工厂类,那项目中类的个数会成倍增加,我们只需要将所有类对象的 ...
工厂模式(上)
一般情况下,工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。其中抽象工厂是最复杂得一个,在本篇文章中,之介绍简单工厂和抽象工厂。
简单工厂(Simple Factory)如下代码,我们根据配置文件的后缀(json、xml、yaml、properties),选择不 同的解析器(JsonRuleConfigParser、XmlRuleConfigParser……),将存储在文件中的 配置解析成内存对象 RuleConfig。
1234567891011121314151617181920212223242526public class RuleConfigSource { public RuleConfig load(String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParser parser = null; if ("json& ...
单例模式(下)
如何理解单例模式中的唯一性这个唯一性指的是进程内只允许创建一个对象。
如何实现线程唯一的单例“进程唯一”指的是进程内唯一,进程间不唯一。“线程唯一”指的是线程内唯 一,线程间可以不唯一。
我们通过一个 HashMap 来存储对象,其中 key 是线程 ID,value 是对象。这样我们就可以 做到,不同的线程对应不同的对象,同一个线程只能对应一个对象。实际上,Java 语言本 身提供了 ThreadLocal 工具类,可以更加轻松地实现线程唯一单例。不过,ThreadLocal 底层实现原理也是基于下面代码中所示的 HashMap。
1234567891011121314public class IdGenerator { private AtomicLong id = new AtomicLong(0); private static final ConcurrentHashMap<Long, IdGenerator> instances = new ConcurrentHashMap<>(); private IdG ...
单例模式(中)
单例存在的问题我们在项目中使用单例,都是用它来表示一些全局唯一类,比如配置信息 类、连接池类、ID 生成器类,但 是,这种使用方法有点类似硬编码(hard code),会带来诸多问题。
1、对 OOP 特性的支持不友好1234567891011121314public class Order { public void create(...) { //... long id = IdGenerator.getInstance().getId(); //... }}public class User { public void create(...) { // ... long id = IdGenerator.getInstance().getId(); //... }}
这种涉及违背了基于接口而非实现的设计原则,如果未来某一天,我们希望针对不同的业务采用不同的 ID 生成算法。 比如,订单 ID ...
单例模式(上)
单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
案例1:处理资源访问冲突12345678910111213141516171819202122232425public class Logger { private FileWriter writer; public Logger() { File file = new File("/Users/wangzheng/log.txt"); writer = new FileWriter(file, true); //true表示追加写入 } public void log(String message) { writer.write(mesasge); }}// Logger类的应用示例:public class UserController ...
NIO优化实现原理
网络 I/O 模型优化最开始的阻塞式 I/O,它在每一个连接创建时,都需要一个用户线程来处理,并且在 I/O 操作 没有就绪或结束时,线程会被挂起,进入阻塞等待状态,阻塞式 I/O 就成为了导致性能瓶颈的 根本原因。
首先是一次简单的TCP数据传输:
首先,应用程序通过系统调用 socket 创建一个套接字,它是系统分配给应用程序的一个文件描述符;
其次,应用程序会通过系统调用 bind,绑定地址和端口号,给套接字命名一个名称;
然后,系统会调用 listen 创建一个队列用于存放客户端进来的连接;
最后,应用服务会通过系统调用 accept 来监听客户端的连接请求。
1. 阻塞式 I/O在整个 socket 通信工作流程中,socket 的默认状态是阻塞的。也就是说,当发出一个不能立 即完成的套接字调用时,其进程将被阻塞,被系统挂起,进入睡眠状态,一直等待相应的操作 响应。从上图中,我们可以发现,可能存在的阻塞主要包括以下三种。
connect阻塞:
accept阻塞:
read、write 阻塞:
2. 非阻塞式 ...
Java的I/O
什么是I/O流我们通常把机器或者应用程序接收外界的信息称为输入流(InputStream),从机器或者应用程 序向外输出的信息称为输出流(OutputStream),合称为输入 / 输出流(I/O Streams)。
Java 的 I/O 分为以下两类:
不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么还要存在字符流呢?
字符到字节必须经过转码,这个过程非常耗时,如果我们不知道编码类型就很容易出现乱码问题,所以提供了直接操作字符的接口。
字节流
字符流
传统I/O的性能问题1. 多次内存复制在传统 I/O 中,我们可以通过 InputStream 从源数据中读取数据流输入到缓冲区里,通过 OutputStream 将数据输出到外部设备(包括磁盘、网络),输入操作在操作系统中的具体流程如下:
JVM 会发出 read() 系统调用,并通过 read 系统调用向内核发起读请求;
内核向硬件发送读指令,并等待读就绪;
内核把将要读取的数据复制到指向的内核缓存中;
操作系统内核将数据复制到用户空间缓 ...
Java的Stream
需求:过滤分组一所中学里身高在 160cm 以上的男女同学
之前的代码实现如下:
123456789101112Map<String, List<Student>> stuMap = new HashMap<String, List<Student>>();for (Student stu: studentsList) { if (stu.getHeight() > 160) { //如果身高大于160 if (stuMap.get(stu.getSex()) == null) { //该性别还没分类 List<Student> list = new ArrayList<Student>(); //新建该性别学生的 list.add(stu);//将学生放进去列表 stuMap.put(stu.getSex(), list);//将列表放到map中 } else ...