常用设计模式
单例模式
优点:实现了整个程序对唯一实例访问的控制。因为单例要求程序只能有一个对象,所以对于那些需要频繁创建和销毁的对象来说可以提高系统的性能,并且可以节省内存空间。
缺点:如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,可能会导致对象状态的丢失
源码中的应用
1. java.lang.Runtime就是经典的单例模式(饿汉式)
应用场景
1. 需要频繁创建和销毁的对象、工具类对象
2. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
3. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
步骤:
1) 构造器私有化 (防止 new )
2) 类的内部创建对象
3) 向外暴露一个静态的公共方法。getInstance
饿汉式
简单来说就是空间换时间,因为上来就实例化一个对象,占用了内存,(也不管你用还是不用)。饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题
//饿汉式 静态常量
class Singleton {
//1.构造器私有化,外部不能new
private Singleton(){}
// 2.本类内部创建对象实例
private final static Singleton instance = new Singleton();
// 提供一个公有 的静态方法,返回对象实例
public static Singleton getInstance(){
return instance;
}
}
优缺点说明:
1) 优点:就是在类装载的时候就完成实例化。避免了线程同步问题。饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的
2) 缺点:在类装载的时候就完成实例化,没有达到懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
3) 这种方式基于ClassLoader机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果
4) 结论:这种单例模式可用,可能造成内存浪费
//饿汉式 静态代码块
class Singleton {
//1.构造器私有化,外部不能new
private Singleton(){}
// 2.本类内部创建对象实例
private static Singleton instance;
static {
instance = new Singleton();
}
// 提供一个公有的静态方法,返回对象实例
public static Singleton getInstance(){
return instance;
}
}
懒汉式
简单来说就是时间换空间,只有当调用getInstance的时候,才会去实例化这个对象。懒汉式本身是非线程安全的,为了实现线程安全有几种写法
//饿汉式(线程不安全)
class Singleton {
private static Singleton instance;
private Singleton(){}
//提供一个静态的公有方法,当使用到该方法时,才去创建 instance
//即懒汉式
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
优缺点说明:
1) 起到了Lazy Loading的效果,但是只能在单线程下使用。
2) 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
3) 结论:在实际开发中,不要使用这种方式.
第二种方式,加锁,唯一的区别就是加了synchronized
// 懒汉式(线程安全,同步方法)
class Singleton {
private static Singleton instance;
private Singleton(){}
//提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
//即懒汉式
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
优缺点:起到了懒加载的效果,也避免了线程同步问题,但每次执行getInstance()方法都要进行
同步。而其实这个方法只执行一次实例化代码就够了,效率较低。
双重校验锁方式
// 双重校验锁
class Singleton{
private static volatile Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
双重校验锁为什么要两个if?
第一个:这个是为了代码提高代码执行效率,由于单例模式只要一次创建实例即可,所以当创建了一个实例之后,再次调用getInstance方法就不必要进入同步代码块,不用竞争锁。直接返回前面创建的实例即可。
第二个:这个校验是防止二次创建实例,假如有一种情况,当singleton还未被创建时,线程t1调用getInstance方法,由于第一次判断singleton==null,此时线程t1准备继续执行,但是由于资源被线程t2抢占了,此时t2页调用getInstance方法,同样的,由于singleton并没有实例化,t2同样可以通过第一个if,然后继续往下执行,同步代码块,第二个if也通过,然后t2线程创建了一个实例singleton。此时t2线程完成任务,资源又回到t1线程,t1此时也进入同步代码块,如果没有这个第二个if,那么,t1就也会创建一个singleton实例,那么,就会出现创建多个实例的情况,但是加上第二个if,就可以完全避免这个多线程导致多次创建实例的问题。
工厂模式(买车例子)
用工厂方法代替new操作的一种模式,简单工厂模式的核心思想就是:有一个专门的类来负责创建实例的过程。
定义与使用场合
现在需要创建几个对象,且这几个对象有共同特征,则不需要具体创建各个对象,而是创建对象工厂类即可。缺点:违反了开闭原则,如果增加一个新产品,如果不修改代码,是做不到的。
例子:造奔驰车和造五菱宏光车(共同特征:造车)
主要有一下几点:1.车的接口 2.不同品牌的车实现接口 3.车工厂类 4.消费者
简单工厂模式
简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。
优点:客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品
缺点:简单工厂模式每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度,违背了“开闭原则”
源码中的使用
Calendar中的静态方法createCalendar的源码就是简单工厂模式
// 车的接口
public interface Car {
void name();
}
// 不同品牌的车实现接口
public class Tesla implements Car{
@Override
public void name() {
System.out.println("特斯拉");
}
}
public class Wuling implements Car{
@Override
public void name() {
System.out.println("五菱宏光");
}
}
// 车工厂类
public class CarFactory {
// 方法一
public static Car getCar(String car){
if (car.equals("五菱")){
return new Wuling();
}else if (car.equals("特斯拉")){
return new Tesla();
}else {
return null;
}
}
// 方法二
// public static Car getWuling(){
// return new Wuling();
// }
// public static Car getTesla(){
// return new Tesla();
// }
}
public class Comsumer {
public static void main(String[] args) {
// Wuling car = new Wuling();
// Tesla car2 = new Tesla();
// 使用工厂创建
Car car = CarFactory.getCar("五菱");
Car car2 = CarFactory.getCar("特斯拉");
car.name();
car2.name();
}
}
工厂方法模式
定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。
一个car接口,一个car工厂接口,每需要一个产品就写出相关的类及其工厂类。由每个工厂来生产对应型号的车。
缺点:每多一个产品,就要就写出相关的类及其工厂类,代码较多。
public interface Car {
void name();
}
public interface CarFactory {
Car getCar();
}
具体的产品类来时间Car接口
public class Wuling implements Car {
@Override
public void name() {
System.out.println("五菱宏光");
}
}
public class Tesla implements Car {
@Override
public void name() {
System.out.println("特斯拉");
}
}
产品类对应的工厂来时间Car工厂
public class WulingFactory implements CarFactory{
@Override
public Car getCar() {
return new Wuling();
}
}
public class TeslaFactory implements CarFactory{
@Override
public Car getCar() {
return new Tesla();
}
}
抽象工厂模式
将工厂抽象成两层,AbsFactory(抽象工厂) 和 具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
// 抽象产品工厂
public interface IProductFactory {
// 生产手机的接口
IphoneProduct iphoneProduct();
// 生产路由器的接口
IRouterProduct routerProduct();
// 生产笔记本的接口
}
// 手机产品接口
public interface IphoneProduct {
void start();
void shutdown();
void callup();
void sendSMS();
}
// 路由器产品接口
public interface IRouterProduct {
void start();
void shutdown();
void openwifi();
void setting();
}
实现抽象工厂
public class XiaomiFactory implements IProductFactory{
@Override
public IphoneProduct iphoneProduct() {
return new XiaomiPhone();
}
@Override
public IRouterProduct routerProduct() {
return new XiaomiRouter();
}
}
实现接口
public class XiaomiPhone implements IphoneProduct{
@Override
public void start() {
System.out.println("开启小米手机");
}
@Override
public void shutdown() {
System.out.println("关闭小米手机");
}
@Override
public void callup() {
System.out.println("小米手机打电话");
}
@Override
public void sendSMS() {
System.out.println("小米手机发短信");
}
}
代理模式
静态代理
代理类和被代理类需要实现同一个接口,通过聚合使得代理对象中有被代理对象的引用
场景
(1)方法前后加日志,记录时间(方法运行的时间)
(2)方法前后加事务,事务的提交或者回滚(AOP)
(3)方法前后加判断调用者是否拥有权限,用于权限的控制
// 创建服务类接口
public interface Rent {
void rent();
}
// 被代理类
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
// 代理类
public class Proxy implements Rent{
private Host host;
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
host.rent();
seeHouse();
hetong();
fare();
}
public void seeHouse(){
System.out.println("中介带你看房");
}
public void fare(){
System.out.println("收中介费");
}
public void hetong(){
System.out.println("签合同");
}
}
// 编写测试类
public class client {
public static void main(String[] args) {
Host host = new Host();
Proxy proxy = new Proxy(host);
proxy.rent();
}
}
动态代理
动态代理的代理类不需要我们编写,由Java自动产生代理类源代码并进行编译最后生成代理对象。
分类两大类:基于接口的动态代理—JDK动态代理,基于类的动态代理----cglib
步骤:
- 指明一系列的接口来创建一个代理对象
- 创建一个调用处理器(InvocationHandler)对象
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类
- 在调用处理器的invoke()方法中采取代理,一方面将调用传递给真实对象,另一方面执行各种需要的操作
public interface Rent {
void rent();
}
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
// 用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
public ProxyInvocationHandler(Rent rent){
this.rent = rent;
}
// 生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}
// 处理代理实例,返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始执行"+method.getName()+"方法");
//执行原对象的相关操作,容易忘记
Object result = method.invoke(rent, args);
System.out.println(method.getName()+"方法执行完毕");
return result;
}
}
public class client {
public static void main(String[] args) {
Host host = new Host();
ProxyInvocationHandler pih = new ProxyInvocationHandler(host);
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
}
}
动态代理的优点
动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。
jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。
动态代理的原理
Proxy类的newProxyInstance方法创建了一个动态代理对象,查看该方法的源码,发现它只是封装了创建动态代理类的步骤
JDK和CGLIB动态代理总结
JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:实现InvocationHandler + 使用Proxy.newProxyInstance产生代理对象 + 被代理的对象必须要实现接口
CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承但是针对接口编程的环境下推荐使用JDK的代理;
适配器模式
作为两个不兼容的接口之间的桥梁。比如电脑上没有网线接口,就需要一个USB转网线转接头。适配器模式是将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的的类的兼容性问题。主要分三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
创建接口
// 接口转换器的抽象实现
public interface NetToUsb {
// 处理请求 网线-> usb
public void handleRequest();
}
// 要被适配的类 网线
public class Adaptee {
public void request(){
System.out.println("连接网线上网");
}
}
// 继承(类适配器,单继承 有局限性)
// 真正的适配器 需要连接usb 连接网线
public class Adapter extends Adaptee implements NetToUsb{
@Override
public void handleRequest() {
super.request();
}
}
// 组合 对象适配器 常用
public class Adapter2 implements NetToUsb{
private Adaptee adaptee;
public Adapter2(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void handleRequest() {
adaptee.request();
}
}
// 客户端类 想上网 插不上网线
public class Computer {
public void net(NetToUsb adapter){
// 上网的具体实现 找一个转接头
adapter.handleRequest();
}
public static void main(String[] args) {
Computer computer = new Computer();
Adaptee adaptee = new Adaptee();
// Adapter adapter = new Adapter();
// computer.net(adapter);
Adapter2 adapter2 = new Adapter2(adaptee);
computer.net(adapter2);
}
}
源码中的使用
(1)java.io.InputStreamReader(该类继承了Read类,但要创建它的对象必须在构造函数中传入一个InputStream)(InputStream→Reader 字节到字符)
(2)java.io.OutputStreamWriter(OutputStream)
装饰器模式
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例
何时使用:在不想增加很多子类的情况下扩展类。
源码中的使用
(1)java.io包(BufferInputStream相当于一个装饰器实现者,使得FilterInputStream读取的数据能暂存在内存中,从减少I/O的角度提高了读取效率)
(2)java.util.Collections#synchronizedList(List)