先说说按照功能区分为:一、创建型模式;二、结构型模式;三、行为型模式。
其中,
一、创建型模式:
①工厂方法模式、
②抽象工厂模式、
③建造者模式、
④原型模式、
⑤单例模式。
二、结构型模式:
①适配器模式、
②装饰模式、
③代理模式、
④外观模式、
⑤桥接模式、
⑥组合模式、
⑦享元模式。
三、行为型模式:
①策略模式、
②模板方法模式、
③观察者模式、
④迭代子模式、
⑤责任链模式、
⑥命令模式、
⑦访问者模式、
⑧备忘录模式、
⑨状态模式、
⑩中介模式、
①①解释器模式。
这里主要对于常用的7种设计模式进行举例说明(工、单、代、策、适、观、装):
一):工厂模式
其主要由三大部分组成:
1、工厂类角色;—往往由一个具体类来实现,是工厂模式的核心,商业及判断逻辑的主要区域。
2、抽象产品角色;—是作为具体产品要继承的或者实现的父类或接口,常用接口来实现。
3、具体产品角色。—工厂类所创建的对象就是具体产品角色的实例,常由一个具体类实现。
举个栗子:
1、简单工厂模式:
//二、抽象产品角色
public interface Huawei{
public void wear();
}
//三、具体产品角色1
public class Mate7 implements Huawei {
@Override
public void wear() {
System.out.println("Wearing Mate7");
}
}
//具体产品角色2
public class Mate10 implements Huawei {
@Override
public void wear() {
System.out.println("Wearing Mate10");
}
}
//一、工厂类角色
public class Wear {
//注意:返回类型为抽象产品角色
public static Huawei wearHuawei(String s)throws Exception{
//判断逻辑,返回具体的产品角色给Client
if(s.equalsIgnoreCase("Mate7")) {
//则新建一个类对象实例
return new Mate7();
}else if(s.equalsIgnoreCase("Mate10")) {
return new Mate10();
}else {
throw new Exception();
}
}
}
/**
* 简单工厂模式:
* 包含的接口和类为:抽象产品角色(接口)Huawei、具体类Mate7、具体类Mate10、工厂类角色Wear
*
*/
//铁粉
public class Fans {
public static void main(String[] args) {
try {
//今天用Mate7---创建抽象产品角色
Huawei huawei = Wear.wearHuawei("Mate7");
//选定,提货出发
huawei.wear();
} catch (Exception e) {
//--------------------
}
}
}
2、抽象工厂模式:
使用目的:给客户端提供一个接口,可以创建多个产品族中的产品对象,但要满足以下条件:
- 系统中有多个产品族,而系统一次只可能消费其中一族产品。
- 属于同一个产品族的产品要一起使用。
先看优点。若使用抽象工厂模式,对于客户来说,不再需要进行采购的“组装”,而是指定一个“大厂”,让其进行“一站式”服务。
看代码:
public static void main(String[] args) {
// 第一步:选定一个“大厂”
ComputerFactory cf = new AmdFactory();
// 从这个大厂造 CPU
CPU cpu = cf.makeCPU();
// 从这个大厂造主板
MainBoard board = cf.makeMainBoard();
// 从这个大厂造硬盘
HardDisk hardDisk = cf.makeHardDisk();
// 将同一个厂子生产的 CPU、主板、硬盘组装在一起
Computer result = new Computer(cpu, board, hardDisk);
}
完美的使用到了封装结果集,但是也有弊端:
使用抽象工厂模式,在后期想要给该产品族中加入新的产品的时候(客户需要再帮他生产显示屏时),也就是“经营范围”需要扩大时,则需要对所有的工厂进行重新修改,这样影响了系统迭代升级的效率。
二)、单例模式
单例模式的核心设计思想:
1、私有的构造方法;
2、私有的静态的当前类对象作为属性;
3、公有的静态的方法返回当前类对象;
这里会涉及到单例模式的6种写法(含优缺点分析):
1)、饿汉式
public class Singleton
{
//私有静态当前类对象
private static Singleton instance = new Singleton();
//私有构造方法
private Singleton() {}
//公有的静态方法
public static Singleton getInstance() {
return instance;//返回当前的类对象
}
}
优点:实现简单,不存在多线程问题,直接声明一个私有对象,然后通过公有方法对外提供一个获取对象的方法。
缺点:类在被加载的时候便创建Singleton实例,如果对象创建后一直没有使用,则会浪费很大的内存空间,此方法不适合创建大对象。
(饿汉式,是先创建好再等待使用,在一些地方合理使用,可以起到优化的作用)
2)、线程不安全的懒汉式
public class Singleton
{
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
//如果实例为空,则在对外公开的方法被调用时,才开始创建实例。此为懒汉式
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:节省内存空间,在使用的时候才会创建;
缺点:多线程下,可能会创建多个实例(有时候如果在单例对象的构造方法中做了某些重要操作,创建多个实例可能会造成线程不安全)。
3)、线程安全懒汉式
public class Singleton
{
private static Singleton instance = null;
private Singleton() {}
public synchronized static Singleton getInstance() {
//与线程不安全懒汉式对比,只是在公共静态方法上添加了synchronized关键字
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:支持多线程,且以懒汉式的方式加载,不浪费内存空间。
缺点:将 synchronized 块加在方法上,会影响并发量,每次调用getInstance()方法都会被线程同步,效率十分低下。但实际应用中,当创建好实例对象之后,就没必要继续进行同步了,因为我们主要是避免再创建实例。
4)、线程安全懒汉式(又叫双锁检验。推荐)
public class SynchronizedSingleton {
/**volatile防止指令重排
volatile关键字,考虑的是,new关键字在虚拟机中执行时其实分为很多步骤,
具体原因可以参考深入理解java虚拟机一书(考虑的是这个new关键字字节码执行时是非原子性的),
而volatile关键字可以防止指令重排。*/
private static volatile SynchronizedSingleton singleton;
private SynchronizedSingleton() {
}
/**只是在实例为空时才进行同步创建
* 为什么做了2次判断?
* A线程和B线程同时进入同步方法0
* 然后都在1位置处判断了实例为null
* 然后都进入了同步块2中
* 然后A线程优先进入了同步代码块2中(B线程也进入了),然后创建了实例
* 此时,如果没有3处的判断,那么A线程创建实例同时,B线程也会创建一个实例
* 所以,还需要做2次判断
* */
public static SynchronizedSingleton getInstance(){//0
if(singleton == null){//1
synchronized (SynchronizedSingleton.class){//2
if(singleton == null){//3
singleton = new SynchronizedSingleton();//4
}
}
}
return singleton;
}
}
优点:支持多线程,并发量高,且以懒汉式加载,不浪费内存空间。
缺点:实现比较麻烦,即用到了volatile,也用到了synchronized,还有双重判断。
5)、内部类
public class Singleton {
private static class SingletonHolder{//创建内部静态类,将实例的创建放入静态类中
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
优点:由于静态内部类跟外部类是平级的,所以外部类加载的时候不会影响内部类,因此实现了lazy loading, 同时也是利用静态变量的方式,使得INSTANCE只会在SingletonHolder加载的时候初始化一次,从而保证不会有多线程初始化的情况,因此也是线程安全的。
6)、枚举式
public enum Singleton{
INSTANCE;
public void whateverMethod() {
}
}
这是知名书籍《Java Effective》—Java高效编程推荐的单例实现方式,这种代码最简练,并且天生线程安全。
三)、代理模式
代理模式是最常使用的模式之一
既然说是代理,那就要对客户端隐藏真实实现,由代理来负责客户端的所有请求。当然,代理只是个代理,它不会完成实际的业务逻辑,而是一层皮而已,但是对于客户端来说,它必须表现得就是客户端需要的真实实现。
静态代理总结:
1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,且一旦接口增加方法,目标对象与代理对象都要维护。
如何解决静态代理中的缺点呢?
答案是当然可以。使用动态代理方式
代理模式的应用场景:
如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:
1、修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。
2、就是采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。
使用代理模式,可以将功能清晰划分,有助于后期维护!
动态代理有以下3个主要特点:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理、接口代理
JDK中生成代理对象的API
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
InvocationHandler h)
//接口
public interface UserDao {
void save();
}
//接口实现及目标对象的创建
public class UserDaoImpl implements UserDao{
public void save() {
System.out.println("-------已保存数据--------");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 创建动态代理对象(JDK代理)
* 动态代理不需要实现接口,但是需要指定接口类型
*/
public class ProxyFactory {
//维护一个目标对象
private Object target;//定义对象
public ProxyFactory(Object target) {
this.target = target;//将参数传递给对象
}
//给定目标对象生成代理对象
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {//此处匿名内部类
@Override//重写reflect的invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务2");
return null;
}
});
}
}
/**
* 动态代理---测试类
*/
public class ProxyTestDynamic {
public static void main(String[] args) {
//目标对象
UserDao target = new UserDaoImpl();
System.out.println(target.getClass());
//给目标对象创建代理对象
UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance();
//内存中动态生成的代理对象
System.out.println(proxy.getClass());
//执行方法 【即代理】
proxy.save();
}
}
注意:代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理,这也是JDK动态代理的一个关于InvocationHandler() {}的要注意的点。
拓展:
JDK动态代理
利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGLiB动态代理
利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。实现CGLIB动态代理必须实现MethodInterceptor(方法拦截器)接口。
区别:
jdk只能针对接口不能针对类实现代理。
CGLib通过继承方式实现代理。所以类或方法最好不要声明成final,因为对于final类或方法,是无法继承的。
Spring如何选择用JDK还是CGLib?
1)当Bean实现接口时,Spring就会用JDK的动态代理。
2)当Bean没有实现接口时,Spring使用CGlib是实现。
3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)。
动态代理的作用是什么:
Proxy类的代码量被固定下来,不会因为业务的逐渐庞大而庞大;
可以实现AOP编程,实际上静态代理也可以实现,总的来说,AOP可以算作是代理模式的一个典型应用;
解耦,通过参数就可以判断真实类,不需要事先实例化,更加灵活多变。
四)、适配器模式
将一个类的接口转换成客户希望的另外 一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式(装饰器模式)的应用场景:
1、需要扩展一个类的功能。
2、动态的为一个对象增加功能,而且还能动态撤销。(继承不能做到这一点,继承的功能是静态的,不能动态增删。)
缺点:产生过多相似的对象,不易排错!