单例模式
单例模式比较好理解,Spring就是典型的例子。被Spring中的容器管理的对象都有对应的scope,配置成singleton说明这个对象就是单例,也就是在Spring容器的生命周期中,这个类只有1个实例。
java中单例模式的写法也有好多种。比如懒汉式、饿汉式、内部类方式、枚举方式等。
单例模式实现时设计到JMM和volatile关键字,详见:单例模式
需要注意的如果使用dcl的话需要初始化过程,这篇Java内存模型之从JMM角度分析DCL文章中说明了dcl的正确用法。
Effectice java中推荐的单例方式写法是使用枚举类型的方式。
工厂模式
工厂模式的意义在于对象的创建、管理可以使用工厂去管理,而不是创建者自身。最典型的工厂模式使用者就是Spring,Spring内部的容器就是一个工厂,所有的bean都由这个容器管理,包括它们的创建、销毁、注入都被这个容器管理。
工厂模式分工厂方法(普通工厂、多个工厂、静态工厂)和抽象工厂。它们的区别在于抽象工厂抽象程度更高,把工厂也抽象成了一个接口,这样可以再每添加一个新的对象的时候而不需要修改工厂的代码。
比如有个Repository接口,用于存储数据,有DatabaseRepository,CacheRepository,FileRepository分别在数据库,缓存,文件中存储数据,定义如下:
public interface Repository {
void save(Object obj);
}
class DatabaseRepository implements Repository {
@Override
public void save(Object obj) {
System.out.println("save in database");
}
}
class CacheRepository implements Repository {
@Override
public void save(Object obj) {
System.out.println("save in cache");
}
}
class FileRepository implements Repository {
@Override
public void save(Object obj) {
System.out.println("save in file");
}
}
简单工厂的使用
public class RepositoryFactory {
public Repository create(String type) {
Repository repository = null;
switch (type) {
case "db":
repository = new DatabaseRepository();
break;
case "cache":
repository = new CacheRepository();
break;
case "file":
repository = new FileRepository();
break;
}
return repository;
}
public static void main(String[] args) {
RepositoryFactory factory = new RepositoryFactory();
factory.create("db").save(new Object());
factory.create("cache").save(new Object());
factory.create("file").save(new Object());
}
}
简单工厂的弊端在于每添加一个新的Repository,都必须修改RepositoryFactory中的代码
抽象工厂的使用
public interface RepositoryFactoryProvider {
Repository create();
}
class DatabaseRepositoryFactory implements RepositoryFactoryProvider {
@Override
public Repository create() {
return new DatabaseRepository();
}
}
class CacheRepositoryFactory implements RepositoryFactoryProvider {
@Override
public Repository create() {
return new CacheRepository();
}
}
class FileRepositoryFactory implements RepositoryFactoryProvider {
@Override
public Repository create() {
return new FileRepository();
}
}
抽象工厂的测试:
RepositoryFactoryProvider dbProvider = new DatabaseRepositoryFactory();
dbProvider.create().save(new Object());
RepositoryFactoryProvider cacheProvider = new CacheRepositoryFactory();
cacheProvider.create().save(new Object());
RepositoryFactoryProvider fileProvider = new FileRepositoryFactory();
fileProvider.create().save(new Object());
抽象工厂把工厂也进行了抽象话,所以添加一个新的Repository的话,只需要新增一个RepositoryFactory即可,原有代码不需要修改。
代理模式
代理模式的作用是使用一个代理类来代替原先类进行操作。比较常见的就是aop中就是使用代理模式完成事务的处理。
代理模式分静态代理和动态代理,静态代理的原理就是对目标对象进行封装,最后调用目标对象的方法即可。静态代理和动态代理区别:待补充
动态代理跟静态代理的区别就是动态代理中的代理类是程序运行的时候生成的。Spring中对于接口的代理使用jdk内置的Proxy和InvocationHandler实现,对于类的代理使用cglib完成。
以1个UserService为例,使用jdk自带的代理模式完成计算方法调用时间的需求:
// UserService接口
public interface IUserService {
void printAll();
}
// UserService实现类
class UserService implements IUserService {
@Override
public void printAll() {
System.out.println("print all users");
}
}
// InvocationHandler策略,这里打印了方法调用前后的时间
@AllArgsConstructor
class UserInvocationHandler implements InvocationHandler {
private IUserService userService;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("start : " + System.currentTimeMillis());
Object result = method.invoke(userService, args);
System.out.println("end : " + System.currentTimeMillis());
return result;
}
}
测试:
IUserService userService = new UserService();
UserInvocationHandler uih = new UserInvocationHandler(userService);
IUserService proxy = (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), new Class[] {IUserService.class}, uih);
proxy.printAll(); // 打印出start : 1489665566456 print all users end : 1489665566457
观察者设计模式
观察者设计模式主要的使用场景在于一个对象变化之后,依赖该对象的对象会收到通知。典型的例子就是rss的订阅,当订阅了博客的rss之后,当博客更新之后,订阅者就会收到新的订阅信息。
jdk内置提供了Observable和Observer,用来实现观察者模式:
// 定义一个Observable
public class MetricsObserable extends Observable {
private Map<String, Long> counterMap = new HashMap<>();
public void updateCounter(String key, Long value) {
counterMap.put(key, value);
setChanged();
notifyObservers(counterMap);
}
}
// Observer
public class AdminA implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("adminA: " + arg);
}
}
public class AdminB implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("adminB: " + arg);
}
}
测试:
MetricsObserable metricsObserable = new MetricsObserable();
metricsObserable.addObserver(new AdminA());
metricsObserable.addObserver(new AdminB());
metricsObserable.updateCounter("request-count", 100l);
打印:
adminB: {request-count=100}
adminA: {request-count=100}
适配器模式
适配器模式比较好理解。像生活中插线口的插头有2个口的,也有3个口的。如果电脑的电源插口只有3个口的,但是我们需要一个2个口的插口的话,这个时候就需要使用插座来外接这个3个口的插头,插座上有2个口的插头。
这个例子跟我们编程一样,当用户系统的接口跟我们系统内部的接口不一致时,我们可以使用适配器来完成接口的转换。
使用继承的方式实现类的适配:
public class Source {
public void method() {
System.out.println("source method");
}
}
interface Targetable {
void method();
void newMethod();
}
class Adapter extends Source implements Targetable {
@Override
public void newMethod() {
System.out.println("new method");
}
}
测试:
Targetable targetable = new Adapter();
targetable.method(); // source method
targetable.newMethod(); // new method
上述方式是用接口和继承的方式实现适配器模式。当然我们也可以使用组合的方式实现(把Source当成属性放到Adapter中)。
在java流、文件使用到了这两个模式:
字节流:InputStream、OutputStream
字符流:Reader、Writer
1、适配器模式
FileInputStream fileinput=new FileInputStream(file);//字节流,读一个字节
InputStreamReader inputStreamReader=new InputStreamReader(fileinput);//字符流,读一个字符
2、装饰者模式
BufferedReader bufferedReader=new BufferedReader(inputStreamReader);//读一行字符
享元模式
线程池中会构造几个核心线程用于处理,这些线程会去取阻塞队列里的任务然后进行执行。这些线程就是会被共享、且被重复使用的。因为线程的创建、销毁、调度都是需要消耗资源的,没有必要每次创建新的线程,而是共用一些线程。这就是享元模式的使用。类似的还有jdbc连接池,对象池等。
之前有一次面试被问到:
Integer.valueOf("1") == Integer.valueOf("1") // true还是false
当时回答的是false,后来翻了下Integer的源码发现Integer里面有个内部类IntegerCache,用于缓存一些共用的Integer。这个缓存的范围可以在jvm启动的时候进行设置。
注:-128到127都相等,128之后不等。见装箱、拆箱知识点。
其实后来想想也应该这么做,我们没有必要每次使用对象的时候都返回新的对象,可以共享这些对象,因为新对象的创建都是需要消耗内存的。