【设计模式】常用

【设计模式】常用

单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

通常我们可以通过一个对象访问一个全局变量,但它不能防止你实例化多个对象。一个最好的办法就是 让类自身负责保存它的唯一实例,这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。

恶汉式单例模式:

// 恶汉式单例
public class Singleton {
	private static Singleton instance = new Singleton(); // 唯一实例
	private Singleton(){} // 私有化构造方法
	
	public static Singleton getInstance() {
		return instance;
	}
}
在类加载的时候,创建实例,以后的每次访问都直接返回 instance,不会有多线程的问题。

懒汉式单例模式:

// 懒汉式单例
public class Singleton {
	private static Singleton instance; // 唯一实例
	private Singleton(){} // 私有化构造方法

	public static Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}
在类加载时,不创建实例,在使用的时候,才会创建实例,创建实例的过程中,判断 instance 是否为空,只有第一次调用 getInstance 才会创建,之后再次调用,都是直接返回 instance.

在多线程情况下,可能出现异常。比如:线程A 和 线程B 同时调用 getInstance 方法,线程A 做判断 instance == null,然后进入 if,准备创建实例,在创建实例之前,线程B 做判断 instance == null,也会进入 if,这样就容易出现不一致性。 可以通过加锁的方式避免多线程中遇到的这种问题.

懒汉式 + 锁:

// 懒汉式单例
public class Singleton{
	private static Singleton instance; // 唯一实例
	private Singleton(){} // 私有化构造方法
	
	public static Singleton getInstance() {
		if (instance == null) {
			synchronized (Singleton.class) {
				if (instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}
懒汉式模式,加一次锁,防止在多线程的时候,创建两个实例。但是只加一次锁,还是不能解决问题,synchronized 给 Singleton.class 加锁,只能限制 其他线程调用 其内部的方法,但是 其他线程可以在 synchronized 外面等,等到线程A 释放锁的时候,其他线程就能 获取锁,并创建实例了,所以更安全的方法是做两次判断.

当 线程B进入synchronized之后,需要先判断一下 instance 是不是为空,如果不为空,说明线程A 刚刚创建了实例,并释放了锁,这时 B就不需要再创建实例了,直接返回就可以了。如果 instance 依然为 空,说明没有线程 创建实例,B可以在保持锁的情况下,创建实例.

在实际使用过程中,怎么在两种模式之间做选择? 功能是否重要 + 使用频率 + 初始化时间 

功能重要(服务启动相关)---> 饿汉模式
使用频率高---> 优先饿汉模式,如果服务加载太慢,可以考虑 懒汉模式
使用频率低---> 懒汉模式
初始化时间长---> 懒汉模式

首先,判断使用单例的业务逻辑:
如果是一些服务启动必须具备的功能,比如:连接数据库,查询功能、配置信息等,就设计为 饿汉模式,让其在初始化的过程中运行,如果代码有错误,就会直接提示,不会出现服务正常启动了,某一些常用的功能有异常问题的情况。

如果是一些 使用频率高 的功能,优先饿汉模式,比如:证书管理、管理员管理的对象。

如果是一些 使用频率低 或者 扩展类的功能,比如:定时任务、或者通过配置才会具有的扩展任务,不是服务启动必须的任务,我们考虑使用 懒汉模式,因为这些任务分两类:服务启动不会用到、不是服务自带的功能,属于扩展功能。

初始化时间长: 懒汉模式

我个人倾向于 使用 饿汉模式,只有对于扩展功能 或者 使用频率低的功能,才考虑 懒汉设计。

单例模式的缺点:

违背了 单一职责原则:单例模式在职责上有时候会过重,即要负责初始化的过程,又要负责初始化的内容,甚至在某些情况下还要负责其他程序,这在一定程度上违反了“单一职责”原则。

掩盖不良设计:没有使用构造方法显式传递参数(饿汉),使用者需要对单例的内容有足够了解
多线程需要特殊处理:在懒汉模式下,多线程需要加锁处理,防止初始化出现不一致的问题

工厂模式:

Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

意图:定义一个创建对象的接口,子类决定实例化哪一个工厂类
主要解决:主要解决接口选择的问题。
何时使用:我们明确地计划不同条件下创建不同实例时。
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
关键代码:创建过程在其子类执行。

优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。

缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

使用场景: 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 3、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。

注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。


创建一个工厂,在使用某些对象实例时,通过工厂实例的创建实例方法,传递不同的参数获取不同的对象实例。

策略模式: 不同的方法(行为)

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
关键代码:实现同一个接口。

优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。

使用场景: 
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 
2、一个系统需要动态地在几种算法中选择一种。
 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。


注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

通过 context 的参数确定最后的行为方法,通过 new OperationAdd()对象传递给 context,在 context 中调用 strategy.doOperation(x,x)方法实现不同的行为。有点 向上转型的意思、

装饰模式:结构型设计模式

封装器是装饰模式的别称.
允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为.
Component :抽象类或者接口,定义模板
ConcreteComponent : 具体类,实现功能

Decorator : 装饰类,继承 Component,添加 component 对象
ConcreteDecorator : 具体类,继承 Decorator ,重写 方法,具体的实现细节

Component抽象类:

public abstract class Component {
	public abstract void operation();
}

ConretetComponent类:

public class ConcreteComponent extends Component {
    @Override
    public void operation() {
        System.out.println("具体对象的操作");
    }
}

Decorator装饰类:

public abstract class Decorator extends Component {
    private Component component = null;
    //通过构造函数传递给被修饰者
    public Decorator(Component component) {
        this.component = component;
    }
    //委托给被修饰者执行
    @Override
    public void operation() {
        if(component != null) {
            this.component.operation();
        }
    }
}

ConcreteDecorator类:

public class ConcreteDecoratorA extends Decorator {
    //定义被修饰者
    public ConcreteDecoratorA(Component component) {
        super(component);
    }
    //定义自己的修饰方法
    private void method1() {
        System.out.println("method1 修饰");
    }
    @Override
    public void operation() {
        this.method1();
        super.operation();
    }
}
public class ConcreteDecoratorB extends Decorator {
    //定义被修饰者
    public ConcreteDecoratorB(Component component) {
        super(component);
    }
    //定义自己的修饰方法
    private void method2() {
        System.out.println("method2 修饰");
    }
    @Override
    public void operation() {
        super.operation();
        this.method2();
    }

}

Client 客户端:

public class Client {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        //第一次修饰
        component = new ConcreteDecoratorA(component);
        //第二次修饰
        component = new ConcreteDecoratorB(component);
        //修饰后运行
        component.operation();
    }
}
原始方法和装饰方法的执行顺序在具体的装饰类是固定的,可以通过方法重载实现多种执行顺序.

实例:

//..
public class LogDao implements Dao {

    private Dao dao;

    public LogDao(Dao dao) {
        this.dao = dao;
    }
    @Override
    public void insert() {
        System.out.println("insert()方法开始时间:" + System.currentTimeMillis());
        dao.insert();
        System.out.println("insert()方法结束时间:" + System.currentTimeMillis());
    }
    @Override
    public void delete() {
        dao.delete();
    }

    @Override
    public void update() {
        System.out.println("update()方法开始时间:" + System.currentTimeMillis());
        dao.update();
        System.out.println("update()方法结束时间:" + System.currentTimeMillis());
    }
}
//...

应用:

何时使用:在不想增加很多子类的情况下扩展类时,通过装饰方式,聚合 和 组合
方法:将具体功能职责划分,同时继承装饰者模式

优点:
装饰类和被装饰类可以独立发展,而不会相互耦合.有效地把类的核心职责和装饰功能分开
装饰模式是继承关系的一个替代方案
装饰模式可以动态地扩展一个实现类的功能

缺点:
多层装饰比较复杂
功能逻辑还是无法复用
扩展还是需要直接修改代码

使用场景:
需要扩展一个类的功能时
需要动态地给一个对象增加功能,并可以动态地撤销时
需要为一批的兄弟类进行改装或加装功能时

应用实例:
旧机包装成新机,手机/电脑内部配件不变,只是换个外壳
换衣小游戏,人还是那个人,不断给她换衣服,还可以一层套一层的
孙悟空有72变,变成什么后就有了它的功能,但本质还是一只猴子

遗留:多层 和 组合的区别

代理模式:

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用
效果:中介隔离作用、开闭原则,增加功能

静态代理:

优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展.

缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理. 同时接口一旦发生改变,代理类也得相应修改.

动态代理:

通过动态处理器实现动态代理,真正的代理对象由JDK再运行时为我们动态的来创建.

动态处理器

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxyHandler implements InvocationHandler {

    private Object object;

    public DynamicProxyHandler(final Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("买房前准备");
        Object result = method.invoke(object, args);
        System.out.println("买房后装修");
        return result;
    }
}

测试类

package main.java.proxy.test;

import main.java.proxy.BuyHouse;
import main.java.proxy.impl.BuyHouseImpl;
import main.java.proxy.impl.DynamicProxyHandler;

import java.lang.reflect.Proxy;

public class DynamicProxyTest {
    public static void main(String[] args) {
        BuyHouse buyHouse = new BuyHouseImpl();
        BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new
                Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse));
        proxyBuyHouse.buyHosue();
    }
}
Proxy.newProxyInstance()方法接受三个参数:

ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法

应用:

优点: 功能逻辑可以复用

缺点: 
JDK 提供的动态代理不能对类做代理,因为代理类的都继承了 Proxy 类.
代码依然有耦合,扩展功能还是需要修改代码

观察者模式、迭代器模式、模版方法模式

continue….

参考文章:
https://refactoringguru.cn/design-patterns/decorator  # 设计模式
https://www.cnblogs.com/adamjwh/p/9036358.html   # 装饰模式
https://www.cnblogs.com/daniels/p/8242592.html   # 代理模式

0 0 vote
Article Rating
Subscribe
提醒
guest
0 评论
Inline Feedbacks
View all comments