美文网首页
设计模式

设计模式

作者: 是你的小凉凉呀 | 来源:发表于2021-12-18 16:55 被阅读0次

    读书的时候有学习了解过设计模式,但是都是浅谈于书面,实际场景是没有的,所以会导致只是知道了这种情况,具体其实中奥妙是很难深刻领会的。
    工作久了,很多业务场景开始支撑我们使用,对于设计模式的体会也是更深刻了一点,抽空来总结下基于工作实践后,我对于设计模式的理解。

    什么是设计模式

    基于官方说法:是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
    使用后的理解:对代码开发经验的总结,是解决特定问题的一系列套路,能快速选择匹配自己实际业务场景。它既是经验的总结,又是一个模板和套路。真的经过实践以后对于设计模式的理解,更加觉得他是一套于己于他人于系统都是多赢的设计方案,他让代码编制真正工程化,项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,我想这可能就是它能被广泛应用的原因。但是它又不是语法规定,而仅仅是一套解决方案。他能为我们是提高代码可复用性、可维护性、可读性、稳健性以及安全性。能让我们在横向和纵向都能快速拓展。

    常用的设计模式

    目前我个人认为最常用的设计模式有:单例模式,工厂模式,观察者模式,装饰器模式,代理模式。(可能有偏差,只是个人理解,和套用实际场景更多的情况)

    单例模式

    单例模式的定义

    个人理解,单例是指单个实例,在整个应用程序当中有且仅有一个实例存在,该实例是我们通过代码指定好的(自行创建的)。

    为什么要使用

    解决在高并发过程中,多个实例出现逻辑错误的情况。
    在特定的业务场景下避免对象重复创建,节约内存。

    实现的两种方式

    饿汉式

    顾名思义,不管有没有使用到该对象,只要程序启动成功,该单实例对象就存在。这种方式的单例模式,写法简单,类在加载时就完成了实例化,避免了线程的问题。但缺点也很明显,实例在类记载时已创建,没有实现懒加载的效果,如果实例不用便一直存在,造成内存浪费。
    思路:
    1.私有化构造器(外部将不能通过new的方式去创建)
    2.本类内部创建静态的对象实例(在类加载时对象实例已创建)
    3.提供得到实例的静态方法

    /**
     * 饿汉式
     */
    public class SingletonHungry {
    
    
        private static SingletonHungry instance = new SingletonHungry();
    
        public static SingletonHungry instance(){
            return instance;
        }
    
        public static void main(String[] args) {
            SingletonHungry instance1 = SingletonHungry.instance();
            SingletonHungry instance2 = SingletonHungry.instance();
            System.out.println(instance1);
            System.out.println(instance2);
        }
    }
    

    上述情况,满足在单线程和多线程中使用。

    懒汉式

    顾名思义,只有在程序当中使用到该对象的时候,该单实例对象才会被实例化。
    在类加载的时候就创建好了,如何做到在用的时候才创建呢?
    在当调用获取对象实例方法的时候创建就好了,然后再获取对象实例前,先判断对象实例是否存在。

    ps:这样很明显是有线程安全问题的,当多个线程进入方法,都会判断到当前实例未空,接着创建了多个实例,这不是我们想要的结果。

    /**
     * 懒汉式-单线程
     */
    public class SingletonLazy {
    
        public static SingletonLazy instance = null ;
    
        public static SingletonLazy instance() {
            if(instance == null) {
                instance = new SingletonLazy() ;
            }
    
            return instance ;
        }
    }
    

    上述编写的代码中,乍一看,没问题,满足了单实例对象。可是细细一琢磨,咋感觉这不对,如果多线程情况下,就很难保证单实例对象了。下面提供一种多线程情况下实现单例的方式:

    /**
     * 懒汉式-多线程
     */
    public class SingletonLazy {
    
        public static SingletonLazy instance = null ;
    
        public static SingletonLazy instance() {
    
            if(instance == null ) {
                synchronized (SingletonLazy.class) {
                    if(instance == null) {
                        instance = new SingletonLazy();
                    }
                }
            }
            return instance ;
        }
    }
    

    上述的代码中满足了多线程的使用场景,就是使用了上锁+双重检查来进行实现。

    实际使用场景

    单例模式的类只允许一个类的实例存在,许多时候整个系统只需要拥有一个全局对象,这样有利于协调系统整体行为。比如:
    1.比如把配置文件存在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂情况下的配置管理。
    2.windows的回收站其实就是一个单例,你双击第二次并不会在出现一个出现窗口,对象只会创建一次
    3..第三方sdk做接口发送短信验证码,如果有几十万个用户同时发送短信,只需调用对象的方法即可
    4.我们一般在调用数据库的时候也采用来实现的,这样避免了内存空间不必要的占用和浪费
    5.再比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目只需要一个SessionFactory就够了,这就是会使用到单例模式。

    总结:
    单例模式适合使用的场景:
    1.有频繁实例化然后销毁的情况,也就是频繁的new对象,可以考虑单例模式。
    2.创建对象耗时过多或者消耗资源过多,却又经常用到的对象
    3.频繁创建IO资源的对象,例如数据库连接池访问本地文件

    工厂模式

    工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。定义一个创建对象的工厂接口,将对象的实际创建工作推迟到具体子工厂类当中。

    1.优点
    1、一个调用者想创建一个对象,只要知道其名称就可以了。
    2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
    3、屏蔽产品的具体实现,调用者只关心产品的接口。
    2.缺点
    每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

    3.为什么要用工厂模式
    (1) 解耦 :把对象的创建和使用的过程分开
    (2)降低代码重复: 如果创建某个对象的过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。
    (3) 降低维护成本 :由于创建过程都由工厂统一管理,所以发生业务逻辑变化,不需要找到所有需要创建某个对象的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。

    简单工厂

    简单工厂模式,又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,把产品的生产方法封装起来放进工厂类,工厂类可以根据参数的不同,返回不同产品类的实例。工厂类就是用来生产产品的类,把生产产品的方法放到工厂类里面去,工厂类里面用switch语句控制生产哪种商品,使用者只需要调用工厂类的静态方法就可以实现产品类的实例化。
    严格的说,简单工厂模式并不是23种常用的设计模式之一,它只算工厂模式的一个特殊实现。
    它违背了我们在设计模式七大原则中所说的“开闭原则”(虽然可以通过反射的机制类避免,后面会提到)。因为每次要新添加一个功能,都需要在switch-case语句(或者if-else语句)中去修改代码,添加分支条件。

    使用场景

    1.需要创建的对象较少。
    2.客户端不关心对象的创建过程。

    实例讲解
    //1.创建抽象产品类,定义具体产品的公共接口
    public abstract class Car {
        abstract void run();
    }
    
    //2.创建具体产品类,定义生产的具体产品
    //具体产品-奥迪车
    public class Audi extends Car {
        
        @Override
        void run() {
            System.out.println("奥迪车,跑起来贼稳...");
        }
    }
    
    //具体产品-宝马车
    public class Bmw extends Car {
        
        @Override
        void run() {
            System.out.println("宝马车,跑起来贼溜...");
        }
    }
    
    //3.创建工厂类,提供具体的产品对象
    public class CarFactory {
    
        public static Car getCar(String type) {
            Car car = null;
            if ("Bmw".equalsIgnoreCase(type)) {
                car = new Bmw();
            } else if ("Audi".equalsIgnoreCase(type)) {
                car = new Audi();
            }
            return car;
        }
    }
    
    //4.外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例
    public class Test {
    
        public static void main(String[] args) {
            //获取奥迪车实例,并执行方法
            Car audi = CarFactory.getCar("audi");
            audi.run();
    
            //获取宝马车实例,并执行方法
            Car bmw = CarFactory.getCar("bmw");
            bmw.run();
        }
    }
    
    //结果输出:
    //奥迪车,跑起来贼稳...
    //宝马车,跑起来贼溜...
    
    //这样的实现有个问题,如果我们新增产品类的话,就需要修改工厂类中的getCar()方法,这很明显不符合开闭原则的。
    
    //5 使用反射机制改善简单工厂
    public class CarFactoryNew {
    
        /**
         * 利用反射解决简单工厂每次增加新产品类都要修改产品工厂的弊端
         * 
         */
        public static Object getClass(Class<? extends Car> clazz) {
            Object obj = null;
    
            try {
                obj = Class.forName(clazz.getName()).newInstance();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return obj;
        }
    }
    
    //此时增加比亚迪汽车,并进行测试
    //比亚迪汽车
    public class Byd extends Car{
    
        @Override
        void run() {
            System.out.println("比亚迪汽车,跑起来贼快");
        }
    }
    
    //测试方法
    public class TestNew {
    
        public static void main(String[] args) {
            Audi audi = (Audi) CarFactoryNew.getClass(Audi.class);
            audi.run();
    
            Bmw bmw = (Bmw) CarFactoryNew.getClass(Bmw.class);
            bmw.run();
    
            Byd byd = (Byd) CarFactoryNew.getClass(Byd.class);
            byd.run();
        }
    }
    
    //输出结果:
    //奥迪车,跑起来贼稳...
    //宝马车,跑起来贼溜...
    //比亚迪汽车,跑起来贼快
    

    应用场景:
      当没有使用工厂模式的时候,每个“产品”类都是分散的,没有使用一个工厂接口把它们整合起来,可能每一类都需要很多不同的参数,使用者要清晰地知道这些参数才能把“产品”类实例化,每个产品参数不同的话,会让使用者非常凌乱,使用“工厂”则可以把参数封装在里面,让使用者不用知道具体参数就可以实例化出所需要的“产品”类。

    工厂方法

    简单工厂模式最大的缺点就是当有新产品要加入到系统中时,必须修改工厂类,这违背了开闭原则。在工厂方法中,我们不在提供统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂。
    工厂方法模式将类的实例化推迟到了工厂类的子类中完成,即由子类来决定应该实例哪一个类。工厂方法模式是简单工厂模式的延伸,是使用频率最高的设计模式之一,是很多开源框架与API类库的核心模式。

    适用场景

    客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体的工厂类创建。
    将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。

    实例讲解
    //1.创建抽象工厂类
    public interface Factory {
        
        Car getCar();
    }
    
    //2.创建具体工厂类
    //创建比亚迪汽车工厂
    public class BydFactory implements Factory {
        
        @Override
        public Car getCar() {
            return new Byd();
        }
    }
    
    //创建奥迪汽车工厂
    public class AudiFactory implements Factory {
    
        @Override
        public Car getCar() {
            return new Audi();
        }
    }
    
    //3.测试方法
    public class Test {
    
        public static void main(String[] args) {
            //奥迪汽车
            AudiFactory audiFactory = new AudiFactory();
            audiFactory.getCar().run();
    
            //比亚迪汽车
            BydFactory bydFactory = new BydFactory();
            bydFactory.getCar().run();
        }
    }
    
    //输出结果:
    //奥迪车,跑起来贼稳...
    //比亚迪汽车,跑起来贼快
    
    优缺点说明:

    符合开闭原则,新增加一种产品时,只需要增加相应的具体产品类和相应的工厂子类即可;
    添加新产品时,除了增加新产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统复杂度;
    一个具体工厂只能创建一种具体的产品;

    抽象工厂

    工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只能生产一类产品,可能会导致系统中存在大量的工厂类,势必增加系统的开销。此时,我们可以考虑将一些相关的产品组成一个“产品族”,由同一个工厂来统一生产

    适用场景

    一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是很重要的,用户无须关心对象的创建过程,将对象的创建和使用解耦
    系统中有多于一个的产品族,而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族
    产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或删除已有的产品登记结构。

    实例讲解
    //1.创建抽象工厂类,定义具体工厂的公共接口
    public abstract class Factory {
    
        abstract Mobile makeMobile();
        abstract Ipad makeIpad();
    }
    
    //2.创建抽象产品类,定义具体产品的公共接口
    //手机产品抽象类
    public abstract class Mobile {
    
        abstract void show();
    }
    
    //平板产品抽象类
    public abstract class Ipad {
    
        abstract void show();
    }
    
    //3.创建具体产品类,定义生产的具体产品
    //华为手机产品
    public class HuaWeiMobile extends Mobile {
        @Override
        void show() {
            System.out.println("华为手机,您手中的世界500强。。。");
        }
    }
    
    //小米手机产品
    public class XiaoMiMobile extends Mobile {
        @Override
        void show() {
            System.out.println("小米手机,为发烧而生");
        }
    }
    
    //华为平板产品
    public class HuaWeiIpad extends Ipad {
        @Override
        void show() {
            System.out.println("华为平板,很不错。。。");
        }
    }
    
    //小米平板产品
    public class XiaoMiIpad extends Ipad {
        @Override
        void show() {
            System.out.println("小米平板,很棒。。。");
        }
    }
    
    //4.创建具体工厂类,定义创建对应具体产品实例的方法
    
    /**
     *     华为工厂
     */
    public class HuaWeiFactory extends Factory {
    
        @Override
        Mobile makeMobile() {
            return new HuaWeiMobile();
        }
    
        @Override
        Ipad makeIpad() {
            return new HuaWeiIpad();
        }
    }
    
    /**
     *     小米工厂
     */
    public class XiaoMiFactory extends Factory {
        @Override
        Mobile makeMobile() {
            return new XiaoMiMobile();
        }
    
        @Override
        Ipad makeIpad() {
            return new XiaoMiIpad();
        }
    }
    
    //5.测试
    
    public class Test {
    
        public static void main(String[] args) {
            HuaWeiFactory huaWeiFactory = new HuaWeiFactory();
            XiaoMiFactory xiaoMiFactory = new XiaoMiFactory();
            //生产华为手机
            huaWeiFactory.makeMobile().show();
            //生产华为平板
            huaWeiFactory.makeIpad().show();
    
            //生产小米手机
            xiaoMiFactory.makeMobile().show();
            //生产小米平板
            xiaoMiFactory.makeIpad().show();
        }
    }
    
    //输出结果
    
    //华为手机,您手中的世界500强。。。
    //华为平板,很不错。。。
    //小米手机,为发烧而生
    //小米平板,很棒。。。
    
    优缺点说明

    1.抽象工厂模式将具体产品的创建延迟到具体工厂的子类中,这样将对象的创建封装起来,可以减少客户端与具体产品类之间的依赖,从而使系统耦合度低,这样更有利于后期的维护和扩展;
    2.新增一种产品类时比如(苹果手机和平板),只需要增加相应的具体产品类和相应的工厂子类即可,符合开闭原则
    3.抽象工厂很难支持新种类产品的变化,需要添加新的产品时(比如新增电视机产品),就必须修改抽象工厂的接口以及所有的子类工厂,这样也就违背了开闭原则
    4.对于新的产品族符合开闭原则;对于新的产品种类不符合开闭原则,这一特性称为开闭原则的倾斜性。

    三种工厂模式角色比对

    组成(角色) 关系 作用
    简单工厂模式
    抽象产品(Product) 具体产品的父类 描述产品的公共接口
    具体产品(Concrete Product) 抽象产品的子类;工厂类创建的目标类 描述生产的具体产品
    工厂(Creator) 被外界调用 根据传入不同的参数从而创建不同具体产品类的实例
    工厂方法
    抽象产品(Product) 具体产品的父类 描述产品的公共接口
    具体产品(Concrete Product) 抽象产品的子类;工厂类创建的目标类 描述生产的具体产品
    抽象工厂(Creator) 具体工厂的父类 描述具体工厂的公共接口
    具体工厂(Concrete Creator) 抽象工厂的子类;被外接调用 描述具体工厂;实现FactoryMethod工厂方法创建产品的实例
    抽象工厂
    抽象产品族(AbstractProduct) 抽象产品的父类 描述抽象产品的公共接口
    抽象产品(Product) 具体产品的父类 描述产品的公共接口
    具体产品(Concrete Product) 抽象产品的子类;工厂类创建的目标类 描述生产的具体产品
    抽象工厂(Creator) 具体工厂的父类 描述具体工厂的公共接口
    具体工厂(Concrete Creator) 抽象工厂的子类;被外接调用 描述具体工厂;实现FactoryMethod工厂方法创建产品的实例

    相关文章

      网友评论

          本文标题:设计模式

          本文链接:https://www.haomeiwen.com/subject/aphgfrtx.html