读书的时候有学习了解过设计模式,但是都是浅谈于书面,实际场景是没有的,所以会导致只是知道了这种情况,具体其实中奥妙是很难深刻领会的。
工作久了,很多业务场景开始支撑我们使用,对于设计模式的体会也是更深刻了一点,抽空来总结下基于工作实践后,我对于设计模式的理解。
什么是设计模式
基于官方说法:是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
使用后的理解:对代码开发经验的总结,是解决特定问题的一系列套路,能快速选择匹配自己实际业务场景。它既是经验的总结,又是一个模板和套路。真的经过实践以后对于设计模式的理解,更加觉得他是一套于己于他人于系统都是多赢的设计方案,他让代码编制真正工程化,项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,我想这可能就是它能被广泛应用的原因。但是它又不是语法规定,而仅仅是一套解决方案。他能为我们是提高代码可复用性、可维护性、可读性、稳健性以及安全性。能让我们在横向和纵向都能快速拓展。
常用的设计模式
目前我个人认为最常用的设计模式有:单例模式,工厂模式,观察者模式,装饰器模式,代理模式。(可能有偏差,只是个人理解,和套用实际场景更多的情况)
单例模式
单例模式的定义
个人理解,单例是指单个实例,在整个应用程序当中有且仅有一个实例存在,该实例是我们通过代码指定好的(自行创建的)。
为什么要使用
解决在高并发过程中,多个实例出现逻辑错误的情况。
在特定的业务场景下避免对象重复创建,节约内存。
实现的两种方式
饿汉式
顾名思义,不管有没有使用到该对象,只要程序启动成功,该单实例对象就存在。这种方式的单例模式,写法简单,类在加载时就完成了实例化,避免了线程的问题。但缺点也很明显,实例在类记载时已创建,没有实现懒加载的效果,如果实例不用便一直存在,造成内存浪费。
思路:
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工厂方法创建产品的实例 |