-
目录 一、单例模式 二、工厂模式 三、代理模式 1、现有业务层存在的问题 2、代理的开发 3、静态代理的开发 (1)静态代理类 (3)调用代理类的方法 4、动态代理开发 (1)前置知识:通过反射调用对象的方法 (2)通过proxy类,动态的为现有业务生成代理对象 一、单例模式 1、只创建一个实例对象的设计模式称为单例模式 2、单例模式的优点:可以节省创建对象的时间和对象占用的空间 3、单例模式的对象必须是无状态的 4、无状态的条件(满足任意之一即可): (1)类本身是没有非静态的成员属性 (2)有非静态的成员属性,但是这些属性是无状态的 5、单例的两种实现: (1)饿汉式: 一开始就创建对象 (2)懒汉式: 等到用到时再创建对象 二、工厂模式 1、使用工厂函数而不是直接通过new来创建对象 2、工厂模式的优点:可以根据不同创建条件创建不同对象,并且可以在启动工厂时,预先创建对象 3、基于配置文件的单例工厂实现 (1)原理:根据类的原先定名创建对象(使用反射) 三、代理模式 1、现有业务层存在的问题 (1)业务层中控制事务的代码和业务逻辑代码出现了耦合 假设运来的代码使用方法A控制十五,后来使用方法B去控制事务,此时可能不慎修改了业务代码,同理,也可能在修改业务代码时不慎修改了控制事务的代码 解决方案: 开发一个代理类,也可以实现接口中的所有业务方法。再实现是,除了控制事务的代码外,直接调用真正的业务层的相应业务方法即可 (2)业务层中控制事务的代码中出现了大量的冗余 2、代理的开发 代理对象可以在客户和目标对象之间起到中介作用,从而为对象目标增添额外的功能 之前的处理方式: action(controller)调用service,service调用dao 使用代理后的处理方式: action(controller)调用proxy,proxy调用service,service调用dao 代理类和目标类必须实现同样的接口,且代理对象会依赖目标对象 3、静态代理的开发 (1)静态代理类 (2)更改目标实现类 (3)调用代理类的方法 4、动态代理开发 (1)前置知识:通过反射调用对象的方法 (2)通过proxy类,动态的为现有业务生成代理对象 ———————————————— 原文链接:https://blog.csdn.net/XHW0901/article/details/126316012
-
文详细介绍了Java中三种常用的设计模式:单例模式、工厂模式和代理模式。对于单例模式,讲解了其概念、优缺点及五种常见的实现方式;在工厂模式部分,通过简单工厂、工厂方法和抽象工厂展示了如何创建不同类型的对象;最后,通过静态和动态代理模式的实践,阐述了如何在实际操作中进行类的代理操作。 一、单例模式 1.概述 单例模式的定义就是确保某一个类只有一个实例,并且提供一个全局访问点。属于设计模式三大类中的创建型模式。 单例模式具有典型的三个特点: 只有一个实例。 自我实例化。 提供全局访问点。 2.优缺点 优点:由于单例模式只生成了一个实例,所以能够节约系统资源,减少性能开销,提高系统效率,同时也能够严格控制客户对它的访问。 缺点:也正是因为系统中只有一个实例,这样就导致了单例类的职责过重,违背了“单一职责原则”,同时也没有抽象类,这样扩展起来有一定的困难。 3.常见实现方式 常见的单例模式实现方式有五种:饿汉式、懒汉式、双重检测锁式、静态内部类式和枚举单例。而在这五种方式中饿汉式和懒汉式又最为常见。下面将一一列举这五种方式的实现方法: 饿汉式:线程安全,调用效率高。但是不能延时加载。示例: public class SingletonDemo1 { //线程安全的 //类初始化时,立即加载这个对象 private static SingletonDemo1 instance = new SingletonDemo1(); private SingletonDemo1() { } //方法没有加同步块,所以它效率高 public static SingletonDemo1 getInstance() { return instance; } } 由于该模式在加载类的时候对象就已经创建了,所以加载类的速度比较慢,但是获取对象的速度比较快,且是线程安全的。 懒汉式:线程不安全。示例: public class SingletonDemo2 { //线程不安全的 private static SingletonDemo2 instance = null; private SingletonDemo2() { } //运行时加载对象 public static SingletonDemo2 getInstance() { if (instance == null) { instance = new SingletonDemo2(); } return instance; } } 由于该模式是在运行时加载对象的,所以加载类比较快,但是对象的获取速度相对较慢,且线程不安全。如果想要线程安全的话可以加上synchronized关键字,但是这样会付出惨重的效率代价。 懒汉式(双重同步锁) public class SingletonDemo3 { private static volatile SingletonDemo3 instance = null; private SingletonDemo3() { } //运行时加载对象 public static SingletonDemo3 getInstance() { if (instance == null) { synchronized(SingletonDemo3.class){ if(instance == null){ instance = new SingletonDemo3(); } } } return instance; } } 由于剩下的几种实现方式暂没有接触过,可暂时参考一张图搞定Java设计模式,单例模式。 注:注意单例模式所属类的构造方法是私有的,所以单例类是不能被继承的。 (这句话表述的有点问题,单例类一般情况只想内部保留一个实例对象,所以会选择将构造函数声明为私有的,这才使得单例类无法被继承。单例类与继承没有强关联关系。) 4.常见应用场景 网站计数器。 项目中用于读取配置文件的类。 数据库连接池。因为数据库连接池是一种数据库资源。 Spring中,每个Bean默认都是单例的,这样便于Spring容器进行管理。 Servlet中Application Windows中任务管理器,回收站。 二、工厂模式 1.对工厂模式的理解 简单工厂:通过工厂类生成不同的类。工厂类返回一个父类型的类,通过if或者switch判断用户给的数据,通过不同的数据返回不同的类。 工厂方法:比较重要的就是抽象类里面的一个抽象方法,所有继承了抽象类的类都必须实现该方法,之后在调用的时候利 ———————————————— 原文链接:https://blog.csdn.net/qq_42674061/article/details/109735623
-
1.什么是设计模式? 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的实验和错误总结出来的。 设计模式是一套被反复使用的,多数人知晓的,经过分类编目的,代码设计经验的总结 2.设计模式的作用是什么? 使用设计模式就是为了重用代码,让代码更容易被他人理解,保证代码可靠性。 3.常见的设计模式 常见的设计模式又23种。 3.1单例模式 单例模式---保证一个类仅有一个实例 当类被频繁地创建与销毁的时候,我们使用单例模式,这样可以减少了内存的开销,避免对资源的多重占用 单例模式条件: 1.构造方法私有 2.提供一个静态方法【公共】返回创建号的当前类对象 两种表示方式 懒汉式 例如: package com.test1; /** * 懒汉式 * @author zxc * */ public class SingleObject1 { private static SingleObject1 sobj=null; private SingleObject1(){} public static SingleObject1 getSinleObject1(){ if(sobj==null){ sobj=new SingleObject1(); } return sobj; } } package com.test1; public class TestMain { public static void main(String[] args) { SingleObject1 s1=SingleObject1.getSinleObject1(); SingleObject1 s2=SingleObject1.getSinleObject1(); System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); if(s1==s2){ System.out.println("是同一对象"); } } } 饿汉式 例如: package com.test1; /** * 饿汉式 * @author zxc * */ public class SingleObject2 { private static SingleObject2 sobj=new SingleObject2(); private SingleObject2(){} //当在多线程情况下使用是为了保证当前类对象只有一个我们就需要添加synchornized public static synchronized SingleObject2 getSinleObject1(){ return sobj; } } package com.test1; public class TestMain { public static void main(String[] args) { SingleObject2 s1=SingleObject2.getSinleObject1(); SingleObject2 s2=SingleObject2.getSinleObject1(); System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); if(s1==s2){ System.out.println("是同一对象"); } } } 懒汉式与饿汉式的区别 相同点:保证当前类的对象只有一个 书写上:1.构造方法私有 2. 提供一个静态方法【公共】返回创建好的当前类对象 不同点: 书写上:懒汉式中保存当前类的对象变量初始为null 饿汉式中保存当前类的对象初始变量为new好的当前类对象 运行速度上:懒汉式比饿汉式稍微差一些 资源利用率:饿汉式比懒汉式稍微差一些 3.2工厂模式 工厂模式---有一个专门的java类充当当前生产对象的工厂 使用工厂模式的条件:1.需求量大 2.牵一发,动全身。 工厂模式中的角色:工厂角色---生产对象 抽象产品角色---【抽象类/接口】 具体产品-----【抽象类/接口子类】 例如:有农场生产各种水果,有西瓜,有苹果,有香蕉 工厂角色---农场 抽象产品角色---水果 西瓜、苹果、香蕉---具体产品 在项目目录下创建出一个菜单文件 苹果=com.test2.PingGuo 西瓜=com.test2.XiGua package com.test2; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.util.HashMap; public class NongChang { public static ShuiGuo maishuiguo(String name){ ShuiGuo sg=null; //读取菜单 HashMap<String, String> menuMap=readMenu(); //根据键得到值 String calssName=menuMap.get(name); try{ //利用反射机制创建对象 Class classobj=Class.forName(calssName); sg=(ShuiGuo)classobj.newInstance(); }catch(Exception e){ e.printStackTrace(); } return sg; } /** * 读取菜单 */ private static HashMap<String, String> readMenu() { HashMap<String, String> menuMap=new HashMap<String, String>(); try { BufferedReader buff=new BufferedReader(new FileReader(new File("menu.txt"))); String menuitem=null; while((menuitem=buff.readLine())!=null){ String muenuarray[]=menuitem.split("="); menuMap.put(muenuarray[0],muenuarray[1]); } buff.close(); }catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return menuMap; } } package com.test2; public interface ShuiGuo { void eat(); } package com.test2; public class PingGuo implements ShuiGuo{ @Override public void eat() { System.out.println("我是苹果,削皮吃"); } } package com.test2; public class XiGua implements ShuiGuo{ @Override public void eat() { System.out.println("我是西瓜"); } } package com.test2; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class TestMain { public static void main(String[] args) { BufferedReader buff=new BufferedReader(new InputStreamReader(System.in)); System.out.println("买水果"); String info; try { info = buff.readLine(); ShuiGuo sg=NongChang.maishuiguo(info); sg.eat(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } 3.3代理模式 代理模式---为其他对象提供一种代理以控制对这个对象的访问。 买火车票不一定在火车站买,也可以去代售点 代理模式被分为兄弟模式和父子模式 缺点:需要额外提供业务功能实现相似的子类。【 工作量大】 兄弟模式---同一个接口的两个子类 例如: package com.test3; public interface SellPiao { void maipiao(); } package com.test3; public class HuoCheZhan implements SellPiao{ @Override public void maipiao() { System.out.println("火车站买票"); } } package com.test3; public class DaiShouDian implements SellPiao{ @Override public void maipiao() { System.out.println("代售点买票"); } } package com.test3; public class TestMain { public static void main(String[] args) { HuoCheZhan hcz=new HuoCheZhan(); hcz.maipiao(); DaiShouDian dsd=new DaiShouDian(); dsd.maipiao(); } } 父子模式---继承关系 例如: package com.test4; public class HuoCheZhan { public void maipao(){ System.out.println("火车站买票"); } } package com.test4; public class DaiShouDian extends HuoCheZhan{ public void maipao(){ System.out.println("代售点买票"); } } package com.test4; public class TestMain { public static void main(String[] args) { HuoCheZhan hcz=new HuoCheZhan(); hcz.maipao(); DaiShouDian dsd=new DaiShouDian(); dsd.maipao(); } } 动态代理---由一个java类来负责创建代理类对象 JDK动态代理---通过java.lang.reflect包Class Proxy类来创建代理类对象 例如: package com.test5; public interface SellPiao { void maipiao(); } package com.test5; public class HuoCheZhan implements SellPiao{ @Override public void maipiao() { System.out.println("火车站买票"); } } package com.test5; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyObject implements InvocationHandler{ //定义目标对象 private Object targetObject; public ProxyObject(Object targetObject){ this.targetObject=targetObject; } //得到代理对象 public Object getProxy(){ //java.lang.reflect包 Class Proxy类 //ClassLoader loader---类加载器 ClassLoader loader=this.getClass().getClassLoader(); //Class<?>[] interfaces---接口反射对象 Class interfaces[]=this.targetObject.getClass().getInterfaces(); //InbocationHander h-this return Proxy.newProxyInstance(loader, interfaces, this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return method.invoke(targetObject, args); } } package com.test5; public class TestMain { public static void main(String[] args) { HuoCheZhan hcz=new HuoCheZhan(); ProxyObject proxyobj=new ProxyObject(hcz); //代理类对象 SellPiao dsd=(SellPiao)proxyobj.getProxy(); dsd.maipiao(); } } 【只能为实现过某个接口的java类提供代理类对象】 CGlib代理---CGlib是一个第三发的开发包,用的时候需要自己实现下载导入到项目中 例如: package com.test6; public class HuoCheZhan { public void maipiao(){ System.out.println("火车站买票"); } } package com.test6; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class ProxyObject implements MethodInterceptor{ //定义目标对象 private Object targetObject; public ProxyObject(Object targetObject){ this.targetObject=targetObject; } //得到代理对象 public Object getPrxoy(){ Enhancer enhancer=new Enhancer(); enhancer.setSuperclass(targetObject.getClass()); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object proxy, Method arg1, Object[] params, MethodProxy methodProxy) throws Throwable { // TODO Auto-generated method stub return methodProxy.invokeSuper(proxy, params); } } package com.test6; public class TestMain { public static void main(String[] args) { HuoCheZhan hcz=new HuoCheZhan(); ProxyObject proxy=new ProxyObject(hcz); HuoCheZhan dsd=(HuoCheZhan)proxy.getPrxoy(); dsd.maipiao(); } } 【所有的java类提供代理类对象】 比较: 1.静态代理是通过在代码中显式定义一个业务实现类一个代理,在代理类中对同名的业务方法进行包装,用户通过代理类调用被包装过的业务方法; 手动创建一个与目标类相同的接口的子类,包装目标类。 2.JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法;【兄弟模式】 通过jdk提供的反射保重Proxy这个类,动态的创建一个与目标类实现相同接口的子类对象,包装目标。 3.CGlib提供的Enhancer这个类,动态的创建一个目标类的子类对象,包装目标类。 ———————————————— 原文链接:https://blog.csdn.net/weixin_52821030/article/details/121773921
-
(注:本文思想主要来源于哈工大计算学部王忠杰教授的《软件构造》) 一、创造型模式 1.工厂方法模式 工厂方法:也被称为“虚拟构造器”。定义用于创建对象的接口,但让子类决定实例化哪个类。工厂方法允许类将实例化延迟到子类。当client不知道要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。 优点:消除了将特定于应用程序的类绑定到代码的需要。 体现了OCP原则(对扩展的开放,对修改已有代码的封闭) 缺点:客户端可能需要创建一个Creator的子类,这样他们就可以创建一个特定ConcreteProduct。 二、结构化模式 1.适配器模式 适配器Adapter:目的: 将某个类/接口转换为client期望的其他形式,适配器允许类一起工作,否则由于接口不兼容而无法工作。通过增加一个接口,将已存在的子类封装起来,client面向接口编程,从而隐藏了具体子类。对象:将旧组件重用到新系统(也称为“包装器”) 比如,可以举个例子: 2.装饰器模式 为对象增加不同侧面的特性 ,对每一个特性构造子类,通过委派机制增加到对象上,例如,下图为一个装饰器模式对应的类图。 可以举以下例子: 三、行为类模型 1.策略模式 针对特定的任务存在不同的算法,但客户端可以在运行时根据动态上下文在算法之间切换。 比如,对客户列表进行排序(冒泡排序、合并排序、快速排序),为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例。 例如,你想实现购物时的两种支付方法,类图绘制如下: 创建不同的子类对象,能够实现不同的结算方式。 2.模板模式 做事情的步骤一样,但具体方法不同 。比如打开、读取、编写不同类型的文档。我们可以让共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现。 不难想到,通常使用继承和重写实现模板模式。而策略模式使用委托来改变整个算法。 3.迭代器 客户端希望遍历被放入容器/集合类的一组ADT对象,无需关心容器的具体类型。也就是说,不管对象被放进哪里,都应该提供同样的遍历方式。 通过迭代的策略模式,我们能够隐藏底层容器的内部实现,支持统一接口的多种遍历策略,易于更改容器类型且促进程序各部分之间的交流。 迭代模式:让自己的集合类实现Iterable接口,并实现自己的独特Iterator迭代器(hasNext, next, remove),允许客户端利用这个迭代器进行显式或隐式的迭代遍历: 4.visitor 对特定类型的object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类 。为ADT预留一个将来可扩展功能的“接入点”,外部实现的功能代码 可以在不改变ADT本身的情况下通过delegation接入ADT。 本质上:将数据和作用于数据上的某种/些特定操作分离开来。 Strategy vs visitor: 二者都是通过delegation建立两个对象的动态联系,但是Visitor强调是的外部定义某种对ADT的操作,该操作于ADT自身关系不大(只是访问ADT),故ADT内部只需要开放accept(visitor)即可,client通过它设定visitor操作并在外部调用。 而Strategy则强调是对ADT内部某些要实现的功能的相应算法的灵活替换。这些算法是ADT功能的重要组成部分,只不过是delegate到外部strategy类而已。 visitor是站在外部client的角度,灵活增加对ADT的各种不同操作(哪怕ADT没实现该操作),strategy则是站在内部ADT的角度,灵活变化对其内部功能的不同配置。 ———————————————— 原文链接:https://blog.csdn.net/m0_73695112/article/details/139197206
-
设计模式总结: 5种创建型,7种结构型,11种行为型1.1 工厂方法模式 工厂方法模式(根据参数不同,工厂方法返回不同的产品对象), 多个工厂方法模式(创建不同产品不需要参数), 静态工厂方法模式(将工厂方法改为静态的,不需要实例化类)1.2 抽象工厂:适合产品多变的情况,要生产新的产品就必须在工厂类里面加生产的方法,违背开闭原则。抽象工厂, 增加一个 工厂类接口,一个接口方法; 各种产品实现这个工厂接口,生产自己对应的产品。1.3 单例模式 :Singleton (懒汉式&饿汉式)特殊的工厂方法模式,一个类只有一个实例: 1. 拥有一个私有的静态实例; 2. 拥有 私有的默认构造函数; 3. 静态工厂方法,如果是懒汉式的必须同步的,防止多线程同时操作; 4.重写clone()函数,返回当前实例对象,默认clone()创建新实例;public class SingletonFactory{ //1.私有的防止外部变量引用; private static SingletonFactory _instance=null; //2.私有的默认构造函数,防止使用构造函数实例化; private SingletonFactory(){ } //3.单例静态工厂方法,同步方式多线程同时执行;synchronized public static SingletonFactory getInstance(){ if(_instance==null){ _instance=new SingletonFactory(); } retrun _instance; } //4.重写克隆函数public SingletonFactory clone(){ return getInstance(); }} 1.4 建造者模式 StringBuilderpublic class Client { public static void main(String[]args){ Builder builder = new ConcreteBuilder();//接口buider,实现接口的具体建造者ConcreteBuilder,导演者Director, Director director = new Director(builder); director.construct(); Product product = builder.retrieveResult(); System.out.println(product.getPart1()); System.out.println(product.getPart2()); }}1.5 原型模式: 复制一个已经存在的实例来返回一个新的实例,而不是新建------------------------------------------------------------------------------------------------------------2.1 适配器模式:类的适配器:对类进行适配。A继承B,实现接口C。对象的适配器:对对象进行包装;A实现接口C,不继承B,包含一个B的对象;接口的适配器:对接口抽象化。A实现所有接口,为每个接口提供一个默认实现;缺省适配器模式: (A extends AbstractB implements interfaceC,那么A即可以选择实现(@Override)接口interfaceC中的方法,也可以选择不实现;A即可以选择实现(@Override)抽象类AbstractB中的方法,也可以选择不实现)2.2 装饰器模式:(io流)一个接口A,一个接口A的实现类B,一个装饰器C。C实现了A,并且有一个私有的类型为A的成员,构造函数初始化它。适配器模式是将一个类A转换为另一个类B;装饰器模式是为一个类A增强新的功能,从而变成B;代理模式是为一个类A转换为操作类B;2.3 代理模式 Proxy实现接口Sourcable,含有一个Source对象2.4 外观模式 jdbc代理模式(一对一)发展而来的外观模式(一对多)客户端访问子系统中的各个类,高度耦合。中间加一个统一接口,降低耦合度,客户端访问该类,该类调用子系统中的各个类。2.5 桥接模式 jdbc客户端通过访问 桥 访问目标实现类,桥抽象化类包含目标接口类对象,可以赋值 目标接口类的实现类的实例化给他,通过桥调用不同的目标实现类对象。2.6 组合2.7 享元-----------------------------------------父类与子类之间:1.策略模式:【实现C中函数指针的功能,不同的比较策略】 (comparable & Comparator);2.模板方法模式两个类之间:3.观察者模式4.迭代子模式 (容器与容器遍历)5.责任链模式 (责任链模式) ( 串的替换---可以使用链式操作)6.命令模式类的状态:7.备忘录模式8.状态模式通过中间类:9.访问者模式10.中介模式11.解释器模式原文链接:https://blog.csdn.net/farphone/article/details/70324649
-
Java软件设计中的设计模式主要分为三大类:创建型模式、结构型模式和行为型模式。这些模式为解决常见设计问题提供了通用的解决方案,提升代码的可复用性、可维护性和可扩展性。 创建型模式 创建型模式(Creational Patterns)主要处理对象创建的方式,目的是将对象的创建与使用分离,并避免硬编码构造过程。通过使用创建型模式,可以提高代码的灵活性、可扩展性和可维护性。以下是五种常见的创建型模式的详细说明。 1. 单例模式(Singleton Pattern) 定义 确保一个类只有一个实例,并提供一个全局访问点来访问该实例。 动机 在某些情况下,整个应用程序中只需要一个类的实例,如日志管理器、数据库连接池、配置管理等。通过单例模式可以确保只有一个实例被创建,避免重复创建带来的资源浪费,并且全局访问点确保所有客户端都能访问到同一个实例。 结构 Singleton类:持有自己的静态实例,并提供一个静态方法来返回这个唯一实例。 构造方法:私有,防止外部类直接实例化对象。 示例 public class Singleton { // 私有的静态实例 private static Singleton instance; // 私有构造方法,防止外部实例化 private Singleton() {} // 静态方法,提供全局访问点 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 应用场景 日志记录器 数据库连接池 配置管理器 优点 控制实例数量,节省资源 提供全局访问点 缺点 不支持多线程(如果不进行同步处理,可能会导致多个实例的创建) 单例对象难以扩展和测试(例如,在单元测试中需要使用多个不同实例时可能会有困难) 多线程改进(双重检查锁定) public 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; } } 2. 工厂方法模式(Factory Method Pattern) 定义 定义一个用于创建对象的接口,但由子类决定具体要实例化的类。工厂方法让一个类的实例化延迟到子类。 动机 当代码中需要创建对象,但不想暴露具体类的构造过程,或者为了扩展方便,希望可以灵活地指定子类来实例化具体对象时,工厂方法模式提供了解决方案。 结构 抽象产品类:定义产品的接口。 具体产品类:实现抽象产品的接口,表示不同类型的产品。 抽象工厂类:定义一个抽象的工厂方法,返回抽象产品类型。 具体工厂类:实现工厂方法,返回具体的产品实例。 示例 // 产品接口 abstract class Product { abstract void use(); } // 具体产品类 class ConcreteProduct extends Product { void use() { System.out.println("Using ConcreteProduct"); } } // 工厂接口 abstract class Creator { abstract Product createProduct(); } // 具体工厂类 class ConcreteCreator extends Creator { Product createProduct() { return new ConcreteProduct(); } } 应用场景 日志记录系统:根据不同的日志级别创建不同的日志处理器。 数据库访问层:使用不同的数据库(如MySQL、Oracle等)时,通过工厂方法灵活选择数据库驱动。 优点 客户端不需要知道具体产品的创建逻辑,只依赖工厂接口。 符合“开放-封闭”原则:可以通过子类扩展新产品,而无需修改现有代码。 缺点 增加了系统的复杂度,需要创建额外的工厂类。 3. 抽象工厂模式(Abstract Factory Pattern) 定义 提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。 动机 如果系统需要多个相互关联的对象,且不希望在客户端直接依赖具体类时,抽象工厂模式通过提供统一的接口,来生成相关的对象族,从而保证了对象之间的一致性和可扩展性。 结构 抽象工厂接口:定义创建一系列相关产品的接口。 具体工厂类:实现抽象工厂接口,负责生成具体的产品。 抽象产品接口:定义产品的通用接口。 具体产品类:实现抽象产品接口,表示不同的产品。 示例 // 抽象产品 interface Button { void paint(); } interface Checkbox { void paint(); } // 具体产品 class WinButton implements Button { public void paint() { System.out.println("Render a button in Windows style."); } } class MacButton implements Button { public void paint() { System.out.println("Render a button in MacOS style."); } } // 抽象工厂 interface GUIFactory { Button createButton(); Checkbox createCheckbox(); } // 具体工厂 class WinFactory implements GUIFactory { public Button createButton() { return new WinButton(); } public Checkbox createCheckbox() { return new WinCheckbox(); } } class MacFactory implements GUIFactory { public Button createButton() { return new MacButton(); } public Checkbox createCheckbox() { return new MacCheckbox(); } } 应用场景 跨平台的GUI工具包:为不同操作系统(如Windows、Mac)创建风格一致的UI组件。 数据库访问:为不同数据库(如MySQL、PostgreSQL)生成一组相关的操作对象。 优点 客户端不需要直接实例化对象,易于扩展。 保证了相关产品之间的一致性。 缺点 代码复杂度增加,需要维护更多的类。 当需要扩展新产品族时,可能需要修改抽象工厂接口。 4. 建造者模式(Builder Pattern) 定义 将一个复杂对象的构建过程与它的表示分离,使得同样的构建过程可以创建不同的表示。 动机 有些对象的构建过程非常复杂,包含多个步骤(如配置文件的解析、复杂UI界面的生成)。建造者模式允许分步骤创建对象,且同样的构造过程可以构建不同的对象。 结构 Builder接口:定义构建复杂对象的步骤。 ConcreteBuilder类:实现Builder接口,完成具体步骤。 Director类:负责按顺序调用建造者的构建步骤。 Product类:复杂对象,由多个部分组成。 示例 // 产品类 class Product { private String partA; private String partB; public void setPartA(String partA) { this.partA = partA; } public void setPartB(String partB) { this.partB = partB; } public void showProduct() { System.out.println("Product with " + partA + " and " + partB); } } // 抽象建造者 interface Builder { void buildPartA(); void buildPartB(); Product getResult(); } // 具体建造者 class ConcreteBuilder implements Builder { private Product product = new Product(); public void buildPartA() { product.setPartA("Part A"); } public void buildPartB() { product.setPartB("Part B"); } public Product getResult() { return product; } } // 指挥者 class Director { public void construct(Builder builder) { builder.buildPartA(); builder.buildPartB(); } } 应用场景 复杂对象的创建,如车辆、房屋、文档生成器。 当构造过程需要按照一定的顺序时,如餐厅订单的构建(前菜、主菜、甜点)。 优点 将构造过程分离,允许同样的构造过程创建不同的对象。 更加灵活,构造过程更容易扩展和维护。 缺点 增加了代码的复杂性,尤其是当产品本身并不复杂时。 5. 原型模式(Prototype Pattern) 定义 通过复制现有对象来创建新对象,而不是通过实例化类。 动机 有时创建对象的成本很高或者很复杂(如加载大量数据或依赖外部资源),直接克隆现有对象可以有效提升性能,避免冗余计算或复杂的初始化过程。 结构 Prototype接口:定义一个clone()方法,用于复制对象。 ConcretePrototype类:实现Prototype接口,负责具体的克隆操作。 示例 // 原型接口 interface Prototype { Prototype clone(); } // 具体原型 class ConcretePrototype implements Prototype { private String state; public ConcretePrototype(String state) { this.state = state; } @Override public Prototype clone() { return new ConcretePrototype(state); } } 结构型模式 结构型模式是设计模式中的一种类型,关注于如何将类或对象组合成更大的结构,以形成更复杂的功能。结构型模式帮助我们简化设计、提高系统的灵活性和可复用性,并有助于在不同的类之间建立良好的关系。 以下是几种常见的结构型模式的详细说明: 1. 适配器模式(Adapter Pattern) 定义 适配器模式允许将一个类的接口转换成客户端所期望的另一种接口,使得原本因接口不兼容而无法一起工作的类可以一起工作。 动机 在系统需要与多个接口不兼容的类交互时,适配器模式提供了一种解决方案。 实现 适配器可以是类适配器或对象适配器。类适配器通过继承来实现,而对象适配器通过组合来实现。 示例 // 目标接口 interface Target { void request(); } // 适配者类 class Adaptee { void specificRequest() { System.out.println("Called specificRequest"); } } // 适配器类 class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } @Override public void request() { adaptee.specificRequest(); } } 优点 使得接口不兼容的类可以协同工作。 提高系统的可复用性和灵活性。 缺点 可能会增加系统的复杂性,过多的适配器会导致代码难以理解。 2. 桥接模式(Bridge Pattern) 定义 桥接模式通过将抽象部分与其实现部分分离,从而使它们可以独立变化。即在抽象类和具体实现之间引入一个桥接接口。 动机 当一个类的抽象和实现之间有多个变化维度时,桥接模式能够将这些变化分离,减少系统的复杂性。 结构 抽象类:定义了高层接口。 实现类:实现了低层接口。 桥接:将抽象类与实现类连接。 示例 // 实现接口 interface Implementor { void operationImpl(); } // 具体实现 class ConcreteImplementorA implements Implementor { @Override public void operationImpl() { System.out.println("ConcreteImplementorA operation"); } } // 抽象类 abstract class Abstraction { protected Implementor implementor; public Abstraction(Implementor implementor) { this.implementor = implementor; } public abstract void operation(); } // 具体抽象类 class RefinedAbstraction extends Abstraction { public RefinedAbstraction(Implementor implementor) { super(implementor); } @Override public void operation() { implementor.operationImpl(); } } 优点 通过分离抽象和实现,提高了系统的灵活性。 可以在不改变抽象和实现的情况下扩展系统。 缺点 可能会增加系统的复杂性,特别是在类的数量较多时。 3. 组合模式(Composite Pattern) 定义 组合模式将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式允许客户端以统一的方式对待单个对象和组合对象。 动机 当需要表示对象的部分和整体的关系时,组合模式提供了一种简单的方法来管理这些对象。 结构 组件:定义了叶子和组合对象的公共接口。 叶子:实现了组件接口,表示树的叶子节点。 组合:实现了组件接口,表示树的组合节点。 示例 // 组件接口 interface Component { void operation(); } // 叶子类 class Leaf implements Component { @Override public void operation() { System.out.println("Leaf operation"); } } // 组合类 class Composite implements Component { private List<Component> children = new ArrayList<>(); public void add(Component component) { children.add(component); } @Override public void operation() { for (Component child : children) { child.operation(); } } } 优点 客户端可以一致地使用单个对象和组合对象。 可以方便地添加新的叶子或组合。 缺点 设计上比较复杂,可能会引入不必要的复杂性。 4. 装饰模式(Decorator Pattern) 定义 装饰模式允许在不改变对象自身的情况下,动态地给一个对象添加一些额外的职责。装饰模式提供了比子类更灵活的替代方案。 动机 当需要在运行时扩展对象的功能时,装饰模式提供了一种灵活的方法。 结构 组件接口:定义了对象的接口。 具体组件:实现了组件接口的基本对象。 装饰者抽象类:实现了组件接口,持有一个组件的引用。 具体装饰者:扩展了装饰者抽象类,添加额外的职责。 ———————————————— 原文链接:https://blog.csdn.net/u012108607/article/details/142852438
-
设计模式:一个程序员对设计模式的理解: “不懂”为什么要把很简单的东西搞得那么复杂。后来随着软件开发经验的增加才开始明白我所看到的“复杂”恰恰就是设计模式的精髓所在,我所理解的“简单”就是一把钥匙开一把锁的模式,目的仅仅是着眼于解决现在的问题,而设计模式的“复杂”就在于它是要构造一个“万能钥匙”,目的是提出一种对所有锁的开锁方案。在真正理解设计模式之前我一直在编写“简单”的代码. 这个“简单”不是功能的简单,而是设计的简单。简单的设计意味着缺少灵活性,代码很钢硬,只在这个项目里有用,拿到其它的项目中就是垃圾,我将其称之为“一次性代码”。 –>要使代码可被反复使用,请用’设计模式’对你的代码进行设计. 很多我所认识的程序员在接触到设计模式之后,都有一种相见恨晚的感觉,有人形容学习了设计模式之后感觉自己好像已经脱胎换骨,达到了新的境界,还有人甚至把是否了解设计模式作为程序员划分水平的标准。 我们也不能陷入模式的陷阱,为了使用模式而去套模式,那样会陷入形式主义。我们在使用模式的时候,一定要注意模式的意图(intent),而不要过多的去关注模式的实现细节,因为这些实现细节在特定情况下,可能会发生一些改变。不要顽固地认为设计模式一书中的类图或实现代码就代表了模式本身。 设计原则:(重要) 1. 逻辑代码独立到单独的方法中,注重封装性–易读,易复用。 不要在一个方法中,写下上百行的逻辑代码。把各小逻辑代码独立出来,写于其它方法中,易读其可重复调用。 2. 写类,写方法,写功能时,应考虑其移植性,复用性:防止一次性代码! 是否可以拿到其它同类事物中应该?是否可以拿到其它系统中应该? 3. 熟练运用继承的思想: 找出应用中相同之处,且不容易发生变化的东西,把它们抽取到抽象类中,让子类去继承它们; 继承的思想,也方便将自己的逻辑建立于别人的成果之上。如ImageField extends JTextField; 熟练运用接口的思想: 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。 把很简单的东西搞得那么复杂,一次性代码,设计模式优势的实例说明:(策略模式) 说明: 模拟鸭子游戏的应用程序,要求:游戏中会出现各种颜色外形的鸭子,一边游泳戏水,一边呱呱叫。 第一种方法:(一次性代码) 直接编写出各种鸭子的类:MallardDuck//野鸭,RedheadDuck//红头鸭,各类有三个方法: quack():叫的方法 swim():游水的方法 display():外形的方法 第二种方法:运用继承的特性,将其中共同的部分提升出来,避免重复编程。 即:设计一个鸭子的超类(Superclass),并让各种鸭子继承这个超类。 public class Duck{ public void quack(){ //呱呱叫 System.out.println(“呱呱叫”); } public void swim(){ //游泳 System.out.println(” 游泳”); } public abstratact void display(); /因为外观不一样,让子类自己去决定了。/ } 对于它的子类只需简单的继承就可以了,并实现自己的display()方法。 //野鸭 public class MallardDuck extends Duck{ public void display(){ System.out.println(“野鸭的颜色…”); } } //红头鸭 public class RedheadDuck extends Duck{ public void display(){ System.out.println(“红头鸭的颜色…”); } } 不幸的是,现在客户又提出了新的需求,想让鸭子飞起来。这个对于我们OO程序员,在简单不过了,在超类中在加一 个方法就可以了。 public class Duck{ public void quack(){ //呱呱叫 System.out.println(“呱呱叫”); } public void swim(){ //游泳 System.out.println(” 游泳”); } public abstract void display(); /因为外观不一样,让子类自己去决定了。/ public void fly(){ System.out.println(“飞吧!鸭子”); } } 对于不能飞的鸭子,在子类中只需简单的覆盖。 //残废鸭 public class DisabledDuck extends Duck{ public void display(){ System.out.println(“残废鸭的颜色…”); } public void fly(){ //覆盖,变成什么事都不做。 } } 其它会飞的鸭子不用覆盖。 这样所有的继承这个超类的鸭子都会fly了。但是问题又出来了,客户又提出有的鸭子会飞,有的不能飞。 点评: 对于上面的设计,你可能发现一些弊端,如果超类有新的特性,子类都必须变动,这是我们开发最不喜欢看到的,一个类变让另一个类也跟着变,这有点不符合OO设计了。这样很显然的耦合了一起。利用继承–>耦合度太高了. 第三种方法: 用接口改进. 我们把容易引起变化的部分提取出来并封装之,来应付以后的变法。虽然代码量加大了,但可用性提高了,耦合度也降低了。 我们把Duck中的fly方法和quack提取出来。 public interface Flyable{ public void fly(); } public interface Quackable{ public void quack(); } 最后Duck的设计成为: public class Duck{ public void swim(){ //游泳 System.out.println(” 游泳”); } public abstract void display(); /因为外观不一样,让子类自 己去决定了。/ } 而MallardDuck,RedheadDuck,DisabledDuck 就可以写成为: //野鸭 public class MallardDuck extends Duck implements Flyable,Quackable{ public void display(){ System.out.println(“野鸭的颜色…”); } public void fly(){ //实现该方法 } public void quack(){ //实现该方法 } } //红头鸭 public class RedheadDuck extends Duck implements Flyable,Quackable{ public void display(){ System.out.println(“红头鸭的颜色…”); } public void fly(){ //实现该方法 } public void quack(){ //实现该方法 } } //残废鸭 只实现Quackable(能叫不能飞) public class DisabledDuck extends Duck implements Quackable{ public void display(){ System.out.println(“残废鸭的颜色…”); } public void quack(){ //实现该方法 } } 点评: 好处: 这样已设计,我们的程序就降低了它们之间的耦合。 不足: Flyable和 Quackable接口一开始似乎还挺不错的,解决了问题(只有会飞到鸭子才实现 Flyable),但是Java接口不具有实现代码,所以实现接口无法达到代码的复用。 第四种方法: 对上面各方式的总结: 继承的好处:让共同部分,可以复用.避免重复编程. 继承的不好:耦合性高.一旦超类添加一个新方法,子类都继承,拥有此方法, 若子类相当部分不实现此方法,则要进行大批量修改. 继承时,子类就不可继承其它类了. 接口的好处:解决了继承耦合性高的问题. 且可让实现类,继承或实现其它类或接口. 接口的不好:不能真正实现代码的复用.可用以下的策略模式来解决. ————————- strategy(策略模式) ————————- 我们有一个设计原则: 找出应用中相同之处,且不容易发生变化的东西,把它们抽取到抽象类中,让子类去继承它们; 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。 –>important. 现在,为了要分开“变化和不变化的部分”,我们准备建立两组类(完全远离Duck类),一个是”fly”相关的,另一个 是“quack”相关的,每一组类将实现各自的动作。比方说,我们可能有一个类实现“呱呱叫”,另一个类实现“吱吱 叫”,还有一个类实现“安静”。 首先写两个接口。FlyBehavior(飞行行为)和QuackBehavior(叫的行为). public interface FlyBehavior{ public void fly(); } public interface QuackBehavior{ public void quack(); } 我们在定义一些针对FlyBehavior的具体实现。 public class FlyWithWings implements FlyBehavior{ public void fly(){ //实现了所有有翅膀的鸭子飞行行为。 } } public class FlyNoWay implements FlyBehavior{ public void fly(){ //什么都不做,不会飞 } 1 2 3 } 针对QuackBehavior的几种具体实现。 public class Quack implements QuackBehavior{ public void quack(){ //实现呱呱叫的鸭子 } } public class Squeak implements QuackBehavior{ public void quack(){ //实现吱吱叫的鸭子 } } public class MuteQuack implements QuackBehavior{ public void quack(){ //什么都不做,不会叫 } } 点评一: 这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。而我们增加一些新 的行为,不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。 最后我们看看Duck 如何设计。 public class Duck{ ———>在抽象类中,声明各接口,定义各接口对应的方法. FlyBehavior flyBehavior;//接口 QuackBehavior quackBehavior;//接口 public Duck(){} public abstract void display(); public void swim(){ //实现游泳的行为 } public void performFly(){ flyBehavior.fly(); –>由于是接口,会根据继承类实现的方式,而调用相应的方法. } public void performQuack(){ quackBehavior.quack();(); } } 看看MallardDuck如何实现。 —–>通过构造方法,生成’飞’,’叫’具体实现类的实例,从而指定’飞’,’叫’的具体属性 public class MallardDuck extends Duck{ public MallardDuck { flyBehavior = new FlyWithWings (); quackBehavior = new Quack(); //因为MallardDuck 继承了Duck,所有具有flyBehavior 与quackBehavior 实例变量} public void display(){ //实现 } } 这样就满足了即可以飞,又可以叫,同时展现自己的颜色了。 这样的设计我们可以看到是把flyBehavior ,quackBehavior 的实例化写在子类了。我们还可以动态的来决定。 我们只需在Duck中加上两个方法。 在构造方法中对属性进行赋值与用属性的setter的区别: 构造方法中对属性进行赋值:固定,不可变; 用属性的setter,可以在实例化对象后,动态的变化,比较灵活。 public class Duck{ FlyBehavior flyBehavior;//接口 QuackBehavior quackBehavior;//接口 public void setFlyBehavior(FlyBehavior flyBehavior){ this.flyBehavior = flyBehavior; } public void setQuackBehavior(QuackBehavior quackBehavior { this.quackBehavior= quackBehavior; } } ————————- static Factory Method(静态工厂) ————————- (1) 在设计模式中,Factory Method也是比较简单的一个,但应用非常广泛,EJB,RMI,COM,CORBA,Swing中都可以看到此模式 的影子,它是最重要的模式之一.在很多地方我们都会看到xxxFactory这样命名的类. (2) 基本概念: FactoryMethod是一种创建性模式,它定义了一个创建对象的接口,但是却让子类来决定具体实例化哪一个类. 通常我们将Factory Method作为一种标准的创建对象的方法。 应用方面: 当一个类无法预料要创建哪种类的对象或是一个类需要由子类来指定创建的对象时我们就需要用到Factory Method 模 式了. ——————————– singelton(单例模式) ——————————– 基本概念: Singleton 是一种创建性模型,它用来确保只产生一个实例,并提供一个访问它的全局访问点.对一些类来说,保证只有一个实例是很重要的,比如有的时候,数据库连接或 Socket 连接要受到一定的限制,必须保持同一时间只能有一个连接的存在. 运用: 在于使用static变量; 创建类对象,一般是在构造方法中,或用一个方法来创建类对象。在这里方法中,加对相应的判断即可。 单态模式与共享模式的区别: 单态模式与共享模式都是让类的实例是唯一的。 但单态模式的实现方式是: 在类的内部.即在构造方法中,或静态的getInstace方法中,进行判断,若实例存在,则直接返回,不进行创建; 共享模式的实现方式是: 每次要用到此实例时,先去此hashtable中获取,若获取为空,则生成实例,且将类的实例放在一人hashtable中,若获取不为空,则直接用此实例。 (2) 实例一: public class Singleton { private static Singleton s; public static Singleton getInstance() { if (s == null) s = new Singleton(); return s; } } // 测试类 class singletonTest { public static void main(String[] args) { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); if (s1==s2) System.out.println(“s1 is the same instance with s2”); else System.out.println(“s1 is not the same instance with s2”); } } singletonTest运行结果是: s1 is the same instance with s2 (3) 实例二: class Singleton { static boolean instance_flag = false; // true if 1 instance public Singleton() { if (instance_flag) throw new SingletonException(“Only one instance allowed”); else instance_flag = true; // set flag for 1 instance } } ——————————– 观察者模式(Observer) ——————————– (1) 基本概念: 观察者模式属于行为型模式,其意图是定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 这一个模式的关键对象是目标(Subject)和观察者(Observer)。一个目标可以有任意数目的依赖它的观察者,一旦目标的状态发生改变,所有的观察者都得到通知,作为对这个通知的响应,每个观察者都将查询目标以使其状态与目标的状态同步。 适用场景: 观察者模式,用于存在一对多依赖关系的对象间,当被依赖者变化时,通知依赖者全部进行更新。因此,被依赖者,应该有添加/删除依赖者的方法,且可以将添加的依赖者放到一个容器中;且有一个方法去通知依赖者进行更新。 (2) 思想: (一) 建立目标(subject)与观察者(observer)接口: 目标(subject)接口: 建立一个注册观察者对象的接口; public void attach(Observer o); 建立一个删除观察者对象的接口; public void detach(Observer o); 建立一个当目标状态发生改变时,发布通知给观察者对象的接口; public void notice(); 观察者(observer)接口: 建立一个当收到目标通知后的更新接口: public void update(); (3) 实例: 老师又电话号码,学生需要知道老师的电话号码以便于在合时的时候拨打,在这样的组合中,老师就是一个被观察者 (Subject),学生就是需要知道信息的观察者,当老师的电话号码发生改变时,学生得到通知,并更新相应的电话记 录。 具体实例如下: Subject代码: public interface Subject{ public void attach(Observer o); public void detach(Observer o); public void notice(); } Observer代码: public interface Observer{ public void update(); } Teacher代码; import java.util.Vector; public class Teacher implements Subject{ private String phone; private Vector students; public Teacher(){ phone = “”; students = new Vector(); } public void attach(Observer o){ students.add(o); } public void detach(Observer o){ students.remove(o); } public void notice(){ for(int i=0;i ———————————————— 原文链接:https://blog.csdn.net/qiaqia609/article/details/46659547
-
访问者模式是一种设计模式,用于解耦数据结构和算法。它通过访问者角色在运行时操作对象结构,实现实现数据的自动响应机制。本文介绍了模式的原理、角色、实现过程,并讨论了其优点和缺点,以及适用场景。 访问者模式 1 访问者模式介绍 访问者模式在实际开发中使用的非常少,因为它比较难以实现并且应用该模式肯能会导致代码的可读性变差,可维护性变差,在没有特别必要的情况下,不建议使用访问者模式. 访问者模式(Visitor Pattern) 的原始定义是:允许在运行时将一个或多个操作应用于一组对象,将操作与对象结构分离。 这个定义会比较抽象,但是我们依然能看出两个关键点: 一个是: 运行时使用一组对象的一个或多个操作,比如,对不同类型的文件(.pdf、.xml、.properties)进行扫描; 另一个是: 分离对象的操作和对象本身的结构,比如,扫描多个文件夹下的多个文件,对于文件来说,扫描是额外的业务操作,如果在每个文件对象上都加一个扫描操作,太过于冗余,而扫描操作具有统一性,非常适合访问者模式。 访问者模式主要解决的是数据与算法的耦合问题, 尤其是在数据结构比较稳定,而算法多变的情况下.为了不污染数据本身,访问者会将多种算法独立归档,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并确保算法的自由扩展. 2 访问者模式原理 访问者模式包含以下主要角色: 抽象访问者(Visitor)角色:可以是接口或者抽象类,定义了一系列操作方法,用来处理所有数据元素,通常为同名的访问方法,并以数据元素类作为入参来确定那个重载方法被调用. 具体访问者(ConcreteVisitor)角色:访问者接口的实现类,可以有多个实现,每个访问者都需要实现所有数据元素类型的访问重载方法. 抽象元素(Element)角色:被访问的数据元素接口,定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。 具体元素(ConcreteElement)角色: 具体数据元素实现类,提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法,其accept实现方法中调用访问者并将自己 “this” 传回。 对象结构(Object Structure)角色:包含所有可能被访问的数据对象的容器,可以提供数据对象的迭代功能,可以是任意类型的数据结构. 客户端 ( Client ) : 使用容器并初始化其中各类数据元素,并选择合适的访问者处理容器中的所有数据对象. 3 访问者模式实现 我们以超市购物为例,假设超市中的三类商品: 水果,糖果,酒水进行售卖. 我们可以忽略每种商品的计价方法,因为最终结账时由收银员统一集中处理,在商品类中添加计价方法是不合理的设计.我们先来定义糖果类和酒类、水果类. /** * 抽象商品父类 **/ public abstract class Product { private String name; //商品名 private LocalDate producedDate; // 生产日期 private double price; //单品价格 public Product(String name, LocalDate producedDate, double price) { this.name = name; this.producedDate = producedDate; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public LocalDate getProducedDate() { return producedDate; } public void setProducedDate(LocalDate producedDate) { this.producedDate = producedDate; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } } /** * 糖果类 **/ public class Candy extends Product{ public Candy(String name, LocalDate producedDate, double price) { super(name, producedDate, price); } } /** * 酒水类 **/ public class Wine extends Product{ public Wine(String name, LocalDate producedDate, double price) { super(name, producedDate, price); } } /** * 水果类 **/ public class Fruit extends Product{ //重量 private float weight; public Fruit(String name, LocalDate producedDate, double price, float weight) { super(name, producedDate, price); this.weight = weight; } public float getWeight() { return weight; } public void setWeight(float weight) { this.weight = weight; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 访问者接口 收银员就类似于访问者,访问用户选择的商品,我们假设根据生产日期进行打折,过期商品不能够出售. 注意这种计价策略不适用于酒类,作为收银员要对不同商品应用不同的计价方法. /** * 访问者接口-根据入参不同调用对应的重载方法 **/ public interface Visitor { public void visit(Candy candy); //糖果重载方法 public void visit(Wine wine); //酒类重载方法 public void visit(Fruit fruit); //水果重载方法 } 1 2 3 4 5 6 7 8 9 10 11 具体访问者 创建计价业务类,对三类商品进行折扣计价,折扣计价访问者的三个重载方法分别实现了3类商品的计价方法,体现了visit() 方法的多态性. /** * 折扣计价访问者类 **/ public class DiscountVisitor implements Visitor { private LocalDate billDate; public DiscountVisitor(LocalDate billDate) { this.billDate = billDate; System.out.println("结算日期: " + billDate); } @Override public void visit(Candy candy) { System.out.println("糖果: " + candy.getName()); //获取产品生产天数 long days = billDate.toEpochDay() - candy.getProducedDate().toEpochDay(); if(days > 180){ System.out.println("超过半年的糖果,请勿食用!"); }else{ double rate = 0.9; double discountPrice = candy.getPrice() * rate; System.out.println("糖果打折后的价格"+NumberFormat.getCurrencyInstance().format(discountPrice)); } } @Override public void visit(Wine wine) { System.out.println("酒类: " + wine.getName()+",无折扣价格!"); System.out.println("原价: "+NumberFormat.getCurrencyInstance().format(wine.getPrice())); } @Override public void visit(Fruit fruit) { System.out.println("水果: " + fruit.getName()); //获取产品生产天数 long days = billDate.toEpochDay() - fruit.getProducedDate().toEpochDay(); double rate = 0; if(days > 7){ System.out.println("超过七天的水果,请勿食用!"); }else if(days > 3){ rate = 0.5; }else{ rate = 1; } double discountPrice = fruit.getPrice() * fruit.getWeight() * rate; System.out.println("水果价格: "+NumberFormat.getCurrencyInstance().format(discountPrice)); } public static void main(String[] args) { LocalDate billDate = LocalDate.now(); Candy candy = new Candy("徐福记",LocalDate.of(2022,10,1),10.0); System.out.println("糖果: " + candy.getName()); double rate = 0.0; long days = billDate.toEpochDay() - candy.getProducedDate().toEpochDay(); System.out.println(days); if(days > 180){ System.out.println("超过半年的糖果,请勿食用!"); }else{ rate = 0.9; double discountPrice = candy.getPrice() * rate; System.out.println("打折后的价格"+NumberFormat.getCurrencyInstance().format(discountPrice)); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 客户端 public class Client { public static void main(String[] args) { //德芙巧克力,生产日期2002-5-1 ,原价 10元 Candy candy = new Candy("德芙巧克力",LocalDate.of(2022,5,1),10.0); Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,11)); visitor.visit(candy); } } 1 2 3 4 5 6 7 8 9 10 11 上面的代码虽然可以完成当前的需求,但是设想一下这样一个场景: 由于访问者的重载方法只能对当个的具体商品进行计价,如果顾客选择了多件商品来结账时,就可能会引起重载方法的派发问题(到底该由谁来计算的问题). 首先我们定义一个接待访问者的类 Acceptable,其中定义了一个accept(Visitor visitor)方法, 只要是visitor的子类都可以接收. /** * 接待者接口(抽象元素角色) **/ public interface Acceptable { //接收所有的Visitor访问者的子类实现类 public void accept(Visitor visitor); } /** * 糖果类 **/ public class Candy extends Product implements Acceptable{ public Candy(String name, LocalDate producedDate, double price) { super(name, producedDate, price); } @Override public void accept(Visitor visitor) { //accept实现方法中调用访问者并将自己 "this" 传回。this是一个明确的身份,不存在任何泛型 visitor.visit(this); } } //酒水与水果类同样实现Acceptable接口,重写accept方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 测试 public class Client { public static void main(String[] args) { // //德芙巧克力,生产日期2002-5-1 ,原价 10元 Candy candy = new Candy("德芙巧克力",LocalDate.of(2022,5,1),10.0); Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,11)); visitor.visit(candy); //模拟添加多个商品的操作 List<Acceptable> products = Arrays.asList( new Candy("金丝猴奶糖",LocalDate.of(2022,6,10),10.00), new Wine("衡水老白干",LocalDate.of(2020,6,10),100.00), new Fruit("草莓",LocalDate.of(2022,10,12),50.00,1) ); Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,17)); for (Acceptable product : products) { product.accept(visitor); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 代码编写到此出,就可以应对计价方式或者业务逻辑的变化了,访问者模式成功地将数据资源(需实现接待者接口)与数据算法 (需实现访问者接口)分离开来。重载方法的使用让多样化的算法自成体系,多态化的访问者接口保证了系统算法的可扩展性,数据则保持相对固定,最终形成⼀个算法类对应⼀套数据。 4 访问者模式总结 访问者模式优点: 扩展性好 在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。 复用性好 通过访问者来定义整个对象结构通用的功能,从而提高复用程度。 分离无关行为 通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。 访问者模式缺点: 对象结构变化很困难 在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。 违反了依赖倒置原则 访问者模式依赖了具体类,而没有依赖抽象类。 使用场景 当对象的数据结构相对稳定,而操作却经常变化的时候。 比如,上面例子中路由器本身的内部构造(也就是数据结构)不会怎么变化,但是在不同操作系统下的操作可能会经常变化,比如,发送数据、接收数据等。 需要将数据结构与不常用的操作进行分离的时候。 比如,扫描文件内容这个动作通常不是文件常用的操作,但是对于文件夹和文件来说,和数据结构本身没有太大关系(树形结构的遍历操作),扫描是一个额外的动作,如果给每个文件都添加一个扫描操作会太过于重复,这时采用访问者模式是非常合适的,能够很好分离文件自身的遍历操作和外部的扫描操作。 需要在运行时动态决定使用哪些对象和方法的时候。 比如,对于监控系统来说,很多时候需要监控运行时的程序状态,但大多数时候又无法预知对象编译时的状态和参数,这时使用访问者模式就可以动态增加监控行为。 ———————————————— 原文链接:https://blog.csdn.net/jbjmh/article/details/136094601
-
1、概念 访问者模式涉及两个角色,一个是访问者,另一个是被访问的资源。 访问者有多个具体的实现,资源也有多个具体的实现。 访问者与资源可以有多种组合,假如有三种类型的访问者与三种类型的资源,则它们的组合方式就有3 * 3 = 9种。 访问者解决的问题就是每种组合都能产生不同的行为。 访问者解决的问题就是每种组合都能产生不同的行为。2、示例 访问者模式解决的问题在日常工作中很常见,比如基于角色的访问权限控制。假如有三种类型的文档:普通、机密、绝密,每个文档实现都维护一个集合,里边记录着被权限访问的用户名及其密码。同时,有三种类型的访问者:匿名、实名、授权。匿名访问者只能够访问普通类型的文档。实名访问者可以访问普通、机密两种类型的文档,但是在访问机密文档时,必需提供正确的用户名。授权访问者可以访问全部的三种文件,但在访问机密文档必需提供正确的用户名,在访问绝密文档时不但要提供正确的用户名,还要提供正确的密码。实现代码如下: package com.zhangxf.visitor; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; // 文档抽象类 abstract class Document { private String name; private String content; private Map<String, String> users; public Document(String docName, String content) { this.name = docName; this.content = content; users = new HashMap<String, String>(); } public String getDocName() { return name; } public String getContent() { return content; } public void addUser(String userName, String userSecret) { users.put(userName, userSecret); } public boolean isUserExist(String visitorName) { return users.containsKey(visitorName); } public boolean isAuthorization(String visitorName, String visitorSecretor) { if (isUserExist(visitorName) && (users.get(visitorName) == visitorSecretor)) { return true; } else { return false; } } // 这个方法是关键,注意它的参数类型是visitor public abstract String visit(Visitor v); } // 普通文档 class CommonDocument extends Document { public CommonDocument(String name, String content) { super(name, content); } @Override public String visit(Visitor v) { // 注意this参数,它代表的是CommonDocment类实例 return v.read(this); } } // 机密文档 class ConfidentialDocument extends Document { public ConfidentialDocument(String name, String content) { super(name, content); } @Override public String visit(Visitor v) { // 注意this参数,它代表的是ConfidentialDocument类实例 return v.read(this); } } // 绝密文档 class TopSecretDocument extends Document { public TopSecretDocument(String name, String content) { super(name, content); } @Override public String visit(Visitor v) { // 注意this参数,它代表的是TopSecretDocument类实例 return v.read(this); } } abstract class Visitor { private String visitorName; private String visitorSecret; public Visitor(String name, String secret) { this.visitorName = name; this.visitorSecret = secret; } public String getVisitorName() { return visitorName; } public String getVisitorSecret() { return visitorSecret; } // 当文档类型是不同时,调用如下三个版本的read public abstract String read(CommonDocument doc); public abstract String read(ConfidentialDocument doc); public abstract String read(TopSecretDocument doc); } // 匿名访问者 class AnonymityVisitor extends Visitor { public AnonymityVisitor() { super(null, null); } @Override public String read(CommonDocument doc) { System.out.println("# Anonymity visitor can read common document: " + doc.getDocName()); return doc.getContent(); } @Override public String read(ConfidentialDocument doc) { System.out.println("# Anonymity visitor can not read confidential document: " + doc.getDocName()); return null; } @Override public String read(TopSecretDocument doc) { System.out.println("# Anonymity visitor can not read top secret document: " + doc.getDocName()); return null; } } // 实名访问者 class RealNameVisitor extends Visitor { public RealNameVisitor(String visitorName) { super(visitorName, null); } @Override public String read(CommonDocument doc) { System.out.println( "# Real name visitor [" + getVisitorName() + "] can read common document: " + doc.getDocName()); return doc.getContent(); } @Override public String read(ConfidentialDocument doc) { // 访问者名字必需正确 if (doc.isUserExist(getVisitorName())) { System.out.println("# Real name visitor [" + getVisitorName() + "] can read confidential document: " + doc.getDocName()); return doc.getContent(); } else { System.out.println("# Real name visitor [" + getVisitorName() + "] can not read confidential document: " + doc.getDocName()); return null; } } @Override public String read(TopSecretDocument doc) { System.out.println("# Real name visitor can not read top secret document: " + doc.getDocName()); return null; } } // 授权访问者 class AuthorizationVisitor extends Visitor { public AuthorizationVisitor(String visitorName, String visitorSecret) { super(visitorName, visitorSecret); } @Override public String read(CommonDocument doc) { System.out.println( "# Authorization visitor [" + getVisitorName() + "] can read common document: " + doc.getDocName()); return doc.getContent(); } @Override public String read(ConfidentialDocument doc) { // 访问者名字必需正确 if (doc.isUserExist(getVisitorName())) { System.out.println("# Authorization visitor [" + getVisitorName() + "] can read confidential document: " + doc.getDocName()); return doc.getContent(); } else { System.out.println("# Authorization visitor [" + getVisitorName() + "] can not read confidential document: " + doc.getDocName()); return null; } } @Override public String read(TopSecretDocument doc) { String visitorName = getVisitorName(); String visitorSecret = getVisitorSecret(); // 访问者名字与密码都要正确 if (doc.isAuthorization(visitorName, visitorSecret)) { System.out.println("# Authorization visitor [" + getVisitorName() + "] can read confidential document: " + doc.getDocName()); return doc.getContent(); } else { System.out.println("# Authorization visitor [" + getVisitorName() + "] can not read confidential document: " + doc.getDocName()); return null; } } } public class VisitorPattern { public static void main(String[] args) { // 构建文档库,为每个文档添加允许的访问者Billy List<Document> docLib = new ArrayList<Document>(); Document doc = new CommonDocument("Common 1", "I am common docment"); doc.addUser("Billy", "xxxxxx"); docLib.add(doc); doc = new ConfidentialDocument("Confidential 1", "I am confidential docment"); doc.addUser("Billy", "xxxxxx"); docLib.add(doc); doc = new TopSecretDocument("TopSecret 1", "I am confidential docment"); doc.addUser("Billy", "xxxxxx"); docLib.add(doc); // 匿名访问者访问文档库 System.out.println("========create anonymity visitor==========="); Visitor v = new AnonymityVisitor(); for(Document item : docLib) { item.visit(v); } // 实名访问者访问文档库 System.out.println("========create real name visitor Billy==========="); v = new RealNameVisitor("Billy"); for(Document item : docLib) { item.visit(v); } // 实名访问者访问文档库,但用户名不正确 System.out.println("========create real name visitor Alisa==========="); v = new RealNameVisitor("Alisa"); for(Document item : docLib) { item.visit(v); } // 授权访问者访问文档库 System.out.println("========create authorization visitor Billy with secret xxxxxx==========="); v = new AuthorizationVisitor("Billy", "xxxxxx"); for(Document item : docLib) { item.visit(v); } // 授权访问者访问文档库,用户名正确但密码不正确 System.out.println("========create authorization visitor Billy with secret yyyyyy==========="); v = new AuthorizationVisitor("Billy", "yyyyyy"); for(Document item : docLib) { item.visit(v); } } } 运行结果: ========create anonymity visitor=========== # Anonymity visitor can read common document: Common 1 # Anonymity visitor can not read confidential document: Confidential 1 # Anonymity visitor can not read top secret document: TopSecret 1 ========create real name visitor Billy=========== # Real name visitor [Billy] can read common document: Common 1 # Real name visitor [Billy] can read confidential document: Confidential 1 # Real name visitor can not read top secret document: TopSecret 1 ========create real name visitor Alisa=========== # Real name visitor [Alisa] can read common document: Common 1 # Real name visitor [Alisa] can not read confidential document: Confidential 1 # Real name visitor can not read top secret document: TopSecret 1 ========create authorization visitor Billy with secret xxxxxx=========== # Authorization visitor [Billy] can read common document: Common 1 # Authorization visitor [Billy] can read confidential document: Confidential 1 # Authorization visitor [Billy] can read confidential document: TopSecret 1 ========create authorization visitor Billy with secret yyyyyy=========== # Authorization visitor [Billy] can read common document: Common 1 # Authorization visitor [Billy] can read confidential document: Confidential 1 # Authorization visitor [Billy] can not read confidential document: TopSecret 1 3、总结 访问者模式的实现有三个关键点:一个是抽象资源的visit方法的参数类型是访问者的抽象类。第二个是在具体资源实例的visit方法中对this指针的使用。第三个就是访问者抽象类中用多态实现的针对每种资源类型的多个版本的read。 访问者模式将资源的表示与资源的访问控制进行解耦,使代码更加容易维护也更容易扩展。 ———————————————— 原文链接:https://blog.csdn.net/dkfajsldfsdfsd/article/details/86596269
-
1.单例设计模式 所谓单例设计模式简单说就是无论程序如何运行,采用单例设计模式的类(Singleton类)永远只会有一个实例化对象产生。具体实现步骤如下: (1) 将采用单例设计模式的类的构造方法私有化(采用private修饰)。 (2) 在其内部产生该类的实例化对象,并将其封装成private static类型。 (3) 定义一个静态方法返回该类的实例。 示例代码如下: class Singleton { private static Singleton instance = new Singleton();// 在内部产生本类的实例化对象 public static Singleton getInstance() { // 通过静态方法返回instance对象 return instance; } private Singleton() { // 将构造方法封装为私有化 } public void print() { System.out.println("Hello World!!!"); } } public class SingletonDemo { public static void main(String args[]) { Singleton s1 = null; // 声明对象 Singleton s2 = null; // 声明对象 Singleton s3 = null; // 声明对象 s1 = Singleton.getInstance(); // 取得实例化对象 s2 = Singleton.getInstance(); // 取得实例化对象 s3 = Singleton.getInstance(); // 取得实例化对象 s1.print(); // 调用方法 s2.print(); // 调用方法 s3.print(); // 调用方法 } } 2.工厂设计模式 程序在接口和子类之间加入了一个过渡端,通过此过渡端可以动态取得实现了共同接口的子类实例化对象。 示例代码如下: interface Animal { // 定义一个动物的接口 public void say(); // 说话方法 } class Cat implements Animal { // 定义子类Cat @Override public void say() { // 覆写say()方法 System.out.println("我是猫咪,喵呜!"); } } class Dog implements Animal { // 定义子类Dog @Override public void say() { // 覆写say()方法 System.out.println("我是小狗,汪汪!"); } } class Factory { // 定义工厂类 public static Animal getInstance(String className) { Animal a = null; // 定义接口对象 if ("Cat".equals(className)) { // 判断是哪个子类的标记 a = new Cat(); // 通过Cat子类实例化接口 } if ("Dog".equals(className)) { // 判断是哪个子类的标记 a = new Dog(); // 通过Dog子类实例化接口 } return a; } } public class FactoryDemo { public static void main(String[] args) { Animal a = null; // 定义接口对象 a = Factory.getInstance(args[0]); // 通过工厂获取实例 if (a != null) { // 判断对象是否为空 a.say(); // 调用方法 } } } 3.代理设计模式 指由一个代理主题来操作真实主题,真实主题执行具体的业务操作,而代理主题负责其他相关业务的处理。比如生活中的通过代理访问网络,客户通过网络代理连接网络(具体业务),由代理服务器完成用户权限和访问限制等与上网相关的其他操作(相关业务)。 示例代码如下: interface Network { // 定义Network接口 public void browse(); // 定义浏览的抽象方法 } class Real implements Network { // 真实的上网操作 public void browse() { // 覆写抽象方法 System.out.println("上网浏览信息!"); } } class Proxy implements Network { // 代理上网 private Network network; public Proxy(Network network) {// 设置代理的真实操作 this.network = network; // 设置代理的子类 } public void check() { // 身份验证操作 System.out.println("检查用户是否合法!"); } public void browse() { this.check(); // 调用具体的代理业务操作 this.network.browse(); // 调用真实的上网操作 } } public class ProxyDemo { public static void main(String args[]) { Network net = null; // 定义接口对象 net = new Proxy(new Real()); // 实例化代理,同时传入代理的真实操作 net.browse(); // 调用代理的上网操作 } } 4.观察者设计模式 所谓观察者模式,举个例子现在许多购房者都密切观察者房价的变化,当房价变化时,所有购房者都能观察到,以上的购房者属于观察者,这便是观察者模式。 java中可以借助Observable类和Observer接口轻松实现以上功能。当然此种模式的实现也不仅仅局限于采用这两个类。 示例代码如下: import java.util.Observable; import java.util.Observer; class House extends Observable { private float price; public void setPrice(float price) { this.setChanged();// 设置变化点 this.notifyObservers(price);// 通知所有观察者价格改变 this.price = price; } public float getPrice() { return this.price; } public House(float price) { this.price = price; } public String toString() { return "房子价格为: " + this.price; } } class HousePriceObserver implements Observer { private String name; public HousePriceObserver(String name) { super(); this.name = name; } @Override public void update(Observable o, Object arg) {// 只要改变了 observable 对象就调用此方法 if (arg instanceof Float) { System.out.println(this.name + "观察的价格更改为:" + ((Float) arg).floatValue()); } } } public class ObserDeom { public static void main(String[] args) { House h = new House(1000000); HousePriceObserver hpo1 = new HousePriceObserver("购房者A"); HousePriceObserver hpo2 = new HousePriceObserver("购房者B"); HousePriceObserver hpo3 = new HousePriceObserver("购房者C"); h.addObserver(hpo1);// 给房子注册观察者 h.addObserver(hpo2);// 给房子注册观察者 h.addObserver(hpo3);// 给房子注册观察者 System.out.println(h);// 输出房子价格 // 修改房子价格,会触发update(Observable o, Object arg)方法通知购房者新的房价信息 h.setPrice(2222222);// System.out.println(h);// 再次输出房子价格 } } 5.适配器模式 如果一个类要实现一个具有很多抽象方法的接口,但是本身只需要实现接口中的部分方法便可以达成目的,所以此时就需要一个中间的过渡类,但此过渡类又不希望直接使用,所以将此类定义为抽象类最为合适,再让以后的子类直接继承该抽象类便可选择性的覆写所需要的方法,而此抽象类便是适配器类。 示例代码如下: interface Window {// 定义Window窗口接口,表示窗口操作 public void open();// 窗口打开 public void close();// 窗口关闭 public void iconified();// 窗口最小化 public void deiconified();// 窗口恢复 public void activated();// 窗口活动 } // 定义抽象类实现接口,在此类中覆写方法,但是所有的方法体为空 abstract class WindowAdapter implements Window { public void open() { };// 窗口打开 public void close() { };// 窗口关闭 public void iconified() { };// 窗口最小化 public void deiconified() { };// 窗口恢复 public void activated() { };// 窗口活动 } // 子类继承WindowAdapter抽象类,选择性实现需要的方法 class WindowImpl extends WindowAdapter { public void open() { System.out.println("窗口打开");// 实现open()方法 } public void close() { System.out.println("窗口关闭");// 实现close()方法 } } public class AdapterDemo { public static void main(String args[]) { Window win = new WindowImpl(); // 实现接口对象 // 调用方法 win.open(); win.close(); } } ———————————————— 原文链接:https://blog.csdn.net/iteye_7566/article/details/82483321
-
下面是我对一些常用设计模式的理解: 模式分为:行为模式、对象模式、结构模式 构建者模式:为产品的创建提供了统一模板,而且可以使用创建者指挥器对产品的构建结构或顺序进行调整。 观察者模式:当一个事件发生,而去通知所有观察者状态改变。 工厂模式:简单工厂不支持开闭原则,而工厂方法不支持多产品族问题,抽象对于产品族扩展是不支持的。 适配器模式:把一种已有的接口适配为当前需要的接口类型,该模式是针对方法的一种设计模式。 单例模式:在一个系统中进存在一个实例的情况下使用单例模式。 桥模式:当一个事物将以多维度方向发展,为了跟好的支持不同维度的扩展而使用的,我理解桥接也是一种组合或聚合关系,也就是has-a的关系。 策略模式:策略模式其实就是一种面向接口编程的思想,而它不同的是面向接口方法编程的。 责任链模式:该模式类似于击鼓传花,如果不符合当前类的职责,那么就交给下一个处理,如果你把击鼓传花的代码写出来了,那么责任链模式也就出来了。 装饰模式:装饰模式个人感觉和责任链模式有点像,但他俩本质是不同的,装饰是类进行修饰(也就是添加功能),而责任链是判断是否为当前处理器的职责,如果是就处理不是就传给下个处理器。共同点都是一个链型结构的模式。 代理模式:可以结合现实中的代理商模式进行理解。除了替厂家卖东西,你还赚了钱可能还做了些小动作等等。代理是面向行为的,为行为进行了再次封装。 享元模式:理解还不透彻后续补上。 外观模式:外观模式讲的是为一组子系统提供更加便捷的一组接口,使子接口不去关心底层实现。 迭代器模式:这个没怎么看,我想应该讲的和java中的iterator迭代类似的吧。 状态模式:状态模式通过把各种状态转移逻辑分布到State的子类之间,来减少相应依赖。当一个对象的行为取决于他的状态,并且它必须在运行时刻根据状态来改变他的行为,这时就可以考虑使用状态模式了。-------需要继续理解 组合模式:需求中体现部分与整体层次结构时,希望用户可以忽略组合对象和单个对象的不同,统一使用组合对象结构中的所有对象时,就应该考虑组合模式。-------需要继续理解 ———————————————— 原文链接:https://blog.csdn.net/lvwenwenjava/article/details/84387285
-
1、设计模式是什么 设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。 2、设计模式分为三大类 创建型模式:共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式 结构型模式:共7种:适配器模式、装饰器模式、代理模式、桥接模式、外观模式、组合模式、享元模式 行为型模式:共11种:策略模式、模板方法模式、观察者模式、责任链模式、访问者模式、中介者模式、迭代器模式、命令模式、状态模式、备忘录模式、解释器模式 3、Java设计模式的六大原则 (1)、开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。 (2)、里氏代换原则(Liskov Substitution Principle) 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。 里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。 (3)、依赖倒转原则(Dependence Inversion Principle) 这个是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。 (4)、接口隔离原则(Interface Segregation Principle) 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。 (5)、迪米特法则(最少知道原则)(Demeter Principle) 为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。 (6)、合成复用原则(Composite Reuse Principle) 原则是尽量使用合成/聚合的方式,而不是使用继承。 4、设计模式的四要素 (1)、模式名称(pattern name) 一个助记名,它用一两个词来描述模式的问题、解决方案和效果。 (2)、问题(problem) 描述了应该在合适使用模式。它解决了设计问题和问题存在的前后因果,它可能描述了特定的设计问题,也可能描述了导致不灵活设计的类或对象结构。 (3)、解决方案(solution) 描述了设计的组成成分,它们之间的相互关系及各自的职责和协作方式。 (4)、效果(consequences) 描述了模式应用的效果及使用模式应权衡的问题。 5、23种设计模式应用场景 设计模式 应用场景 Abstract Factory(抽象工厂) 产品对象家族 Builder(建造者) 如何创建一个组合对象 Factory Method(工厂方法) 被实例化的子类 Prototype(原型) 被实例化的类 Singleton(单态) 一个类的唯一实例 Adapter(适配器) 对象的接口 Bridge(桥模式) 对象的实现 Composite(组合) 一个对象的结构和组成 Decorator(装饰) 对象的职责,不生成子类 Façade(外观) 一个子系统的接口 Flyweight(享元) 对象的存储开销 Proxy(代理) 如何访问一个对象,该对象的位置 Chain of Responsibility(职责链) 满足一个请求的对象 Command(命令) 何时、怎样满足一个请求 Interpreter(解释器) 一个语言的文法和解释 Iterator(迭代器) 如何遍历、访问一个聚合的各元素 Mediator(中介) 对象间如何交互、与谁交互 Memento(备忘录) 一个对象中哪些私有信息存放在该对象之外,以及在什么时候进行存储 Observer(观察者) 多个对象依赖于另外一个对象,而这些对象又如何保持一致 State(状态) 对象的状态 Strategy(策略) 算法设计 Template Method(模板方法) 算法中的某些步骤 Visitor(访问者) 某些可作用于一个(组)对象上的操作,但不修改这些对象的类 6、常见的七种设计模式 (1)创建型-单例模式 单例模式可以确保系统中某个类只有一个实例,该类自行实例化并向整个系统提供这个实例的公共访问点,除了该公共访问点,不能通过其他途径访问该实例。 优点: 系统中只存在一个共用的实例对象,无需频繁创建和销毁对象,节约了系统资源,提高系统的性能可以严格控制客户怎么样以及何时访问单例对象。 单例模式分为懒汉式、饿汉式、饿汉式同步锁、双重校验锁、静态内部类、枚举类,每一种都有自己的优缺点,可以根据自己的项目实际需要选择适合的单例模式。 示例图: 单例模式案例和详细介绍:java设计模式之创建型: 单例模式 (2)创建型-工厂方法模式 实例化对象不是用new,用工厂方法替代。将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。 工厂方法模式分为三种: 1、简单工厂模式: 建立一个工厂类,并定义一个接口对实现了同一接口的产品类进行创建 2、工厂方法模式: 工厂方法模式是对简单工厂模式的改进,简单工厂的缺陷在于不符合“开闭原则”,每次添加新产品类就需要修改工厂类,不利于系统的扩展维护。而工厂方法将工厂抽象化,并定义一个创建对象的接口。每增加新产品,只需增加该产品以及对应的具体实现工厂类,由具体工厂类决定要实例化的产品是哪个,将对象的创建与实例化延迟到子类,这样工厂的设计就符合“开闭原则”了,扩展时不必去修改原来的代码 3、静态工厂方法模式: 静态工厂模式是将工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。 工厂方法模式案例和详细介绍:java设计模式之创建型 : 工厂方法模式 (3)创建型-抽象工厂模式 抽象工厂是指一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象。 优缺点: 增加产品族:支持“开放-封闭"原则 增加新产品的等级结构:需要修改所有的工厂角色,不支持“开放-封闭”原则 总之,它以一种倾斜的方式支持增加新的产品族,而不支持增加新的产品等级 抽象工厂模式VS和工厂模式: 抽象工厂模式:为创建一组(有多类)相关或依赖的对象提供创建接口。 工厂模式:为一类对象提供创建接口 抽象工厂模式案例和详细介绍:java设计模式之创建型 - 抽象工厂模式 (4)行为型-责任链模式 一种处理请求的模式,它让多个处理器都有机会处理该诘求,直到其中某个处理成功为止。责任链模式把多个处理器串成链,然后让请求在链上传递。 责任链模式优点: 降低了对象之间的耦合度。处理者不需要知道客户的任何信息,客户也不要知道处理者是如何实现方法的。 高了系统的灵活性。当我们想要新增处理器到整个链条中时,所付出的代价是非常小的 责任链模式缺点: 降低了系统的性能。对比较长的职责链,请求的处理可能涉及多个处理对象 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。 责任链模式的UML结构图如下: 责任链模式案例和详细介绍:java设计模式之行为型: 责任链模式 (5)结构型-代理模式 由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。 1-1、静态代理模式: 优点: 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务,公共也就可以交给代理角色,实现了业务的分工,公共业务发生扩展的时候,方便集中管理! 缺点: 一个真实角色就会产生一个代理角色;代码量会翻倍开发效率会变低,也许,这样无法理解到代理模式的好处。举个例子也许能更好理解,比如说我们想要在原有固定功能上新增业务,按照开闭原则我们是不能对原有代码进行修改的。但是我们可以通过代理模式,增加代理,在实现原有功能的情况下写入新的功能,创建对象时也就可以使用代理,完成操作。 1-2、动态代理模式 虽然静态代理模式可以很好的解决开闭原则,但是每有一个真实角色就会产生一个代理,代码量翻倍过于臃肿,开发效率较低。因此,我们就使用动态代理模式进行设计。 优点: ①可以使真实角色的操作更加纯粹!不用去关注一些公共的业务。 ②公共也就可以交给代理角色!实现了业务的分工。 ③公共业务发生扩展的时候,方便集中管理。 ④一个动态代理类代理的是一个接口,一般就是对应的一类业务。 ⑤一个动态代理类可以代理多个类,只要是实现了同一个接口即可! 代理模式案例和详细介绍:java设计模式之结构型: 代理模式 (6)结构型-装饰器模式 动态的将新功能附加到对象上。在对象功能的拓展方面,比继承更有弹性。同时装饰者模式也体现了开闭原则。 但是装饰器模式也存在缺点,首先会产生很多的小对象,增加了系统的复杂性,第二是排错比较困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。 装饰器模式案例和详细介绍:java设计模式之结构型 :装饰器模式 (7)行为型-观察者模式 对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。 观察者模式优点: 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。 目标与观察者之间建立了一套触发机制。 观察者模式缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。 注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。 ———————————————— 原文链接:https://blog.csdn.net/l546492845/article/details/126541681
-
Java软件设计中的设计模式主要分为三大类:创建型模式、结构型模式和行为型模式。这些模式为解决常见设计问题提供了通用的解决方案,提升代码的可复用性、可维护性和可扩展性。 创建型模式 创建型模式(Creational Patterns)主要处理对象创建的方式,目的是将对象的创建与使用分离,并避免硬编码构造过程。通过使用创建型模式,可以提高代码的灵活性、可扩展性和可维护性。以下是五种常见的创建型模式的详细说明。 1. 单例模式(Singleton Pattern) 定义 确保一个类只有一个实例,并提供一个全局访问点来访问该实例。 动机 在某些情况下,整个应用程序中只需要一个类的实例,如日志管理器、数据库连接池、配置管理等。通过单例模式可以确保只有一个实例被创建,避免重复创建带来的资源浪费,并且全局访问点确保所有客户端都能访问到同一个实例。 结构 Singleton类:持有自己的静态实例,并提供一个静态方法来返回这个唯一实例。 构造方法:私有,防止外部类直接实例化对象。 示例 public class Singleton { // 私有的静态实例 private static Singleton instance; // 私有构造方法,防止外部实例化 private Singleton() {} // 静态方法,提供全局访问点 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 应用场景 日志记录器 数据库连接池 配置管理器 优点 控制实例数量,节省资源 提供全局访问点 缺点 不支持多线程(如果不进行同步处理,可能会导致多个实例的创建) 单例对象难以扩展和测试(例如,在单元测试中需要使用多个不同实例时可能会有困难) 多线程改进(双重检查锁定) public 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; } } 2. 工厂方法模式(Factory Method Pattern) 定义 定义一个用于创建对象的接口,但由子类决定具体要实例化的类。工厂方法让一个类的实例化延迟到子类。 动机 当代码中需要创建对象,但不想暴露具体类的构造过程,或者为了扩展方便,希望可以灵活地指定子类来实例化具体对象时,工厂方法模式提供了解决方案。 结构 抽象产品类:定义产品的接口。 具体产品类:实现抽象产品的接口,表示不同类型的产品。 抽象工厂类:定义一个抽象的工厂方法,返回抽象产品类型。 具体工厂类:实现工厂方法,返回具体的产品实例。 示例 // 产品接口 abstract class Product { abstract void use(); } // 具体产品类 class ConcreteProduct extends Product { void use() { System.out.println("Using ConcreteProduct"); } } // 工厂接口 abstract class Creator { abstract Product createProduct(); } // 具体工厂类 class ConcreteCreator extends Creator { Product createProduct() { return new ConcreteProduct(); } } 应用场景 日志记录系统:根据不同的日志级别创建不同的日志处理器。 数据库访问层:使用不同的数据库(如MySQL、Oracle等)时,通过工厂方法灵活选择数据库驱动。 优点 客户端不需要知道具体产品的创建逻辑,只依赖工厂接口。 符合“开放-封闭”原则:可以通过子类扩展新产品,而无需修改现有代码。 缺点 增加了系统的复杂度,需要创建额外的工厂类。 3. 抽象工厂模式(Abstract Factory Pattern) 定义 提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。 动机 如果系统需要多个相互关联的对象,且不希望在客户端直接依赖具体类时,抽象工厂模式通过提供统一的接口,来生成相关的对象族,从而保证了对象之间的一致性和可扩展性。 结构 抽象工厂接口:定义创建一系列相关产品的接口。 具体工厂类:实现抽象工厂接口,负责生成具体的产品。 抽象产品接口:定义产品的通用接口。 具体产品类:实现抽象产品接口,表示不同的产品。 示例 // 抽象产品 interface Button { void paint(); } interface Checkbox { void paint(); } // 具体产品 class WinButton implements Button { public void paint() { System.out.println("Render a button in Windows style."); } } class MacButton implements Button { public void paint() { System.out.println("Render a button in MacOS style."); } } // 抽象工厂 interface GUIFactory { Button createButton(); Checkbox createCheckbox(); } // 具体工厂 class WinFactory implements GUIFactory { public Button createButton() { return new WinButton(); } public Checkbox createCheckbox() { return new WinCheckbox(); } } class MacFactory implements GUIFactory { public Button createButton() { return new MacButton(); } public Checkbox createCheckbox() { return new MacCheckbox(); } } 应用场景 跨平台的GUI工具包:为不同操作系统(如Windows、Mac)创建风格一致的UI组件。 数据库访问:为不同数据库(如MySQL、PostgreSQL)生成一组相关的操作对象。 优点 客户端不需要直接实例化对象,易于扩展。 保证了相关产品之间的一致性。 缺点 代码复杂度增加,需要维护更多的类。 当需要扩展新产品族时,可能需要修改抽象工厂接口。 4. 建造者模式(Builder Pattern) 定义 将一个复杂对象的构建过程与它的表示分离,使得同样的构建过程可以创建不同的表示。 动机 有些对象的构建过程非常复杂,包含多个步骤(如配置文件的解析、复杂UI界面的生成)。建造者模式允许分步骤创建对象,且同样的构造过程可以构建不同的对象。 结构 Builder接口:定义构建复杂对象的步骤。 ConcreteBuilder类:实现Builder接口,完成具体步骤。 Director类:负责按顺序调用建造者的构建步骤。 Product类:复杂对象,由多个部分组成。 示例 // 产品类 class Product { private String partA; private String partB; public void setPartA(String partA) { this.partA = partA; } public void setPartB(String partB) { this.partB = partB; } public void showProduct() { System.out.println("Product with " + partA + " and " + partB); } } // 抽象建造者 interface Builder { void buildPartA(); void buildPartB(); Product getResult(); } // 具体建造者 class ConcreteBuilder implements Builder { private Product product = new Product(); public void buildPartA() { product.setPartA("Part A"); } public void buildPartB() { product.setPartB("Part B"); } public Product getResult() { return product; } } // 指挥者 class Director { public void construct(Builder builder) { builder.buildPartA(); builder.buildPartB(); } } 应用场景 复杂对象的创建,如车辆、房屋、文档生成器。 当构造过程需要按照一定的顺序时,如餐厅订单的构建(前菜、主菜、甜点)。 优点 将构造过程分离,允许同样的构造过程创建不同的对象。 更加灵活,构造过程更容易扩展和维护。 缺点 增加了代码的复杂性,尤其是当产品本身并不复杂时。 5. 原型模式(Prototype Pattern) 定义 通过复制现有对象来创建新对象,而不是通过实例化类。 动机 有时创建对象的成本很高或者很复杂(如加载大量数据或依赖外部资源),直接克隆现有对象可以有效提升性能,避免冗余计算或复杂的初始化过程。 结构 Prototype接口:定义一个clone()方法,用于复制对象。 ConcretePrototype类:实现Prototype接口,负责具体的克隆操作。 示例 // 原型接口 interface Prototype { Prototype clone(); } // 具体原型 class ConcretePrototype implements Prototype { private String state; public ConcretePrototype(String state) { this.state = state; } @Override public Prototype clone() { return new ConcretePrototype(state); } } 结构型模式 结构型模式是设计模式中的一种类型,关注于如何将类或对象组合成更大的结构,以形成更复杂的功能。结构型模式帮助我们简化设计、提高系统的灵活性和可复用性,并有助于在不同的类之间建立良好的关系。 以下是几种常见的结构型模式的详细说明: 1. 适配器模式(Adapter Pattern) 定义 适配器模式允许将一个类的接口转换成客户端所期望的另一种接口,使得原本因接口不兼容而无法一起工作的类可以一起工作。 动机 在系统需要与多个接口不兼容的类交互时,适配器模式提供了一种解决方案。 实现 适配器可以是类适配器或对象适配器。类适配器通过继承来实现,而对象适配器通过组合来实现。 示例 // 目标接口 interface Target { void request(); } // 适配者类 class Adaptee { void specificRequest() { System.out.println("Called specificRequest"); } } // 适配器类 class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } @Override public void request() { adaptee.specificRequest(); } } 优点 使得接口不兼容的类可以协同工作。 提高系统的可复用性和灵活性。 缺点 可能会增加系统的复杂性,过多的适配器会导致代码难以理解。 2. 桥接模式(Bridge Pattern) 定义 桥接模式通过将抽象部分与其实现部分分离,从而使它们可以独立变化。即在抽象类和具体实现之间引入一个桥接接口。 动机 当一个类的抽象和实现之间有多个变化维度时,桥接模式能够将这些变化分离,减少系统的复杂性。 结构 抽象类:定义了高层接口。 实现类:实现了低层接口。 桥接:将抽象类与实现类连接。 示例 // 实现接口 interface Implementor { void operationImpl(); } // 具体实现 class ConcreteImplementorA implements Implementor { @Override public void operationImpl() { System.out.println("ConcreteImplementorA operation"); } } // 抽象类 abstract class Abstraction { protected Implementor implementor; public Abstraction(Implementor implementor) { this.implementor = implementor; } public abstract void operation(); } // 具体抽象类 class RefinedAbstraction extends Abstraction { public RefinedAbstraction(Implementor implementor) { super(implementor); } @Override public void operation() { implementor.operationImpl(); } } 优点 通过分离抽象和实现,提高了系统的灵活性。 可以在不改变抽象和实现的情况下扩展系统。 缺点 可能会增加系统的复杂性,特别是在类的数量较多时。 3. 组合模式(Composite Pattern) 定义 组合模式将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式允许客户端以统一的方式对待单个对象和组合对象。 动机 当需要表示对象的部分和整体的关系时,组合模式提供了一种简单的方法来管理这些对象。 结构 组件:定义了叶子和组合对象的公共接口。 叶子:实现了组件接口,表示树的叶子节点。 组合:实现了组件接口,表示树的组合节点。 示例 // 组件接口 interface Component { void operation(); } // 叶子类 class Leaf implements Component { @Override public void operation() { System.out.println("Leaf operation"); } } // 组合类 class Composite implements Component { private List<Component> children = new ArrayList<>(); public void add(Component component) { children.add(component); } @Override public void operation() { for (Component child : children) { child.operation(); } } } 优点 客户端可以一致地使用单个对象和组合对象。 可以方便地添加新的叶子或组合。 缺点 设计上比较复杂,可能会引入不必要的复杂性。 4. 装饰模式(Decorator Pattern) 定义 装饰模式允许在不改变对象自身的情况下,动态地给一个对象添加一些额外的职责。装饰模式提供了比子类更灵活的替代方案。 动机 当需要在运行时扩展对象的功能时,装饰模式提供了一种灵活的方法。 结构 组件接口:定义了对象的接口。 具体组件:实现了组件接口的基本对象。 装饰者抽象类:实现了组件接口,持有一个组件的引用。 具体装饰者:扩展了装饰者抽象类,添加额外的职责。 示例 // 组件接口 interface Component { void operation(); } // 具体组件 class ConcreteComponent implements Component { @Override public void operation() { System.out.println("ConcreteComponent operation"); } } // 装饰者抽象类 abstract class Decorator implements Component { protected Component component; public Decorator(Component component) { this.component = component; } @Override public void operation() { component.operation(); } } // 具体装饰者 class ConcreteDecoratorA extends Decorator { public ConcreteDecoratorA(Component component) { super(component); } @Override public void operation() { super.operation(); System.out.println("ConcreteDecoratorA operation"); } } 优点 可以动态地添加职责,增加灵活性。 可以通过多个装饰者组合来扩展功能。 缺点 可能会导致系统中出现大量小的类,增加管理的复杂性。 5. 代理模式(Proxy Pattern) 定义 代理模式为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端与真实对象之间起到中介作用。 动机 在某些情况下,需要控制对某个对象的访问,比如延迟加载、安全控制、日志记录等。 结构 主题接口:定义了真实对象和代理的公共接口。 真实对象:实现了主题接口,表示被代理的对象。 代理类:实现了主题接口,持有真实对象的引用并控制对它的访问。 示例 // 主题接口 interface Subject { void request(); } // 真实对象 class RealSubject implements Subject { @Override public void request() { System.out.println("RealSubject request"); } } // 代理类 class Proxy implements Subject { private RealSubject realSubject; @Override public void request() { if (realSubject == null) { realSubject = new RealSubject(); } realSubject.request(); } } 优点 控制访问真实对象的权限,可以实现懒加载。 可以在请求前后添加额外的操作(如日志、安全控制等)。 缺点 可能会引入不必要的复杂性,特别是在多层代理的情况下。 行为型模式 行为型模式是设计模式中的一种类型,主要关注对象之间的交互和职责分配。它们帮助管理复杂的控制流和对象之间的关系,以便于创建可扩展和可维护的系统。行为型模式通常关注如何使对象之间的交互更加灵活,并提供一种更好的方法来处理系统中的操作。 以下是几种常见的行为型模式的详细说明: 1. 责任链模式(Chain of Responsibility Pattern) 定义 责任链模式将请求的发送者和接收者解耦,通过一系列处理者对象形成一个链,每个处理者决定将请求处理还是将其传递给下一个处理者。 动机 在系统中,可能会有多个处理者需要处理相同的请求,责任链模式能够避免将请求直接发送给某个具体的处理者。 结构 处理者接口:定义处理请求的方法。 具体处理者:实现处理者接口,处理特定的请求。 责任链:将多个处理者连接成一个链。 示例 // 处理者接口 interface Handler { void setNext(Handler next); void handleRequest(String request); } // 具体处理者A class ConcreteHandlerA implements Handler { private Handler next; @Override public void setNext(Handler next) { this.next = next; } @Override public void handleRequest(String request) { if (request.equals("A")) { System.out.println("Handler A handled request"); } else if (next != null) { next.handleRequest(request); } } } // 具体处理者B class ConcreteHandlerB implements Handler { private Handler next; @Override public void setNext(Handler next) { this.next = next; } @Override public void handleRequest(String request) { if (request.equals("B")) { System.out.println("Handler B handled request"); } else if (next != null) { next.handleRequest(request); } } } 优点 降低了请求发送者和处理者之间的耦合度。 可以动态增加处理者。 缺点 可能会导致请求的处理时间不确定,特别是请求在链中经过多个处理者时。 2. 命令模式(Command Pattern) 定义 命令模式将请求封装为对象,从而使得用户可以将请求参数化、排队或记录请求日志,并支持可撤销的操作。 动机 在需要将操作的调用者与执行者解耦时,命令模式提供了一种灵活的解决方案。 结构 命令接口:定义命令的接口。 具体命令:实现命令接口,定义执行的操作。 调用者:调用命令对象来执行请求。 接收者:实际执行请求的对象。 示例 // 命令接口 interface Command { void execute(); } // 具体命令 class ConcreteCommand implements Command { private Receiver receiver; public ConcreteCommand(Receiver receiver) { this.receiver = receiver; } @Override public void execute() { receiver.action(); } } // 接收者 class Receiver { public void action() { System.out.println("Receiver action"); } } // 调用者 class Invoker { private Command command; public void setCommand(Command command) { this.command = command; } public void invoke() { command.execute(); } } 优点 通过将请求封装为对象,命令模式实现了请求的参数化。 可以支持可撤销的操作。 缺点 可能会导致系统中出现大量的命令类,增加了复杂性。 3. 观察者模式(Observer Pattern) 定义 观察者模式定义了一种一对多的依赖关系,使得当一个对象状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。 动机 在需要建立对象之间的发布-订阅关系时,观察者模式提供了一种有效的解决方案。 结构 主题接口:定义注册和通知观察者的方法。 具体主题:实现主题接口,管理观察者并通知它们。 观察者接口:定义更新方法。 具体观察者:实现观察者接口,以便在主题状态变化时进行更新。 示例 // 主题接口 interface Subject { void attach(Observer observer); void detach(Observer observer); void notifyObservers(); } // 具体主题 class ConcreteSubject implements Subject { private List<Observer> observers = new ArrayList<>(); private String state; @Override public void attach(Observer observer) { observers.add(observer); } @Override public void detach(Observer observer) { observers.remove(observer); } @Override public void notifyObservers() { for (Observer observer : observers) { observer.update(state); } } public void setState(String state) { this.state = state; notifyObservers(); } } // 观察者接口 interface Observer { void update(String state); } // 具体观察者 class ConcreteObserver implements Observer { @Override public void update(String state) { System.out.println("Observer updated with state: " + state); } } 优点 支持广播通信,减少了对象之间的耦合。 可以动态地添加和移除观察者。 缺点 可能会造成观察者的过度依赖,导致状态更新过于频繁。 4. 状态模式(State Pattern) 定义 状态模式允许一个对象在其内部状态改变时改变其行为。对象看起来似乎修改了其类。 动机 当一个对象的行为与其状态相关时,状态模式提供了一种有效的方法来管理这些状态和相应的行为。 结构 状态接口:定义状态的接口。 具体状态:实现状态接口,定义与具体状态相关的行为。 上下文:维护一个对某一状态对象的引用,负责状态的切换。 示例 // 状态接口 interface State { void handle(); } // 具体状态A class ConcreteStateA implements State { @Override public void handle() { System.out.println("Handling State A"); } } // 具体状态B class ConcreteStateB implements State { @Override public void handle() { System.out.println("Handling State B"); } } // 上下文 class Context { private State state; public void setState(State state) { this.state = state; } public void request() { state.handle(); } } 优点 简化了代码,避免了复杂的条件判断。 通过状态的封装,提高了系统的可扩展性。 缺点 状态类的数量可能会增加,导致系统的复杂性。 5. 策略模式(Strategy Pattern) 定义 策略模式定义了一系列算法,将每一个算法封装起来,并使它们可以互相替换。策略模式让算法独立于使用它的客户端。 动机 在需要在运行时选择算法或行为时,策略模式提供了一种灵活的解决方案。 结构 策略接口:定义了一个算法的接口。 具体策略:实现策略接口,封装具体的算法。 上下文:维护一个对某一策略对象的引用,负责执行策略。 示例 // 策略接口 interface Strategy { void execute(); } // 具体策略A class ConcreteStrategyA implements Strategy { @Override public void execute() { System.out.println("Executing Strategy A"); } } // 具体策略B class ConcreteStrategyB implements Strategy { @Override public void execute() { System.out.println("Executing Strategy B"); } } // 上下文 class Context { private Strategy strategy; public void setStrategy(Strategy strategy) { this.strategy = strategy; } public void executeStrategy() { strategy.execute(); } } 优点 提高了系统的灵活性和可扩展性。 客户端可以根据需要选择策略。 缺点 可能会导致策略类的数量增加。 6. 迭代器模式(Iterator Pattern) 定义 迭代器模式提供一种方法访问一个聚合对象的各个元素,而又不暴露该对象的内部表示。 动机 在需要遍历集合但不希望暴露集合的内部结构时,迭代器模式提供了一种清晰的解决方案。 结构 迭代器接口:定义遍历的方法(如next()、hasNext())。 具体迭代器:实现迭代器接口,管理对集合的访问。 聚合接口:定义创建迭代器的方法。 具体聚合:实现聚合接口,提供存储元素的集合。 示例 import java.util.ArrayList; import java.util.List; // 迭代器接口 interface Iterator { boolean hasNext(); Object next(); } // 具体迭代器 class ConcreteIterator implements Iterator { private List<Object> items; private int position = 0; public ConcreteIterator(List<Object> items) { this.items = items; } @Override public boolean hasNext() { return position < items.size(); } @Override public Object next() { return items.get(position++); } } // 聚合接口 interface Aggregate { Iterator createIterator(); } // 具体聚合 class ConcreteAggregate implements Aggregate { private List<Object> items = new ArrayList<>(); public void addItem(Object item) { items.add(item); } @Override public Iterator createIterator() { return new ConcreteIterator(items); } } // 使用示例 public class IteratorPatternExample { public static void main(String[] args) { ConcreteAggregate aggregate = new ConcreteAggregate(); aggregate.addItem("Item 1"); aggregate.addItem("Item 2"); aggregate.addItem("Item 3"); Iterator iterator = aggregate.createIterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } } 优点 提供了一种统一的遍历方式,支持多种聚合对象。 隐藏了集合的内部实现,使得聚合对象的实现可以独立于迭代器的实现。 缺点 可能会增加系统的复杂性,特别是在需要多个迭代器时。 7. 中介者模式(Mediator Pattern) 定义 中介者模式定义一个中介对象来封装一系列对象之间的交互,使得对象之间的交互不直接,减少了对象之间的耦合。 动机 在复杂系统中,多个对象之间的交互可能会导致高耦合性,中介者模式可以降低这种耦合。 结构 中介者接口:定义各个同事对象之间的交互方法。 具体中介者:实现中介者接口,协调同事对象之间的交互。 同事接口:定义同事对象的接口。 具体同事:实现同事接口,并通过中介者与其他同事进行交互。 示例 // 中介者接口 interface Mediator { void notify(Object sender, String event); } // 具体中介者 class ConcreteMediator implements Mediator { private ColleagueA colleagueA; private ColleagueB colleagueB; public void setColleagueA(ColleagueA colleagueA) { this.colleagueA = colleagueA; } public void setColleagueB(ColleagueB colleagueB) { this.colleagueB = colleagueB; } @Override public void notify(Object sender, String event) { if (sender == colleagueA) { // 处理ColleagueA的事件 System.out.println("Mediator received event from ColleagueA: " + event); } else if (sender == colleagueB) { // 处理ColleagueB的事件 System.out.println("Mediator received event from ColleagueB: " + event); } } } // 同事接口 interface Colleague { void setMediator(Mediator mediator); } // 具体同事A class ColleagueA implements Colleague { private Mediator mediator; @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } public void doSomething() { System.out.println("ColleagueA does something."); mediator.notify(this, "Action from ColleagueA"); } } // 具体同事B class ColleagueB implements Colleague { private Mediator mediator; @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } public void doSomething() { System.out.println("ColleagueB does something."); mediator.notify(this, "Action from ColleagueB"); } } // 使用示例 public class MediatorPatternExample { public static void main(String[] args) { ConcreteMediator mediator = new ConcreteMediator(); ColleagueA colleagueA = new ColleagueA(); colleagueA.setMediator(mediator); ColleagueB colleagueB = new ColleagueB(); colleagueB.setMediator(mediator); mediator.setColleagueA(colleagueA); mediator.setColleagueB(colleagueB); colleagueA.doSomething(); colleagueB.doSomething(); } } 优点 减少了对象之间的依赖,提高了系统的灵活性。 可以集中处理对象之间的交互逻辑。 缺点 可能会导致中介者变得复杂,成为系统的单点故障。 8. 备忘录模式(Memento Pattern) 定义 备忘录模式允许在不暴露对象内部状态的情况下,捕获并外部存储一个对象的内部状态,以便在以后可以恢复到该状态。 动机 在需要保存对象的历史状态以便在需要时恢复时,备忘录模式提供了一种简单的实现。 结构 备忘录:用于存储对象的状态。 发起人:创建备忘录并负责恢复状态。 管理者:管理多个备忘录的存储。 示例 // 备忘录 class Memento { private String state; public Memento(String state) { this.state = state; } public String getState() { return state; } } // 发起人 class Originator { private String state; public void setState(String state) { this.state = state; } public Memento saveStateToMemento() { return new Memento(state); } public void getStateFromMemento(Memento memento) { state = memento.getState(); } } // 管理者 class Caretaker { private List<Memento> mementoList = new ArrayList<>(); public void add(Memento state) { mementoList.add(state); } public Memento get(int index) { return mementoList.get(index); } } // 使用示例 public class MementoPatternExample { public static void main(String[] args) { Originator originator = new Originator(); Caretaker caretaker = new Caretaker(); originator.setState("State #1"); caretaker.add(originator.saveStateToMemento()); originator.setState("State #2"); caretaker.add(originator.saveStateToMemento()); originator.setState("State #3"); System.out.println("Current State: " + originator.state); originator.getStateFromMemento(caretaker.get(0)); System.out.println("Restored State: " + originator.state); } } 优点 可以在不暴露实现细节的情况下恢复对象的状态。 提高了对象状态管理的灵活性。 缺点 可能会增加系统的内存开销,特别是在需要存储大量状态时。 9. 访问者模式(Visitor Pattern) 定义 访问者模式允许在不改变对象结构的前提下,定义作用于这些对象的新操作。通过将操作封装在访问者中,可以对一组对象进行操作。 动机 在需要对对象结构中的元素执行一些操作时,访问者模式提供了一种清晰的解决方案。 结构 访问者接口:定义访问的方法。 具体访问者:实现访问者接口,定义具体的操作。 元素接口:定义接受访问者的方法。 具体元素:实现元素接口,接受访问者。 示例 // 访问者接口 interface Visitor { void visit(ElementA elementA); void visit(ElementB elementB); } // 具体访问者 class ConcreteVisitor implements Visitor { @Override public void visit(ElementA elementA) { System.out.println("Visiting Element A"); } @Override public void visit(ElementB elementB) { System.out.println("Visiting Element B"); } } // 元素接口 interface Element { void accept(Visitor visitor); } class ElementA implements Element { @Override public void accept(Visitor visitor) { visitor.visit(this); } } // 具体元素B class ElementB implements Element { @Override public void accept(Visitor visitor) { visitor.visit(this); } } // 使用示例 public class VisitorPatternExample { public static void main(String[] args) { ElementA elementA = new ElementA(); ElementB elementB = new ElementB(); Visitor visitor = new ConcreteVisitor(); elementA.accept(visitor); // 访问ElementA elementB.accept(visitor); // 访问ElementB } } 优点 将操作与对象结构分离,使得新增操作变得更加容易。 可以对对象结构中的元素进行多种操作,而无需修改元素的结构。 缺点 增加了新元素时的复杂性,因为需要修改访问者接口和所有的具体访问者。 对象结构中的元素与访问者的紧密耦合可能会影响可维护性。 10. 解释器模式(Interpreter Pattern) 定义 解释器模式为一种语言的文法定义一个表示,并定义一个解释器,用于处理该语言的句子。 动机 在需要解释一些特定语言或表达式的场景下,解释器模式可以简化语言处理的复杂度。 结构 抽象表达式:定义一个接口以实现解释操作。 终结符表达式:实现与文法中的终结符相关的解释操作。 非终结符表达式:实现与文法中的非终结符相关的解释操作。 上下文:存储解释器所需的状态信息。 示例 import java.util.HashMap; import java.util.Map; // 抽象表达式 interface Expression { int interpret(Map<String, Expression> variables); } // 终结符表达式 class TerminalExpression implements Expression { private String key; public TerminalExpression(String key) { this.key = key; } @Override public int interpret(Map<String, Expression> variables) { Expression variable = variables.get(key); return variable.interpret(variables); } } // 非终结符表达式 class AddExpression implements Expression { private Expression leftExpression; private Expression rightExpression; public AddExpression(Expression left, Expression right) { leftExpression = left; rightExpression = right; } @Override public int interpret(Map<String, Expression> variables) { return leftExpression.interpret(variables) + rightExpression.interpret(variables); } } // 使用示例 public class InterpreterPatternExample { public static void main(String[] args) { // 定义变量 Map<String, Expression> variables = new HashMap<>(); variables.put("x", new TerminalExpression("5")); variables.put("y", new TerminalExpression("10")); // 表达式:x + y Expression expression = new AddExpression(variables.get("x"), variables.get("y")); int result = expression.interpret(variables); System.out.println("Result: " + result); // 输出:Result: 15 } } 优点 可以通过组合简单的表达式来构建复杂的表达式。 符合开闭原则,新增表达式时可以很容易地扩展。 缺点 解析复杂的文法时,可能会导致类的数量急剧增加,影响可读性。 性能问题:对于大型文法的解释可能会有性能瓶颈。 ———————————————— 原文链接:https://blog.csdn.net/u012108607/article/details/142852438
-
一、创建型模式 1.单例模式(Singleton Pattern) 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。 1.1 饿汉式 特点:类加载时就初始化,线程安全 // 构造方法私有化 private Singleton() { } // 饿汉式创建单例对象 private static Singleton singleton = new Singleton(); public static Singleton getInstance() { return singleton; } 1.2 懒汉式 特点:第一次调用才初始化,避免内存浪费。 /* * 懒汉式创建单例模式 由于懒汉式是非线程安全, 所以加上线程锁保证线程安全 */ private static Singleton singleton; public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } 1.3 双重检验锁(double check lock)(DCL) 特点:安全且在多线程情况下能保持高性能 private volatile static Singleton singleton; private Singleton (){} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } 1.4 静态内部类 特点:效果类似DCL,只适用于静态域 private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } 1.5 枚举 特点:自动支持序列化机制,绝对防止多次实例化 public enum Singleton { INSTANCE; } 1.6 破坏单例的几种方式与解决方法 1.6.1 反序列化 Singleton singleton = Singleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/test.txt")); oos.writeObject(singleton); oos.flush(); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/test.txt")); Singleton singleton1 = (Singleton)ois.readObject(); ois.close(); System.out.println(singleton);//com.ruoyi.base.mapper.Singleton@50134894 System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton@5ccd43c2 可以看到反序列化后,两个对象的地址不一样了,那么这就是违背了单例模式的原则了,解决方法只需要在单例类里加上一个readResolve()方法即可,原因就是在反序列化的过程中,会检测readResolve()方法是否存在,如果存在的话就会反射调用readResolve()这个方法。 private Object readResolve() { return singleton; } //com.ruoyi.base.mapper.Singleton@50134894 //com.ruoyi.base.mapper.Singleton@50134894 1.6.2 反射 Singleton singleton = Singleton.getInstance(); Class<Singleton> singletonClass = Singleton.class; Constructor<Singleton> constructor = singletonClass.getDeclaredConstructor(); constructor.setAccessible(true); Singleton singleton1 = constructor.newInstance(); System.out.println(singleton);//com.ruoyi.base.mapper.Singleton@32a1bec0 System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton@22927a81 同样可以看到,两个对象的地址不一样,这同样是违背了单例模式的原则,解决办法为使用一个布尔类型的标记变量标记一下即可,代码如下: private static boolean singletonFlag = false; private Singleton() { if (singleton != null || singletonFlag) { throw new RuntimeException("试图用反射破坏异常"); } singletonFlag = true; } 但是这种方法假如使用了反编译,获得了这个标记变量,同样可以破坏单例,代码如下: Class<Singleton> singletonClass = Singleton.class; Constructor<Singleton> constructor = singletonClass.getDeclaredConstructor(); constructor.setAccessible(true); Singleton singleton = constructor.newInstance(); System.out.println(singleton); // com.ruoyi.base.mapper.Singleton@32a1bec0 Field singletonFlag = singletonClass.getDeclaredField("singletonFlag"); singletonFlag.setAccessible(true); singletonFlag.setBoolean(singleton, false); Singleton singleton1 = constructor.newInstance(); System.out.println(singleton1); // com.ruoyi.base.mapper.Singleton@5e8c92f4 如果想使单例不被破坏,那么应该使用枚举的方式去实现单例模式,枚举是不可以被反射破坏单例的。 1.7 容器式单例 当程序中的单例对象非常多的时候,则可以使用容器对所有单例对象进行管理,如下: public class ContainerSingleton { private ContainerSingleton() {} private static Map<String, Object> singletonMap = new ConcurrentHashMap<>(); public static Object getInstance(Class clazz) throws Exception { String className = clazz.getName(); // 当容器中不存在目标对象时则先生成对象再返回该对象 if (!singletonMap.containsKey(className)) { Object instance = Class.forName(className).newInstance(); singletonMap.put(className, instance); return instance; } // 否则就直接返回容器里的对象 return singletonMap.get(className); } public static void main(String[] args) throws Exception { SafetyDangerLibrary instance1 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class); SafetyDangerLibrary instance2 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class); System.out.println(instance1 == instance2); // true } } 1.8 ThreadLocal单例 不保证整个应用全局唯一,但保证线程内部全局唯一,以空间换时间,且线程安全。 public class ThreadLocalSingleton { private ThreadLocalSingleton(){} private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = ThreadLocal.withInitial(() -> new ThreadLocalSingleton()); public static ThreadLocalSingleton getInstance(){ return threadLocalInstance.get(); } public static void main(String[] args) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance()); System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance()); }).start(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance()); System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance()); }).start(); // Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3 // Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc // Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3 // Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc } } 可以看到上面线程0和1他们的对象是不一样的,但是线程内部,他们的对象是一样的,这就是线程内部保证唯一。 1.9 总结 适用场景: 需要确保在任何情况下绝对只需要一个实例。如:ServletContext,ServletConfig,ApplicationContext,DBPool,ThreadPool等。 优点: 在内存中只有一个实例,减少了内存开销。 可以避免对资源的多重占用。 设置全局访问点,严格控制访问。 缺点: 没有接口,扩展困难。 如果要扩展单例对象,只有修改代码,没有其它途径。 2.工厂方法模式(Factory Method) 2.1 简单工厂模式 简单工厂模式不是23种设计模式之一,他可以理解为工厂模式的一种简单的特殊实现。 2.1.1 基础版 // 工厂类 public class CoffeeFactory { public Coffee create(String type) { if ("americano".equals(type)) { return new Americano(); } if ("mocha".equals(type)) { return new Mocha(); } if ("cappuccino".equals(type)) { return new Cappuccino(); } return null; } } // 产品基类 public interface Coffee { } // 产品具体类,实现产品基类接口 public class Cappuccino implements Coffee { } 基础版是最基本的简单工厂的写法,传一个参数过来,判断是什么类型的产品,就返回对应的产品类型。但是这里有一个问题,就是参数是字符串的形式,这就很容易会写错,比如少写一个字母,或者小写写成了大写,就会无法得到自己想要的产品类了,同时如果新加了产品,还得在工厂类的创建方法中继续加if,于是就有了升级版的写法。 2.1.2 升级版 // 使用反射创建对象 // 加一个static变为静态工厂 public static Coffee create(Class<? extends Coffee> clazz) throws Exception { if (clazz != null) { return clazz.newInstance(); } return null; } 升级版就很好的解决基础版的问题,在创建的时候在传参的时候不仅会有代码提示,保证不会写错,同时在新增产品的时候只需要新增产品类即可,也不需要再在工厂类的方法里面新增代码了。 2.1.3 总结 适用场景: 工厂类负责创建的对象较少。 客户端只需要传入工厂类的参数,对于如何创建的对象的逻辑不需要关心。 优点: 只需要传入一个正确的参数,就可以获取你所需要的对象,无须知道创建的细节。 缺点: 工厂类的职责相对过重,增加新的产品类型的时需要修改工厂类的判断逻辑,违背了开闭原则。 不易于扩展过于复杂的产品结构。 2.2 工厂方法模式 工厂方法模式是指定义一个创建对象的接口,让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。 工厂方法模式主要有以下几种角色: 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它和具体工厂之间一一对应。 2.2.1 代码实现 // 抽象工厂 public interface CoffeeFactory { Coffee create(); } // 具体工厂 public class CappuccinoFactory implements CoffeeFactory { @Override public Coffee create() { return new Cappuccino(); } } // 抽象产品 public interface Coffee { } // 具体产品 public class Cappuccino implements Coffee { } 2.2.2 总结 适用场景: 创建对象需要大量的重复代码。 客户端(应用层)不依赖于产品类实例如何被创建和实现等细节。 一个类通过其子类来指定创建哪个对象。 优点: 用户只需要关系所需产品对应的工厂,无须关心创建细节。 加入新产品符合开闭原则,提高了系统的可扩展性。 缺点: 类的数量容易过多,增加了代码结构的复杂度。 增加了系统的抽象性和理解难度。 3.抽象工厂模式(Abstract Factory) 抽象工厂模式是指提供一个创建一系列相关或相互依赖对象的接口,无须指定他们具体的类。 工厂方法模式中考虑的是一类产品的生产,如电脑厂只生产电脑,电话厂只生产电话,这种工厂只生产同种类的产品,同种类产品称为同等级产品,也就是说,工厂方法模式只考虑生产同等级的产品,但是现实生活中许多工厂都是综合型工厂,能生产多等级(种类)的产品,如上面说的电脑和电话,本质上他们都属于电器,那么他们就能在电器厂里生产出来,而抽象工厂模式就将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,如上图所示纵轴是产品等级,也就是同一类产品;横轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。 抽象工厂模式的主要角色如下: 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。 3.1 代码实现 // 咖啡店 抽象工厂 public interface CoffeeShopFactory { // 咖啡类 Coffee createCoffee(); // 甜点类 Dessert createDessert(); } // 美式风格工厂 public class AmericanFactory implements CoffeeShopFactory { @Override public Coffee createCoffee() { return new Americano(); } @Override public Dessert createDessert() { return new Cheesecake(); } } // 意式风格工厂 public class ItalyFactory implements CoffeeShopFactory { @Override public Coffee createCoffee() { return new Cappuccino(); } @Override public Dessert createDessert() { return new Tiramisu(); } } 类图 3.2 总结 产品族:一系列相关的产品,整合到一起有关联性 产品等级:同一个继承体系 适用场景: 客户端(应用层)不依赖于产品类实例如何被创建和实现等细节。 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码。 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。 优点: 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。 缺点: 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。 4.原型模式(Prototype) 原型模式是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。调用者不需要知道任何创建细节,不调用构造函数。 原型模式包含如下角色: 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。 访问类:使用具体原型类中的 clone() 方法来复制新的对象。 @Data @AllArgsConstructor @NoArgsConstructor public class Student implements Cloneable { private String name; private String sex; private Integer age; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public static void main(String[] args) throws Exception{ Student stu1 = new Student("张三", "男", 18); Student stu2 = (Student)stu1.clone(); stu2.setName("李四"); System.out.println(stu1);// Student(name=张三, sex=男, age=18) System.out.println(stu2);// Student(name=李四, sex=男, age=18) } } 可以看到,把一个学生复制过来,只是改了姓名而已,其他属性完全一样没有改变,需要注意的是,一定要在被拷贝的对象上实现Cloneable接口,否则会抛出CloneNotSupportedException异常。 4.1 浅克隆 创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。 @Data public class Clazz implements Cloneable { private String name; private Student student; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } @Data @AllArgsConstructor @NoArgsConstructor public class Student implements Serializable { private String name; private String sex; private Integer age; } public static void main(String[] args) throws Exception{ Clazz clazz1 = new Clazz(); clazz1.setName("高三一班"); Student stu1 = new Student("张三", "男", 18); clazz1.setStudent(stu1); System.out.println(clazz1); // Clazz(name=高三一班, student=Student(name=张三, sex=男, age=18)) Clazz clazz2 = (Clazz)clazz1.clone(); Student stu2 = clazz2.getStudent(); stu2.setName("李四"); System.out.println(clazz1); // Clazz(name=高三一班, student=Student(name=李四, sex=男, age=18)) System.out.println(clazz2); // Clazz(name=高三一班, student=Student(name=李四, sex=男, age=18)) } 可以看到,当修改了stu2的姓名时,stu1的姓名同样也被修改了,这说明stu1和stu2是同一个对象,这就是浅克隆的特点,对具体原型类中的引用类型的属性进行引用的复制。同时,这也可能是浅克隆所带来的弊端,因为结合该例子的原意,显然是想在班级中新增一名叫李四的学生,而非让所有的学生都改名叫李四,于是我们这里就要使用深克隆。 4.2 深克隆 创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。 @Data public class Clazz implements Cloneable, Serializable { private String name; private Student student; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } protected Object deepClone() throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } } public static void main(String[] args) throws Exception{ Clazz clazz1 = new Clazz(); clazz1.setName("高三一班"); Student stu1 = new Student("张三", "男", 18); clazz1.setStudent(stu1); Clazz clazz3 = (Clazz)clazz1.deepClone(); Student stu3 = clazz3.getStudent(); stu3.setName("王五"); System.out.println(clazz1); // Clazz(name=高三一班, student=Student(name=张三, sex=男, age=18)) System.out.println(clazz3); // Clazz(name=高三一班, student=Student(name=王五, sex=男, age=18)) } 可以看到,当修改了stu3的姓名时,stu1的姓名并没有被修改了,这说明stu3和stu1已经是不同的对象了,说明Clazz中的Student也被克隆了,不再指向原有对象地址,这就是深克隆。这里需要注意的是,Clazz类和Student类都需要实现Serializable接口,否则会抛出NotSerializableException异常。 4.3 克隆破坏单例与解决办法 PS:上面例子有的代码,这里便不重复写了,可以在上面的代码基础上添加以下代码 // Clazz类 private static Clazz clazz = new Clazz(); private Clazz(){} public static Clazz getInstance() {return clazz;} // 测试 public static void main(String[] args) throws Exception{ Clazz clazz1 = Clazz.getInstance(); Clazz clazz2 = (Clazz)clazz1.clone(); System.out.println(clazz1 == clazz2); // false } 可以看到clazz1和clazz2并不相等,也就是说他们并不是同一个对象,也就是单例被破坏了。 解决办法也很简单,首先第一个就是不实现Cloneable接口即可,但是不实现Cloneable接口进行clone则会抛出CloneNotSupportedException异常。第二个方法就是重写clone()方法即可,如下: @Override protected Object clone() throws CloneNotSupportedException { return clazz; } // 测试输出 System.out.println(clazz1 == clazz2) // true 可以看到,上面clazz1和clazz2是相等的,即单例没有被破坏。 另外我们知道,单例就是只有一个实例对象,如果重写了clone()方法保证单例的话,那么通过克隆出来的对象则不可以重新修改里面的属性,因为修改以后就会连同克隆对象一起被修改,所以是需要单例还是克隆,在实际应用中需要好好衡量。 4.4 总结 适用场景: 类初始化消耗资源较多。 new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)。 构造函数比较复杂。 循环体中生产大量对象时。 优点: 性能优良,Java自带的原型模式是基于内存二进制流的拷贝,比直接new一个对象性能上提升了许多。 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建的过程。 缺点: 必须配备克隆(或者可拷贝)方法。 当对已有类进行改造的时候,需要修改代码,违反了开闭原则。 深克隆、浅克隆需要运用得当。 5.建造者模式(Builder) 建造者模式是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。用户只需指定需要建造的类型就可以获得对象,建造过程及细节不需要了解。 建造者(Builder)模式包含如下角色: 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。 具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。 产品类(Product):要创建的复杂对象。 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。 5.1 常规写法 //产品类 电脑 @Data public class Computer { private String motherboard; private String cpu; private String memory; private String disk; private String gpu; private String power; private String heatSink; private String chassis; } // 抽象 builder类(接口) 组装电脑 public interface ComputerBuilder { Computer computer = new Computer(); void buildMotherboard(); void buildCpu(); void buildMemory(); void buildDisk(); void buildGpu(); void buildHeatSink(); void buildPower(); void buildChassis(); Computer build(); } // 具体 builder类 华硕ROG全家桶电脑(手动狗头) public class AsusComputerBuilder implements ComputerBuilder { @Override public void buildMotherboard() { computer.setMotherboard("Extreme主板"); } @Override public void buildCpu() { computer.setCpu("Inter 12900KS"); } @Override public void buildMemory() { computer.setMemory("芝奇幻峰戟 16G*2"); } @Override public void buildDisk() { computer.setDisk("三星980Pro 2T"); } @Override public void buildGpu() { computer.setGpu("华硕3090Ti 水猛禽"); } @Override public void buildHeatSink() { computer.setHeatSink("龙神二代一体式水冷"); } @Override public void buildPower() { computer.setPower("雷神二代1200W"); } @Override public void buildChassis() { computer.setChassis("太阳神机箱"); } @Override public Computer build() { return computer; } } // 指挥者类 指挥该组装什么电脑 @AllArgsConstructor public class ComputerDirector { private ComputerBuilder computerBuilder; public Computer construct() { computerBuilder.buildMotherboard(); computerBuilder.buildCpu(); computerBuilder.buildMemory(); computerBuilder.buildDisk(); computerBuilder.buildGpu(); computerBuilder.buildHeatSink(); computerBuilder.buildPower(); computerBuilder.buildChassis(); return computerBuilder.build(); } } // 测试 public static void main(String[] args) { ComputerDirector computerDirector = new ComputerDirector(new AsusComputerBuilder()); // Computer(motherboard=Extreme主板, cpu=Inter 12900KS, memory=芝奇幻峰戟 16G*2, disk=三星980Pro 2T, gpu=华硕3090Ti 水猛禽, power=雷神二代1200W, heatSink=龙神二代一体式水冷, chassis=太阳神机箱) System.out.println(computerDirector.construct()); } 上面示例是建造者模式的常规用法,指挥者类ComputerDirector在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合,于是就有了下面的简化写法。 5.2 简化写法 // 把指挥者类和抽象建造者合在一起的简化建造者类 public class SimpleComputerBuilder { private Computer computer = new Computer(); public void buildMotherBoard(String motherBoard){ computer.setMotherboard(motherBoard); } public void buildCpu(String cpu){ computer.setCpu(cpu); } public void buildMemory(String memory){ computer.setMemory(memory); } public void buildDisk(String disk){ computer.setDisk(disk); } public void buildGpu(String gpu){ computer.setGpu(gpu); } public void buildPower(String power){ computer.setPower(power); } public void buildHeatSink(String heatSink){ computer.setHeatSink(heatSink); } public void buildChassis(String chassis){ computer.setChassis(chassis); } public Computer build(){ return computer; } } // 测试 public static void main(String[] args) { SimpleComputerBuilder simpleComputerBuilder = new SimpleComputerBuilder(); simpleComputerBuilder.buildMotherBoard("Extreme主板"); simpleComputerBuilder.buildCpu("Inter 12900K"); simpleComputerBuilder.buildMemory("芝奇幻峰戟 16G*2"); simpleComputerBuilder.buildDisk("三星980Pro 2T"); simpleComputerBuilder.buildGpu("华硕3090Ti 水猛禽"); simpleComputerBuilder.buildPower("雷神二代1200W"); simpleComputerBuilder.buildHeatSink("龙神二代一体式水冷"); simpleComputerBuilder.buildChassis("太阳神机箱"); // Computer(motherboard=Extreme主板, cpu=Inter 12900K, memory=芝奇幻峰戟 16G*2, disk=三星980Pro 2T, gpu=华硕3090Ti 水猛禽, power=雷神二代1200W, heatSink=龙神二代一体式水冷, chassis=太阳神机箱) System.out.println(simpleComputerBuilder.build()); } 可以看到,对比常规写法,这样写确实简化了系统结构,但同时也加重了建造者类的职责,也不是太符合单一职责原则,如果construct() 过于复杂,建议还是封装到 Director 中。 5.3 链式写法 // 链式写法建造者类 public class SimpleComputerBuilder { private Computer computer = new Computer(); public SimpleComputerBuilder buildMotherBoard(String motherBoard){ computer.setMotherboard(motherBoard); return this; } public SimpleComputerBuilder buildCpu(String cpu){ computer.setCpu(cpu); return this; } public SimpleComputerBuilder buildMemory(String memory){ computer.setMemory(memory); return this; } public SimpleComputerBuilder buildDisk(String disk){ computer.setDisk(disk); return this; } public SimpleComputerBuilder buildGpu(String gpu){ computer.setGpu(gpu); return this; } public SimpleComputerBuilder buildPower(String power){ computer.setPower(power); return this; } public SimpleComputerBuilder buildHeatSink(String heatSink){ computer.setHeatSink(heatSink); return this; } public SimpleComputerBuilder buildChassis(String chassis){ computer.setChassis(chassis); return this; } public Computer build(){ return computer; } } // 测试 public static void main(String[] args) { Computer asusComputer = new SimpleComputerBuilder().buildMotherBoard("Extreme主板") .buildCpu("Inter 12900K") .buildMemory("芝奇幻峰戟 16G*2") .buildDisk("三星980Pro 2T") .buildGpu("华硕3090Ti 水猛禽") .buildPower("雷神二代1200W") .buildHeatSink("龙神二代一体式水冷") .buildChassis("太阳神机箱").build(); System.out.println(asusComputer); } 可以看到,其实链式写法与普通写法的区别并不大,只是在建造者类组装部件的时候,同时将建造者类返回即可,使用链式写法使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。比较常见的mybatis-plus中的条件构造器就是使用的这种链式写法。 5.4 总结 适用场景: 适用于创建对象需要很多步骤,但是步骤顺序不一定固定。 如果一个对象有非常复杂的内部结构(属性),把复杂对象的创建和使用进行分离。 优点: 封装性好,创建和使用分离。 扩展性好,建造类之间独立、一定程度上解耦。 缺点: 产生多余的Builder对象。 产品内部发生变化,建造者都要修改,成本较大。 与工厂模式的区别: 建造者模式更注重方法的调用顺序,工厂模式更注重创建对象。 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的都一样。 关注点不同,工厂模式只需要把对象创建出来就可以了,而建造者模式中不仅要创建出这个对象,还要知道这个对象由哪些部件组成。 建造者模式根据建造过程中的顺序不一样,最终的对象部件组成也不一样。 与抽象工厂模式的区别: 抽象工厂模式实现对产品族的创建,一个产品族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。 建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。 建造者模式所有函数加到一起才能生成一个对象,抽象工厂一个函数生成一个对象 二、结构型模式 1.代理模式(Proxy Pattern) 代理模式是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在访问对象和目标对象之间起到中介作用。 Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。 代理(Proxy)模式分为三种角色: 抽象角色(Subject): 通过接口或抽象类声明真实角色和代理对象实现的业务方法。 真实角色(Real Subject): 实现了抽象角色中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。 代理角色(Proxy) : 提供了与真实角色相同的接口,其内部含有对真实角色的引用,它可以访问、控制或扩展真实角色的功能。 1.1 静态代理 静态代理就是指我们在给一个类扩展功能的时候,我们需要去书写一个静态的类,相当于在之前的类上套了一层,这样我们就可以在不改变之前的类的前提下去对原有功能进行扩展,静态代理需要代理对象和目标对象实现一样的接口。 public static void main(String[] args) { // 正常播放 new Controller().excuse(new PlayAction(new Player())); // 暂停播放 new Controller().excuse(new PauseAction(new Player())); // 停止播放 new Controller().excuse(new StopAction(new Player())); } 3.2 总结 适用场景: 现实语义中具备“命令”的操作(如命令菜单,shell命令...)。 请求调用者和请求接收者需要解耦,使得调用者和接收者不直接交互。 需要抽象出等待执行的行为,比如撤销操作和恢复操作等。 需要支持命令宏(即命令组合操作)。 优点: 通过引入中间件(抽象接口),解耦了命令的请求与实现。 扩展性良好,可以很容易地增加新命令。 支持组合命令,支持命令队列。 可以在现有的命令的基础上,增加额外功能。 缺点: 具体命令类可能过多。 增加 了程序的复杂度,理解更加困难。 4.职责链模式(chain of responsibility pattern) 职责链模式是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。当一个请求从链式的首端发出时,会沿着链的路径依次传递给每一个节点对象,直至有对象处理这个请求为止。 职责链模式主要包含以下角色: 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。 4.1 代码实现 多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定。 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 可动态指定一组对象处理请求。 优点: 将请求与处理解耦。 请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转发给下一级节点对象。 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果。 链路结构灵活,可以通过改变链路结构动态地新增或删减责任。 易于扩展新的请求处理类(节点),符合开闭原则。 缺点: 责任链太长或者处理时间过长,会影响整体性能。 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃。 5.状态模式(State Pattern) 状态模式也称为状态机模式(State Machine Pattern),是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。 状态模式包含以下主要角色: 环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。 具体状态(Concrete State)角色:实现抽象状态所对应的行为。 双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。 11.2 Spring中的解释器模式 可以看到Spring中解释器写的是比较完善的,不仅有先乘除后加减和先括号进行运算的日常计算规则,而且对于空格也并没有要求,仅需要写出完整的表达式即可运算出来。 11.3 总结 适用场景: 一些重复出现的问题可以用一种简单的语言来进行表述。 一个简单语法需要解释的场景。 优点: 扩展性强:在解释器模式中由于语法是由很多类表示的,当语法规则更改时,只需修改相应的非终结符表达式即可;若扩展语法时,只需添加相应非终结符类即可。 增加了新的解释表达式的方式。 易于实现文法:解释器模式对应的文法应当是比较简单且易于实现的,过于复杂的语法并不适合使用解释器模式。 缺点: 语法规则较复杂时,会引起类膨胀。 执行效率比较低 ———————————————— 原文链接:https://blog.csdn.net/pyy542718473/article/details/127248128
-
1、什么是Spring MVC ?简单介绍下你对springMVC的理解? Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。 2、SpringMVC的流程? (1)用户发送请求至前端控制器DispatcherServlet; (2)DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handler; (3)处理器映射器根据请求url找到具体的处理器Handler,生成处理器对象及处理器拦截器(如果有则生成),一并返回给DispatcherServlet; (4)DispatcherServlet 调用 HandlerAdapter处理器适配器,请求执行Handler; (5)HandlerAdapter 经过适配调用 具体处理器进行处理业务逻辑; (6)Handler执行完成返回ModelAndView; (7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet; (8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析; (9)ViewResolver解析后返回具体View; (10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中) (11)DispatcherServlet响应用户。 前端控制器 DispatcherServlet:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。 处理器映射器 HandlerMapping:根据请求的URL来查找Handler 处理器适配器 HandlerAdapter:负责执行Handler 处理器 Handler:处理器,需要程序员开发 视图解析器 ViewResolver:进行视图的解析,根据视图逻辑名将ModelAndView解析成真正的视图(view) 视图View:View是一个接口, 它的实现类支持不同的视图类型,如jsp,freemarker,pdf等等 3、Springmvc的优点: (1)可以支持各种视图技术,而不仅仅局限于JSP; (2)与Spring框架集成(如IoC容器、AOP等); (3)清晰的角色分配:前端控制器(dispatcherServlet) ,请求到处理器映射(handlerMapping),处理器适配器(HandlerAdapter),视图解析器(ViewResolver)。 (4) 支持各种请求资源的映射策略。 4、SpringMVC怎么样设定重定向和转发的? (1)转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4" (2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com" 5、 SpringMVC常用的注解有哪些? @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。 @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。 @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。 6、SpingMvc中的控制器的注解一般用哪个?有没有别的注解可以替代? 答:一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替。 7、springMVC和struts2的区别有哪些? (1)springmvc的入口是一个servlet即前端控制器(DispatchServlet),而struts2入口是一个filter过虑器(StrutsPrepareAndExecuteFilter)。 (2)springmvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。 (3)Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。 8、如何解决POST请求中文乱码问题,GET的又如何处理呢? (1)解决post请求乱码问题:在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8; <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> (2)get请求中文参数出现乱码解决方法有两个: ①修改tomcat配置文件添加编码与工程编码一致,如下: <ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/> ②另外一种方法对参数进行重新编码: String userName = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8") ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。 9、SpringMvc里面拦截器是怎么写的: 有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可: <!-- 配置SpringMvc的拦截器 --> <mvc:interceptors> <!-- 配置一个拦截器的Bean就可以了 默认是对所有请求都拦截 --> <bean id="myInterceptor" class="com.zwp.action.MyHandlerInterceptor"></bean> <!-- 只针对部分请求拦截 --> <mvc:interceptor> <mvc:mapping path="/modelMap.do" /> <bean class="com.zwp.action.MyHandlerInterceptorAdapter" /> </mvc:interceptor> </mvc:interceptors> 10、注解原理: (1)什么是注解: Java 注解就是代码中的一些特殊标记(元信息),用于在编译、类加载、运行时进行解析和使用,并执行相应的处理。它本质是继承了 Annotation 的特殊接口,其具体实现类是 JDK 动态代理生成的代理类,通过反射获取注解时,返回的也是 Java 运行时生成的动态代理对象 $Proxy1。通过代理对象调用自定义注解的方法,会最终调用 AnnotationInvocationHandler 的 invoke 方法,该方法会从 memberValues 这个Map中查询出对应的值,而 memberValues 的来源是Java常量池。 注解在实际开发中非常常见,比如 Java 原生的 @Overried、@Deprecated 等,Spring的 @Controller、@Service等,Lombok 工具类也有大量的注解,不过在原生 Java 中,还提供了元 Annotation(元注解),他主要是用来修饰注解的,比如 @Target、@Retention、@Document、@Inherited 等。 @Target:标识注解可以修饰哪些地方,比如方法、成员变量、包等,具体取值有以下几种:ElementType.TYPE/FIELD/METHOD/PARAMETER/CONSTRUCTOR/LOCAL_VARIABLE/ANNOTATION_TYPE/PACKAGE/TYPE_PARAMETER/TYPE_USE @Retention:什么时候使用注解:SOURCE(编译阶段就丢弃) / CLASS(类加载时丢弃) / RUNTIME(始终不会丢弃),一般来说,我们自定义的注解都是 RUNTIME 级别的,因为大多数情况我们是根据运行时环境去做一些处理,一般需要配合反射来使用,因为反射是 Java 获取运行是的信息的重要手段 @Document:注解是否会包含在 javadoc 中; @Inherited:定义该注解与子类的关系,子类是否能使用。 (2)如何自定义注解? ① 创建一个自定义注解:与创建接口类似,但自定义注解需要使用 @interface ② 添加元注解信息,比如 @Target、@Retention、@Document、@Inherited 等 ③ 创建注解方法,但注解方法不能带有参数 ④ 注解方法返回值为基本类型、String、Enums、Annotation 或其数组 ⑤ 注解可以有默认值; @Target(FIELD) @Retention(RUNTIME) @Documented public @interface CarName { String value() default ""; } 11、SpringMvc怎么和AJAX相互调用的? 通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 : (1)加入Jackson.jar (2)在配置文件中配置json的映射 (3)在接受Ajax方法里面可以直接返回Object、List等,但方法前面要加上@ResponseBody注解。 12、Spring MVC的异常处理 ? 答:可以将异常抛给Spring框架,由Spring框架来处理;我们只需要配置简单的异常处理器,在异常处理器中添视图页面即可。 13、SpringMvc的控制器是不是单例模式?如果是,有什么问题?怎么解决? 答:是单例模式,在多线程访问的时候有线程安全问题,解决方案是在控制器里面不能写可变状态量,如果需要使用这些可变状态,可以使用ThreadLocal机制解决,为每个线程单独生成一份变量副本,独立操作,互不影响。 14、如果在拦截请求中,我想拦截get方式提交的方法,怎么配置? 答:可以在@RequestMapping注解里面加上method=RequestMethod.GET。 16、怎样在方法里面得到Request,或者Session? 答:直接在方法的形参中声明request,SpringMvc就自动把request对象传入。 16、如果想在拦截的方法里面得到从前台传入的参数,怎么得到? 答:直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。 17、如果前端传入多个参数,并且参数都是同个对象的,如何快速得到这个对象? 答:直接在方法中声明这个对象,SpringMvc就自动会把属性赋值到这个对象里面。 18、SpringMvc中函数的返回值是什么? 答:返回值可以有很多类型,有String,ModelAndView。ModelAndView类把视图和数据都合并的一起的,但一般用String比较好。 19、SpringMvc用什么对象从后台向前台传递数据的? 答:通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前端就可以通过el表达式拿到。 20、怎么样把ModelMap里面的数据放入Session里面? 答:可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。 ———————————————— 原文链接:https://blog.csdn.net/a745233700/article/details/80963758
上滑加载中
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签