适配器模式
将一个类的接口转换成另一个接口。 Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
目标接口Target
1 | public interface Target { |
待适配类Adaptee,与目标接口不一致
1 | public class Adaptee { |
适配器类
1 | public class Adapter implements Target { |
测试类
1 | public class AdapterClient { |
适配器模式
将一个类的接口转换成另一个接口。 Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
目标接口Target
1 | public interface Target { |
待适配类Adaptee,与目标接口不一致
1 | public class Adaptee { |
适配器类
1 | public class Adapter implements Target { |
测试类
1 | public class AdapterClient { |
单例模式
懒汉式——线程不安全
1 | public class Singleton0 { |
懒汉式——线程安全,但浪费内存
1 | public class Singleton1 { |
饿汉式——线程安全,但浪费内存
1 | public class Singleton2 { |
登记式/静态内部类——推荐
1 | public class Singleton3 { |
双检锁(DCL)——推荐
1 | public class Singleton4 { |
枚举——推荐
1 | public enum Singleton5 { |
建造者模式
建造者模式与抽象工厂方式都是用来创建复杂的大对象,但builder模式是一步步创建,一般不是直接返回对象。好处是对象的构建代码与标识代码分离了。
要建造的产品
1 | public class Product { |
建造者接口
1 | public interface Builder { |
具体的建造者
1 | public class ConcreteBuilder implements Builder { |
Director类
1 | public class Director { |
具体使用的测试类
1 | public class Client { |
原型模式
原型模式通过复制原型(prototype)获得创建新对象的能力,原型本身就是个工厂。用于隔离类的使用者和具体类型之间的耦合关系,隐藏了对象创建的细节,避免了调用过多参数的构造方法而带来的性能影响。与抽象工厂方法的却别在于重在自身的复制。
原型接口,自行实现clone,也可以是实现Cloneable的抽象类
1 | public interface Prototype { |
具体对象
1 | public class ConcretePrototypeA implements Prototype { |
测试类
1 | public class Test { |
抽象工厂模式
核心思想:向客户端提供一个接口,使得客户端在不必指定具体产品的情况下,创建多个产品族中的产品对象
与工厂方法模式的区别:工厂方法模式中一种工厂只能创建一种具体产品。而在抽象工厂模式中一种具体工厂可以创建多个种类的具体产品
抽象产品(CPU、主板)
1 | public interface Cpu { |
具体CPU产品
1 | // AMD CPU |
具体主板产品
1 | // AMD主板 |
抽象工厂类
1 | public interface AbstractFactory { |
具体工厂类
1 | // AMD工厂 |
测试类
1 | public class ComputerEngineer { |
工厂方法模式
工厂类
1 | // 抽象工厂类 |
产品类
1 | // 抽象产品类 |
测试类
1 | public class Test { |
简单工厂模式与OOP原则
已遵循的原则
- 依赖倒置原则
- 迪米特法则
- 里氏替换原则
- 接口隔离原则
- 单一职责原则(每个工厂只负责创建自己的具体产品,没有简单工厂中的逻辑判断)
- 开闭原则(增加新的产品,不像简单工厂那样需要修改已有的工厂,而只需增加相应的具体工厂类)
未遵循的原则
- 开闭原则(虽然工厂对修改关闭了,但更换产品时,客户代码还是需要修改)
简单工厂模式
1 | abstract class Car { |
1 | public class BMW extends Car { |
1 | class Factory { |
1 | public class Driver { |
简单工厂模式与OOP原则
已遵守的原则
- 依赖倒置原则
- 迪米特法则
- 里氏替换原则
- 接口隔离原则
未遵循的原则
- 开闭原则(如上文所述,利用配置文件+反射或者注解可以避免这一点)
- 单一职责原则(工厂类即要负责逻辑判断又要负责实例创建)
六大原则:
按照使用场景,设计模式分为三大类:
UML类图常用关系:
Callable提供了带返回值的子线程执行结果,Future提供了获取子线程结果的途径
1 | Callable<String> callable = () -> null; |
但是,这种直接get()
的方式是同步阻塞的,当然,如果轮询isDone()
的话仍然是换汤不换药。关于老生常谈的同步、异步、阻塞、非阻塞,这篇《I/O模型》从Java的视角出发来讲解,特别是NIO
和AIO
,指出AIO
并不是字面上的异步含义,值得一看。
那么对于Future
模式,除了上文的将来式get()
这种不优雅的同步阻塞方案,还有没有其他的方式可以拿到子线程结果呢?
很容易想到的一种方式是使用回调。如AIO
提供了java.nio.channels.CompletionHandler
作为回调接口,当I/O操作结束后,系统将会调用CompletionHandler
的completed
或failed
方法来结束一次调用
1 | /* Server */ |
JDK5
的NIO
已经提供了相关的API,虽然操作更为复杂一些,但在此基础上,诸如Netty
等通信框架已经发展的十分繁荣。AIO
似乎并没有达到预计的效果,但这种回调方式显然要比直接get()
的粗暴方式要更为优雅。
那么有没有不那么粗暴又方便一些的回调方案呢?
答案是有的,一些开源的工具已经为我们提供了这个功能,例如接下来要介绍的Google扩展包Guava
中提供的并发工具com.google.common.util.concurrent.ListenableFuture
1 | public static void main(String... args) { |
和Future
的get()
会阻塞主线程不同,带监听器的ListenableFuture
可以异步处理Callable
结果,最终打印结果:
1 | call execute.. |
从ListeningExecutorService
这个修饰后的线程池出发,看看如何修饰后如何将提交的Callable
输出为ListenableFuture
,而非Future
,主要来看submit
方法。
1 | public abstract class AbstractListeningExecutorService extends AbstractExecutorService |
这里的submit
和newTaskFor
最终执行的都是子类AbstractListeningExecutorService
中的,这是Guava
包内的,而非J.C.U
包内的AbstractExecutorService
,返回了TrustedListenableFutureTask
的实例,看一下依赖关系,能很清楚地看到这是ListenableFuture
的一个实现类。
那么是如何进行回调的呢?接着从刚才的实现类TrustedListenableFutureTask
来看,主要做的工作是InterruptibleTask
里,实现了Runnable
的run
方法。
1 | class TrustedListenableFutureTask<V> extends FluentFuture.TrustedFuture<V> |
这里总结一下lock()
和unlock()
的整体过程
参考
[1] Java并发之AQS详解