从0到1开始学习,一点点的进大厂。现在我们来学设计模式
反复使用、多人知晓、经过分类编目、代码设计经验的总结。在程序员世界中,本没有设计模式,写代码的人多了,他们便总结出来一套能够提高开发和维护效率的套路。
好处:
1) 提升自己应对面试
2)不再编写bullshit-code
3)提升复杂逻辑的代码的设计和开发能力
4)有助于学习源码,学习框架事半功倍
提供创建对象的机制,提升已有代码的灵活性和可复用性。
单例是最简单的设计模式之一,此模式保证某个类在运行期间,只有一个实例对外提供服务。使用单例要保证,一个类只有一个实例,并为该类实例提供一个全局访问点。
特点:
线程不安全
public class Singleton2 {
private static Singleton2 singleton2;
private Singleton2() { }
public static Singleton2 getInstance() {
singleton2 = new Singleton2();
return singleton2;
}
}
特点:
使用synchronized锁,锁住创建爱你单例的方法,防止多个线程同时调用
但是,因为我们加了synchronized锁,导致我们的这个函数,并发度非常低。
public class Singleton4 {
private static Singleton4 singleton4;
private Singleton4() {}
public static synchronized Singleton4 getInstance() {
if (singleton4 != null) {
singleton4 = new Singleton4();
}
return singleton4;
}
}
保证可见性,并且可以实现指令重排
public class Singleton3 {
private static volatile Singleton3 singleton3;
private Singleton3() {}
public static Singleton3 getInstance() {
if (singleton3 != null) {
synchronized (Singleton3.class) {
if (singleton3 != null) {
singleton3 = new Singleton3();
}
}
}
return singleton3;
}
}
特点 :
线程安全的,因为在类加载期间初始化私有的静态实例,保证instance实例创建过程中是线程安全的。
不支持延时加载,获取实例速度比较快,但是如果对象比较大,而且一直没有使用的时候,会造成资源浪费
public class Singleton1 {
private static Singleton1 SINGLETON_1 = new Singleton1();
private Singleton1() {}
public static Singleton1 getInstance() {
return SINGLETON_1;
}
}
特点 :
支持懒加载,在静态内部类中创建单例,在装载内部类的时候,才会创建单例对象
public class Singleton5 {
private Singleton5() {}
private static class Singleton05Handler{
private static Singleton5 singleton5 = new Singleton5();
}
public static Singleton5 getInstance(){
return Singleton05Handler.singleton5;
}
}
public class DestroySingleton {
public static void main(String[] args) throws Exception {
Class<Singleton5> singleton5Class = Singleton5.class;
Constructor<Singleton5> singleton5Constructor = singleton5Class.getDeclaredConstructor();
//暴利反射
singleton5Constructor.setAccessible(true);
Singleton5 singleton5 = singleton5Constructor.newInstance();
System.out.println(singleton5 == Singleton5.getInstance());
}
}
结果:false
那么我们怎么优化呢?
public class Singleton5 {
private Singleton5() {
if (Singleton05Handler.singleton5 != null){
throw new RuntimeException("不允许反射");
}
}
private static class Singleton05Handler {
private static Singleton5 singleton5 = new Singleton5();
}
public static Singleton5 getInstance() {
return Singleton05Handler.singleton5;
}
}
最终结果:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at com.plan.dream.pattern.singleton.DestroySingleton.main(DestroySingleton.java:21)
Caused by: java.lang.RuntimeException: 不允许反射
at com.plan.dream.pattern.singleton.Singleton5.<init>(Singleton5.java:16)
... 5 more
序列化与反序列化也能破坏我们的单例
public class SerializableSingleton {
public static void main(String[] args) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("temp.obj"));
objectOutputStream.writeObject(Singleton5.getInstance());
File file = new File("temp.obj");
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
Singleton5 singleton5 = (Singleton5) objectInputStream.readObject();
System.out.println(singleton5 == Singleton5.getInstance());
}
}
结果 false
这个我们怎么解决呢?
public class Singleton5 implements Serializable {
private Singleton5() {
if (Singleton05Handler.singleton5 != null){
throw new RuntimeException("不允许反射");
}
}
private static class Singleton05Handler {
private static Singleton5 singleton5 = new Singleton5();
}
public static Singleton5 getInstance() {
return Singleton05Handler.singleton5;
}
private Object readResolve(){
return Singleton05Handler.singleton5;
}
}
**再执行上一个序列化-反序列化的方法**
结果:true
为什么反序列化就能破坏我们的单例呢?为什么加了一个方法就解决了呢?
主要是这个方法导致了我们反序列化破坏了单例。那么我们一层层去看。
这里我们追到了这个方法,这个方法就是反序列化我们的Obj,我们点进去看看仔细看看这个方法。
仔细看了这个方法后,我们能看到这个switch,如果是对象,就调用checkResolve方法,那么我们继续点readOrdinaryObject进去看一下。
第一个框,三目运算 表达的含义就是:判断一个实现序列化的接口的类,可以在运行时,被实例化,就返回true。如果为true,就通过反射调用无参构造,创建一个新对象。
第二个框就是判断,如果实现了序列化接口的类中如果有readResolve方法,就返回true。
第三个框通过反射的方式,调用被反序列化的类的readResolve方法。
特点:
阻止反射对单例的破坏,在反射方法中不允许使用反射来创建枚举对象
阻止序列化对单例的破坏,在序列化中,只是将枚举对象的name属性输出到了结果中,返序列化的时候,就会通过Enum的valueOf方法,来根据名字去查找对应的枚举对象,因此反序列化后,他们的对象是同一个。
public enum SingletonEnum {
/**
* 注释
*/
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static SingletonEnum getInstance() {
return INSTANCE;
}
}
定义一个工程类,根据传入的参数不同返回不同的实力,被创建的实例具有共同的父类或接口(多态)。
1)需要创建的对象比较少
2)客户端不关心对象的创建
1)抽象产品:定义产品规范,描述了产品的特性和功能
2)具体产品:实现或集成抽象产品的子类
3)具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品
场景:我要给我们的会员提供奖品,分别是京东卡和微信红包,下面就是简单工厂的实现。
好处:
1)封装了创建对象的过程,可以通过参数直接获取对象,把对象的创建和业务逻辑分开,避免了可能会修改客户端代码的问题。
2)如果要实现新产品,直接修改工厂类,不需要再在源码中进行修改,降低了客户端修改代码的可能性,更加容易扩展。
缺点:
再增加新产品的时候,还是需要修改工厂的代码。违背了开闭原则。
概念:
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法是一个产品类的实力化延迟到其工厂 的子类。
工厂方法就是封装对象的创建过程,提升创建对象方法的可复用性。
工厂方法中的主要角色:
抽象工厂:提供创建产品的接口,调用者通过它访问具体工厂方法来创建产品。
具体工厂:主要实现抽象工厂中的抽象方法,完成具体产品的创建。
抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
具体产品: 实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是一一对应的。
因为需要controller去调用,调用的时候,也要关心像简单工厂那样,要按输入类型分别创建对象。那么我又做了一个优化:
优点:
用户只需要知道具体工厂,就可以获取到想要的产品,而不需要关注产品创建的过程。
在系统新增产品时,只需要添加具体的产品和对应的工厂就可以了,而不需要对原工厂进行修改
缺点:
增加系统的复杂度
抽象工厂模式比工厂方法模式的抽象程度更高,在工厂方法模式中每一个具体的工厂只需生产一种具体的产品,但是抽象工厂模式中一个具体工厂可以生产一组相关的具体产品,这样一组产品被称为产品族。产品族中的每一个产品,都分属于某一个产品继承等级结构。抽象工厂模式提供一个,创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。抽象工厂模式中的具体工厂,不只创建一种产品,而是负责创建一个产品族。
产品等级结构即继承结构,如一个抽象类是手机,其子类有苹果手机、华为手机,则抽象手机与具体品牌的手机之间,构成了一个产品等级结构。如手机是父类,而具体品牌的手机是子类。
在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如苹果手机,苹果笔记本。苹果手机位于手机产品等级结构中,苹果笔记本位于笔记本产品等级结构中。
产品等级结构:手机
public interface AbstractPhone {
}
public class ApplePhone implements AbstractPhone {
}
public class HwPhone implements AbstractPhone {
}
产品等级结构:笔记本
public interface AbstractNoteBook {
}
public class AppleNoteBook implements AbstractNoteBook {
}
public class HwNoteBook implements AbstractNoteBook {
}
抽象工厂:
public interface ElectronicsAbstractFactory {
/**
* 生产phone
*
* @param
* @return com.plan.dream.pattern.abstract_factory.product.AbstractPhone
* @throws
* @method createPhone
* @author Rocky Qian
* @version 1.0
* @date 2023/10/27 12:24
*/
AbstractPhone createPhone();
/**
* 生产notebook
*
* @param
* @return com.plan.dream.pattern.abstract_factory.product.AbstractNoteBook
* @throws
* @method createNoteBook
* @author Rocky Qian
* @version 1.0
* @date 2023/10/27 12:25
*/
AbstractNoteBook createNoteBook();
}
工程厂实现类
public class AppleElectronicsFactory implements ElectronicsAbstractFactory {
@Override
public AbstractPhone createPhone() {
return new ApplePhone();
}
@Override
public AbstractNoteBook createNoteBook() {
return new AppleNoteBook();
}
}
工厂实现类
public class HwElectronicsFactory implements ElectronicsAbstractFactory {
@Override
public AbstractPhone createPhone() {
return new HwPhone();
}
@Override
public AbstractNoteBook createNoteBook() {
return new HwNoteBook();
}
}
调用方
public class Client {
private AbstractPhone phone;
private AbstractNoteBook noteBook;
public Client(ElectronicsAbstractFactory factory) {
this.phone = factory.createPhone();
this.noteBook = factory.createNoteBook();
}
public AbstractPhone getPhone() {
return phone;
}
public void setPhone(AbstractPhone phone) {
this.phone = phone;
}
public AbstractNoteBook getNoteBook() {
return noteBook;
}
public void setNoteBook(AbstractNoteBook noteBook) {
this.noteBook = noteBook;
}
public static void main(String[] args) {
Client client = new Client(new AppleElectronicsFactory());
final AbstractPhone phone = client.getPhone();
final AbstractNoteBook noteBook = client.getNoteBook();
System.out.println(phone);
System.out.println(noteBook);
}
}
输出结果:
com.plan.dream.pattern.abstract_factory.product.impl.ApplePhone@f39991
com.plan.dream.pattern.abstract_factory.product.impl.AppleNoteBook@12b3a41
软件使用者,更关心共性功能,软件创建者,需要找到共性功能,并且隐藏细节。
优点:
对于不同产品系列有比较多共性特征时,可以使用抽象工厂模式,有助于提升组件的复用性。
当需要提升代码的扩展性,并降低维护成本时,把对象的创建和使用过程分开,能有效地将代码统一到一个级别上
解决跨平台带来的兼容性问题
缺点:
增加新的产品等级结构会增大难度,需要对原有结构进行较大的修改,违背了开闭原则。
也被称为生成器模式,就是将一个复杂对象的构建与表示分离,使得同样的构建过程,可以创建不同的表示。
建造者模式要解决什么问题呢?
建造者模式可以将部件和其组装过程分开,一步步创建一个复杂的对象,用户只需要指定复杂对象的类型,就可以得到该对象。比如:一台电脑,由CPU、内存、主板、显卡、电源、磁盘等组成,对于大多数用户而言,并不需要知道这些部件的装配细节,并且几乎不会使用单独某个部件,而是使用一台完整的电脑。而建造者模式就是负责将这些部件进行组装,使其变成一台完整的电脑,供用户使用。
都有哪些角色呢?
抽象建造者(Builder):声明了所有类型生成器,通用的创建对象的步骤。
具体建造者(ConcreteBuilder):实现了具体的构造过程
产品(Product):最终由具体建造者生成的对象
指挥者(Director):负责安排复杂对象的构造顺序
抽象建造者
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.builder.service
* @ClassName: Builder
* @author: Rocky Qian
* @description: 构造者类
* @date: 2023/10/27 14:12
* @version: 1.0
*/
public abstract class Builder {
/**
* 让具体构建者构建
*/
protected Computer computer = new Computer();
/**
* 建造cpu
*
* @param
* @return void
* @throws
* @method buildCpu
* @author Rocky Qian
* @version 1.0
* @date 2023/10/27 14:15
*/
public abstract void buildCpu();
/**
* 建造主板
*
* @param
* @return void
* @throws
* @method builderMotherBoard
* @author Rocky Qian
* @version 1.0
* @date 2023/10/27 14:15
*/
public abstract void builderMotherBoard();
/**
* 生产电脑
*
* @param
* @return com.plan.dream.pattern.builder.service.entity.Computer
* @throws
* @method createComputer
* @author Rocky Qian
* @version 1.0
* @date 2023/10/27 14:15
*/
public abstract Computer createComputer();
}
具体建造者1
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.builder.service.impl
* @ClassName: AppleBuilder
* @author: Rocky Qian
* @description: 具体构造者
* @date: 2023/10/27 14:16
* @version: 1.0
*/
public class AppleBuilder extends Builder {
@Override
public void buildCpu() {
System.out.println("制作苹果Cpu");
computer.setCpu("苹果CPU");
}
@Override
public void builderMotherBoard() {
System.out.println("制作苹果主板");
computer.setMotherBoard("苹果主板");
}
@Override
public Computer createComputer() {
return computer;
}
具体建造者2
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.builder.service.impl
* @ClassName: LenovoBuilder
* @author: Rocky Qian
* @description: 具体构造者
* @date: 2023/10/27 14:17
* @version: 1.0
*/
public class LenovoBuilder extends Builder {
@Override
public void buildCpu() {
System.out.println("制作联想Cpu");
computer.setCpu("联想CPU");
}
@Override
public void builderMotherBoard() {
System.out.println("联想苹果主板");
computer.setMotherBoard("联想主板");
}
@Override
public Computer createComputer() {
return computer;
}
产品
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.builder.service.entity
* @ClassName: Computer
* @author: Rocky Qian
* @description: 产品
* @date: 2023/10/27 14:14
* @version: 1.0
*/
@Data
public class Computer {
private String cpu;
private String motherBoard;
}
指挥者
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.builder.service.director
* @ClassName: Director
* @author: Rocky Qian
* @description: 指挥者类
* @date: 2023/10/27 14:18
* @version: 1.0
*/
public class Director {
private final Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public Computer construct(){
builder.buildCpu();
builder.builderMotherBoard();
return builder.createComputer();
}
}
调用者
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.builder.service.client
* @ClassName: Client
* @author: Rocky Qian
* @description:
* @date: 2023/10/27 14:17
* @version: 1.0
*/
public class Client {
public static void main(String[] args) {
//创建一个指挥者,传参要具体的构造者
final Director director = new Director(new AppleBuilder());
//获取电脑
System.out.println(director.construct());
}
}
执行结果:
制作苹果Cpu
制作苹果主板
Computer(cpu=苹果CPU, motherBoard=苹果主板)
场景:
我们原来系统会生成一些待办事项,来督促用户进行一些操作。每次创建这个对象的时候,因为这个对象属性很多,而且还要嵌套一些业务(一些判断代码去掉了)。那么我们在新增这个对象的时候,就十分麻烦(我们用lombok的注解@Builder能搞定哈,现在我们是学习)。那么我们怎么能更优雅的使用建造者模式,来创建我们的对象呢?
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ToDoWorkVo {
@ApiModelProperty("主键")
private Long id;
@ApiModelProperty("类型")
private Integer type;
@ApiModelProperty("待办事项id")
private Long eventId;
@ApiModelProperty("广场id")
private Integer plazaId;
@ApiModelProperty("店铺id")
private Integer storeId;
@ApiModelProperty("绑定类型 1.店铺 2.角色 3.用户")
private Integer bindType = 1;
@ApiModelProperty("角色类型(bind_type=2时) 1.店长,2.区经,3.店员,4.财务")
private Integer roleType;
@ApiModelProperty("用户id(bind_type=3时)")
private Long userId;
@ApiModelProperty("待办备注")
private String content;
@ApiModelProperty("开始时间")
private String startDate;
@ApiModelProperty("创建时间")
private Date createTime;
}
使用步骤:
1)目标类的构造方法一定要传入一个Builder对象
2)builder类一定位于目标类的内部,并且使用static修饰
3)builder类对象一共内置各种set方法,并且set的返回值一定是builder本身
4)builder类提供一个build()方法,实现目标对象的创建。
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.builder.builder2
* @ClassName: TodoWorkBuilder
* @author: Rocky Qian
* @description:
* @date: 2023/10/27 15:58
* @version: 1.0
*/
public class TodoWorkBuilder {
private TodoWorkBuilder(Builder builder) {
}
public static class Builder {
private Long id;
private Integer type;
private Long eventId;
private Integer plazaId;
private Integer storeId;
private Integer bindType = 1;
private Integer roleType;
private Long userId;
private String content;
private String startDate;
private Date createTime;
public Builder setId(Long id) {
this.id = id;
return this;
}
public Builder setType(Integer type) {
this.type = type;
return this;
}
public Builder setEventId(Long eventId) {
this.eventId = eventId;
return this;
}
public Builder setPlazaId(Integer plazaId) {
this.plazaId = plazaId;
return this;
}
public Builder setStoreId(Integer storeId) {
this.storeId = storeId;
return this;
}
public Builder setBindType(Integer bindType) {
this.bindType = bindType;
return this;
}
public Builder setRoleType(Integer roleType) {
this.roleType = roleType;
return this;
}
public Builder setUserId(Long userId) {
this.userId = userId;
return this;
}
public Builder setContent(String content) {
this.content = content;
return this;
}
public Builder setStartDate(String startDate) {
this.startDate = startDate;
return this;
}
public Builder setCreateTime(Date createTime) {
this.createTime = createTime;
return this;
}
public TodoWorkBuilder build() {
return new TodoWorkBuilder(this);
}
}
public void send() {
System.out.println("构造完成");
}
}
调用
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.builder.builder2
* @ClassName: Client
* @author: Rocky Qian
* @description:
* @date: 2023/10/27 16:04
* @version: 1.0
*/
public class Client {
public static void main(String[] args) {
final TodoWorkBuilder build = new TodoWorkBuilder.Builder().setId(1L).setType(1).
setEventId(2L).setPlazaId(1231).setStoreId(321).
setBindType(1).setRoleType(32).setUserId(23L).
setCreateTime(new Date()).setContent("321312312").build();
build.send();
}
}
结果:
构造完成
工厂模式是用来创建不用但是通关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
建造者模式是来创建一种类型的复杂对象,通过设置不同的可选参数,定制化地创建不同的对象。
优点:
1)建造者模式封装性很好,使用建造者模式可以有效地封装变化,在使用建造者模式场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中,对整理而言可以获得比较高的稳定性。
2)在建造者模式中,客户端可以不用知道产品的内部组成细节,将产品本身和产品创建过程进行解耦,使得相同的创建过程可以创建不同的产品对象。
3)可以更加精细地控制产品的创建过程,将复杂产品的创建步骤,分别在不同的方法中,使得创建过程更加清晰,也更方便使程序来控制创建过程。
4)创建者模式也很容易进行扩展,如果有新需求,通过实现一个新的建造者就可以完成,基本上不用修改,之前已经测试通过的代码,因此也不会对原有功能引入风险,符合开闭原则。
缺点:
建造者模式,所创建的产品一般具有很多相同点,其组成部分相似,如果产品之间产品差异性比较大,则不适用建造者模式,因此适用范围有一定的限制。
建造者模式创建的是复杂对象,其产品的各个部分经常面临剧烈的变化,但将他们组合在一起的算法却相对稳定。所以在以下场景是适用的:
1)创建的对象比较复杂,由多个部件构成,各部件面临着复杂的变化,但部件之间的建造顺序是稳定的;
2)创建复杂对象的算法独立于该对象的组成部分以及他们的装配方式,即产品的构建过程和最终表示是独立的。
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
原型模式主要解决什么问题呢?
如果创建对象的成本比较高,比如对象中的数据是经过复杂的计算得到的,或者需要从RPC接口或者数据库等比较慢的IO中获取到的。这种情况我们就可以用原型模式。从其他已有的对象中,进行拷贝。而不是每次都创建新对象,进行一些耗时的操作。
角色:
抽象原型类:主要是有clone抽象方法,这个方法返回的是自己本身。这个原型类可以是接口
具体原型类:实现了抽象类型类的视线方法, 返回了自己的原型类
客户端:调用者
克隆对象中所有变量的值与原型对象的值完全相同,包括基本数据类型和引用数据类型,引用数据类型变量存储的地址也是完全一样。
具体原型类:
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.prototype
* @ClassName: PrototypeClass
* @author: Rocky Qian
* @description: 原型类
* @date: 2023/10/27 18:01
* @version: 1.0
*/
public class ConcretePrototype implements Cloneable {
public ConcretePrototype(){
System.out.println("具体的原型对象创建成功了");
}
/**
* clone 众所周知,object的clone方法是浅克隆,我们现在直接用就好了
*
* @param
* @return com.plan.dream.pattern.prototype.shallow.ConcretePrototype
* @throws
* @method clone
* @author Rocky Qian
* @version 1.0
* @date 2023/10/27 18:21
*/
@Override
protected ConcretePrototype clone() throws CloneNotSupportedException {
System.out.println("克隆对象复制成功");
return (ConcretePrototype) super.clone();
}
}
测试结果:
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.prototype.shallow
* @ClassName: TestConcretePrototype
* @author: Rocky Qian
* @description:
* @date: 2023/10/27 18:17
* @version: 1.0
*/
public class TestConcretePrototype {
public static void main(String[] args) throws CloneNotSupportedException {
//创建一个新对象
final ConcretePrototype c1 = new ConcretePrototype();
//新对象clone出另外一个对象
final ConcretePrototype c2 = c1.clone();
//查看两个对象的地址值是否一致
System.out.println(c1 == c2);
}
}
输出结果:
具体的原型对象创建成功了
克隆对象复制成功
false
浅克隆中,确实创建了一个新对象,但是构造方法只执行了一次。那么浅克隆肯定不是new操作,他的底层是用二进制的形式操作的。
克隆对象的所有基本类型变量含有的值与原型对象完全一致。但是引用数据类型不是,引用数据类型成员变量是创建了新的对象。
引用类型
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.prototype.deep
* @ClassName: Student
* @author: Rocky Qian
* @description: 引用类型
* @date: 2023/10/27 18:26
* @version: 1.0
*/
public class Student {
private int age;
private String name;
public Student() {
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
基本数据类型
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.prototype
* @ClassName: PrototypeClass
* @author: Rocky Qian
* @description: 原型类
* @date: 2023/10/27 18:01
* @version: 1.0
*/
public class ConcretePrototype implements Cloneable {
private Student student;
public ConcretePrototype() {
System.out.println("具体的原型对象创建成功了");
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public void getStudentInfo() {
System.out.println("学生姓名:" + student.getName() + " 学生年龄:" + student.getAge());
}
/**
* clone 众所周知,object的clone方法是浅克隆,我们现在直接用就好了
*
* @param
* @return com.plan.dream.pattern.prototype.shallow.ConcretePrototype
* @throws
* @method clone
* @author Rocky Qian
* @version 1.0
* @date 2023/10/27 18:21
*/
@Override
protected ConcretePrototype clone() throws CloneNotSupportedException {
System.out.println("克隆对象复制成功");
return (ConcretePrototype) super.clone();
}
}
新的浅克隆测试类
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.prototype.shallow
* @ClassName: TestConcretePrototype
* @author: Rocky Qian
* @description:
* @date: 2023/10/27 18:17
* @version: 1.0
*/
public class TestConcretePrototype {
public static void main(String[] args) throws CloneNotSupportedException {
//new 一个原型类
ConcretePrototype c1 = new ConcretePrototype();
//new 一个引用类
Student s1 = new Student(1, "小明");
//set 引用成员变量
c1.setStudent(s1);
//浅克隆
ConcretePrototype c2 = c1.clone();
//获取到浅克隆后的引用类
Student s2 = c2.getStudent();
//并把引用类赋值
s2.setAge(2);
s2.setName("小刚");
//分别把原型类和浅克隆后的类信息打印
c1.getStudentInfo();
c2.getStudentInfo();
//比较两个类中引用成员变量
System.out.println(s1 == s2);
}
}
输出结果:
具体的原型对象创建成功了
克隆对象复制成功
学生姓名:小刚 学生年龄:2
学生姓名:小刚 学生年龄:2
true
深克隆
/**
* @ProjectName: dream
* @Package: com.plan.dream.pattern.prototype.deep
* @ClassName: Test1ConcretePrototype
* @author: Rocky Qian
* @description:
* @date: 2023/10/27 19:26
* @version: 1.0
*/
public class Test1ConcretePrototype {
public static void main(String[] args) throws Exception {
//new 一个原型类 new 一个引用类型数据 set引用类型成员变量
ConcretePrototype c1 = new ConcretePrototype();
Student s1 = new Student(1, "小明");
c1.setStudent(s1);
//序列化原型类
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
oos.writeObject(c1);
oos.close();
//反序列化成新对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
ConcretePrototype c2 = (ConcretePrototype) ois.readObject();
ois.close();
//获取新对象中的引用成员变量
Student s2 = c2.getStudent();
//给引用成员变量赋新值
s2.setAge(2);
s2.setName("小刚");
//分别获取原型类和新类的成员变量信息
c1.getStudentInfo();
c2.getStudentInfo();
//比较两个成员变量是否为同一个对象
System.out.println(s1 == s2);
}
}
打印结果:
具体的原型对象创建成功了
学生姓名:小明 学生年龄:1
学生姓名:小刚 学生年龄:2
false
最终结果,两个student对象s1、s2,经过clone发现,是同一个对象,即使重新赋值,也是同一个对象,并且s1、s2的值也是一致的。
github消息系统会有发送邮件的功能,邮件内容都有一个模板,从数据库获取用户的信息,放到模板中生成一份完整的邮件。然后交给发送邮件接口。
@Data
public class Mail {
/**
* receiver:收件人
* subject:邮件标题
* appellation:称呼
* context:邮件内容
* tail:邮件结尾
*/
private String receiver;
private String subject;
private String appellation;
private String context;
private String tail;
public Mail (MsgTemplate msgTemplate) {
this.subject = msgTemplate.getMsgSubject();
this.context = msgTemplate.getMsgContext();
}
}
Template
public class MsgTemplate {
private final String msgSubject = "[GitHub] A first-party GitHub OAuth application has been added to your account";
private final String msgContext = "A first-party GitHub OAuth application (Git Credential Manager) with gist, repo";
public String getMsgSubject() {
return msgSubject;
}
public String getMsgContext() {
return msgContext;
}
}
发送邮件
public class Client {
private final static int MAX_SEND_NUM = 5;
public static void main(String[] args) {
int sendNum = 0;
Mail mail = new Mail(new MsgTemplate());
mail.setTail("Thanks,\n" +
"The GitHub Team");
//发送邮件
while (sendNum < MAX_SEND_NUM) {
mail.setAppellation("Hay");
mail.setReceiver(new Random().nextInt(99999999) + "@qq.com");
sendMsg(mail);
sendNum++;
}
}
/**
* 发送邮件
* @param mail
*/
public static void sendMsg(Mail mail) {
System.out.print("标题:" + mail.getSubject() + " 收件人:" + mail.getReceiver());
System.out.println(" ...发送成功");
}
}
以上是我们平时的写法,但是呢,因为我们要频繁创建Mail对象。
我们把Mail做一下优化,并把调用方进行优化。
新Mail
public class Mail implements Cloneable {
/**
* receiver:收件人
* subject:邮件标题
* appellation:称呼
* context:邮件内容
* tail:邮件结尾
*/
private String receiver;
private String subject;
private String appellation;
private String context;
private String tail;
public Mail setReceiver(String receiver) {
this.receiver = receiver;
return this;
}
public Mail setSubject(String subject) {
this.subject = subject;
return this;
}
public Mail setAppellation(String appellation) {
this.appellation = appellation;
return this;
}
public Mail setContext(String context) {
this.context = context;
return this;
}
public Mail setTail(String tail) {
this.tail = tail;
return this;
}
public String getReceiver() {
return receiver;
}
public String getSubject() {
return subject;
}
public String getAppellation() {
return appellation;
}
public String getContext() {
return context;
}
public String getTail() {
return tail;
}
@Override
protected Mail clone() throws CloneNotSupportedException {
return (Mail) super.clone();
}
public Mail(MsgTemplate msgTemplate) {
this.subject = msgTemplate.getMsgSubject();
this.context = msgTemplate.getMsgContext();
}
}
新client
public class Client {
private final static int MAX_SEND_NUM = 5;
public static void main(String[] args) throws CloneNotSupportedException {
int sendNum = 0;
Mail mail = new Mail(new MsgTemplate());
mail.setTail("Thanks,\n" +
"The GitHub Team");
//发送邮件
while (sendNum < MAX_SEND_NUM) {
//进行clone
Mail cloneMail = mail.clone();
cloneMail.setAppellation("Hay");
cloneMail.setReceiver(new Random().nextInt(99999999) + "@qq.com");
sendMsg(cloneMail);
sendNum++;
}
}
/**
* 发送邮件
* @param mail
*/
public static void sendMsg(Mail mail) {
System.out.print("标题:" + mail.getSubject() + " 收件人:" + mail.getReceiver());
System.out.println(" ...发送成功");
}
}
优点:
1)创建新的对象实例比较复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
2)原型模式提供了简化的创建结构。工厂方法模式常常需要一个与产品类等级结构相同的工厂等级接口。而原型模式不需要这样,原型模式的产品复制是通过封装在原型类中的克隆方法实现的,无需专门的工厂类来创建产品。
3)可以使用深克隆的方式保存对象状态。使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用。比如恢复到某一历史状态,可以辅助实现撤销操作。
缺点:
需要为每个类都装备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违反了开闭原则。
使用场景:
1)资源优化场景:
当对象初始化需要使用很多外部资源时,比如IO、数据文件、CPU、网络
2)复杂依赖场景:
A对象创建依赖B,B又依赖C,C又依赖D…所以创建过程是一连串的get和set。
3)性能和安全要求的场景
同一个用户在一个会话周期内,可能会反复登录平台或使用一些受限功能,每一次访问请求都会访问授权服务器进行授权,但如果每次都过new产生一个对象会非常繁琐。
4)同一个对象可能被多个修改者使用的场景
一个商品需要提供给订单、物流、会员等多个服务,而且各个调用者可能需要修改数据时、
5)需要保存原始对象状态的场景
记录历史操作的场景。
主要总结了一些类与对象组合在一起的经典结构,这些经典结构可以解决对象的特定场景问题。
由于一些原因,客户端并不想或不能直接访问一个对象,此时可以通过一个称为“代理”的第三者来实现间接访问,该方案对应的设计模式被称为代理模式。代理模式让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许将请求提交给对象前后进行一些处理。现实生活中的场景:代购。
软件开发中的代理:
代理模式中引入了一个新的代理对象,代理对象在客户端对象和目标对象之间起到了中介的作用,它去掉客户端不能看到的内容和服务或者增加客户需要的额外的新服务。
代理模式的角色:
抽象主体类:声明了真是主题和代理主题的共同接口
真实主题类:实现了抽象主体中的具体业务,是代理对象代表的真实对象,也是最终要引用的对象。
代理类:也实现了抽象主体中的具体业务,其内部包含了对真实主体的引用。
场景:
我们要使用dao接口,给dao接口实现类,增加事务代理。
抽象主体类
public interface MemberDao {
/**
* insert
*
* @param
* @return void
* @throws
* @method insert
* @author Rocky Qian
* @version 1.0
* @date 2023/10/30 14:22
*/
void insert();
}
真实类
public class MemberDaoImpl implements MemberDao {
/**
* insert
*
* @param
* @return void
* @throws
* @method insert
* @author Rocky Qian
* @version 1.0
* @date 2023/10/30 14:22
*/
@Override
public void insert() {
System.out.println("新增成功");
}
}
代理类
ublic class MemberDaoProxy implements MemberDao{
private MemberDao target;
public MemberDaoProxy(MemberDao target){
this.target = target;
}
/**
* 代理
*
* @param
* @return void
* @throws
* @method insert
* @author Rocky Qian
* @version 1.0
* @date 2023/10/30 14:25
*/
@Override
public void insert() {
System.out.println("开始事务");
target.insert();
System.out.println("关闭事务");
}
}
测试类
public class Client {
public static void main(String[] args) {
//目标对象
final MemberDaoImpl memberDao = new MemberDaoImpl();
//代理对象
final MemberDaoProxy memberDaoProxy = new MemberDaoProxy(memberDao);
//执行代理方法
memberDaoProxy.insert();
}
}
输出结果:
开启事务
新增成功
关闭事务
静态代理:
优点:可以不在修改目标类的前提下,扩展目标类的功能
缺点:
冗余由于代理对象要实现和目标对象一致的接口,会产生很多的代理。
不易维护,一旦接口中增加了方法,目标对象和代理对象都需要修改。
JDK动态代理的实现:
动态代理利用了jdk api。动态地在内存对象中构建代理对象,从而实现对目标对象的代理功能,动态代理又称为JDK代理或接口代理。
静态代理和动态代理的区别
1)静态代理是在编译期就已经实现了,编译完之后代理类是一个实际的class文件
2)动态代理是运行时生成的,即编译完成后没有实际的class文件,而是在运行时,生成类字节码,并加载到jvm中。
JDK中生成代理对象主要涉及的类有:
1)主要用的方法:java.lang.reflect.Proxy类,newProxyInstance( )方法
2)java.lang.reflect.InvocationHandler 类 ,invoke方法
代码实现
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
/**
* 获取代理类
*
* @param
* @return java.lang.Object
* @throws
* @method getProxyInstance
* @author Rocky Qian
* @version 1.0
* @date 2023/10/30 14:58
*/
public Object getProxyInstance() {
return Proxy.newProxyInstance(
//目标类使用的类加载器
target.getClass().getClassLoader(),
//目标对象实现的接口类型,
target.getClass().getInterfaces(),
//创建一个事件处理器
new InvocationHandler() {
/**
* invoke
*
* @param proxy 代理对象
* @param method 对应于代理对象上调用的接口的实例
* @param args 对应了处理对象在调用接口方法是传递的实际参数
* @return java.lang.Object 返回目标对象方法的返回值,没有返回值就返回null
* @throws
* @method invoke
* @author Rocky Qian
* @version 1.0
* @date 2023/10/30 14:53
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//增强
System.out.println("开启事务");
method.invoke(target, args);
System.out.println("关闭事务");
return null;
}
}
);
}
}
测试类
public class Client {
public static void main(String[] args) {
//获取真实类
final MemberDaoImpl memberDaoImpl = new MemberDaoImpl();
//使用代理类工厂
final ProxyFactory proxyFactory = new ProxyFactory(memberDaoImpl);
//创建代理类
final MemberDao memberDao = (MemberDao) proxyFactory.getProxyInstance();
//执行代理方法
memberDao.insert();
}
}
输出结果:
开启事务
新增成功
关闭事务
Java类虚拟机类加载过程主要分为三个阶段:装载、链接、初始化,其中链接阶段可分为三部分,验证、准备、解析。其中装载主要完成三件事:
1)通过一个类的全限定名获取定义此类的二级制字节流
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3)在内存中生成一个代表这个类的java.lang.class对象,作为方法区对这个类的各种数据访问入口
由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第一点,获取类的二进制字节流就有很多途径:
1)从本地获取
2)从网络获取
3)从运行时计算生成,这种场景使用最多的就是动态代理,在java.lang.reflect,Proxy类中,就是用了ProxyGenerator.generatorProxyClass来为特定接口生成形式为"$Proxy"的代理类的二进制字节流。
所以,动态代理就是想办法,根据接口或者目标类对象,计算出代理类的字节码,然后加载到JVM中。
由于我们无法获取到动态代理类的class文件,没办法知道调用过程。这里我们通过阿里的阿尔萨斯(arthas),帮助我们来看我们整个代理类的执行过程。
arthas 获取类信息步骤:
1.下载
2.在我们的测试类中,写一个死循环
public class Client {
public static void main(String[] args) {
//获取真是类
final MemberDaoImpl memberDaoImpl = new MemberDaoImpl();
//使用代理类工厂、创建代理类
final MemberDao proxy = (MemberDao)new ProxyFactory(memberDaoImpl).getProxyInstance();
System.out.println(proxy.getClass());
// 执行代理方法
proxy.insert();
while (true){
}
}
}
3.cmd中 java -jar arthas-boot.jar
4.获取我们测试类的编号,等待一会
5.jad + 我们类的全量包名
6.得出以下代码
package com.sun.proxy;
import java.lang.annotation.Inherited;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Inherited {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals",Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("java.lang.annotation.Inherited").getMethod("annotationType", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
public final boolean equals(Object object) {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final Class annotationType() {
try {
return (Class)this.h.invoke(this, m3, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
**CGLib(code generator library)**是一个第三方代码生成类库,运行时在内存中生成一个子类对象从而实现对目标类对象功能的扩展,CGLib为没有实现接口的类实现代理,为JDK代理提供了补充。
1)最底层是字节码
2)ASM是操作字节码工具
3)CGLib基于ASM字节码工具操作字节码(即动态生成代理,对方法进行增强)
4)Sping AOP基于CGLib进行封装,实现CGLib方式的动态代理,如果使用CGLib需要引入CGLib的jar包,如果有了spring-core的jar包,则无需引入,因为Spring中包含了CGLib。
代码实现
1.获取代理类必须实现MethodInterceptor接口
2.Enhancer增强类
3.实现MethodInterceptor中intercept方法
所需要的实体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Member {
private String name;
private Integer age;
}
无接口的类
public class MemberServiceImpl {
/**
* 获取会员
*
* @param
* @return java.util.List
* @throws
* @method getMemberList
* @author Rocky Qian
* @version 1.0
* @date 2023/10/31 10:54
*/
public void getMemberList() {
final List<Member> list = Collections.singletonList(new Member("小明", 2));
System.out.println(list);
}
}
代理类
public class MemberTransactionProxy implements MethodInterceptor {
/**
* 生成CGLIB动态代理方法
*
* @param target 需要被代理的目标类
* @return java.lang.Object 代理类对象
* @throws
* @method getTransactionProxy
* @author Rocky Qian
* @version 1.0
* @date 2023/10/31 10:56
*/
public Object getTransactionProxy(Object target) {
//增强类,用来创建动态代理类
final Enhancer enhancer = new Enhancer();
//设置代理类的父类字节码对象
enhancer.setSuperclass(target.getClass());
//设置回调
enhancer.setCallback(this);
//创建动态代理对象,并返回
return enhancer.create();
}
/**
* 实现回调方法
*
* @param o 代理对象
* @param method 目标对象中的方法实例
* @param objects 参数列表
* @param methodProxy 代理对象中的方法的实例
* @return java.lang.Object
* @throws Throwable
* @method intercept
* @author Rocky Qian
* @version 1.0
* @date 2023/10/31 11:03
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("开启事务");
//执行代理方法
final Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("关闭事务");
return o1;
}
}
测试类
public class Client {
public static void main(String[] args) {
//创建目标类
final MemberServiceImpl memberService = new MemberServiceImpl();
System.out.println(memberService.getClass());
//获取代理方法
final MemberTransactionProxy memberTransactionProxy = new MemberTransactionProxy();
//获取代理类
MemberServiceImpl impl = (MemberServiceImpl) memberTransactionProxy.getTransactionProxy(memberService);
System.out.println(impl.getClass());
//代理类增强方法
impl.getMemberList();
}
}
结果:
class com.plan.dream.pattern.proxy.service.MemberServiceImpl
class com.plan.dream.pattern.proxy.service.MemberServiceImpl$$EnhancerByCGLIB$$bfef93f6
开启事务
[Member(name=小明, age=2)]
关闭事务
1)JDK代理和CGLIB代理
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使字节码生成代理类,在JDK1.6之前比使用java反射效率高点。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK代理优化之后,在调用次数减少的情况下,JDK代理效率高于CGLib代理效率,只有当大量调用的时候,JDK1.6、JDK1.7比CGLib代理效率低一点,但是到了JDK1.8,JDK的代理效率高于CGLib代理,所以如果有接口,就使用JDK代理,如果没有接口就使用CGLib代理。
2)动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明了的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke( )),这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类都需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂性,而动态代理不会出现该问题。
优点:
代理模式在与客户端与目标之间起到一个中介的作用,保护目标对象的作用。
代理对象可以扩展目标对象的功能。
代理模式可能加客户端与目标对象分离,在一定程度上降低了系统的耦合度。
缺点:
增加了系统的复杂度
1)功能增强
需要对一个对象的访问提供一些额外操作时,可以使用代理模式。
2)远程代理
RPC框架也可以看做一种代理模式,
3)防火墙
当浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网,当互联网返回值响应时,代理服务器再把它转给浏览器
4)保护代理
控制访问一个对象,如果需要,可以给不同的用户提供不同级别的使用权限。
将抽象部分与他的实现部分分离,使他们都已独立地变化。桥接模式用一种巧妙的方式处理多层集成存在的问题,用抽象关系来取代传统的多层继承,将类之间的静态继承转系转变为动态地组合关系,使系统更加灵活,并易于扩展,有效的控制了系统中类的个数(避免了集成层级的指数级爆炸操作)。其实,桥接模式就是将两个独立变化的维度,进行了解耦,不是将两者耦合在一起,形成多层继承的结构。
1)抽象化角色(Abstraction)
主要定义了这个角色的行为,并且包含了实现化对象的引用
2)实现化角色(Implementor)
主要定了这个角色必需的行为和属性,并提供了扩展抽象化角色调用
3)扩展抽象化角色(ReFinedAbstraction)
是抽象化角色的子类,继承了抽象化角色
4)具体实现化角色(ImplementorA、ImplementorD)
是对实现化角色的视线
场景:我有两种支付渠道微信、支付宝,支付模式有面容和指纹。我们将支付渠道和支付模式进行解耦。
抽象化角色
public interface PayMode {
/**
* 对支付模式风控
*
* @param uid
* @return boolean
* @throws
* @method security
* @author Rocky Qian
* @version 1.0
* @date 2023/11/1 10:59
*/
boolean security(String uid);
}
实现化角色
在这里插入代码片
扩展抽象化角色A
public class WxPay extends Pay {
public WxPay(PayMode payMode) {
super(payMode);
}
/**
* 微信支付
*
* @param uid 用户id
* @param tradeId 商户id
* @param amount 金额
* @return java.lang.String
* @throws
* @method transfer
* @author Rocky Qian
* @version 1.0
* @date 2023/11/1 13:42
*/
@Override
public String transfer(String uid, String tradeId, BigDecimal amount) {
System.out.println("微信支付开始");
if (payMode.security(uid)) {
System.out.println("微信支付成功");
return "200";
} else {
System.out.println("微信支付失败");
}
return "500";
}
}
扩展抽象化角色B
public class ZfbPay extends Pay {
public ZfbPay(PayMode payMode) {
super(payMode);
}
/**
* 支付宝支付
*
* @param uid 用户id
* @param tradeId 商户id
* @param amount 金额
* @return java.lang.String
* @throws
* @method transfer
* @author Rocky Qian
* @version 1.0
* @date 2023/11/1 13:42
*/
@Override
public String transfer(String uid, String tradeId, BigDecimal amount) {
System.out.println("支付宝支付开始");
if (payMode.security(uid)) {
System.out.println("支付宝支付成功");
return "200";
} else {
System.out.println("支付宝支付失败");
}
return "500";
}
}
具体实现化角色A
public class PayFingerPrintMode implements PayMode {
/**
* 指纹支付
*
* @param uid
* @return boolean
* @throws
* @method security
* @author Rocky Qian
* @version 1.0
* @date 2023/11/1 11:04
*/
@Override
public boolean security(String uid) {
System.out.println("指纹风控通过");
return true;
}
}
具体实现化角色B
public class PayPaceMode implements PayMode {
/**
* 刷脸支付
*
* @param uid
* @return boolean
* @throws
* @method security
* @author Rocky Qian
* @version 1.0
* @date 2023/11/1 11:04
*/
@Override
public boolean security(String uid) {
System.out.println("刷脸风控通过");
return true;
}
}
client
public class Client {
public static void main(String[] args) {
WxPay wxPay = new WxPay(new PayFingerPrintMode());
wxPay.transfer("11", "11", new BigDecimal(11));
}
}
输出结果:
微信支付开始
指纹风控通过
微信支付成功
优点:
1)分离抽象接口及其实现部分,桥接模式使用“对象之间的关联关系”解耦了抽象和实现之间固有绑定关系,使得抽象和实现可以沿着各自的维度来变化
2)在很多情况下,桥接模式可以取代多层继承方案,多层继承违背了单一职责原则,复用性差,类的数量多,桥接模式很好的解决了这些问题。
3)桥接模式提高了系统的扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合开闭原则。
缺点:
1)桥接模式的使用增加系统的理解和设计难度,由于关联关系建立在抽象层中,要求开发者一开始就要对抽象层,进行设计和编程
2)桥接模式要求正确识别出系统中的两个独立变化的维度,因此具有一定的局限性,并且如果正确的进行维度的划分,也需要相当丰富的经验
1)需要平台独立性的应用程序时,比如,不同数据库的JDBC驱动程序,硬盘驱动程序等
2)需要在某种统一协议下增加更多组件,比如,在支付场景,我们同时支持微信、支付宝、各大银行支付组件等。这里的统一协议都是收款、支付、扣款,而组件就是支付宝、微信
3)基于信息驱动的场景,虽然消息的行为比较统一,主要包括发送、接收、处理和回执,但其实具体客户端的实现却通常各不一致,比如手机短信、邮件信息、QQ消息、微信消息等
4)拆分比较复杂的类对象时,当一个类中包含了大量对象和方法时,既不方便阅读,又不方便修改
5)希望从多个独立维度上扩展,比如系统功能性和非功能性的角度,业务或技术角度等
动态地给一个对象添加一些额外的职责,就扩展工鞥呢而言,装饰着模式提供了一种比使用子类更加灵活的替代方案。装饰者模式是一种用于继承的技术。他通过一种无需定义子类的方式,给对象动态的增加职责,用对象之间的关联关系,取代类之间的继承关系。
抽象构建角色(component):他是抽象构建角色和抽象装饰类的共同父类
具体构建角色(concreteComponent):他是抽象构建角色的子类,用于定义具体构建对象,被装饰的类
抽象装饰类角色(Decotator):是抽象构建角色的子类,是对具体构建角色增加职责
具体装饰类角色(concreteDecotator):他是抽象装饰类的子类,他负责向构建角色添加新的功能。
抽象构建类
public abstract class Component {
/**
* 抽象构建类-抽象方法
*
* @param
* @return void
* @throws
* @method operation
* @author Rocky Qian
* @version 1.0
* @date 2023/11/1 16:01
*/
public abstract void operation();
}
具体构建类
public class ConcreteComponent extends Component {
/**
* 具体构建类的基本方法
*
* @param
* @return void
* @throws
* @method operation
* @author Rocky Qian
* @version 1.0
* @date 2023/11/1 16:02
*/
@Override
public void operation() {
System.out.println("基本功能");
}
}
抽象装饰类
public class Decorator extends Component {
/**
* 维持一个对抽象构建对象的引用
*/
private Component component;
public Decorator(Component component) {
this.component = component;
}
/**
* 装饰方法
*
* @param
* @return void
* @throws
* @method operation
* @author Rocky Qian
* @version 1.0
* @date 2023/11/1 16:05
*/
@Override
public void operation() {
//调用原有的业务方法,并没有真正的进行装饰,而且提供了一个统一的接口,将装饰的过程交给子类完成
component.operation();
}
}
具体装饰类
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
@Override
public void operation() {
//调用原有的方法
super.operation();
print();
}
/**
* 装饰方法
*
* @param
* @return void
* @throws
* @method print
* @author Rocky Qian
* @version 1.0
* @date 2023/11/1 16:09
*/
public void print(){
System.out.println("进行装饰");
}
}
调用
public class Client {
public static void main(String[] args) {
final ConcreteDecorator concreteComponent = new ConcreteDecorator(new ConcreteComponent());
concreteComponent.operation();
}
}
结果:
基本功能
进行装饰
优点:
1)对于扩展一个对象的功能,装饰模式比继承更加铃铎,不会导致类的个数急剧增加
2)可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可在运行时选择不同的具体装饰类,从而实现不同的行为
3)可以对一个对象进行多次装饰,通过使用不同的具体装饰类,以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到更强大的对象。
4)具体构建类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构建类和具体装饰类,原有类库代码无需改变,符合开闭原则。
缺点:
1)使用装饰模式进行系统设计时将产生很多小对象。这些对象的区别在于他们之间相互连接的方式有所不同。而不是他们的类或者属性值不同,大量的小对象产生势必会占用更多的系统资源,在一定程度上影响程序的性能。
2)装饰器模式提供了一种比继承更加灵活、机动的解决方案,但同时也意味着比继承更容易出错。排错也更加困难,对于多次装饰的对象,在调试寻找错误时可能需要逐级排查,较为繁琐。
1)快速动态扩展和撤销一个类的功能场景。比如,有的场景下对API接口安全性要求比较高,那么就可以使用装饰模式对传输的字符串进行加密解密或者压缩,如果安全性不高,则可以不使用
2)不支持继承扩展类的场景。比如,适用final关键字的类,或者系统中存在大量通过继承产生的子类
将类的接口转换为客户期望的另一个接口,适配器可以让不兼容的两个类一起协同工作。适配器模式,是用来做适配的,他将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类,可以一起工作。适配器模式有两种方式实现:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。类适配器的耦合度比对象适配器的高,且要求程序员了解现有组件库中相关组件的内部接口,所以应用相对较少。
目标抽象类
public interface Target {
/**
* 目标抽象类-请求接口
*
* @param
* @return void
* @throws
* @method request
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 11:02
*/
void request();
}
被适配的类
public class Adaptee {
/**
* 被适配的类-被适配的方法
*
* @param
* @return void
* @throws
* @method adapteeRequest
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 11:04
*/
public void adapteeRequest() {
System.out.println("被适配的方法执行了");
}
}
适配器类
public class Adapter extends Adaptee implements Target {
/**
* 适配器类-适配方法
*
* @param
* @return void
* @throws
* @method request
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 11:05
*/
@Override
public void request() {
System.out.println("开始调用类适配器");
super.adapteeRequest();
System.out.println("调用类适配器解释");
}
}
调用者
public class Client {
public static void main(String[] args) {
//调用适配器
Target target = new Adapter();
//执行适配器方法
target.request();
}
}
结果:
开始调用类适配器
被适配的方法执行了
调用类适配器解释
目标抽象类
public interface Target {
/**
* 目标抽象类-请求接口
*
* @param
* @return void
* @throws
* @method request
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 11:02
*/
void request();
}
被适配的类
public class Adaptee {
/**
* 被适配的类-被适配的方法
*
* @param
* @return void
* @throws
* @method adapteeRequest
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 11:04
*/
public void adapteeRequest() {
System.out.println("被适配的方法执行了");
}
}
适配器类
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
/**
* 适配器类-适配方法
*
* @param
* @return void
* @throws
* @method request
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 11:05
*/
@Override
public void request() {
System.out.println("开始调用类适配器");
adaptee.adapteeRequest();
System.out.println("调用类适配器解释");
}
}
调用者
public class Client {
public static void main(String[] args) {
//调用适配器
Target target = new Adapter(new Adaptee());
//执行适配器方法
target.request();
}
}
结果:
开始调用类适配器
被适配的方法执行了
调用类适配器解释
假设我们有一台电脑,目前只能读取SD卡信息,这时我们想使用电脑读取TF卡的内容,就需要将TF卡加上卡套,转换成SD卡,创建一个读卡器,将TF卡中的内容读取出来。
类适配器UML
代码实现
Computer
public class Computer {
/**
* 读取sd卡
*
* @param sdCard
* @return java.lang.String
* @throws
* @method read
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 13:56
*/
public String read(SdCard sdCard){
return sdCard.readSd();
}
}
**SdCard **
public interface SdCard {
/**
* 写入sd卡
*
* @param msg
* @return void
* @throws
* @method writeSd
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 13:49
*/
void writeSd(String msg);
/**
* 读取sd
*
* @param
* @return java.lang.String
* @throws
* @method readSd
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 13:49
*/
String readSd();
}
**SdCardImpl **
public class SdCardImpl implements SdCard {
/**
* 写入数据
*
* @param msg
* @return void
* @throws
* @method writeSd
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 13:50
*/
@Override
public void writeSd(String msg) {
System.out.println("成功写入Sd卡数据");
}
/**
* 读取数据成功
*
* @param
* @return java.lang.String
* @throws
* @method readSd
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 13:50
*/
@Override
public String readSd() {
System.out.println("成功读取Sd卡数据");
return "200";
}
}
**TfCard **
public interface TfCard {
/**
* 写入Tf卡
*
* @param msg
* @return void
* @throws
* @method writeSd
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 13:49
*/
void writeTf(String msg);
/**
* 读取Tf
*
* @param
* @return java.lang.String
* @throws
* @method readSd
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 13:49
*/
String readTf();
}
TfCardImpl
public class TfCardImpl implements TfCard {
/**
*
*
* @param msg
* @return void
* @throws
* @method writeTf
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 13:52
*/
@Override
public void writeTf(String msg) {
System.out.println("成功写入Tf卡数据");
}
@Override
public String readTf() {
System.out.println("成功读取Tf卡数据");
return "200";
}
}
SdAdapterTf
public class SdAdapterTf extends TfCardImpl implements SdCard {
/**
* 写适配器
*
* @param msg
* @return void
* @throws
* @method writeSd
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 13:58
*/
@Override
public void writeSd(String msg) {
System.out.println("适配器写入Tf");
writeTf(msg);
}
/**
* 读适配器
*
* @param
* @return java.lang.String
* @throws
* @method readSd
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 13:58
*/
@Override
public String readSd() {
System.out.println("适配器读取Tf");
return readTf();
}
}
Client
public class Client {
public static void main(String[] args) {
System.out.println(new Computer().read(new SdAdapterTf()));
}
}
对象适配器的代码就不实现了,有空可以自己尝试一下。
优点:
1)将目标类和适配者类解耦,通过引入一个适配器类,来重用现有的适配者类,无需修改原有结构
2)增加了类的透明性和复用性,将具体业务实现过程,封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性。同一个适配者类可以在多个不同的系统中复用。
3)灵活性和扩展性都非常好,通过使用配置文件可以很方便的更换适配器,也可以在不修改原有代码的基础上,增加新的适配器类,符合开闭原则。
缺点:
类适配器:
1)对于Java等不支持多重继承的语言,一次只能适配一个适配者类,不同同事适配多个适配者。
2)适配者类不是最终类
对象适配器:
1)与适配器模式比较,在该模式下要在适配器中置换适配者类的某些方法比较麻烦。
1)统一过各类的接口设计时
某个功能的实现,需要依赖多个外部系统。通过适配器模式,将他们的接口适配为同一的接口定义
2)需要依赖外部系统时
当我们把项目中依赖的一个外部系统,替换成为另一个外部系统的时候,利用适配器模式,可以减少对代码的改动
3)原有接口无法修改时,或者原有接口功能太老但又需要兼容
jdk1.0 Enumeration到Iterator的替换,使用适配器模式保留Enumeration类,并将其,实现,替换为直接调用的Iterator。
4)适配不同的数据格式时:
Slf4j日志框架,定义打印日志的统一接口,提供针对不同日志框架的适配器。
代理模式:在不改变原始类接口的条件下,为原始类定义一个代理类。主要是控制访问,这时他与装饰器最大的区别。
桥接模式:桥接模式的目的是将接口部分与实现部分分离,从而让他们较容易、也相对独立。
装饰器模式:在不改变原有接口的情况下,对原始类进行增强,并且支持多个装饰器嵌套使用。
适配器模式:将一类的接口转换成为,客户希望的另一个接口。适配器模式让那些不兼容的类可以一起工作。
也叫门面模式,为了子系统中的一组接口,提供统一的接口,它定义了一个更级别的接口,使子系统更易于使用。外观模式,是一种通过为多个复杂的子系统提供一个一致的接口。而使这些子系统,更加容易被访问的模式。该模式对外,有一个统一的接口,外部应用程序不用关系把内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。门面模式基于迪米特法则和接口隔离原则,两个有交互的系统,只暴露有限的接口。
门面类,充当了系统中“服务员”的角色,他为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。如果没有门面类,每个客户类,需要和多个子系统之间进行复杂的交互,系统的耦合度将会很大。
子系统A
public class SubSystemA {
/**
* A系统A功能
*
* @param
* @return void
* @throws
* @method methodA
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 15:20
*/
public void methodA() {
System.out.println("A系统A功能");
}
}
子系统B
public class SubSystemB {
/**
* B系统B功能
*
* @param
* @return void
* @throws
* @method methodB
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 15:21
*/
public void methodB() {
System.out.println("B系统B功能");
}
}
子系统C
public class SubSystemC {
/**
* C系统C功能
*
* @param
* @return void
* @throws
* @method methodB
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 15:21
*/
public void methodC() {
System.out.println("C系统C功能");
}
}
外观类
public class Facade {
private SubSystemA subSystemA = new SubSystemA();
private SubSystemB subSystemB = new SubSystemB();
private SubSystemC subSystemC = new SubSystemC();
/**
* 外观类方法
*
* @param
* @return void
* @throws
* @method method
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 15:22
*/
public void method(){
System.out.println("调用外观类,执行子系统方法");
subSystemA.methodA();
subSystemB.methodB();
subSystemC.methodC();
System.out.println("执行完成");
}
}
结果:
调用外观类,执行子系统方法
A系统A功能
B系统B功能
C系统C功能
执行完成
调用
public class Client {
public static void main(String[] args) {
final Facade facade = new Facade();
facade.method();
}
}
优点:
1)他对客户端屏蔽了子系统组件,减少了客户端所需要处理的对象数据,并使子系统使用起来更加容易,通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
2)它实现了子系统与客户端之间的松耦合关系,这使子系统的变化不会影响到调用他的客户端,只需要调整外观类即可
3)一个子系统的修改对其他子系统没有任何影响,儿子系统内部变化也不会影响到外观对象。
缺点:
1)不能很好的控制客户端直接使用子系统,如果客户端访问子系统做太多的限制,则减少了灵活性和可变性。
2)如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则
1)简化复杂系统
当我们开发了一整套的电商系统后(包括订单、商品、支付、会员等系统),我们不能让用户一次使用这些系统后,才能完成商品的购买,而是用户需要一个门户网站或者手机App这样的简化的门片系统来提供在线的购物功能。
2)减少客户端处理的系统数量
比如在web应用中,系统之间的调用可能需要处理DataBase数据库,Model业务对象等,其中DataBase对象就需要处理打开数据库、关闭连接等操作。然后转换为Model业务对象,实在太慢反,如果我们创建一个数据库的使用门面(Dao层),那么实现起来就很容易。
3)让多个系统(对象)为多个系统(对象)工作
线程池ThradPool其实就是一个门面模式,他为系统提供了统一的线程对象的创建、销毁、使用等。
4)联合更多的系统来扩展原有系统
当我们的电商系统中需要一些新功能时,比如人脸识别。我们可以不需要自行研发,而是购买其他公司提供的服务,这时我们通过门面系统就能方便快速地进行扩展。
5)作为一个简洁的中间层
门面模式还以用来隐藏或者封装系统中的分层结构,同时作为一个简化的中间层来使用。比如秒杀、库存、钱包等场景中,我们需要共享有状态的数据时(比如商品库存、账户里的钱),在不改变原有系统的前提下,通过一个中间的共享层(如秒杀活动的商品总数统一放在redis中),就能统一进行各种服务的调用。
组合模式最初只是解决树形结构的场景,更多的是处理对象组织结构之间的问题。而组合关系则是,通过将不同对象封装起来,完成一个统一功能。不要搞混组合模式和组合关系。将对象组合成树形结构,以表示整个部分的层次结构。组合模式可以让用户,统一对待单个对象和对象的组合。
组合模式中的角色:
**Component:**抽象根节点,声明了所有子类共有的行为和属性
**Composite:**树枝节点,实现了根节点中定义的行为,其中还管理了集合,可以存储子节点
**Leaf:**叶子节点,存储在树枝节点的在最底层,叶子节点下没有其他分支,是系统遍历的最小单位
Component
public abstract class Component {
/**
* 增加节点
*
* @param c
* @return void
* @throws
* @method add
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 17:43
*/
public abstract void add(Component c);
/**
* 移除节点
*
* @param c
* @return void
* @throws
* @method remove
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 17:44
*/
public abstract void remove(Component c);
/**
* 获取子节点
*
* @param i
* @return com.plan.dream.pattern.composite.demo1.Component
* @throws
* @method getChild
* @author Rocky Qian
* @version 1.0
* @date 2023/11/2 17:44
*/
public abstract Component getChild(int i);
/**
* 具体业务方法
*
* @param
* @return void
* @throws
* @method operation
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 9:54
*/
public abstract void operation();
Composite
public class Composite extends Component {
private List<Component> componentList = new ArrayList();
/**
* add
*
* @param c 对象
* @return void
* @throws
* @method add
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 9:55
*/
@Override
public void add(Component c) {
componentList.add(c);
}
/**
* 移除 c
*
* @param c
* @return void
* @throws
* @method remove
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 9:56
*/
@Override
public void remove(Component c) {
componentList.remove(c);
}
/**
* 获取子类
*
* @param i
* @return com.plan.dream.pattern.composite.demo1.Component
* @throws
* @method getChild
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 9:56
*/
@Override
public Component getChild(int i) {
return componentList.get(i);
}
/**
* 具体业务方法
*
* @param
* @return void
* @throws
* @method operation
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 9:56
*/
@Override
public void operation() {
System.out.println(componentList);
}
Leaf
public class Leaf extends Component {
/**
* 新增
*
* @param c
* @return void
* @throws
* @method add
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 9:58
*/
@Override
public void add(Component c) {
}
/**
* 删除
*
* @param c
* @return void
* @throws
* @method remove
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 9:58
*/
@Override
public void remove(Component c) {
}
/**
* 无子节点
*
* @param i
* @return com.plan.dream.pattern.composite.demo1.Component
* @throws
* @method getChild
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 9:58
*/
@Override
public Component getChild(int i) {
return null;
}
/**
* 叶子节点的方法
*
* @param
* @return void
* @throws
* @method operation
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 9:58
*/
@Override
public void operation() {
System.out.println("叶子节点");
}
}
实现Windows,列出某一文件下的所有文件和文件夹
抽象根节点
public abstract class Entry {
/**
* 获取名字
*
* @param
* @return java.lang.String
* @throws
* @method getName
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 10:21
*/
public abstract String getName();
/**
* 获取大小
*
* @param
* @return int
* @throws
* @method getSize
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 10:21
*/
public abstract int getSize();
/**
* 打印
*
* @param
* @return void
* @throws
* @method print
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 10:22
*/
public abstract void print();
/**
* 新增
*
* @param entry
* @return com.plan.dream.pattern.composite.demo2.Entry
* @throws
* @method add
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 11:18
*/
public abstract Entry add(Entry entry);
}
树枝节点
public class Directory extends Entry {
/**
* 文件夹和问价
*/
private List<Entry> entries = new ArrayList<Entry>();
/**
* 文件名
*/
private String name;
public Directory(List<Entry> entries, String name) {
this.entries = entries;
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return 0;
}
@Override
public void print() {
System.out.println(this);
}
@Override
public Entry add(Entry entry) {
entries.add(entry);
return this;
}
}
叶子节点
public class File extends Entry {
private String name;
private int size;
public File(String name ,int size){
this.name = name;
this.size = size;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return size;
}
@Override
public void print() {
System.out.println(this);
}
@Override
public Entry add(Entry entry) {
return null;
}
}
1)透明组合模式
透明组合模式中,抽象根节点角色中声明了,所有用于管理成员对象的方法,比如在示例中component声明了add、remove等方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准模式。透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其,提供了add(),remove()等方法是没有意义的,这在编译阶段不会出错。但是在运行阶段,如果调用这些方法可能会出错。
2)安全组合模式
在抽象根角色中,没有声明任何用于管理,成员对象的方法,而是在树枝节点类中声明了,并实现了这些方法。安全组合模式的缺点就是不够透明,因为叶子节点和容器节点具有不同的方法, 且容器节点中,那些用于管理成员对象的方法,没有在抽象根节点中定义,因此客户端不能完全针对抽象变成,必须有区别的对待叶子节点和叶子节点
优点:
1)组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次。他让客户端忽略了层次的差异,方便对整个层次结构进行控制
2)客户端可以一致地使用一个组合接口或其中单个对象,不必关系处理的是单个对象,还是整个组合结构。简化了客户端代码
3)组合模式中,增加了新的树枝节点和叶子节点都很方便,无须对现有的类库进行任何修改,符合开闭原则
4)组合模式为树形结构的面向对象的实现,提供了一种灵活的解决方案,通过叶子节点和树枝几点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单
缺点:
1)使用组合模式的前提,你的业务场景必须能够表示成树形结构。所以,组合模式的应用场景很局限,他不是一种很常用的设计模式。
1)处理树形结构,如公司人员组织架构、订单信息
2)跨越多个层次结构,聚合数据,比如,统计文件夹下的文件数
3)统一处理一个结构中的多个对象,比如遍历文件夹下的所有XML类型文件内容
摒弃了在每个对象中保存所有数据的方式。而是通过共享多个对象所共有的相同状态,从而让我们能在有效的内存容量中载入更多对象。享元模式要解决的核心问题就是节约内存,使用的办法是找出相似对象之间共有特征,然后复用这些特征,所谓"享元",就是被共享的单元。享元模式通过共享技术,实现相同或者相似的对象的重用,在逻辑上每一个出现的字符,都有一个对象与之对应,然而在物理上,他们却是共享一个享元对象。享元模式中的两种状态:内部状态:不会随着环境的改变而改变的,可共享部分。外部状态:随着环境改变而改变,不可共享部分。
**享元工厂角色:**负责创建、管理享元角色,作为存储享元对象的享元池,用户获取对象时,先从享元池中获取,有则返回,没有创建新的返回给用户。
**抽象享元角色:**定义了具体享元角色中的公共的方法
**可共享的具体享元角色:**在具体享元类中,需要将内部状态和外部状态分开处理
**非共享的具体角色:**重写了抽象享元角色的方法
享元抽象类
public abstract class Flyweight {
/**
* 可共享的
*
* @param state
* @return void
* @throws
* @method operation
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 15:07
*/
public abstract void operation(String state);
}
具体享元类
public class ConcreteFlyweight extends Flyweight {
/**
* 内部状态:inState作为一个成员变量,同一个享元对象的内部状态是一致的。
*/
private final String inState;
public ConcreteFlyweight(String inState) {
this.inState = inState;
}
/**
* 外部状态在使用的时候,通常是有外部设置,不保存在享元对象中,即使是同一个对象
*
* @param state
* @return void
* @throws
* @method operation
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 15:14
*/
@Override
public void operation(String state) {
System.out.println("内部状态: " + inState + " 外部状态:" + state);
}
}
不同享享元类
public class UnSharedFlyweight extends Flyweight {
private String inState;
public UnSharedFlyweight(String inState) {
this.inState = inState;
}
@Override
public void operation(String state) {
System.out.println("使用不共享对象,内部状态:" + inState + " 外部状态:" + state);
}
}
享元工厂
public class FlyweightFactory {
/**
* 用于存储享元对象,充当享元池效果 存储了对象传递之间共有的状态,其实就是一创建这些对象时,就有这些状态
*/
private Map<String, Flyweight> pool = new ConcurrentHashMap<>();
/**
* 让我们享元对象之间状态的传递
*
* @param
* @return
* @throws
* @method FlyweightFactory
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 16:38
*/
public FlyweightFactory() {
//添加对应享元类内部状态
pool.put("A", new ConcreteFlyweight("A"));
pool.put("B", new ConcreteFlyweight("A"));
pool.put("C", new ConcreteFlyweight("A"));
pool.put("D", new ConcreteFlyweight("A"));
}
/**
* @param key
* @return com.plan.dream.pattern.flyweight.demo1.Flyweight
* @throws
* @method getFlyweight
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 16:41
*/
public Flyweight getFlyweight(String key) {
Flyweight result = null;
if (pool.containsKey(key)) {
System.out.println("享元池中存在,直接使用,key:" + key);
result = pool.get(key);
} else {
System.out.println("享元池中不存在,先添加,key:" + key);
result = new ConcreteFlyweight("A");
pool.put(key, result);
}
return result;
}
}
调用者
public class Client {
public static void main(String[] args) {
//先获取工厂对象
final FlyweightFactory flyweightFactory = new FlyweightFactory();
//通过工厂对象获取共享的享元对象
final Flyweight a1 = flyweightFactory.getFlyweight("F");
a1.operation("a1");
final Flyweight a2 = flyweightFactory.getFlyweight("F");
a2.operation("a2");
System.out.println(a1 == a2);
//非共享享元对象,就是普通对象,直接用就可以了
final UnSharedFlyweight u1 = new UnSharedFlyweight("A");
final UnSharedFlyweight u2 = new UnSharedFlyweight("A");
System.out.println(u1 == u2);
}
}
输出结果
享元池中不存在,先添加,key:F
内部状态: A 外部状态:a1
享元池中存在,直接使用,key:F
内部状态: A 外部状态:a2
true
false
五子棋中有大量的黑白子,他们的形状大小都是一样的,只是出现的位置不同,所以一个棋子作为一个独立的对象,存储在内存中,会导致大量的内存的浪费,我们使用享元模式来进行优化。
抽象享元类
public abstract class GobangFlyweight {
/**
* 获取颜色
*
* @param
* @return java.lang.String
* @throws
* @method getColor
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 17:08
*/
public abstract String getColor();
/**
* 显示
*
* @param
* @return void
* @throws
* @method display
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 17:09
*/
public void display() {
System.out.println("棋子颜色:" + this.getColor());
}
}
具体享元类
public class BlackGobang extends GobangFlyweight {
/**
* 获取颜色
*
* @param
* @return java.lang.String
* @throws
* @method getColor
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 17:10
*/
@Override
public String getColor() {
return "黑色";
}
}
具体享元类
public class WriteGobang extends GobangFlyweight {
/**
* 获取颜色
*
* @param
* @return java.lang.String
* @throws
* @method getColor
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 17:10
*/
@Override
public String getColor() {
return "白色";
}
}
享元工厂
public class GobangFactory {
/**
* 享元池
*/
private final Map<String, GobangFlyweight> pool = new ConcurrentHashMap<>();
/**
* 初始化
*
* @param
* @return
* @throws
* @method GobangFactory
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 17:21
*/
private GobangFactory() {
pool.put("b", new BlackGobang());
pool.put("w", new WriteGobang());
}
/**
* 静态内部类
*
* @param
* @return com.plan.dream.pattern.flyweight.demo2.GobangFactory
* @throws
* @method getInstance
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 17:20
*/
public static GobangFactory getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* 全局工厂所以用到单例
*/
private static class SingletonHolder {
private static final GobangFactory INSTANCE = new GobangFactory();
}
/**
* 获取棋子
*
* @param key
* @return com.plan.dream.pattern.flyweight.demo2.GobangFlyweight
* @throws
* @method getGobang
* @author Rocky Qian
* @version 1.0
* @date 2023/11/3 17:24
*/
public GobangFlyweight getGobang(String key) {
return pool.get(key);
}
}
调用类
public class Client {
public static void main(String[] args) {
final GobangFlyweight w = GobangFactory.getInstance().getGobang("w");
final GobangFlyweight b = GobangFactory.getInstance().getGobang("b");
final GobangFlyweight b1 = GobangFactory.getInstance().getGobang("b");
w.display();
System.out.println(b == b1);
}
}
输出
棋子颜色:白色
true
优点
1)极大减少内存中相似或相同是对象数量,节约系统资源,提升系统性能
2)享元模式中的外部状态独立,且不影响内部状态
缺点
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使逻辑变得复杂
1)一个系统有大量相同或相似的对象,造成内存的大量消耗
2)JDK中valueOf
从2023年10月23日,重阳节开始学习,复杂的设计模式一天学习一个,简单的一天三四个。为什么要疯狂学习呢?哎,也许是年纪大了,更也许是压力太大了。跟同龄的一比,自己确实很菜。没办法,提升自己吧,我把这个专题定义为我要进大厂,这也是我的短期目标。总之不管市场行情怎么样,学习肯定没错的,加油吧。。。