1、单例模式:确保一个类只有一个实例对象,并提供一个全局访问点。
如何保证内存中只有一个实例对象呢?做到下面三点即可:
(1)一个私有的静态实例变量。
(2)一个私有的构造方法。
(3)一个公有的静态的取得静态实例的方法
代码实现:
(1)饿汉式:在类加载时直接进行创建,不用考虑线程安全问题
//在类加载时直接被创建实例实例对象方式:饿汉式
class SingletonTest{
// int age;
private static SingletonTest test=new SingletonTest();
private SingletonTest(){
}
//提供公有的访问方法
public static SingletonTest getInstance(){
return test;
}
}
(2)懒汉式:在类加载时不进行创建,调用时进行创建(延迟加载),需要考虑线程安全问题
//懒汉式:在类加载时不进行创建,在调用getInstance才进行创建,延迟加载,考虑线程安全
class SingletonTest2{
//在类加载时不创建
private static SingletonTest2 singleton=null;
private SingletonTest2(){}
//在调用时进行创建
//在多线程时单例
public static SingletonTest2 getInstance(){
//当多线程开始进行调用,先判断有没有对象再决定是否加锁
if(singleton==null){
synchronized(SingletonTest2.class) {
if(singleton==null)
singleton = new SingletonTest2();
}
}
return singleton;
}
}
(3)静态内部类方式:在类加载时不进行创建,调用时在静态内部类中进行创建(延迟加载),不用考虑线程安全问题
//使用静态内部类的方式实现单例模式:在类加载时不直接进行创建,在调用getInstance时创建(延迟加载),不用考虑线程安全问题
class SingletonTest3{
//静态内部类
private static class SingleHolder{
private static final SingletonTest3 single=new SingletonTest3();
}
private SingletonTest3(){}
public SingletonTest3 getInstance(){
return SingleHolder.single;
}
}
2、工厂模式:
Spring:Bean加工厂
解决问题:根据不同条件返回不同的实例对象,隐藏了实例对象实现的细节。
interface food{}
class A implements food{}
class B implements food{}
class C implements food{}
public class StaticFactory {
private StaticFactory(){}
public static food getA(){ return new A(); }
public static food getB(){ return new B(); }
public static food getC(){ return new C(); }
}
class Client{
//客户端代码只需要将相应的参数传入即可得到对象
//用户不需要了解工厂类内部的逻辑。
public void get(String name){
food x = null ;
if ( name.equals("A")) {
x = StaticFactory.getA();
}else if ( name.equals("B")){
x = StaticFactory.getB();
}else {
x = StaticFactory.getC();
}
}
}
3、代理模式:静态代理和动态代理
代理(Proxy)是一种设计模式:提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。
解决问题:代理可以在不改动目标对象的基础上,增加其他额外的功能(扩展功能)
代理类型概念:
- 目标对象:要操作对象
- 代理对象:在目标对象的基础上增加额外新功能后,重新生成一个对象为代理对象
(1)静态代理
实现特点:目标对象和代理对象实现了同一接口,如图所示:
缺点:可扩展性,因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
/**
* 目标对象和代理对象共同实现的接口
*/
public interface Subject {
void execute();
}
/**
* 目标对象实现的接口
*/
public interface Subject {
void execute();
}
/**
* 代理对象实现的接口
*/
public class StaticProxy implements Subject {
//引入目标对象:代理是在目标对象的基础上增加了新功能,目标对象就是固定的某一个
private RealSubject target;
//在调用代理对象是指明目标对象
public StaticProxy(RealSubject target){
this.target=target;
}
/*目标对象是卖水的,代理对象也是卖水的,只是在目标对象的基础上增加了额外金额,
所以代理对象和目标对象实现的是同一个方法*/
@Override
public void execute() {
System.out.println("在执行业务目标之前增加一些新功能");
target.execute();
System.out.println("在执行业务目标之后增加一些新功能");
}
}
/**
*测试类
*/
public class Test {
public static void main(String[] args) {
Subject sub=new StaticProxy(new RealSubject());
sub.execute();
}
}
(2)动态代理
动态代理中,代理类并不是在Java代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法调用次数、添加日志功能等等,动态代理分为jdk动态代理和cglib动态代理。
JDK动态代理
特点:
1.代理对象不需要实现接口,目标对象必须的实现接口
2.JDK自带的一种代理方式,不需要额外的导入jar包
/**
* 接口
*/
public interface Subject {
void execute();
void executeDel();
}
/**
* 目标对象
*/
public class RealSubject implements Subject {
@Override
public void execute() {
System.out.println("目标对象:执行业务代码--增加");
}
@Override
public void executeDel() {
System.out.println("目标对象:执行业务代码--删除");
}
/*
JDK代理类不用实现任何接口
*/
public class JDKProxy {
//引用一个目标对象,目标对象不是固定的某一个
private Object target;
//调用代理对象是传入目标对象进行代理
public JDKProxy(Object target){//所有的对象都可以传入
this.target=target;
}
//处理目标对象,生成代理对象
public Object getInstance(){//得到一个代理对象
//Proxy是JDK提供的动态代理类的主类,需要通过的他的静态方法newProxyInstance动态的生成一个代理对象
//生成代理对象是在内存中的,看不见,没有对应得物理文件
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
//代理目标对象以后要干什么
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在目标对象现有的功能上增加新的功能
//target:目标对象的所有的方法
//args:目标对象的参数
System.out.println("在目标对象之前增加新功能");
Object result= method.invoke(target,args);//反射?
System.out.println("在目标对象之后增加新功能");
return result;
}
});
}
}
/*
*测试类
*/
public class Test {
public static void main(String[] args) {
//注意不能直接new 目标对象,必须带上接口
Subject real=new RealSubject();
Subject sub=(Subject)new JDKProxy(real).getInstance();
sub.executeDel();
}
}
CGLib动态代理,又称子类代理
不是JDK自带的代理,需要引入外部jar包 ,针对没有接口的代理方式,用目标对象作为父类接口来用产生一个子类的代理对象
/**
*目标对象
*/
public class Target {
public void executeAdd(){
System.out.println("目标对象:添加");
}
}
/*
代理类实现一个接口,拦截器的原理,将目标方法拦截下来,增加新的功能
*/
public class CGLibProxy implements MethodInterceptor{
//引入目标对象
private Object target;
public CGLibProxy(Object target){
this.target=target;
}
//生成一个代理对象
public Object getInstance(){
//增强类,作用和JDK代理Proxy
Enhancer en=new Enhancer();
//指明父类,目标对象
en.setSuperclass(target.getClass());
//调用回调函数
en.setCallback(this);
//创建代理类
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("之前增加新功能");
Object result= method.invoke(target,objects);
System.out.println("之后增加新功能");
return result;
}
}
/**
*测试类
*/
public class Test {
public static void main(String[] args) {
Target tar=(Target)new CGLibProxy(new Target()).getInstance();
tar.executeAdd();
}
}
4、适配器模式
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁
举个简单的例子,当我们给手机充电时,由于手机充电口是5V,而插座提供的是220V交流电,因此我们通常需要使用充电器将220V交流电转换成可供手机充电用的5V直流电,这个充电器就是一个适配器
意图:适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。
主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
- 类适配:创建新类,继承源类,并实现新接口
class adapter extends oldClass implements newFunc{}
- 对象适配:创建新类持源类的实例,并实现新接口,例如
class adapter implements newFunc { private oldClass oldInstance ;}
- 接口适配:创建新的抽象类实现旧接口方法。例如
abstract class adapter implements oldClassFunc { void newFunc();}
类适配示例:
有一个Source类,拥有一个方法,待适配,目标接口是Targetable,通过Adapter类,将Source的功能扩展到Targetable里。
其代码:
public class OldClass {
public void method(){
System.out.println("输出220V的交流电");
}
}
/**
* 新的环境
*/
public interface Newfun {
void method();
}
/**
* 适配器继承老的实现新的
*/
public class Adapter extends OldClass implements Newfun{
@Override
public void method() {
super.method();
System.out.println("经过适配器转换");
System.out.println("输出5v的直流电");
}
}
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
Adapter adapter = new Adapter();
adapter.method();
}
}
5、装饰者模式
装饰器模式(Decorator Pattern)允许向一个现有的对象(在不改变现有对象的基础上)添加新的功能,同时又不改变其结构
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活,不想过多的创建子类对象。
举例:装饰模式为已有类动态附加额外的功能就像LOL、王者荣耀等类Dota游戏中,英雄升级一样,每次英雄升级都会附加一个额外技能点学习技能;换肤等都可以用装饰者模式。
比如:咖啡店里咖啡中可以加不同的配料–摩卡、牛奶、糖、奶泡;不同的饮品加上不同的配料有不同的价钱。
示例:
我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。然后我们创建一个实现了 Shape 接口的抽象装饰类 ShapeDecorator,并把 Shape 对象作为它的实例变量。
RedShapeDecorator 是实现了 ShapeDecorator 的实体类。
DecoratorPatternDemo,我们的演示类使用 RedShapeDecorator 来装饰 Shape 对象
装饰者模式的优缺点:
优点
1、装饰者模式可以提供比继承更多的灵活性
2、可以通过一种动态的方式来扩展一个对象的功能,在运行时选择不同的装饰器,从而实现不同的行为。
3、通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
4、具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
缺点
1、会产生很多的小对象,增加了系统的复杂性
2、这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
装饰者的使用场景
1、在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
2、需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
6、策略模式
完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务。
在软件系统中,有许多算法可以实现某一功能,如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。
策略模式:
意图:定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。
主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
关键代码:实现同一个接口。
应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
策略模式包含如下角色:
- Context: 环境类
- Strategy: 抽象策略类
- ConcreteStrategy: 具体策略类
策略模式优缺点:
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
策略模式使用场景:
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2、一个系统需要动态地在几种算法中选择一种。
3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
7、观察者模式
观察者模式很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。
定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知
例如:微信公众号,当公众号中的内容发生改变是,所有订阅(关注)公众号的人都会收到更新过的内容
结构图:
该模式包含四个角色
- 抽象被观察者角色:也就是一个抽象主题,它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
- 抽象观察者角色:为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
- 具体被观察者角色:也就是一个具体的主题,在集体主题的内部状态改变时,所有登记过的观察者发出通知。
- 具体观察者角色:实现抽象观察者角色所需要的更新接口,一边使本身的状态与制图的状态相协调
观察者模式优缺点:
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点:
1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。