网站首页 编程语言 正文
设计模式01-单例和工厂
设计模式概念
模式是一套被反复使用、多数人知晓的、经过分类编写的、成功代码设计经验的总结;它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。 (性能,安全,可靠)
设计模式的作用
- 可以提高程序员的思维能力、编程能力和设计能力。
- 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
- 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
设计模式类别
分为三大类:
- 创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。
- 结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。
- 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
设计模式遵循的原则有7个:
1. 开闭原则(Open Close Principle)
对扩展开放,对修改关闭。
问题:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化
,而不是通过修改已有的代码来实现变化。
2. 里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。
里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。
LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
问题 :有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。解决方案:当使用继承时,遵循里氏替换原则。类B继承类A时,
除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法
。【有时候我们可以采用final的手段强制来遵循】
3. 依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。高层模块不应该依赖低层模块,两者都应该依赖其抽象。
问题:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
4. 接口隔离原则(Interface Segregation Principle)
使用多个隔离的接口来降低耦合度。
问题:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
5. 迪米特法则(最少知道原则)(Demeter Principle)
一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
问题:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
解决方法:尽量降低类与类之间的耦合。
6. 合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。继承实际上破坏了类的封装性,超类的方法可能会被子类修改。
问题:B类如果继承了A类,A类可继承方法m的实现细节暴露给B类,如果A类发生方法m改变,那么B的实现也不得不发生改变。
解决方法:使用合成或者聚合,不要使用继承
7. 单一职责原则(Single responsibility principle)
一个类只负责一个功能领域的响应职责。
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。另外,多个职责耦合在一起,会影响复用性。
问题:比如一个类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
解决方法:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。
常用设计模式
工厂模式
工厂模式属于创建型设计模式,它提供了一种创建对象的最佳方式
。隐藏复杂的逻辑处理过程, 只关心执行结果。直接用new可以完成的不需要用工厂模式。在需要生成复杂对象
的地方使用。
静态工厂模式
静态工厂模式又可以称为简单工厂模式
概念:在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。(生成同类型产品)
优点:功能强大,对象创建和使用分离,程序员可以只关心对象使用,不用关心对象如何创建
。
缺点:耦合度高(所有产品都在工厂创建,一旦异常其他产品也受影响),扩展性不强(每次添加一个产品,工厂类都要变化)
,违背开闭原则 。
- 定义接口
package com.aaa.fatory;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 14:52
* @description:学习软件开发技术
* @modified By:
* @version: 1.0
*/
public interface SoftwareTechnology {
void studyST();
}
-
编写实现类
-
测试
@Test
public void staticFactoryTest(){
SoftwareTechnology java = TechnologyFactory.teach(1);
java.studyST();
SoftwareTechnology python = TechnologyFactory.teach(2);
python.studyST();
}
两个对象都被创建出来了
工厂(方法)模式:
工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
概念解释:
工厂本身不再创建产品,而是规定了工厂规范,即工厂接口,而将产品创建都交给子工厂创建。
优点: 遵循了开闭原则(不需要修改工厂类,就可以增加产品 解耦,职责单一
(每个工厂只负责创建对应的产品)
缺点: 增加系统复杂度(每新加一个产品需要新加一个工厂)
- 定义工厂接口
package com.aaa.designmode.factory;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 15:04
* @description:总工厂,相当于总公司,只定义规范 ,不参与生产
* @modified By:
* @version:
*/
public interface TechnologyFactoryInterface {
/**
* 生产规范,教学规范
*/
SoftwareTechnology teachST();
}
- 编写工厂实现类
package com.aaa.designmode.factory;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 15:06
* @description:
* @modified By:
* @version:
*/
public class ZhengZhouFactory implements TechnologyFactoryInterface{
@Override
public SoftwareTechnology teachST() {
return new JavaTechnology();
}
}
package com.aaa.designmode.factory;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 15:06
* @description:
* @modified By:
* @version:
*/
public class WuHanFactory implements TechnologyFactoryInterface{
@Override
public SoftwareTechnology teachST() {
return new PythonTechnology();
}
}
测试
@Test
public void factoryMethodTest(){
//学Java
TechnologyFactoryInterface zhengZhouFactory = new ZhengZhouFactory();
SoftwareTechnology softwareTechnology1 = zhengZhouFactory.teachST();
softwareTechnology1.studyST();
//学python
TechnologyFactoryInterface wuHanFactory = new WuHanFactory();
SoftwareTechnology softwareTechnology2 = wuHanFactory.teachST();
softwareTechnology2.studyST();
}
同样两个对象也被创建出来了
抽象工厂
概念:抽象工厂是工厂方法的升级版,为相关或者相互依赖的对象提供一个接口,而且无须指定他们的具体实现类。
概念解释:抽象工厂模式相对于工厂方法模式来说,就是工厂方法模式是针对一个产品系列的,而抽象工厂模式是针对多个产品系列的,即工厂方法模式是一个产品系列一个工厂类,而抽象工厂模式是多个产品系列一个工厂类
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:难以支持新种类的产品。因为抽象工厂接口确定了可以被创建的产品集合,所以难以扩展抽象工厂以生产新种类的产品。
- 定义抽象接口(包括简单和工厂方法)
package com.aaa.fatory;
/**
* @author : Student尚
* @version : 1.0
* @createTime : 2022/9/25 18:31
* @description :
*/
public interface HardwareTechnology {
void studyHT();
}
package com.aaa.designmode.factory;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 15:04
* @description:
* @modified By:
* @version:
*/
public interface TechnologyFactoryAbstractInterface {
/**
* 生产规范,返回软件技术
*/
SoftwareTechnology teachST();
/**
* 生产规范,返回硬件技术
*/
HardwareTechnology teachHT();
}
- 编写实现工厂
package com.aaa.designmode.factory;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 15:06
* @description:
* @modified By:
* @version:
*/
public class ZhengZhouFactoryNew implements TechnologyFactoryAbstractInterface{
@Override
public SoftwareTechnology teachST() {
return new JavaTechnology();
}
@Override
public HardwareTechnology teachHT() {
return new PhoneTechnology();
}
}
测试
@Test
public void factoryAbstract(){
TechnologyFactoryAbstractInterface tfai = new ZhengZhouFactoryNew();
SoftwareTechnology softwareTechnology = tfai.teachST();
softwareTechnology.studyST();
HardwareTechnology hardwareTechnology = tfai.teachHT();
hardwareTechnology.studyHT();
}
总结:
无论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦,让类的创建和使用过程实现松耦合。
单例(态)模式
概念:
一种常用的软件设计模式。所谓单例,就是让一个类在项目运行中只存在一个对象,即使用到这个类的地方很多,也只存在一个对象。
好处:
- 节省内存
- 有些情况下不用单例模式可能会引起代码逻辑错误(例如:网站访问量统计功能 application.setAttrbute(“count”,100);
单例模式要点:
- 单例模式的类只提供私有的构造函数
- 类定义中含有一个该类的静态私有对象;
- 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
实现方式(7种):
懒汉(slacker):该单例类非常懒,只有在自身需要的时候才会行动
,从来不知道及早做好准备。特点是运行时获得对象的速度比较慢,但加载类的时候比较快。整个应用的生命周期只有一部分时间在占用资源。
1. 懒汉线程不安全:
package com.aaa.designmode.singleton;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 16:09
* @description:
* @modified By:
* @version:
*/
public class SlackerSingleton {
/**
* 1、构造器私有
*/
private SlackerSingleton() {
}
/**
* 2. 类定义中含有一个该类的静态私有对象;
*/
private static SlackerSingleton singleton;
/**
* 3. 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
*/
public static SlackerSingleton getInstance(){
if(singleton ==null){
singleton = new SlackerSingleton();
}
return singleton;
}
}
测试:
@Test
public void singletonTest(){
Person person1 = new Person();
Person person2 = new Person();
//普通类new出来的内存区域不同,所以下面的返回值为false
System.out.println(person1==person2);
SlackerSingleton singleton1 = SlackerSingleton.getInstance();
SlackerSingleton singleton2 = SlackerSingleton.getInstance();
System.out.println(singleton1==singleton2);
}
2. 懒汉线程安全
上面的getinstance()方法在单线程的场景下不会出现问题,但是在多线程场景下,会出现线程安全问题。
package com.aaa.designmode.singleton;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 16:09
* @description:
* @modified By:
* @version:
*/
public class SlackerSingleton {
/**
* 1、构造器私有
*/
private SlackerSingleton() {
}
/**
* 2. 类定义中含有一个该类的静态私有对象;
*/
private static SlackerSingleton singleton;
/**
* 3. 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
*/
public static SlackerSingleton getInstance(){
if(singleton ==null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton = new SlackerSingleton();
}
return singleton;
}
}
package com.aaa.designmode.singleton;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 16:06
* @description:
* @modified By:
* @version:
*/
public class TestSingleton {
public static void main(String[] args) {
Runnable r1= () -> {
SlackerSingleton instance = SlackerSingleton.getInstance();
System.out.println(instance);
};
Runnable r2= () -> {
SlackerSingleton instance = SlackerSingleton.getInstance();
System.out.println(instance);
};
for (int i = 0; i < 20; i++) {
new Thread(r1).start();
new Thread(r2).start();
}
}
}
多线程测试不要在Test方法里测,要在main方法测试,有可能测试失败(已踩坑)
可以发现内存地址不一致,说明不是单例的。
解决方案:加同步锁
package com.aaa.singleton;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 16:09
* @description:
* @modified By:
* @version:
*/
public class SlackerSingleton {
/**
* 1、构造器私有
*/
private SlackerSingleton() {
}
/**
* 2. 类定义中含有一个该类的静态私有对象;
*/
private static SlackerSingleton singleton;
/**
* 3. 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
*/
public static synchronized SlackerSingleton getInstance(){
if(singleton == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
singleton = new SlackerSingleton();
}
return singleton;
}
}
同样的测试代码:
3. 懒汉线程安全双重加锁(难点)
此代码看上去没什么问题,但是在同一时间多线程的情况下,可能出现JVM指令重排的问题,从而导致某一个线程获取的单例对象没有初始化对象。
指令重排
指令重排为了提高性能,在遵守 as-if-serial 语义(即不管怎么重排序,单线程下程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守。)的情况下,编译器和处理器常常会对指令做重排序。
一般重排序可以分为如下三种类型:
编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
指令级并行重排序:现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
内存系统重排序:由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
int a = 0;
// 线程 A
a = 1; // 1
boolen flag = true; // 2
// 线程 B
if (flag) { // 3
int i = a; // 4
}
单看上面的程序好像没有问题,最后 i 的值是 1。但是为了提高性能,编译器和处理器常常会在不改变数据依赖的情况下对指令做重排序。
假设线程 A 在执行时被重排序成先执行代码 2,再执行代码 1; 而线程 B 在线程 A 执行完代码 2 后,读取了 flag变量。由于条件判断为真,线程 B 将读取变量 a。此时,变量 a 还根本没有被线程 A 写入,那么 i 最后的值是 0,导致执行结果不正确。
那么如何程序执行结果正确呢?这里可以使用 volatile 关键字。
这个例子中, 使用 volatile 不仅保证了变量的内存可见性,还禁止了指令的重排序,即保证了 volatile 修饰的变量编译后的顺序与程序的执行顺序一样。那么使用 volatile 修饰 flag 变量后,在线程 A 中,保证了代码 1 的执行顺序一定在代码 2 之前。
volatile 禁止编译器进行指令重排
package com.aaa.designmode.singleton;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 16:09
* @description:
* @modified By:
* @version:
*/
public class SlackerSingleton {
/**
* 1、构造器私有
*/
private SlackerSingleton() {
}
/**
* 2. 类定义中含有一个该类的静态私有对象;
* volatile 禁止编译器进行指令重排
*/
private volatile static SlackerSingleton singleton;
/**
* 3. 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
*/
public static synchronized SlackerSingleton getInstance(){
if(singleton ==null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton = new SlackerSingleton();
}
return singleton;
}
}
4. 饿汉线程安全
饿汉(starving):该单例类非常饿,迫切需要吃东西,所以它在类加载的时候就立即创建对象。特点是加载类的时候比较慢,但运行时获得对象的速度比较快。从加载到应用结束会一直占用资源。
package com.aaa.designmode.singleton;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 16:09
* @description:饿汉式单例模式
* @modified By:
* @version:
*/
public class StarvingSingleton {
/**
* 1、构造器私有
*/
private StarvingSingleton() {
}
/**
* 2. 类定义中含有一个该类的静态私有对象;
*/
private final static StarvingSingleton singleton= new StarvingSingleton();
/**
* 3. 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
*/
public static StarvingSingleton getInstance(){
return singleton;
}
}
测试
public static void main(String[] args) {
StarvingSingleton ss1 = StarvingSingleton.getInstance();
StarvingSingleton ss2 = StarvingSingleton.getInstance();
System.out.println(ss1==ss2);
}
结果为true
5. 饿汉静态线程安全
package com.aaa.designmode.singleton;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 16:09
* @description:饿汉式单例模式
* @modified By:
* @version:
*/
public class StarvingSingleton {
/**
* 1、构造器私有
*/
private StarvingSingleton() {
}
/**
* 2. 类定义中含有一个该类的静态私有对象;
*/
private final static StarvingSingleton singleton;
/**
* 静态代码块
*/
static {
singleton = new StarvingSingleton();
}
/**
* 3. 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
*/
public static StarvingSingleton getInstance(){
return singleton;
}
}
6. 枚举单例模式
什么是枚举类型?
jdk1.5之后出现的一种java类型,可以提前知道一个类的对象个数。
例如一年四季,提前知道四个季节,不会再出现第五个季节。
第一种构建枚举类型的方式,借鉴单例(饿汉式)
package com.aaa.designmode.singleton;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 17:02
* @description:季节
* @modified By:
* @version:
*/
public class Season {
private Season(){
}
public static final Season Spring= new Season();
public static final Season Summer= new Season();
public static final Season Autumn= new Season();
public static final Season Winter= new Season();
}
package com.aaa.designmode.singleton;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 17:07
* @description:
* @modified By:
* @version:
*/
public enum SeasonEnum {
Spring,
Summer,
Autumn,
Winter
}
测试
public static void main(String[] args) {
Season season1 = Season.Spring;
Season season2 = Season.Spring;
System.out.println(season1==season2);
}
7. 静态内部类
package com.aaa.designmode.singleton;
/**
* @author :Teacher陈
* @date :Created in 2022/9/24 17:14
* @description:静态内部类单例模式
* @modified By:
* @version: 1.0
*/
public class StatticInnerSingleton {
/**
* 1、私有化构造器
*/
private StatticInnerSingleton() {
}
/**
* 2、静态内部类
*/
static class TempClass{
private final static StatticInnerSingleton singleton=new StatticInnerSingleton();
}
/**
* 3、公有的静态获取实例的方法
*/
public static StatticInnerSingleton getInstance(){
return TempClass.singleton;
}
}
原文链接:https://blog.csdn.net/qq_60969145/article/details/127040383
相关推荐
- 2022-04-03 C#字符串内存驻留机制分析_C#教程
- 2022-12-15 Android入门之Toast的使用教程_Android
- 2022-09-12 轻量级域名解析服务器之dnsmasq的介绍与部署_服务器其它
- 2023-07-10 Spring事务的传播机制
- 2022-09-05 用两个队列模拟一个栈
- 2022-11-23 Python字符串格式化实例讲解_python
- 2022-11-09 WPF使用WrapPanel实现虚拟化效果_C#教程
- 2022-08-11 boost字符串处理函数format的用法_C 语言
- 最近更新
-
- window11 系统安装 yarn
- 超详细win安装深度学习环境2025年最新版(
- Linux 中运行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存储小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基础操作-- 运算符,流程控制 Flo
- 1. Int 和Integer 的区别,Jav
- spring @retryable不生效的一种
- Spring Security之认证信息的处理
- Spring Security之认证过滤器
- Spring Security概述快速入门
- Spring Security之配置体系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置权
- redisson分布式锁中waittime的设
- maven:解决release错误:Artif
- restTemplate使用总结
- Spring Security之安全异常处理
- MybatisPlus优雅实现加密?
- Spring ioc容器与Bean的生命周期。
- 【探索SpringCloud】服务发现-Nac
- Spring Security之基于HttpR
- Redis 底层数据结构-简单动态字符串(SD
- arthas操作spring被代理目标对象命令
- Spring中的单例模式应用详解
- 聊聊消息队列,发送消息的4种方式
- bootspring第三方资源配置管理
- GIT同步修改后的远程分支