-
ConcurrentHashMap融合了hashtable和hashmap二者的优势。hashtable是做了同步的,hashmap未考虑同步。所以hashmap在单线程情况下效率较高。hashtable在的多线程情况下,同步操作能保证程序执行的正确性。但是hashtable每次同步执行的时候都要锁住整个结构。看下图:图左侧清晰的标注出来,lock每次都要锁住整个结构。ConcurrentHashMap正是为了解决这个问题而诞生的。ConcurrentHashMap锁的方式是稍微细粒度的。 ConcurrentHashMap将hash表分为16个桶(默认值),诸如get,put,remove等常用操作只锁当前需要用到的桶。试想,原来 只能一个线程进入,现在却能同时16个写线程进入(写线程才需要锁定,而读线程几乎不受限制,之后会提到),并发性的提升是显而易见的。更令人惊讶的是ConcurrentHashMap的读取并发,因为在读取的大多数时候都没有用到锁定,所以读取操作几乎是完全的并发操作,而写操作锁定的粒度又非常细,比起之前又更加快速(这一点在桶更多时表现得更明显些)。只有在求size等操作时才需要锁定整个表。而在迭代时,ConcurrentHashMap使用了不同于传统集合的快速失败迭代器的另一种迭代方式,我们称为弱一致迭代器。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出 ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数 据,iterator完成后再将头指针替换为新的数据,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变,更重要的,这保证了多个线程并发执行的连续性和扩展性,是性能提升的关键。下面分析ConcurrentHashMap的源码。主要是分析其中的Segment。因为操作基本上都是在Segment上的。先看Segment内部数据的定义。从上图可以看出,很重要的一个是table变量。是一个HashEntry的数组。Segment就是把数据存放在这个数组中的。除了这个量,还有诸如loadfactor、modcount等变量。看segment的get 函数的实现:加上hashentry的代码:可以看出,hashentry是一个链表型的数据结构。在segment的get函数中,通过getFirst函数得到第一个值,然后就是通过这个值的next,一路找到想要的那个对象。如果不空,则返回。如果为空,则可能是其他线程正在修改节点。比如上面说的弱一致迭代器在将指针更改为新值的过程。而之前的 get操作都未进行锁定,根据bernstein条件,读后写或写后读都会引起数据的不一致,所以这里要对这个e重新上锁再读一遍,以保证得到的是正确值。readValueUnderLock中就是用了lock()进行加锁。put操作已开始就锁住了整个segment。这是因为修改操作时不能并发的。同样,remove操作也是如此(类似put,一开始就锁住真个segment)。但要注意一点区别,中间那个for循环是做什么用的呢?(截图未完全,可以自己找找代码查看一下)。从代码来看,就是将定位之后的所有entry克隆并拼回前面去,但有必要吗?每次删除一个元素就要将那之前的元素克隆一遍?这点其实是由entry的不变性来决定的,仔细观察entry定义,发现除了value,其他 所有属性都是用final来修饰的,这意味着在第一次设置了next域之后便不能再改变它,取而代之的是将它之前的节点全都克隆一次。至于entry为什么要设置为不变性,这跟不变性的访问不需要同步从而节省时间有关。原文链接:https://blog.csdn.net/csdn_ds/article/details/72528283
-
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。 statck:堆栈类,先进后出 hashtable:就比hashmap多了个线程安全 Collections的synchronizedXxxx()方法包装的集合 ConcurrentXxxx:从jdk1.5提供,通过分段锁实现线程安全,使用这类方法既可以不怎么影响效率,又可以保证安全,建议使用。 具体原理可参照:http://blog.csdn.net/csdn_ds/article/details/72528283 除了这些之外,其他的都是非线程安全的类和接口。 线程安全的类其方法是同步的,每次只能一个访问。是重量级对象,效率较低,一般用的不多,只有在特殊情况下会考虑使用。 1、Vector、ArrayList、LinkList之间的区别 Vector : 基于Array的List,其实就是封装了Array所不具备的一些功能方便我们使用,它不可能走出Array的限制。性能也就不可能超越Array。所以,在可能的情况下,我们要多运用Array。另外很重要的一点就是Vector“synchronized”的,这个也是Vector和ArrayList的唯一的区别。 ArrayList:同Vector一样是一个基于数组实现的,但是不同的是ArrayList不是同步的。所以在性能上要比Vector优越一些,但是当运行到多线程环境中时,可需要自己在管理线程的同步问题。 LinkedList:LinkedList不同于前面两种List,它不是基于Array的,所以不受Array性能的限制。它每一个节点(Node)都包含两方面的内容:1.节点本身的数据(data);2.下一个节点的信息(nextNode)。所以当对LinkedList做添加,删除动作的时候就不用像基于Array的List一样,必须进行大量的数据移动。只要更改nextNode的相关信息就可以实现了。这就是LinkedList的优势。 2、HashTable跟HashMap的区别 HashTable是线程安全的,即HashTable的方法都提供了同步机制;HashMap不是线程安全的,即不提供同步机制 ;HashTable不允许插入空值,HashMap允许! 3、StringBuffer和StringBuilder的区别 StringBuffer是线程安全的,StringBuilder是线程不安全的。 题外:此处记录一下java中8中基本的数据类型: byte、int、short、long、float、double、boolean、char ———————————————— 原文链接:https://blog.csdn.net/csdn_ds/article/details/72528300
-
我用思维导图对JVM的内存结构做简单的划分,如下图所示:下面我们对各个区进行说明。堆:也称heap堆区。堆是jvm内存中占用空间最大的一个区域。主要分为新生代、老年代、永久代(jdk1.8以后叫元空间,到1.9以后又被移除)新生代:在new一个对象时,会把堆新生代的内存空间进行判断,如果内存空间够则放入新生代(如果是大对象,例如数据很多的容器对象,有可能直接放入老年代)。如果内存空间不够放入该对象,则触发young gc,如果触发15次新生代空间还不够则把之前使用的数据迁移到老年代并释放新生代所有的空间。如果老年代的空间不够用,则进行full gc。如果老年代内存也不够用则抛出OOM并结束线程。gc的种类:minor GC(又叫young GC):用于收集年轻代中的非存活对象,在新生代空间不足时触发,young gc 可能会触发线程暂停。所以在一些并发比较高或者集中处理一些行为中java会出现卡顿的现象就是出于这个原因major gc:在老年代空间不足时触发。full gc的效率比较低,应尽量减少full gc的发生。目前只有CMS收集器有单独收集老年代的行为mixed GC(混合收集):主要收集年轻代和部分老年代的非存活对象。目前G1收集器采用这种行为。full gc:主要收集整个堆(新生代和老年代)中的非存活对象。System.gc()调用时候会触发full gc 。我们可以通过-XX:DisableExplicitGC来禁止System.gc()的行为。在多次young gc后把新生代存活的对象迁移到老年代(在一定条件下才会触发。更加详细的信息可参阅《hotspot实战》由于篇幅有限不在这里详述),如果老年代空间不足时会触发full gc。full gc后老年代内存还是不够用则OOM我们看下新生代的内存分配,如下图:对象的分配过程:新生成的对象在年轻代Eden区中分配内存,当Eden空间已满时,触发Minor GC,将不再被其他对象所引用的对象进行回收,存活下来的对象被转移到Survivor0区。Survivor0区满后触发Minor GC,将Survivor0区存活下来的对象转移到Survivor1区,同时,清空Survivor0区,保证总有一个Survivor区为空。经过多次Minor GC后,仍然存活的对象被转移到老年代,进入老年代的Minor GC次数可以通过参数-XX:MaxTenuringThreshold=<N>进行设置,默认为15次。当老年代已满时会触发Major GC(即:Full GC,因此执行Major GC时会先执行Minor GC)。分代收集的原因:将对象按照存活概率进行分类,主要是为了减少扫描范围和执行GC的频率,同时,对不同区域采用不同的回收算法,提高回收效率。年轻代中存在两块相同大小的Survivor区的原因:解决内存碎片化,即:保证分配对象(如:大对象)时有足够的连续内存空间。对象进入老年代的触发条件:对象的年龄达到15岁时。默认的情况下,对象经过15次Minor GC后会被转移到老年代中。对象进入老年代的Minor GC次数可以通过JVM参数:-XX:MaxTenuringThreshold进行设置,默认为15次。动态年龄判断。当一批存活对象的总大小超过Survivor区内存大小的50%时,按照年龄的大小(年龄大的存活对象优先转移)将部分存活对象转移到老年代中。大对象直接进入老年代 。当需要创建一个大于年轻代剩余空间的对象(如:一个超大数组)时,该对象会被直接存放到老年代中,可以通过参数-XX:PretenureSizeThreshold(默认值是0,即:任何对象都会先在年轻代分配内存)进行设置。Minor GC后的存活对象太多无法放入Survivor区时, 会将这些对象直接转移到老年代中。字符串常量池:字符串常量池是Java中的一个特殊的存储区域,用于存储字符串常量。在Java中,字符串常量是不可变的,因此可以被共享。这样可以减少内存的使用,提高程序的性能。在JDK8中,字符串常量池存储在堆中。静态变量:静态变量是指在类中定义的变量,它们的值在整个程序运行期间都不会改变。在JDK8中取消了永久代,方法区变成了一个逻辑上的区域,因此,静态变量的内存在堆中进行分配(JDK7及以前,静态变量的内存在永久代中进行分配)。它们的生命周期与类的生命周期相同。线程本地缓冲区:tlabTLAB(Thread Local Allocation Buffer)是Java虚拟机中的一个优化技术,主要用于提高对象的分配效率。每个线程都有自己的TLAB,用于分配对象。当一个线程需要分配对象时,它会先在自己的TLAB中分配,如果TLAB中的空间不足,则会向堆中申请空间。上面对内存的堆区进行了阐述。由于不同的jdk版本处理内存的方式不一样,会有些出入敬请谅解
-
想要对java虚拟机更深入的了解,可以查看《HotSpot实战》。需要电子版的请扫我头像关注我的个人号,发送000006领取电子书我们知道java程序是把java源文件编译成字节码.class文件,然后交给JVM执行。那么java到底是解释执行还是编译执行的语言呢?这个没有固定的答案,具体要要看用什么样的JVM。JVM把class文件编译成机器码执行那就是编译执行,如果JVM对class加载后由JVM解释执行就是解释执行。有的JVM即有yo解释执行也有编译执行。JVM的种类:hotspot jvm:这是最常用的JVM实现,由Oracle开发。HotSpot JVM提供了高效的执行引擎和垃圾回收机制,支持多种垃圾回收算法,如Parallel GC、CMS GC、G1 GC等。它广泛应用于服务器和桌面应用程序中openJ9 jvm:由IBM开发,专注于高性能和低内存消耗。OpenJ9 JVM在性能和资源利用方面表现出色,适用于需要高性能和资源优化的应用场景graalm: 由Oracle开发,是一个通用虚拟机,支持多种编程语言。GraalVM不仅支持Java,还支持其他语言如JavaScript、Python等,适用于需要多语言支持和高性能的应用zing jvm: 由Azul Systems开发,专注于低延迟和高吞吐量。Zing JVM在金融交易等对延迟要求极高的场景中表现出色dalvik jvm: 用于Android平台,是Android特有的JVM变体。Dalvik JVM优化了移动设备的资源利用,适用于Android应用程序的运行我们后面重点讲hotspot jvm也是应用最广泛的hotspot源码下载:cid:link_0犹豫篇幅限制,关于源码编译这里不再熬述。1 java虚拟机与程序的生命周期: 1.1 执行了System.exit()方法 1.2 程序正常执行结束 1.3 程序在执行过程中遇到了异常或错误异常中止(一般是主线程的异常中止) 1.4 由于操作系统的错误而导致java虚拟机进程的中止2 类加载,链接,初始化 2.1 加载:查找并加载类的二进制数据JVM规范规定类加载器在预料类将要被使用时预先加载它,有个预热的过程。类加载器在程序主动使用某一个类时才报告错误。加载完成以后进入到连接阶段 2.2 连接将已经读入到内存的二进制数据合并到虚拟机的运行环境中,然后进行一系列的验证,确保被加载类的正确性。 2.3 准备阶段在该阶段,java虚拟机为类的静态变量分配内存,并设置默认的初始值 2.4 解析该阶段java虚拟机会把类的二进制数据中的符号引用替换为直接引用 2.5 初始化为类的静态成员变量赋予正确的初始值3 java程序对类的使用主要分为两种 3.1 主动使用创建类的实例。例如:new Test()访问某个类或接口的静态变量调用类的静态方法反射初始化一个类的子类虚拟机启动时被表明为启动的类 3.2 被动使用除掉以上的情况属于被动使用,不会导致类的初始化。虚拟机实现必须在每个类或接口被java程序首次使用时才初始化。类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其方法存进运行时数据区的方法区内。然后在堆区创建一个Java.lang.Class对象,用来封装在类在方法区内的数据结构。
-
com.amazonaws.SdkClientException: Unable to execute HTTP request: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target https上传到obs报这个错,是因为我服务端没有安装obs的https证书吗
-
Java中的设计模式是一种被广泛应用于软件开发的解决方案,旨在解决常见的软件设计问题。设计模式分为三大类:创建型模式、结构型模式和行为型模式。以下是一些常见的设计模式及其原理: 1. 创建型模式 单例模式(Singleton Pattern) 原理:单例模式确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这通常用于需要全局唯一实例的场景,比如配置管理、数据库连接池等。 实现方式: 私有构造函数:防止外部直接实例化。 静态实例:类中持有一个私有的静态实例。 公共静态方法:提供一个全局访问点来获取实例,并确保线程安全(使用 synchronized 或双重检查锁定等技术)。 示例代码: public class Singleton { private static Singleton instance; private Singleton() { // 私有构造函数,防止外部实例化 } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); // 创建实例 } return instance; } } 工厂方法模式(Factory Method Pattern) 原理:工厂方法模式定义一个用于创建对象的接口,但由子类决定实例化哪个类。工厂方法模式将对象的创建推迟到子类,从而实现了创建和使用的解耦。 实现方式: 抽象工厂类:定义一个工厂方法,返回抽象产品。 具体工厂类:实现工厂方法,创建具体产品。 抽象产品类:定义产品的公共接口。 具体产品类:实现产品接口。 示例代码: // 抽象产品 interface Product { void use(); } // 具体产品 class ConcreteProductA implements Product { public void use() { System.out.println("Using Product A"); } } class ConcreteProductB implements Product { public void use() { System.out.println("Using Product B"); } } // 抽象工厂 abstract class Creator { public abstract Product factoryMethod(); } // 具体工厂 class ConcreteCreatorA extends Creator { public Product factoryMethod() { return new ConcreteProductA(); } } class ConcreteCreatorB extends Creator { public Product factoryMethod() { return new ConcreteProductB(); } } 抽象工厂模式(Abstract Factory Pattern) 原理:抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。它创建多个产品对象,每个产品属于一个产品族。 实现方式: 抽象工厂:定义创建产品对象的接口。 具体工厂:实现抽象工厂接口,生成具体的产品。 抽象产品:定义产品的接口。 具体产品:实现抽象产品接口。 示例代码: // 抽象产品A interface AbstractProductA { void operationA(); } // 具体产品A1 class ProductA1 implements AbstractProductA { public void operationA() { System.out.println("Operation A1"); } } // 具体产品A2 class ProductA2 implements AbstractProductA { public void operationA() { System.out.println("Operation A2"); } } // 抽象工厂 interface AbstractFactory { AbstractProductA createProductA(); } // 具体工厂1 class ConcreteFactory1 implements AbstractFactory { public AbstractProductA createProductA() { return new ProductA1(); } } // 具体工厂2 class ConcreteFactory2 implements AbstractFactory { public AbstractProductA createProductA() { return new ProductA2(); } } 2. 结构型模式 适配器模式(Adapter Pattern) 原理:适配器模式将一个类的接口转换成客户端所期望的另一种接口,使得原本因接口不匹配而不能一起工作的类可以一起工作。它通过包装一个对象,使其符合目标接口。 实现方式: 目标接口:客户端期望的接口。 适配者类:需要适配的类,具有目标接口所不具备的接口。 适配器类:实现目标接口,并将请求转发到适配者类。 示例代码: // 目标接口 interface Target { void request(); } // 适配者类 class Adaptee { public void specificRequest() { System.out.println("Specific request"); } } // 适配器类 class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } public void request() { adaptee.specificRequest(); } } 装饰者模式(Decorator Pattern) 原理:装饰者模式动态地给一个对象添加一些额外的职责。它比生成子类更灵活,允许在运行时为对象添加功能。 实现方式: 组件接口:定义对象的接口。 具体组件:实现组件接口的类。 装饰者抽象类:持有一个组件对象,并实现组件接口。 具体装饰者:扩展装饰者抽象类,为对象添加新功能。 示例代码: // 组件接口 interface Component { void operation(); } // 具体组件 class ConcreteComponent implements Component { public void operation() { System.out.println("Concrete Component Operation"); } } // 装饰者抽象类 abstract class Decorator implements Component { protected Component component; public Decorator(Component component) { this.component = component; } public void operation() { component.operation(); } } // 具体装饰者 class ConcreteDecorator extends Decorator { public ConcreteDecorator(Component component) { super(component); } public void operation() { super.operation(); addedBehavior(); } private void addedBehavior() { System.out.println("Added Behavior"); } } 代理模式(Proxy Pattern) 原理:代理模式为其他对象提供一种代理以控制对这个对象的访问。代理对象可以在客户端和真实对象之间起到中介作用,可以增加额外的功能或控制访问权限。 实现方式: 主题接口:定义真实对象和代理对象的共同接口。 真实主题:实现主题接口,定义实际的业务逻辑。 代理对象:实现主题接口,控制对真实主题的访问。 示例代码: // 主题接口 interface Subject { void request(); } // 真实主题 class RealSubject implements Subject { public void request() { System.out.println("Real Subject Request"); } } // 代理对象 class Proxy implements Subject { private RealSubject realSubject; public void request() { if (realSubject == null) { realSubject = new RealSubject(); } realSubject.request(); } } 3. 行为型模式 策略模式(Strategy Pattern) 原理:策略模式定义一系列算法,将每一个算法封装起来,并使它们可以互换。策略模式让算法独立于使用它的客户端独立变化。 实现方式: 策略接口:定义所有支持的算法的共同接口。 具体策略:实现策略接口的具体算法。 上下文:使用策略对象,调用策略的方法。 示例代码: // 策略接口 interface Strategy { void execute(); } // 具体策略A class ConcreteStrategyA implements Strategy { public void execute() { System.out.println("Strategy A"); } } // 具体策略B class ConcreteStrategyB implements Strategy { public void execute() { System.out.println("Strategy B"); } } // 上下文 class Context { private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public void executeStrategy() { strategy.execute(); } } **观察者模式(Observer Pattern ———————————————— 原文链接:https://blog.csdn.net/dataiyangu/article/details/140572071
-
目录 一、单例模式 二、工厂模式 三、代理模式 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
上滑加载中
推荐直播
-
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步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签