# ross-design **Repository Path**: zhang_jun_fa/ross-design ## Basic Information - **Project Name**: ross-design - **Description**: 学习设计模式,各种案例, - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-03-30 - **Last Updated**: 2023-08-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # java 设计模式学习案例 ## 设计模式之间的关系 ![1680244451171](image/README/1680244451171.png) ## 工厂方法模式 ### 概念 **定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。** ![通用类图](image/说明/1680051735987.png "通用类图") 在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。 ### 使用场景 工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。 ## 抽象工厂方法 ### 概念 **为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。** ![1680162838117](image/README/1680162838117.png) ## 代理模式 ### 静态代理 1. 通过继承实现静态代理 通过继承被代理对象,重写被代理方法,可以对其进行代理。 优点:被代理类无需实现接口 缺点:只能代理这个类,要想代理其他类,要想代理其他类需要写新的代理方法。 cglib动态代理就是采用这种方式对类进行代理。不过类是由cglib帮我们在内存中动态生成的。 ``` package com.ross.mode.two; /** * @version 1.0.0 * @className: Tank * @description: 通过组合实现静态代理 * @author: zhangjunfa * @date: 2023/3/30 17:22 */ public class Tank implements Movable { public void move() { System.out.println("我在跑步,我跑了1KM。。。"); } public static void main(String[] args) { Tank tank = new Tank(); new LogProxy(tank).move(); } } class LogProxy implements Movable { private Movable movable; public LogProxy(Movable movable) { this.movable = movable; } public void move() { System.out.println("我在跑步前做的准备,。"); movable.move(); System.out.println("跑步结束后做的准备,。"); } } ``` 1. 通过组合实现静态代理 定义一个 Movable 接口被代理类需要和代理类都需要实现该接口。(接口在这里的目的就是起一个规范作用保证被代理类和代理类都实现了move()方法)。代理类需要将该接口作为属性,实例化时需要传入该接口的对象,这样该代理类就可以实现代理所有实现Movable的类了。 优点:可以代理所有实现接口的类。 缺点:被代理的类必须实现接口。 JDK动态代理就是采用的这种方式实现的。同样的代理类是由JDK自动帮我们在内存生成的。 ``` public class Tank implements Movable{ @Override public void move() { System.out.println("Tank moving cla...."); } public static void main(String[] args) { Tank tank = new Tank(); new LogProxy(tank).move(); } } class LogProxy implements Movable{ private Movable movable; public LogProxy(Movable movable) { this.movable = movable; } @Override public void move() { System.out.println("方法执行前...."); movable.move(); System.out.println("方法执行后...."); } } interface Movable { void move(); } ``` ### 动态代理 1. JDK动态代理 通过java提供的Proxy类帮我们创建代理对象。 优点:可以生成所有实现接口的代理对象 缺点:JDK反射生成代理必须面向接口, 这是由Proxy的内部实现决定的。生成代理的方法中你必须指定实现类的接口,它根据这个接口来实现代理类生成的所实现的接口。 ``` package com.ross.mode.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @version 1.0.0 * @className: Tank * @description: JDK 动态代理 * @author: zhangjunfa * @date: 2023/3/30 18:25 */ public class Tank implements Movable { public void move() { System.out.println("我要跑步,跑了1KM"); } public static void main(String[] args) { Tank tank = new Tank(); // reflection 反射 通过二进制字节码分析类的属性和方法 //newProxyInstance: 创建代理对象 // 参数一: 被代理类对象 // 参数二:接口类对象 被代理对象所实现的接口 // 参数三:调用处理器。 被调用对象的那个方法被调用后该如何处理 Movable movable = (Movable) Proxy.newProxyInstance(Tank.class.getClassLoader(), new Class[]{Movable.class}, new LogProxy(tank)); movable.move(); } } class LogProxy implements InvocationHandler { private Movable movable; public LogProxy(Movable movable) { this.movable = movable; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("跑步:" + method.getName() + "()执行前"); // 此处相当于 movable.move() Object invoke = method.invoke(movable, args); System.out.println("跑步:" + method.getName() + "()执行后"); return invoke; } } interface Movable { void move(); } ``` 2. CGLIB动态代理 CGLib(Code Generate Library) 与JDK动态代理不同的是,cglib生成代理是被代理对象的子类。因此它拥有继承方法实现静态代理的优点:不需要被代理对象实现某个接口。 缺点:不能给final类生成代理,因为final类无法拥有子类。 使用cglib生成代理类也很简单,只要指定父类和回调方法即可 ``` package com.ross.mode.proxy; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * @version 1.0.0 * @className: TankCglib * @description: cglib动态代理 * @author: zhangjunfa * @date: 2023/3/30 18:32 */ public class TankCglib { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); // 增强者 enhancer.setSuperclass(Tank.class); // 指定父类 enhancer.setCallback(new TimeMethodInterceptor()); // 当被代理对象的方法调用的时候会调用 该对象的intercept Tank tank = (Tank) enhancer.create(); // 动态代理的生成 tank.move(); // 生成之后会调用 } } class TimeMethodInterceptor implements MethodInterceptor { public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("生成的类名" + o.getClass().getName()); System.out.println("生成的类的父类" + o.getClass().getSuperclass().getName()); System.out.println("方法执行前,被代理的方法" + method.getName()); Object result = null; result = methodProxy.invokeSuper(o, objects); System.out.println("方法执行后,被代理的方法" + method.getName()); return result; } } class Tank { public void move() { System.out.println("我要跑步了,跑了1KM。。"); } } ``` 3. SpringAop ## 建造者模式 ### 概念 建造者模式又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象 建造者模式是一步一步建造一个复杂对象,它允许用户只通过指定复杂对象的类型和内容就可以建造他们,用户不需要指定内部的具体构建细节 ### 解决问题 建造者模式主要解决的问题是在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的过程构成;由于需求的变化,这个复杂对象的各个部分经常面临着重大的变化,但是将它们组合在一起的过程却相对稳定。 ### 建造者模式四个角色: Product(产品角色):一个具体的产品对象 Builder(抽象建造者):创建一个Product对象的各个部件指定的接口/抽象类 ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件 Director(指挥者):构建一个使用Builder的对象,它主要是用于创建一个复杂的对象,它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程 ![1680489011717](image/README/1680489011717.png) ### 案例: ``` package com.ross.mode.entity; /** * @version 1.0.0 * @className: Student * @description: * @author: zhangjunfa * @date: 2023/4/3 10:39 */ public class Student { private String id; private String name; private String classId; private Integer age; /** * 私有构造器 * * @param builder */ private Student(Builder builder) { this.id = builder.id; this.name = builder.name; this.classId = builder.classId; this.age = builder.age; } /** * 建造者模式 */ public static final class Builder { private String id; private String name; private String classId; private Integer age; public Builder id(String id) { this.id = id; return this; } public Builder name(String name) { this.name = name; return this; } public Builder classId(String classId) { this.classId = classId; return this; } public Builder age(Integer age) { this.age = age; return this; } public Student build() { return new Student(this); } } } ``` 测试: ``` @Slf4j public class ApiTest { @Test public void test_Builder03() { // 建造者模式 Student student = new Student.Builder() .id("20010924") .name("RossZhang") .classId("软件01班") .age(23) .build(); log.info("测试结果:{}", student); } } ``` ### 常见案例 1。StringBuilder就是建造者模式精简后的用法,StringBuilder继承了Appendable接口: 2。lombok添加@Builder就是建造者模式 ### 优缺点 * 优点:建造者模式的主要优点在于客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象,每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,符合“开闭原则”,还可以更加精细地控制产品的创建过程。 * 缺点:其主要缺点是由于建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,因此其使用范围受到一定的限制,如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。 ## 原型模式 ### 概念 原型模式主要用于对象的创建,使用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。原型模式又分为浅克隆和深克隆 ![1680491112596](image/README/1680491112596.png "原型模式类图") 原型模式的核心是就是原型类 Prototype,Prototype 类需要具备以下两个条件: * (1)实现 Cloneable 接口:在 Java 中 Cloneable 接口的作用就是在运行时通知虚拟机可以安全地在实现了 Cloneable 接口的类上使用 clone() 方法,只有在实现了 Cloneable 的类才可以被拷贝,否则在运行时会抛出 CloneNotSupportedException 异常。 * (2)重写 Object 类中的 clone() 方法:Java 中所有类的父类都是 Object,Object 中有一个clone() 方法用于返回对象的拷贝,但是其作用域 protected,一般的类无法调用,因此,Prototype 类需要将 clone() 方法的作用域修改为 public。 原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。 ### 解决问题 * 对象创建过程复杂或消耗资源较大,采用克隆的方式可以避免资源浪费。 * 希望在运行时动态地生成对象实例的复制。 * 需要避免使用构造函数来创建新对象的情况。 * 要避免子类化的情况下的工厂方法。 * 希望避免使用new操作符创建每个对象。 * 需要创建具有相同属性的对象组时。 * 如果对象的创建需要不同的数据输入,但是除此之外对象实例都是相同的。 总的来说,如果需要创建大量相似对象,而且每个对象都需要一段时间和资源来构造,那么使用原型模式是非常合适的。 ### 案例 1. 浅克隆 浅克隆是指只复制对象的基本数据类型属性,而不复制对象的引用类型属性。这意味着新对象和原对象共享同一个引用类型属性,如果更改新对象或原对象中的引用类型属性,则会影响到另一个对象。实现浅克隆需要重写Cloneable接口中的clone()方法。 ``` public class Email implements Cloneable { private Attachment attachment = null; private Date date; public Email() { this.attachment = new Attachment(); } public Attachment getAttachment() { return attachment; } public void setAttachment(Attachment attachment) { this.attachment = attachment; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public void display() { System.out.println("查看邮件!!"); } @Override public Object clone() { Email email = null; try { email = (Email) super.clone(); return email; } catch (CloneNotSupportedException e) { System.out.println("Clone failure!"); } return null; } } ``` ``` public class Attachment { private String name; public Attachment() { } public Attachment(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void downland() { System.out.println("下载文件!文件名:" + this.name); } } ``` ``` /* 1.email和copyEmail对象地址不一样。 2.复制对象时仅仅复制对象本身,包括基本属性(Date),但该对象的属性引用其他对象时(Attachment),该引用对象不会被复制, 即拷贝出来的对象与被拷贝出来的对象中的属性引用的对象是同一个。所以Date修改后email和copyEmail的Date值不一样, 但是Attachment的name值修改后会一样。 */ @Test public void test_prototype() { Attachment attachment = new Attachment("Java开发者的自我修养"); Email email, copyEmail; email = new Email(); email.setDate(new Date(System.currentTimeMillis() - 7 * 24 * 3600 * 1000)); email.setAttachment(attachment); //浅克隆新的对象 copyEmail copyEmail = (Email) email.clone(); //这里就是判断通过克隆方式创建的对象是否是全新的对象,false为全新对象 System.out.println("邮件地址(克隆的对象地址)是否相同?" + (email == copyEmail)); System.out.println(email); System.out.println(copyEmail); //通过下面几行代码,证实浅拷贝的特点 System.out.println("email和copyEmail的未更改之前的Attachment的Name"); System.out.println("email的Attachment的Name: " + email.getAttachment().getName()); System.out.println("copyEmail的Attachment的Name: " + copyEmail.getAttachment().getName()); //给email设置新的Attachment对象的属性Name email.getAttachment().setName("Java开发之1024个搞事技巧"); System.out.println("email和copyEmail的更改之后的Attachment的Name"); System.out.println("email的Attachment的Name: " + email.getAttachment().getName()); System.out.println("copyEmail的Attachment的Name: " + copyEmail.getAttachment().getName()); System.out.println("比较email.Attachment和copyEmail.Attachment: " + (email.getAttachment().getName() == copyEmail.getAttachment().getName())); System.out.println("email对象: " + email.getAttachment()); System.out.println("copyEmail对象: " + copyEmail.getAttachment()); //给email设置新的Attachment对象的属性Name // 修改email的日期 email.setDate(new Date()); System.out.println("email和copyEmail的更改之后的date属性"); System.out.println("email的date: " + email.getDate()); System.out.println("copyEmail的date: " + copyEmail.getDate()); } ``` 结果: ``` 邮件地址(克隆的对象地址)是否相同?false com.ross.mode.Email@7791a895 com.ross.mode.Email@3a5ed7a6 email和copyEmail的未更改之前的Attachment的Name email的Attachment的Name: Java开发者的自我修养 copyEmail的Attachment的Name: Java开发者的自我修养 email和copyEmail的更改之后的Attachment的Name email的Attachment的Name: week 2 attachment copyEmail的Attachment的Name: week 2 attachment 比较email.Attachment和copyEmail.Attachment: true email对象: com.ross.mode.Attachment@6325a3ee copyEmail对象: com.ross.mode.Attachment@6325a3ee email和copyEmail的更改之后的date属性 email的date: Mon Apr 03 15:40:22 CST 2023 copyEmail的date: Mon Mar 27 15:40:22 CST 2023 ``` 1.email和copyEmail对象地址不一样。 2.复制对象时仅仅复制对象本身,包括基本属性(Date),但该对象的属性引用其他对象时(Attachment),该引用对象不会被复制, 即拷贝出来的对象与被拷贝出来的对象中的属性引用的对象是同一个。所以Date修改后email和copyEmail的Date值不一样, 但是Attachment的name值修改后会一样。 1. 深克隆 深克隆是指复制对象及其所有引用类型属性。这意味着新对象和原对象不共享同一个引用类型属性,更改新对象或原对象中的引用类型属性不会影响到另一个对象。实现深克隆需要在Cloneable接口的clone()方法中使用递归来实现对象的深度复制。 ``` public class Student implements Serializable { private String id; private String name; private ClassInfo classInfo; public Student() { } public Student(String id, String name, ClassInfo classInfo) { this.id = id; this.name = name; this.classInfo = classInfo; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public ClassInfo getClassInfo() { return classInfo; } public void setClassInfo(ClassInfo classInfo) { this.classInfo = classInfo; } //使用序列化技术实现深克隆 public Object deepClone() throws IOException, ClassNotFoundException { //1.创建一个字节数组输出流 ByteArrayOutputStream bos = new ByteArrayOutputStream(); //2.用对象输入流包装 ObjectOutputStream oos = new ObjectOutputStream(bos); //3.将调用该方法的对象写出 oos.writeObject(this); //4.创建一个字节数组输入流,读取刚刚输出到字节数组的对象this ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); //5.用对象输入流包装 ObjectInputStream ois = new ObjectInputStream(bis); //6.返回深克隆对象 return (Student) ois.readObject(); } } ``` ``` public class ClassInfo implements Serializable { private String classId; private String className; public ClassInfo() { } public ClassInfo(String classId, String className) { this.classId = classId; this.className = className; } public String getClassId() { return classId; } public void setClassId(String classId) { this.classId = classId; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } } ``` 测试: ``` @Test public void test_Prototype() throws IOException, ClassNotFoundException { ClassInfo classInfo = new ClassInfo("2001120301", "01软件1班"); Student student = new Student("20014521", "RossZhang", classInfo); Student copyStudent = (Student) student.deepClone(); System.out.println(student); System.out.println(copyStudent); //这里就是判断通过克隆方式创建的对象是否是全新的对象,false为全新对象 System.out.println("学生类地址(克隆的对象地址)是否相同?" + (student == copyStudent)); System.out.println("*********************************************************"); //通过下面几行代码,证实浅拷贝的特点 System.out.println("student和copyStudent的未更改之前的ClassInfo的Name"); System.out.println("student的ClassInfo的Name: " + student.getClassInfo().getClassName()); System.out.println("copyStudent的ClassInfo的Name: " + copyStudent.getClassInfo().getClassName()); //给student设置新的ClassInfo对象的属性Name student.getClassInfo().setClassName("12信息技术管理01班"); System.out.println("student和copyStudent的更改之后的ClassInfo的Name"); System.out.println("student的ClassInfo的Name: " + student.getClassInfo().getClassName()); System.out.println("copyStudent的ClassInfo的Name: " + copyStudent.getClassInfo().getClassName()); /* com.ross.mode.Student@63e2203c com.ross.mode.Student@4d3167f4 学生类地址(克隆的对象地址)是否相同?false ********************************************************* student和copyStudent的未更改之前的ClassInfo的Name student的ClassInfo的Name: 01软件1班 copyStudent的ClassInfo的Name: 01软件1班 student和copyStudent的更改之后的ClassInfo的Name student的ClassInfo的Name: 12信息技术管理01班 copyStudent的ClassInfo的Name: 01软件1班 */ } ``` 1、student和copyStudent对象地址不一样。 2.复制对象时不光复制对象本身,包括基本属性(Date)和该对象的属性引用其他对象 (classInfo),该引用对象也会被复制, 即拷贝出来的对象与被拷贝出来的对象中的属性引用的对象不是同一个。所以Name的student的ClassInfo的Name值不一样, 但是 classInfo 的name值修改后也会不一样。 ## 单例模式 ### 概念 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例 单例模式有一个特点就是不允许外部直接创建,也就是 `new Singleton()`,因此这里在默认的构造函数上添加了私有属性 `private`。 ### 案例 ``` public enum Speak { INSTANCE; public void test() { System.out.println("Hi,你好!!!"); } } ``` 测试: ``` @Test public void test_Enum(){ Speak.INSTANCE.test(); } ``` ## 适配器模式 ### 概念 将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作 ### 解决问题 将不同厂家的功能接口,在自己的项目中使用,此时就需要适配器模式。 如:充电器案例,数据线案例 ### 案例 案例见项目:ross-adapter-01 ### 优缺点 优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。 缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。 ## 桥接模式 ### 概念 桥接(Bridge)模式属于结构型设计模式。通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。把抽象(abstraction)与行为实现(implementation)分离开来,从而可以保持各部分的独立性以及应对它们的功能扩展。 ![1680597127211](image/README/1680597127211.png) * Abstraction:抽象类。 * RefinedAbstraction:扩充抽象类。 * Implementr:实现类接口。 * ConcreteImplmentor:具体实现类 。 ### 解决问题 * 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时 * 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时 * 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时 ### 案例 假设:一个甜品店,售卖的食品有蛋糕、奶茶和牛奶,但是每个商品都有各种口味:草莓味、芒果味、香蕉味。 ![1680597290930](image/README/1680597290930.png) 如果在新增一个菠萝,我们是不是要重新新增一套奶茶和水果绑定吗? 此时可以用桥接模式:创建两个父类,一个食品类一个水果类。两个父类各自包含了相应的子类,然后根据需要将食品和水果进行组合。如下: ![1680597430082](image/README/1680597430082.png) 食物 ``` public abstract class FoodAbstract { protected FruitInterface fruitInterface; public FoodAbstract(FruitInterface fruitInterface) { this.fruitInterface = fruitInterface; } public void setFruitInterface(FruitInterface fruitInterface) { this.fruitInterface = fruitInterface; } public abstract void add(); } ``` ``` public class CakeImpl extends FoodAbstract { public CakeImpl(FruitInterface fruitInterface) { super(fruitInterface); } @Override public void add() { fruitInterface.addFruit("蛋糕"); } } ``` 水果接口 ``` public interface FruitInterface { /** * 添加 * * @param food */ public void addFruit(String food); } ``` ``` public class BananaInterfaceImpl implements FruitInterface { public void addFruit(String food) { System.out.println("香蕉:" + food); } } ``` 测试: ``` @Test public void test_Fruit4() { FoodAbstract cake = new MilkTeaImpl(new BananaInterfaceImpl()); cake.add(); // 结果:香蕉:奶茶 } ``` ### 优缺点 * 优点: 桥接模式可以取代多层继承的方案。 多层继承违背了单一职责原则,复用性较差,类的个数也非常多。桥接模式可以极大的减少子类的个数,从而降低管理和维护的成本。 桥接模式极大的提高了系统可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有的系统,符合开闭原则。 * 缺点 会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程 正确识别出系统中两个独立变化的维度并不是一件容易的事情 ## 组合模式 ### 概念 组合(Composite)模式是一种对象的行为模式。将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。 组合模式的本质:统一叶子对象和组合对象。 组合模式的目的:让客户端不再区分操作的是组合对象还是叶子对象,而是以一个统一的方式来操作。 ![1680921793602](image/README/1680921793602.png) 涉及到三个角色: * 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象定义出公共的接口及其默认行为,可以用来管理所有的子对象。合成对象通常把它所包含的子对象当做类型为Component的对象。在安全式的合成模式里,构件角色并不定义出管理子对象的方法,这一定义由树枝构件对象给出。 * 树叶构件(Leaf)角色:树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。 树枝构件(Composite)角色: * 代表参加组合的有下级子对象的对象。树枝构件类给出所有的管理子对象的方法,如add()、remove()以及getChild()。 ### 解决问题 ### 案例 ![1682137110960](image/README/1682137110960.png) ### 优缺点 组合模式是一种结构型设计模式。 优点: * 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码; * 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”; 缺点: * 设计较复杂,客户端需要花更多时间理清类之间的层次关系; * 不容易限制容器中的构件; * 不容易用继承的方法来增加构件的新功能; ## 装饰器模式 很简单的一句总结,就是套了一层,就可以为所欲为(手动滑稽)! ### 优缺点 优点:装饰类和被装饰类都互不受影响,不会改变原有逻辑,并且使用装饰器模式可以动态扩展功能。 缺点:如果装饰类还是一个接口,那么就可能存在多层装饰,维护起来还是比较麻烦。 与代理模式的区别:确实装饰器模式看起来跟代理模式特别的相似,但是对于代理模式来说可以作出一些操作改变原有代码,也就是说带有侵入性。而装饰器的定义是不能改变原有方法,只能对原有方法作出增强。 ## 外观模式 ![1683275199890](image/README/1683275199890.png) 在外观类的method1中按照先A方法中的method后B方法中method,method2相反,这样主方法只需要调用外观类就可以使用A,B中的方法而不需要知道A,B2个类,这就属于外观模式。