设计模式

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

什么是设计模式

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

常用的设计模式

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

单例模式

单例模式的定义

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

为什么要使用

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

实现的两种方式

饿汉式

顾名思义,不管有没有使用到该对象,只要程序启动成功,该单实例对象就存在。这种方式的单例模式,写法简单,类在加载时就完成了实例化,避免了线程的问题。但缺点也很明显,实例在类记载时已创建,没有实现懒加载的效果,如果实例不用便一直存在,造成内存浪费。
思路:
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工厂方法创建产品的实例
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容