设计原则

内容来自:《设计模式》、《Head First 设计模式》

组件生命周期:组件定义(结构)、创建、服役(行为)、销毁(java自带)

创建型:单例、工厂、抽象工厂、建造者、原型

结构型:代理、装饰、适配器、组合、桥梁、外观、享元

行为型

  • 模板方法、命令、责任链、策略、迭代器
  • 中介者、观察者、备忘录、访问者、状态、解释器

混合:命令链、工厂策略、观察中介者、规格

SOLID(单一职责、开闭原则、里氏替换、接口隔离以及依赖反转)

  • 单一职责SRP(single responsibility principle),每个类只负责自己的事情,比如负责上传下载、负责数据库操作
  • 开闭原则(Open Close Principle),扩展新类而不是修改旧类,对扩展开放,对修改封闭,找出应用中可能需要变化地方,独立出来
  • 里氏替换原则LSP,继承父类而不改变父类,父类的性质在子类仍然有
  • 接口隔离原则ISP(Interface segregation principle),每个类建立在自己的专用接口,而不是建立万能接口
    • 客户端不应该依赖它不需要的接口
    • 一个类对另一个类的依赖应该建立在最小的接口上
    • 一个大类实现多个接口,要使用某个方法时,只将该类转为该接口类,只能用该接口方法,用不了该大类的其他方法,实现接口隔离
  • 依赖倒置原则DIP(Dependency Inversion Principle),针对接口编程,而不是针对实现(Animal animal = getAnimal() 就可以避免硬编码),高层不依赖底层,两者都依赖抽象,细节(实现)应该依赖抽象
  • 迪米特法则LoD(law of demeter),无需直接交互的两个类,如果需要交互,使用中间者
    • 过度使用会产生大量的中介类,复杂性增加,通信效率降低
  • 合成复用原则CRP(composite reuse principle)多用组合,少用继承

阅读总结

高效阅读:

  • 慢一些,理解了能少记
  • 做练习,记笔记
  • 烦躁时离开,加快速度没有帮助,效率低时注意停下来休息
  • 大声说出来,当做指导他人
  • 运用起来,进行重构

以往是代码复用,设计模式是经验复用

7大设计原则

结合实际

  • 开闭原则,扩展新类而不是修改旧类,对扩展开放,对修改封闭,找出应用中可能需要变化地方,独立出来

  • 里氏替换原则LSP,继承父类而不改变父类,父类的性质在子类仍然有

  • 单一职责SRP(single responsibility principle),每个类只负责自己的事情,比如负责上传下载、负责数据库操作

  • 接口隔离原则ISP(Interface segregation principle),每个类建立在自己的专用接口,而不是建立万能接口

  • 迪米特法则LoD(law of demeter),无需直接交互的两个类,如果需要交互,使用中间者

    • 过度使用会产生大量的中介类,复杂性增加,通信效率降低
  • 依赖倒置原则DIP,针对接口编程,而不是针对实现(Animal animal = getAnimal() 就可以避免硬编码),高层不依赖底层,两者都依赖抽象,细节(实现)应该依赖抽象

  • 合成复用原则CRP(composite reuse principle)多用组合,少用继承

策略模式

鸭子案例

  1. 鸭子可以swim、quack、display
    1. 通过继承Duck,实现不同的抽象display
  2. 鸭子需要能飞/能跳舞
    1. 直接在Duck类添加Fly方法,将会导致橡皮鸭也能飞
      1. 为了解决这个问题,就需要重写橡皮鸭的fly方法(违反了里氏替换原则)
        1. 根据里氏替换原则,子类要能完全替代父类
      2. 此时如果需要一只模型鸭,不会飞也不会叫,就需要覆盖fly和quack方法
      3. 这样会导致牵一发动全身,添加新行为就需要浏览所有子类,覆盖这些不使用的方法
    2. 使用Fly接口,会导致代码无法复用,上百个鸭子都需要使用实现他
  3. 找出会变的和不会变的,会变的为FlyBehavior接口,具体实现为FlyWithWings、FlyNoWay
    1. 每个Duck都有FlyBehavior属性,实现Duck时初始化具体的Fly实现
    2. 此时可以做到代码复用,以及运行时改变(通过setBehavior)
    3. 在类初始化时设置对应的fly属性(如果直接new 将针对实现编程,需要解决)

设计模式

创建型

将对象的创建和使用分离,降低系统的耦合度,使用者无需关注对象的创建细节

  • 工厂模式:对象创建由工厂完成

  • 建造者模式:对象创建由建造者完成

  • 原型模式:原来对象克隆完成

  • 单例模式:系统始终只有一个实例

img

单例模式

volatile static ,两if、synchronized、注意访问范围、static

public class Singleton {
    private volatile static Singleton singleton;
    private singleton() {}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

静态内部类

静态内部类只有在引用时才会加载

public class Singleton {
    private singleton() {}
    private static class SingletonHandler {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHandler.INSTANCE;
    }
}

工厂方法

做多种口味的披萨,需要if判断new哪种类型,但是可能有些披萨会过季,不再使用,也有可能会有新口味的披萨

  • 直接通过if new就需要修改代码(不符合对扩展开放,对修改封闭)
  • 并且不符合依赖倒置原则(披萨店依赖了具体口味的披萨)

做披萨的过程较为复杂,不希望公开

使用工厂模式,可以简化生成复杂对象的难度,解耦。

简单工厂

简单工厂中,将if判断new对象封装进入factory中,对客户封装了对象初始化的过程。

createPizza方法通常为静态方法

优点:

  • 对象的创建和使用解耦

缺点:

  • 不灵活,增加新的产品就需要修改工厂类代码

工厂方法

定义一个用于创建对象的接口或抽象类,让子类决定实例化那个类,抽象类中保留通用的流程

orderPizza是公用的,但是createPizza具体由子类实现

类图

优点:

  • 遵循开闭原则,添加新产品只需要添加新工厂

缺点:

  • 增加类的数量,产品类型多,工厂类也多

抽象工厂

围绕超级工厂创建其他工厂

在工厂方法模式中,一个具体的工厂负责生产一类具体的产品,但如果一个具体的工厂生产多种类型的产品,如:生产不同肤色的男人和女人就需要抽象工厂模式。

【看不懂】【设计模式】p156

结构型

关注怎么组合对象/类

  • 结构型模式关心类的组合,由多个类组成更大的(继承)
  • 对象结构型模式关心类与对象的组合,通过关联关系在一个类中定义另一个类的实例对象

合成复用原则,使用关联替代继承,所以大部分都是对象结构型模式

适配器模式:两个不兼容的接口适配

桥接模式:相同功能抽象化和实现化解耦,抽象和实现可以独立升级

过滤器模式:使用不用的标准来过滤一组对象

组合模式:相似对象进行组合,形成树形结构

装饰器模式:在现有对象添加新功能,但是不改变其结构

外观模式:现有系统添加接口,隐藏系统的复杂性

享元模式:重用现有的同类对象,如果没有匹配的,就创建

代理模式:一个类代表另一个类的功能

img

代理模式

用代理对象代替对真实对象的访问,在不改变目标对象的前提下,提供额外的功能操作,扩展目标对象的功能

和装饰模式很像,静态代理就是装饰器

静态代理

手动完成,不灵活,对象:房东(RealSubject)、中介(ProxySubject)、接口Subject(租房)

image-20210723220726271

动态代理

运行时动态生成类字节码,加载到JVM中

实现:JDK动态代理、CGLIB动态代理

JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,需要满足以下要求:

  1. 实现InvocationHandler接口,重写invoke()

  2. 使用Proxy.newProxyInstance()产生代理对象

  3. 被代理的对象必须要实现接口

CGLib 必须依赖于CGLib的类库,需要满足以下要求:

  1. 实现MethodInterceptor接口,重写intercept()

  2. 使用Enhancer对象.create()产生代理对象

JDK动态代理目标类和代理类都实现同一个接口,目标类和代理类是平级的。

Cglib动态代理给目标类生个孩子(子类,也就是代理类),目标类和代理类是父子继承关系。

JDK动态代理

// 方法
public static Object newProxyInstance(
        ClassLoader loader, // 类加载器,加载代理对象
        Class<?>[] interfaces, // 被代理的接口
        InvocationHandler h   // 实现了invocationHandler接口的对象,如1
    ) throws IllegalArgumentException {
}

// 1 
public interface InvocationHandler {
    /**
     * 当你使用代理对象调用方法的时候实际会调用到这个方法
     */
    public Object invoke(
                Object proxy,  // 动态生成的代理类
                Method method,  // 要调用的方法
                Object[] args  // 对应参数
            ) throws Throwable;
}

使用

JDK动态代理

image-20210723224812363

  1. 定义一个接口及其实现类;
  2. 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象
实现InvocationHandler接口
// 具体增强
class SingerHandler implements InvocationHandler{
    SingerService singer;

    public SingerHandler(SingerService singer){
        this.singer = singer;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object obj = null;
        // 方法前
        obj = method.invoke(singer, args);
        // 方法后
        return obj;
    }

}

Factory

// 2 if factory
public class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目标类的类加载
                target.getClass().getInterfaces(),  // 代理需要实现的接口,可指定多个
                new SingerHandler(target)   // 代理对象对应的自定义 InvocationHandler
        );
    }
}
使用
public class Main {
    public static void main(String[] args) throws Exception {
        SingerService star = new JoyChouService();
        // 方式 1
        SingerService proxy = 
            (SingerService) Proxy.newProxyInstance(
                    ClassLoader.getSystemClassLoader(), 
                    new Class[]{Singer.class},
                    new SingerHandler(star)
                );
        // 方式 2   工厂
        proxy = JdkProxyFactory.getProxy(star);

        proxy.sing();
    }
}

CGLIB动态代理机制

JDK动态代理只能代理实现了接口的类

CGLIB是一个基于ASM字节码的生成库,允许在运行时对字节码进行修改和动态生成,通过继承的方式实现代理

Spring的AOP模块中,如果目标对象实现了接口,则默认采用JDK动态代理,否则采用CGLIB动态代理

public interface MethodInterceptor extends Callback{
    // 拦截被代理类中的方法
    public Object intercept(
                Object obj, // 被代理的对象
                java.lang.reflect.Method method,  // 需要增强的方法
                Object[] args, // 方法参数
                MethodProxy proxy // 调用原始方法
        ) throws Throwable;
}
实现流程
  1. 定义一个类;
  2. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类;
依赖引入
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>
获取代理类
public class CglibProxyFactory {

    public static Object getProxy(Class<?> clazz) {
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类,将为其创建子类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        // enhancer.setCallback(new SingerMethodInterceptor());
        // or
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj,
                                   Method method,
                                   Object[] args,
                                   MethodProxy methodProxy) throws Throwable {
                //调用方法前
                System.out.println("before method " + method.getName());
                // 原始方法
                Object object = methodProxy.invokeSuper(obj, args);
                //调用方法后
                System.out.println("after method " + method.getName());
                return object;
            }
        })
        // 创建代理类
        return enhancer.create();
    }
}

/** 实现MethodIntercptor接口 */
/**
 * 自定义MethodInterceptor
 */
public class SingerMethodInterceptor implements MethodInterceptor {
    /**
     * @param obj         被代理的对象(需要增强的对象)
     * @param method      被拦截的方法(需要增强的方法)
     * @param args        方法入参
     * @param methodProxy 用于调用原始方法
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //调用方法前
        System.out.println("before method " + method.getName());
        // 原始方法
        Object object = methodProxy.invokeSuper(obj, args);
        //调用方法后
        System.out.println("after method " + method.getName());
        return object;
    }
}
使用
JoyChouService joyChouService = 
    (JoyChouService) CglibProxyFactory.getProxy(JoyChouService.class);

joyChouService.sing("java");

对比

JDK动态代理和CGLIB动态代理

  • 灵活性:CGLIB更灵活
    • JDK动态代理只能代理实现了接口的类或者直接代理接口,而CGLIB可以代理未实现任何接口的类
  • 效率:JDK动态代理更快

静态和动态

  • 灵活性:

    • 动态代理灵活不需要实现接口,可以直接代理实现类,不需要每个目标类都创建一个代理类
    • 静态代理每增加一个方法,目标对象和代理对都需要修改
  • JVM层面:

    • 静态代理在编译时,就已经将接口、实现类、代理变成了class文件
    • 动态代理是运行时动态生成类字节码,并加载到JVM中的

使用场景

  • Mybatis的Mapper
    • 动态代理
    • UserMapper等各种dao,mybatis帮我们实现了MapperProxy
  • Seata的DataSourceProxy
    • 协调器与本地事务沟通什么时候提交、回滚,原数据源可能不能直接与协调器沟通,所以需要代理数据源,对原数据源包装
  • DruidDataSource的Proxy
    • 监控链

装饰模式【重要】

视频学习

对象结构型

先现有对象添加新功能,但是不改变原结构

装饰器继承并组合需要增强的类,对原方法进行扩展

和适配器很像

  • 适配器连接两个类,使用一个类增强另一个类
  • 装饰器是只增强一个类,不连接别的类

书籍学习

装饰者模式

https://www.cnblogs.com/of-fanruice/p/11565679.html

抽象奶茶(被装饰者)有多种实现,调料(装饰物)继承被装饰者,装饰者可以使用被装饰者的方法并改造

使用场景:扩展一个类的功能,动态增加功能,动态撤销

使用场景

  • springSession进行session和redis关联,HttpRequestWrapper
    • wrapper继承httpSession,从redis中添加、获取属性
  • MybatisPlus提取QueryWrapper
  • Spring的BeanWraper,增强bean
  • Spring Webflux的WebHandlerDecorator
  • 某个类使用时需要扩展

适配器模式

视频简介

电影播放器想转中文字幕,我们有字幕翻译机,不应该直接修改电影播放器

使用适配器,组合/继承,实现play方法中使用字幕功能

再调用适配器时就能获得有字幕的播放

不改变原类,使用新类实现扩展原功能

适配器继承/组合需要适配的类,实现原来的接口(播放),对原方法使用被适配的类方法进行扩展(播放且有字幕)

使用场景:

  • tomcat将tomcat.Request转为servlet.Request
    • tomcat -> CoyoteAdapter -> servletRequest
  • Spring AOP中的advisor Adapter,增强器的适配器
    • 前置、后置、返回、结束 Advisor
    • 底层的目标方法
  • SpringMvc的HandlerAdapter
    • controller.hello()
    • servlet.doGet()
  • SpringBoot中的WebMvcConfigurerAdapter为什么存在又取消

书籍简介

把类的接口换成客户端期望的另一种接口,让原本不匹配的的两个类能在一起工作

过多的使用会让系统混乱

场景:

  • 系统需要复用现有类,而该类的接口不符合系统的需求,可以使用适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作

  • 多个组件功能类似,但接口不统一且可能会经常切换时,可使用适配器模式,使得客户端可以以统一的接口使用它们

类的适配器

//适配器Adapter继承自Adaptee,同时又实现了目标(Target)接口。
public class Adapter extends Adaptee implements Target {

    //目标接口要求调用Request()这个方法名,但源类Adaptee没有方法Request()
    //因此适配器补充上这个方法名
    //但实际上Request()只是调用源类Adaptee的SpecificRequest()方法的内容
    //所以适配器只是将SpecificRequest()方法作了一层封装,封装成Target可以调用的Request()而已
    @Override
    public void Request() {
        this.SpecificRequest();
    }

}

对象的适配器

class Adapter implements Target{  
    // 直接关联被适配类  
    private Adaptee adaptee;  

    // 可以通过构造函数传入具体需要适配的被适配类对象  
    public Adapter (Adaptee adaptee) {  
        this.adaptee = adaptee;  
    }  

    @Override
    public void Request() {  
        // 这里是使用委托的方式完成特殊功能  
        this.adaptee.SpecificRequest();  
    }  
}  

桥接模式

视频简介

桥接抽离出经常变化的类,组合进来

将抽象和实现解耦,两者可以独立变化

手机有在线上和线下时有不同的价格,可能以后还有团购、学生渠道,将经常变化的抽离出来,组合进手机

省去了很多类

使用场景

  • 存在两个独立变化的纬度,两个纬度都需要扩展(手机和渠道价格)
  • 不希望使用继承导致系统类增加时
  • 抽象化角色(渠道)和具体化角色(手机)更灵活
  • InputStreamReader桥接+适配器
    • byte stream to character stream
    • 桥接:继承reader,组合stream适配器
    • 适配:StreamDecoder,继承reader,组合inputstream

门面模式

提供统一的接口,为多个复杂的子系统提供一个一致的接口

门面组合其他类,统一从门面进入,访问别的类的方法

场景:

  • 医院看病,需要挂号、门诊、取药等,让接待人员统一完成
  • 三层开发模式
  • 分布式系统的网关
  • tomca中的RequestFacade

组合模式

把一组相似的对象,当做单一的对象,如树形菜单,一个菜单有多个菜单

场景

  • 层级关系
  • 部门层级结构
  • 组合别的对象也是,都是object

享元模式

共享内容为内部状态,不能共享的为外部状态,给外部提供改变外部状态的方法

通常要使用工厂模式,享元工厂维护享元池

去足疗店,服务员是公用的,服务员的个人信息是不变的,外部状态是改变的

所有的池化都是享元

抽出共享的和不共享(并添加修改方法)

@AllArgsConstructor
public class BeautifulWaitress extends AbstractWaitressFlyweight{
    String id;//工号
    String name;//名字
    int age;//年龄
    //以上是不变的

    @Override
    void service() {
        System.out.println("工号:"+id+";"+name+" "+age+" 正在为您服务...");
        //改变外部状态
        this.canService = false;
    }

    @Override
    void end() {
        System.out.println("工号:"+id+";"+name+" "+age+" 服务结束...请给五星好评");

        this.canService = true;
    }
}

工厂


public class ZuDao {

    private static Map<String,AbstractWaitressFlyweight> pool = new HashMap<>();
    //享元,池子中有对象
    static {
        BeautifulWaitress waitress =
                new BeautifulWaitress("1111","张三",18);

        BeautifulWaitress waitress2 =
                new BeautifulWaitress("9527","李四",20);
        pool.put(waitress.id,waitress);
        pool.put(waitress2.id,waitress2);
    }

    public void addWaitress(AbstractWaitressFlyweight waitressFlyweight){
        pool.put(UUID.randomUUID().toString(),waitressFlyweight);
    }

    public static AbstractWaitressFlyweight getWaitress(String name){
        AbstractWaitressFlyweight flyweight = pool.get(name);
        if (flyweight == null) {
            for (AbstractWaitressFlyweight value : pool.values()) {
                //当前共享对象能否是否
                if(value.isCanService()){
                    return value;
                }
            };
            return null;
        }

        return flyweight;

    }

}

行为型

  • 模板方法模式:父类定义算法框架,一些实现放到子类(食谱定义做菜流程,每步实现要看自己)

  • 策略模式:每种算法独立封装,不同情况使用不同算法(装备武器)

  • 状态模式:每种状态独立封装,不同状态内部封装不同的行为

  • 命令模式:将请求封装为一个对象,发出请求的责任和执行请求的责任分开

  • 责任链模式:处理着为链式结构,依次调用

  • 备忘录模式:核心抽离进行保存

  • 解释器模式:定义语法解析规则

  • 观察者模式:被观察者组合多个观察者,状态变化则通知

  • 中介者模式:取消类/对象的直接调用关系,使用中介者维护

  • 迭代器模式:数据结构的遍历规则

  • 访问者模式:分离对象结构,与元素的执行算法

类行为型模式:模板方法、解释器模式

img

责任链模式

想要让一个以上的对象有机会能处理某个请求时

优点:

  • 解耦请求的发送者和接受者
  • 简化对象,不需要知道链的结构
  • 通过改变链中的成员或者次序,动态新增或者删除责任

用途:

  • 多条件流程:权限控制
  • ERP系统审批流程:项目经理、人事、总经理
  • Java过滤器底层实现Filter
/**
 * 抽象处理角色
 */
public abstract class AbstractHandler {
    protected AbstractHandler next;

    public void setNext(AbstractHandler next) {
        this.next = next;
    }

    public abstract void handlerRequest();
}

/**
 * 具体处理者A
 */
public class ConcreteHandler_A extends AbstractHandler{
    public void handlerRequest() {
        if(this.next != null){
            this.next.handlerRequest();
        }
    }
}

/**
 * 责任链客户端
 */
public class HandlerClient {
    public static void main(String[] args) {
        AbstractHandler firstHandler = new HandlerClient().getFirstHandler();
        // 调用第一个处理者的handler方法
        firstHandler.handlerRequest();
    }

    /**
     * 设置责任链 并返回第一个处理者
     * @return
     */
    public AbstractHandler getFirstHandler(){
        AbstractHandler a = new ConcreteHandler_A();
        AbstractHandler b = new ConcreteHandler_B();
        a.setNext(b);
        return a;
    }
}

模板方法

父类定义算法框架,一些实现放到子类(食谱定义做菜流程,每步实现要看自己)

场景

  • spring整个继承体系,beanFactory定义骨架

策略模式

每种算法独立封装,不同情况使用不同算法(装备武器)

组合其他策略类

场景:

  • 避免多重if else、switch
  • spring的InstantiationStrategy
  • 线程池拒绝策略

状态模式

和策略模式基本一样

  • 但是策略模式关注的是使用不同的策略
  • 状态模式的关注是状态的自动流转,在必要时能切换下一个状态

环境类组合其他状态类,每个状态类中定义其下一个状态类,环境类中能控制进入下一个状态

使用场景

  • 流程框架和状态机

中介者模式

网状调用,改为星状调用

飞机起飞联系控制台,而不是联系所有飞机,确定是否能起飞

场景:

  • SpringMVC的DispatcherServlet,提取controller,model,view调用
  • 分布式系统中的网关
  • 迪米特法则
  • 门面模式针对封装内部,中介者协调多对多交互

观察者模式

对象变化时,依赖其(被其组合的)的对象都得到通知

被观察者组合所有观察者,被观察者发生改变时,调用所有观察者的方法

场景:

  • spring事件机制,监听器(事件来了,就遍历观察者通知)
  • vue双向绑定,理解:值是被观察者,拥有对应的input框,被修改时遍历所有框,进行值修改
  • 响应式编程
  • swing中的Jbutton,addActionListener(ActionListener),button拥有多个内部类,状态变更则通知所有listener

解释器模式【TODO】

解释器主要是能用来构建语法树,进行解析语法的

命令模式

命令模式就像controll、service、dao,一层套一层的感觉

迭代器模式

提供一个迭代器,能访问聚合对象,不暴露聚合对象的内部

对象内组合迭代器,迭代器访问对象内部信息

迭代器模式中,隐藏了对象内部的结构,提供内部统一的迭代器,其能够遍历获取内部元素

访问者模式

访问者模式中,对象中每个元素都提供accept访问器的方法,让访问器拥有该元素,并对该元素进行修改

一个对象,组合多个元素,如果想修改元素,但是不改变对象,可以使用访问者模式

每个元素提供accept方法(访问者作为入参,依赖访问者),将自身传递给访问者,访问者能够自由修改元素属性

优点:?

缺点:

  1. 违反开闭原则,对象组合新的元素时,需要实现accept方法,访问者也需要添加处理对应元素的方法
    1. 【TODO】这主要是对象与访问者的扩展性不强,那为什么不把vistor方法统一到抽象元素中
    2. 且将vistor的visit方法统一?
  2. 违反依赖倒置,依赖了具体元素【TODO】那不就是元素统一就行了?不过就比较难定制化了
  3. 破坏封装,具体元素对访问者暴露了细节

场景

其他

img

状态模式

状态模式的核心就是封装,状态的变更引起行为的变更,从外部看像这个对象对应的类发生变化了一样

State:抽象状态角色

接口或抽象类,负责对象状态定义,封装环境角色以及状态变化

ConcreteState:具体状态角色

本状态的行为管理、趋向状态处理

  • —— 本状态下要做的事,如何过渡到下一状态

Context:环境角色

定义客户端需要的接口,负责具体状态转化

抽象状态

public abstract class State {
    // 供子类访问
    protected Context context;
    // 环境
    public void setContext(Context context) {this.context = context;}
    // 行为1
    public abstract void handle1();
    // 行为2
    public abstract void handle2();
}

具体状态

public class ConcreteState1 extends State {
     @Override
    public void handle1() {
        //本状态下必须处理的逻辑
    }
    @Override
    public void handle2() {
        //设置当前状态为stat2
        super.context.setCurrentState(Context.STATE2);
        //过渡到state2状态,由Context实现
        super.context.handle2();
    }
}
public class ConcreteState2 extends State {
    @Override
    public void handle1() {
        //设置当前状态为state1
        super.context.setCurrentState(Context.STATE1);
        //过渡到state1状态,由Context实现
        super.context.handle1();
    }
    @Override
    public void handle2() {
        //本状态下必须处理的逻辑
    }
}

环境角色

public class Context {
    //定义状态
    public final static State STATE1 = new ConcreteState1();
    public final static State STATE2 = new ConcreteState2();
    //当前状态
    private State CurrentState;
    //获得当前状态
    public State getCurrentState() {
        return CurrentState;
    }
    //设置当前状态
    public void setCurrentState(State currentState) {
        this.CurrentState = currentState;
        //切换状态
        this.CurrentState.setContext(this);
    }
    //行为委托
    public void handle1(){
        this.CurrentState.handle1();
    }
    public void handle2(){
        this.CurrentState.handle2();
    }
}

使用

public class Client {
    public static void main(String[] args) {
        //定义环境角色
        Context context = new Context();
        //初始化状态
        context.setCurrentState(new ConcreteState1());
        //行为执行
        context.handle1();
        context.handle2();
    }
}

学习过程

221008【设计原则+创建模式简介】

大厂学苑第一集,七大设计原则,单一职责(作用),合成复用原则(组合),里氏替换原则(继承),接口封闭原则(接口),开闭原则(拆分)、依赖倒置原则(依赖使用)、迪米特法则(交互)

设计原则:类首先只做一件事,多用组合,少用继承,使用继承时不改变父类,他的接口应该是专用的,增加新功能时不改变原类,扩展新类,使用时依赖他的抽象,与其他不相关的类交互时使用中间者

设计模式由类的定义、创建、执行、销毁完成

221009 【适配+桥接+装饰】

跳过单例到16集

适配器继承/组合需要适配的类,实现原来的接口(播放),对原方法使用被适配的类方法进行扩展(播放且有字幕)

桥接抽离出经常变化的类,组合进来

装饰器继承并组合需要增强的类,对原方法进行扩展

221010【代理】

疑问:

JDK静态代理被代理类必须要实现一个接口,为什么

CGLIB底层通过操作字节码,直接将代理类继承被代理类

221011【门面+组合+享元+模板】

看到25

门面模式,通过一个类统一提供别的类的入口

组合模式,部门的关系,部门下面有部门

享元模式,抽离出不变的与变化的,变化的为外部状态,提供方法给别人修改状态,通常和工厂模式使用,形成池子

模板方法,父类定义运行骨架,子类进行实现细节

221011【策略模式+状态模式】

看到27

策略模式组合其他策略类,在不同情况下使用不同策略

状态模式中,场景组合状态类,状态类定义了其下一个状态,场景能够规定进入下一个状态。

但是感觉没有很深的理解,只是简单的学习了,学习的时间也比较短,感觉时间不够用。

221103【中介者+观察者】

看了27、28

中介者:多个对象互相调用,改为通过中介与其他对象调用,就像平时得和所有亲戚交流,记得他们,亲戚也得直接和你交流,但是也可以通过妈妈,就能联系到所有亲戚,亲戚也能通过妈妈了解到你

观察者:被观察者发生改变,对拥有的所有观察者进行提醒,双向绑定、事件机制(监听器)

221106【gradle】

下载:https://gradle.org/releases/

v6.8.2,complete

GRADLE_HOME(指向根目录)、PATH(添加%GRADLE_HOME%\bin)、GRADLE_USER_HOME【可以指向安装目录自己创建的.gradle文件夹】

配置加速,给gradle安装目录下init.d文件夹,放一个init.gradle文件

gradle.projectsLoaded {
    rootProject.allprojects {
        buildscript {
            repositories {
                def JCENTER_URL = 'https://maven.aliyun.com/repository/jcenter'
                def GOOGLE_URL = 'https://maven.aliyun.com/repository/google'
                def NEXUS_URL = 'http://maven.aliyun.com/nexus/content/repositories/jcenter'
                all { ArtifactRepository repo ->
                    if (repo instanceof MavenArtifactRepository) {
                        def url = repo.url.toString()
                        if (url.startsWith('https://jcenter.bintray.com/')) {
                            project.logger.lifecycle "Repository ${repo.url} replaced by $JCENTER_URL."
                            println("buildscript ${repo.url} replaced by $JCENTER_URL.")
                            remove repo
                        }
                        else if (url.startsWith('https://dl.google.com/dl/android/maven2/')) {
                            project.logger.lifecycle "Repository ${repo.url} replaced by $GOOGLE_URL."
                            println("buildscript ${repo.url} replaced by $GOOGLE_URL.")
                            remove repo
                        }
                        else if (url.startsWith('https://repo1.maven.org/maven2')) {
                            project.logger.lifecycle "Repository ${repo.url} replaced by $REPOSITORY_URL."
                            println("buildscript ${repo.url} replaced by $REPOSITORY_URL.")
                            remove repo
                        }
                    }
                }
                jcenter {
                    url JCENTER_URL
                }
                google {
                    url GOOGLE_URL
                }
                maven {
                    url NEXUS_URL
                }
            }
        }
        repositories {
            def JCENTER_URL = 'https://maven.aliyun.com/repository/jcenter'
            def GOOGLE_URL = 'https://maven.aliyun.com/repository/google'
            def NEXUS_URL = 'http://maven.aliyun.com/nexus/content/repositories/jcenter'
            all { ArtifactRepository repo ->
                if (repo instanceof MavenArtifactRepository) {
                    def url = repo.url.toString()
                    if (url.startsWith('https://jcenter.bintray.com/')) {
                        project.logger.lifecycle "Repository ${repo.url} replaced by $JCENTER_URL."
                        println("buildscript ${repo.url} replaced by $JCENTER_URL.")
                        remove repo
                    }
                    else if (url.startsWith('https://dl.google.com/dl/android/maven2/')) {
                        project.logger.lifecycle "Repository ${repo.url} replaced by $GOOGLE_URL."
                        println("buildscript ${repo.url} replaced by $GOOGLE_URL.")
                        remove repo
                    }
                    else if (url.startsWith('https://repo1.maven.org/maven2')) {
                        project.logger.lifecycle "Repository ${repo.url} replaced by $REPOSITORY_URL."
                        println("buildscript ${repo.url} replaced by $REPOSITORY_URL.")
                        remove repo
                    }
                }
            }
            jcenter {
                url JCENTER_URL
            }
            google {
                url GOOGLE_URL
            }
            maven {
                url NEXUS_URL
            }
        }
    }
}

idea中 build tools -> gradle 使用刚刚添加的gradle根目录和仓库路径

use gradle from specified location

221113【解释器、命令、迭代器、访问者】

p33-36

解释器主要是能用来构建语法树,进行解析语法的,但是我没看懂

命令模式就像controll、service、dao,一层套一层的感觉

迭代器模式中,隐藏了对象内部的结构,提供统一的迭代器,其能够遍历获取内部元素

访问者模式中,对象中每个元素都提供accept访问器的方法,让访问器拥有该元素,并对该元素进行修改