前言:
当前小伙伴们对“设计模式六大原则举例”大体比较关怀,大家都需要知道一些“设计模式六大原则举例”的相关内容。那么小编同时在网络上汇集了一些有关“设计模式六大原则举例””的相关内容,希望大家能喜欢,小伙伴们一起来学习一下吧!1、你所知道的设计模式有哪些
Java 中一般认为有 23 种设计模式,我们不需要所有的都会,但是其中常用的几种设计模式应该去掌握。下面列出了所有的设计模式。需要掌握的设计模式我单独列出来了,当然能掌握的越多越好。
总体来说设计模式分为三大类:
创建型模式,共 5 种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共 7 种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共 11 种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
2、单例模式(Binary Search)
2.1 单例模式定义
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个 Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态。
2.2 单例模式的特点
● 单例类只能有一个实例。
● 单例类必须自己创建自己的唯一实例。
● 单例类必须给所有其他对象提供这一实例
单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。
2.3 单例的四大原则
● 构造私有
● 以静态方法或者枚举返回实例
● 确保实例只有一个,尤其是多线程环境
● 确保反序列换时不会重新构建对象
2.4 实现单例模式的方式
1)饿汉式(立即加载):
饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以本身就是线程安全的。Singleton 通过将构造方法限定为 private 避免了类在外部被实例化,在同一个虚拟机范围内,Singleton 的唯一实例只能通过 getInstance()方法访问。(事实上,通过Java 反射机制是能够实例化构造方法为 private 的类的,会使 Java 单例实现失效)
package com.atguigu.interview.chapter02;
/**
* @author atguigu
*
* 饿汉式(立即加载)
*/
public class Singleton {
/**
* 私有构造
*/
private Singleton() {
System.out.println( "构造函数 Singleton1" );
}
/**
* 初始值为实例对象
*/
private static Singleton single = new Singleton();
/**
* 静态工厂方法
* @return 单例对象
*/
public static Singleton getInstance() {
System.out.println( "getInstance" );
return single;
}
public static void main(String[] args){
System.out.println( "初始化" );
Singleton instance = Singleton.getInstance();
}
}
2)懒汉式(延迟加载):
该示例虽然用延迟加载方式实现了懒汉式单例,但在多线程环境下会产生多个Singleton 对象
package com.atguigu.interview.chapter02;
/**
* @author atguigu
*
* 懒汉式(延迟加载)
*/
public class Singleton2 {
/**
* 私有构造
*/
private Singleton2() {
System.out.println( "构造函数 Singleton2" );
}
/**
* 初始值为 null
*/
private static Singleton2 single = null;
/**
* 静态工厂方法
* @return 单例对象
*/
public static Singleton2 getInstance() {
if (single == null){
System.out.println( "getInstance" );
single = new Singleton2();
}
return single;
}
public static void main(String[] args){
System.out.println( "初始化" );
Singleton2 instance = Singleton2.getInstance();
}
}
3)同步锁(解决线程安全问题):
在方法上加 synchronized 同步锁或是用同步代码块对类加同步锁,此种方式虽然解决了多个实例对象问题,但是该方式运行效率却很低下,下一个线程想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行。
package com.atguigu.interview.chapter02;
/**
* @author atguigu
*
* 同步锁(解决线程安全问题)
*/
public class Singleton3 {
/**
* 私有构造
*/
private Singleton3() {}
/**
* 初始值为 null
*/
Private static Singleton3 single = null;
Public synchronized
static Singleton3 getInstance() {
if (single == null){
single = new Singleton3();
}
}
return single;
}
}
4)双重检查锁(提高同步锁的效率):
使用双重检查锁进一步做了优化,可以避免整个方法被锁,只对需要锁的代码部分加锁,可以提高执行效率。
package com.atguigu.interview.chapter02;
/**
* @author atguigu
* 双重检查锁(提高同步锁的效率)
*/
public class Singleton4 {
/**
* 私有构造
*/
private Singleton4() {}
/**
* 初始值为 null
* 加 volatile 关键字是为了防止 创建对象时的指令重排问题,导致其他线程使用对象时造成空指针问题。
*/
Private volatile static Singleton4 single = null;
/**
* 双重检查锁
* @return 单例对象
*/
public static Singleton4 getInstance() {
if (single == null) {
synchronized (Singleton4. class ) {
if (single == null) {
single = new Singleton4();
}
}
}
return single;
}
}
5) 静态内部类:
这种方式引入了一个内部静态类(static class),静态内部类只有在调用时才会加载,它保证了 Singleton 实例的延迟初始化,又保证了实例的唯一性。它把 singleton 的实例化操作放到一个静态内部类中,在第一次调用 getInstance() 方法时,JVM 才会去加载 InnerObject 类,同时初始化 singleton 实例,所以能让 getInstance() 方法线程安全。
特点是:即能延迟加载,也能保证线程安全。
静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。
package com.atguigu.interview.chapter02;
/**
* @author atguigu
*
* 静态内部类(延迟加载,线程安全)
*/
public class Singleton5 {
/**
* 私有构造
*/
private Singleton5() {}
/**
* 静态内部类
*/
private static class InnerObject{
private static Singleton5 single = new Singleton5();
}
public static Singleton5 getInstance() {
return InnerObject.single;
}
}
6)内部枚举类实现(防止反射和反序列化攻击):
事实上,通过 Java 反射机制是能够实例化构造方法为 private 的类的。这也就是我们现在需要引入的枚举单例模式。
package com.atguigu.interview.chapter02;
/**
* @author atguigu
*/
public class SingletonFactory {
/**
* 内部枚举类
*/
private enum EnumSingleton{
Singleton;
private Singleton6 singleton;
//枚举类的构造方法在类加载是被实例化
private EnumSingleton(){
singleton = new Singleton6();
}
public Singleton6 getInstance(){
return singleton;
}
}
public static Singleton6 getInstance() {
return EnumSingleton.Singleton.getInstance();
}
}
class Singleton6 {
public Singleton6(){}
}
3、工厂设计模式(Factory)
3.1 什么是工厂设计模式?
工厂设计模式,顾名思义,就是用来生产对象的,在 java 中,万物皆对象,这些对象都需要创建,如果创建的时候直接 new 该对象,就会对该对象耦合严重,假如我们要更换对象,所有 new 对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则,如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦
3.2 简单工厂(Simple Factory)
定义:
一个工厂方法,依据传入的参数,生成对应的产品对象;
角色:
1、抽象产品
2、具体产品
3、具体工厂
4、产品使用者
使用说明:
先将产品类抽象出来,比如,苹果和梨都属于水果,抽象出来一个水果类 Fruit,苹果和梨就是具体的产品类,然后创建一个水果工厂,分别用来创建苹果和梨。代码如下:
水果接口:
public interface Fruit {
void whatIm();
}
苹果类:
public class Apple implements Fruit {
@Override
public void whatIm() {
System.out.println( "苹果" );
}
}
梨类:
public class Pear implements Fruit {
@Override
public void whatIm() {
System.out.println( "梨" );
}
}
水果工厂:
public class FruitFactory {
public Fruit createFruit(String type) {
if (type.equals( "apple" )) { //生产苹果
return new Apple();
} else if (type.equals( "pear" )) { //生产梨
return new Pear();
}
return null;
}
}
使用工厂生产产品:
public class FruitApp {
public static void main(String[] args) {
FruitFactory mFactory = new FruitFactory();
Apple apple = (Apple) mFactory.createFruit( "apple" ); //获得苹果
Pear pear = (Pear) mFactory.createFruit( "pear" ); //获得梨
apple.whatIm();
pear.whatIm();
}
}
以上的这种方式,每当添加一种水果,就必然要修改工厂类,违反了开闭原则;所以简单工厂只适合于产品对象较少,且产品固定的需求,对于产品变化无常的需求来说显然不合适。
3.3 工厂方法(Factory Method)
定义:
将工厂提取成一个接口或抽象类,具体生产什么产品由子类决定;
角色:
1、抽象产品
2、具体产品
3、抽象工厂
4、具体工厂
使用说明:
和上例中一样,产品类抽象出来,这次我们把工厂类也抽象出来,生产什么样的产品由子类来决定。代码如下:
水果接口、苹果类和梨类:
代码和上例一样
抽象工厂接口:
public interface FruitFactory {
Fruit createFruit(); //生产水果
}
苹果工厂:
public class AppleFactory implements FruitFactory {
@Override
public Apple createFruit() {
return new Apple();
}
}
梨工厂:
public class PearFactory implements FruitFactory {
@Override
public Pear createFruit() {
return new Pear();
}
}
使用工厂生产产品:
public class FruitApp {
public static void main(String[] args){
AppleFactory appleFactory = new AppleFactory();
PearFactory pearFactory = new PearFactory();
Apple apple = appleFactory.createFruit(); //获得苹果
Pear pear = pearFactory.createFruit(); //获得梨
apple.whatIm();
pear.whatIm();
}
}
以上这种方式,虽然解耦了,也遵循了开闭原则,但是如果我需要的产品很多的话,需要创建非常多的工厂,所以这种方式的缺点也很明显。
3.4 抽象工厂(Abstract Factory)
定义:
为创建一组相关或者是相互依赖的对象提供的一个接口,而不需要指定它们的具体类。
角色:
1、抽象产品
2、具体产品
3、抽象工厂
4、具体工厂
使用说明:
抽象工厂和工厂方法的模式基本一样,区别在于,工厂方法是生产一个具体的产品,而抽象工厂可以用来生产一组相同,有相对关系的产品;重点在于一组,一批,一系列;举个例子,假如生产小米手机,小米手机有很多系列,小米 note、红米 note等;假如小米 note 生产需要的配件有 825 的处理器,6 英寸屏幕,而红米只需要650 的处理器和 5 寸的屏幕就可以了。用抽象工厂来实现:
cpu接口和实现类:
public interface Cpu {
void run();
class Cpu650 implements Cpu {
@Override
public void run() {
System.out.println( "650 也厉害" );
}
}
class Cpu825 implements Cpu {
@Override
public void run() {
System.out.println( "825 更强劲" );
}
}
}
屏幕接口和实现类:
public interface Screen {
void size();
class Screen5 implements Screen {
@Override
public void size() {
System.out.println( "" + "5 寸" );
}
}
class Screen6 implements Screen {
@Override
public void size() {
System.out.println( "6 寸" );
}
}
}
抽象工厂接口:
public interface PhoneFactory {
Cpu getCpu(); //使用的 cpu
Screen getScreen(); //使用的屏幕
}
小米手机工厂:
public class XiaoMiFactory implements PhoneFactory {
@Override
public Cpu.Cpu825 getCpu() {
return new Cpu.Cpu825(); //高性能处理器
}
@Override
public Screen.Screen6 getScreen() {
return new Screen.Screen6(); //6 寸大屏
}
}
红米手机工厂:
public class HongMiFactory implements PhoneFactory {
@Override
public Cpu.Cpu650 getCpu() {
return new Cpu.Cpu650(); //高效处理器
}
@Override
public Screen.Screen5 getScreen() {
return new Screen.Screen5(); //小屏手机
}
}
使用工厂生产产品:
public class PhoneApp {
Javapublic static void main(String[] args){
HongMiFactory hongMiFactory = new HongMiFactory();
XiaoMiFactory xiaoMiFactory = new XiaoMiFactory();
Cpu.Cpu650 cpu650 = hongMiFactory.getCpu();
Cpu.Cpu825 cpu825 = xiaoMiFactory.getCpu();
cpu650.run();
cpu825.run();
Screen.Screen5 screen5 = hongMiFactory.getScreen();
JavaScreen.Screen6 screen6 = xiaoMiFactory.getScreen();
screen5.size();
screen6.size();
}
}
以上例子可以看出,抽象工厂可以解决一系列的产品生产的需求,对于大批量,多系列的产品,用抽象工厂可以更好的管理和扩展。
3.5 三种工厂方式总结
1、对于简单工厂和工厂方法来说,两者的使用方式实际上是一样的,如果对于产品的分类和名称是确定的,数量是相对固定的,推荐使用简单工厂模式;
2、抽象工厂用来解决相对复杂的问题,适用于一系列、大批量的对象生产。
4、代理模式(Proxy)
4.1 什么是代理模式?
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。用图表示如下:
4.2 为什么要用代理模式?
中介隔离作用:
在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
开闭原则,增加功能:
代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要修改已经封装好的委托类。
4.3 有哪几种代理模式?
我们有多种不同的方式来实现代理。
如果按照代理创建的时期来进行分类的话,可以分为两种:静态代理、动态代理。
● 静态代理是由程序员创建或特定工具自动生成源代码,再对其编译。在程序员运行之前,代理类.class 文件就已经被创建了。
● 动态代理是在程序运行时通过反射机制动态创建的。
4.4 静态代理(Static Proxy)
第一步:创建服务类接口
public interface BuyHouse {
void buyHouse();
}
第二步:实现服务接口
public class BuyHouseImpl implements BuyHouse {
@Override
public void buyHouse() {
System.out.println( "我要买房" );
}
}
第三步:创建代理类
public class BuyHouseProxy implements BuyHouse {
private BuyHouse buyHouse;
public BuyHouseProxy(final BuyHouse buyHouse) {Java
this .buyHouse = buyHouse;
}
@Override
public void buyHouse() {
System.out.println( "买房前准备" );
buyHouse.buyHouse();
System.out.println( "买房后装修" );
}
}
第四步:编写测试类
public class HouseApp {
public static void main(String[] args) {
BuyHouse buyHouse = new BuyHouseImpl();
BuyHouseProxy buyHouseProxy = new BuyHouseProxy(buyHouse);
buyHouseProxy.buyHouse();
}
}
静态代理总结:
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:我们得为每一个服务创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
4.5 JDK 动态代理(Dynamic Proxy)
在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由 JDK 在运行时为我们动态的来创建。
第一步:创建服务类接口
代码和上例一样
第二步:实现服务接口
代码和上例一样
第三步:编写动态处理器
public class DynamicProxyHandler implements InvocationHandler {
private Object object;
public DynamicProxyHandler(final Object object) {Java
this .object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Th
rowable {
System.out.println( "买房前准备" );
Object result = method.invoke(object, args);
System.out.println( "买房后装修" );
Javareturn result;
}
}
第四步:编写测试类
public class HouseApp {
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.buyHouse();
}
}
Proxy 是所有动态生成的代理的共同的父类,这个类有一个静态方法Proxy.newProxyInstance(),接收三个参数:
● ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
● Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
● InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
JDK动态代理总结:
优点:相对于静态代理,动态代理大大减少了开发任务,同时减少了对业务接口的依赖,降低了耦合度。
缺点:Proxy 是所有动态生成的代理的共同的父类,因此服务类必须是接口的形式,不能是普通类的形式,因为 Java 无法实现多继承。
4.6 CGLib 动态代理(CGLib Proxy)
JDK 实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要 CGLib 了。CGLib 采用了底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对 final 修饰的类进行代理。
JDK 动态代理与 CGLib 动态代理均是实现 Spring AOP 的基础。
Cglib子类代理实现方法:
(1)引入 cglib 的 jar 文件,asm 的 jar 文件
(2)代理的类不能为 final
(3)目标业务对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
第一步:创建服务类
public class BuyHouse2 {
public void buyHouse() {
System.out.println( "我要买房" );Java
}
}
第二步:创建 CGLIB代理类
public class CglibProxy implements MethodInterceptor {
private Object target;
public CglibProxy(Object target) {
this .target = target;
}
/**
* 给目标对象创建一个代理对象
* @return 代理对象
*/
public Object getProxyInstance() {
//1.工具类
Enhancer enhancer = new Enhancer();
//2.设置父类Java
enhancer.setSuperclass(target.getClass());
//3.设置回调函数
enhancer.setCallback( this );
//4.创建子类(代理对象)
return enhancer.create();
}
Javapublic Object intercept(Object object, Method method, Object[] args, Metho
dProxy methodProxy) throws Throwable {
System.out.println( "买房前准备" );
//执行目标对象的方法
Object result = method.invoke(target, args);
System.out.println( "买房后装修" );
return result;
}
}
第三步:创建测试类
public class HouseApp {
public static void main(String[] args) {
BuyHouse2 target = new BuyHouse2();
CglibProxy cglibProxy = new CglibProxy(target);
BuyHouse2 buyHouseCglibProxy = (BuyHouse2) cglibProxy.getProxyInstan
ce();
buyHouseCglibProxy.buyHouse();
}
}
CGLib代理总结:
CGLib 创建的动态代理对象比 JDK 创建的动态代理对象的性能更高,但是 CGLIB 创建代理对象时所花费的时间却比 JDK 多得多。所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 合适,反之使用 JDK 方式要更为合适一些。同时由于 CGLib 由于是采用动态创建子类的方法,对于 final 修饰的方法无法进行代理。
4.7 简述动态代理的原理, 常用的动态代理的实现方式
动态代理的原理: 使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。
代理对象决定是否以及何时将方法调用转到原始对象上
动态代理的方式
基于接口实现动态代理: JDK 动态代理
基于继承实现动态代理: Cglib、Javassist 动态代理
标签: #设计模式六大原则举例 #java设计模式的好处有哪些