• 设计模式学习


    设计模式六大原则

    ●开闭原则

    ​ 对扩展开放,对修改关闭,在代码层面而言就是在你有新的需求的时候,你应当增加新的对象来实现,而不是修改原来的对象

    ​ 继承

    ●里氏替换原则

    ​ 基类可以出现,那么子类一定也能行

    • 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法
    • 子类中可以增加自己特有的方法
    • 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松
    • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格

    接口隔离原则

    用多个专门的接口比使用单一的总接口要好。一个类对另外一个类的依赖性应当是建立在最小的接口上的;

    一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。

    举个栗子:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。

    解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系

    ●依赖倒转原则

    ​ 针对接口编程

    高层模块不应该依赖低层模块,两者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象

    也就是说,尽量依赖接口,不要依赖细节

    例如:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

    解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。

    ●迪米特法则,最少知道原则

    ​ 减少耦合

    又叫做最少知道原则,就是说一个对象应当对其它对象有尽可能少的了解,类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大,所以一个对象应该对其他对象保持最少的了解,也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息,迪米特法则初衷在于降低类之间的耦合。由于每个类尽量减少对其它类的依赖,因此。很容易使得系统的功能模块独立,相互之间不存在(或很少代码体现有)依赖关系。

    举个栗子:Leader想从Teacher那里知道现有Student的的总数,它们之间的调用关系应该为Leader—>Teacher—>Student,Leader与Student并无直接联系,所以在Leader类的方法中不应该出现Student类

    合成复用原则

    尽量合成或者聚合而不是继承

    模板方法模式:

    Spring中jdbcTemplate、hibernateTemplate 等以Template结尾的对数据库操作的类,它们就使用到了模板模式。

    包装器设计模式:

    我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。

    工厂模式

    工厂设计模式: Spring使用工厂模式通过BeanFactory、ApplicationContext 创建bean对象。

    简单工厂:一个工厂类根据传入的参数决定创建出那一种产品类的实例。

    优点:创建对象分离开,解耦,工厂负责创建对象,程序员只需要去调用就行。

    创建一个汽车的接口,定义一个汽车名字的方法,然后定义几个品牌类去实现这个接口,

    然后去写一个工厂

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bUnncBVE-1670295589080)(…/img/image-20220831145555660.png)]

    当时当想给添加别的汽车品牌的时候,就需要添加代码,违反开闭原则,需要修改工厂的代码

    缺点:当我们新增类的时候,需要去修改工厂配置,增加创建对象的分支,每次都要去修改

    抽象工厂模式:

    工厂的工厂:一个大工厂,里面有具体的不同产品的小工厂,每个生成的工厂都能按照简单工厂模式提供对象,他们自己决定生产哪一些产品

    比如苹果有创建苹果的工厂,各自有各自的工厂,免除了简单工厂模式的每次新增就会修改工厂代码,各自负责自己的工厂创建,每个工厂决定到底生产哪些产品

    单例模式

    单例设计模式:Spring中的Bean默认都是单例的。

    概念

    全世界就只要一个—在整个java程序中,只有这个类的一个实例

    比如Student a = new Student(); 就是Student类只创建这一个实例,只能有这一个对象存在

    主要解决:一个全局使用的类频繁地创建与销毁。在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)

    何时使用:当您想控制实例数目,节省系统资源的时候。

    关键代码:构造函数是私有的。

    1、为了保证只有一个对象,不能new对象,所以设置构造方法私有。

    2、只能通过方法或者属性获取对象,如果通过属性获取,这个属性是可以修改的,所以属性只能

    是私有的。所以只能通过方法获取。

    3、由于我们不能new对象,所以获取对象的方法定是静态的。属性也得是静态的,因为不是静态的,静态的方法访问不到

    **缺点:**没有接口,不能继承,与单一 职责原则冲突,一 个类应该只关心内部逻辑, 而不关心外面怎么样来实例化。

    使用场景:

    • 1、要求生产唯一序列号。
    • 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
    • 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

    **注意事项:**getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

    **枚举:**对象固定,私有构造器

    饿汉

    类加载后一开始就会创建对象,

    缺点就是占内存

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ZQ7CksV-1670295589081)(…/img/image-20220831165809527.png)]

    懒汉模式

    需要的时候才会去创建对象

    好处:节省内存 坏处用的时候才创建稍微有点慢

    线程不安全,当创建了100个线程,调用getInstance方法时,可能会有多个线程同时看到没有new,就会执行多次new,调用多次构造方法(也就是会进行多次初始化)

    线程安全 给getInstance方法加synchronize锁

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kf8Pfus0-1670295589082)(…/img/image-20220831165549693.png)]

    双重校验锁–安全的懒汉式

    我们可以选择不给getInstance方法加synchronize锁,而是在这个方法里面去进行加synchronize锁,因为方法锁的范围太广,其他线程阻塞的范围就大,时间就长。

    双锁是懒汉为了解决线程安全演变来的

    当多线程调用getInstance,不知道是不是第一次调用,有没有创建对象,所以要判断一下属性是否为空

    为空需要创建对象,但是在多线程的情况下,访问这个方法的情况下,多个线程可能会创建多个对象,就不是单例了

    所以在判断是不是为空后,需要给这个对象加synchronize锁。

    假设第一个线程执行完,接下来的线程就会来进行竞争锁,它还是要继续判断是否为空,不为空,直接return

    synchroniz不能防止指令重排序,instance = new Instance () ;可能会发生指令重排序,就是另一个线程会访问到Instance 仍然为空,再次进入到第一个if循环

    类属性加volatile的原因:第一个线程加锁之后创建了对象,能够让其他线程及时发现 instance 不是空了,这样其他线程就不用进入第一层判断了,加快了程序速度;如果没有加volatile,不能防止指令重排序,可能对象创建了,但是句柄的地址没有传给属性,判断时还是空,这样就需要继续走第一层if。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F2yfo0tB-1670295589082)(…/img/image-20220905152440380.png)]

    建造者模式

    它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成.

    建造者模式包括的角色:

    (1)Builder:给出一个抽象接口或抽象类,以规范产品的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建,一般由子类具体实现。

    (2)ConcreteBuilder:Builder接口的实现类,并返回组建好对象实例

    (3**)Director(指挥者):调用具体建造者来创建复杂对象的各个部分**,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

    (4)Product:要创建的复杂对象,产品类。

    建造者模式的使用场景:

    (1)当产品有复杂的内部构造时(参数很多)。

    (2)需要生产的产品的属性相互依赖,这些属性的赋值顺序比较重要时(因为在调用ConcreteBuilder的赋值方法时是有先后顺序的)。

    建造者模式优缺点

    建造者模式的优点:

    (1)建造模式是将复杂的内部创建封装在内部,对于外部调用的人来说,只需要传入建造者和建造工具,对于内部是如何建造成成品的,调用者无需关心,良好的封装性是建造者模式的优点之一。

    (2)建造者类逻辑独立,易拓·1`展。

    建造者模式的缺点:

    很明显产生了多余的Build对象以及Dirextor对象,消耗了内存。

    要组装一台电脑(Computer类),我们假设它有三个部件:CPU 、主板以及内存。
    
    在Computer类中提供三个set方法分别设置这三个属性。
    public class Computer {
        private String mCpu;
        private String mMainboard;
        private String mRam;
     
        public void setmCpu(String mCpu) {
            this.mCpu = mCpu;
        }
     
        public void setmMainboard(String mMainboard) {
            this.mMainboard = mMainboard;
        }
     
        public void setmRam(String mRam) {
            this.mRam = mRam;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    Builder类	
    里面提供了安装CPU、主板和内存的抽象方法,以及组装成电脑的create方法
    public abstract class Builder {
        public abstract void buildCpu(String cpu);
        public abstract void buildMainboard(String mainboard);
        public abstract void buildRam(String ram);
        public abstract Computer create();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    Builder实现类
    里面不仅新建了Computer的实例,还提供了安装CPU、主板和内存的具体实现方法,并且在组装成电脑的create方法中将该Computer对象实例返回
    public class MyComputerBuilder extends Builder {
        private Computer mComputer = new Computer();
        @Override
        public void buildCpu(String cpu) {
            mComputer.setmCpu(cpu);
        }
     
        @Override
        public void buildMainboard(String mainboard) {
            mComputer.setmMainboard(mainboard);
        }
     
        @Override
        public void buildRam(String ram) {
            mComputer.setmRam(ram);
        }
     
        @Override
        public Computer create() {
            return mComputer;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    指挥者(Director)类用来规范组装电脑的流程顺序,先安装主板,再安装CPU,最后安装内存并组装成电脑。
    public class Direcror {
        Builder mBuild=null;
        public Direcror(Builder build){
            this.mBuild=build;
        }
        public Computer CreateComputer(String cpu,String mainboard,String ram){
            //规范建造流程,这个顺序是由它定的
           this.mBuild.buildMainboard(mainboard);
           this.mBuild.buildCpu(cpu);
           this.mBuild.buildRam(ram);
           return mBuild.create();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    Builder mBuilder = new MyComputerBuilder();
    Direcror mDirecror=new Direcror(mBuilder);
    mDirecror.CreateComputer("i7","Intel主板","mRam");//返回Computer实例对象
    
    • 1
    • 2
    • 3

    观察者模式

    定义对象间的一种一对多的关系,当一个对象状态发生改变时,所有依赖这个对象的都会得到通知,自动更新,比如微信的订阅,当作者发文则通知订阅者。

    观察者模式: Spring事件驱动模型就是观察者模式很经典的一-个应用。

    代理模式

    代理设计模式**:SpringAOP功能的实现。**动态代理

    适配器模式

    适配器模式:SpringAOP的增强或通知(Advice)使用到了适配器模式、springMVC中也是用到了适配器模式适配Controller。

    责任链模式

    项目中责任链模式也比较常用,从其概念可以看出其适合的应用场景:链!

    避免将一个请求的发送者和接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。
    解耦,不仅是该模式的作用和特点,更是软件设计的原则之一。如何来判定某个对象是否有机会处理链上的请求,这个判断的过程是不是像极了过滤器?所以过滤器的设计可以说是该模式的一个特点。

    此外,采用职责链模式不仅可以方便扩展(当增加一个接受者时,只需要在链上的适当位置插入对应的处理方法即可),而且可以替换掉代码中可能存在的switch-case或者if-else,从代码简洁的考量也是一个不错的回答角度。(当然,替换掉switch-case或if-else不仅仅只有这种设计模式可以达到)

    请求的发送者和接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。
    解耦,不仅是该模式的作用和特点,更是软件设计的原则之一。如何来判定某个对象是否有机会处理链上的请求,这个判断的过程是不是像极了过滤器?所以过滤器的设计可以说是该模式的一个特点。

    此外,采用职责链模式不仅可以方便扩展(当增加一个接受者时,只需要在链上的适当位置插入对应的处理方法即可),而且可以替换掉代码中可能存在的switch-case或者if-else,从代码简洁的考量也是一个不错的回答角度。(当然,替换掉switch-case或if-else不仅仅只有这种设计模式可以达到)

  • 相关阅读:
    sql创建临时表,获取查询数据后删除临时表,清理空间
    Vue公共loading升级版(处理并发异步差时响应)
    python subprocess 起来的进程怎么查看是否正在运行
    java毕业设计电影网站系统设计Mybatis+系统+数据库+调试部署
    DevOps | 如何快速提升团队软件开发成熟度,快速提升研发效能?
    XGBoost算法讲解和公式推导
    c++字符串相关接口
    编写第一个Go程序
    Python日期与时间模块datetime、time、Calendar、dateuil 相关使用讲解
    “存量竞争” 体验为王,火山引擎边缘云助力内容社区破局
  • 原文地址:https://blog.csdn.net/edwer93/article/details/128199290