一.什么是设计模式

设计模式(Design  Pattern)是前辈们对代码开发经验的总结,是解决一系列特定问题的套路。

它不是语法规定,而是一套用来提高代码复用性,可读性,可维护性,稳健性,安全性的解决方案

设计模式的雏形:

1995年,GOF(Gang  of  Four,四人/四人帮)合作出版了《设计模式:可复用的面向对象软件的基础》一书,共收集了23种设计模式,从此有了设计模式的历程碑,人称【GoF设计模式】

设计模式的本质是面向对象设计的实际应用,是对类的封装,继承,多态,以及类的关联,和组合关系的充分理解

使用设计模式有以下优点:

  • 可以提高程序员的思维能力,编程和设计能力
  • 使得程序设计更加标准化,代码编制更容易加工,从而缩短软件开发周期
  • 使设计代码重用性高,可读性高,可靠性高,灵活性,可维护性强

OOP的七大原则:

开闭原则:对扩展开放,对修改关闭

  • 指的是在面向对象的设计中,当有新的需求时,不会优先改变源码,而是通过其它方式(继承,多态等)在源码的基础上拓展新功能

里氏替换原则:继承必须确保父类的所拥有的性质在子类依旧成立

  • 指的是在程序设计中,对于子类继承父类,子类中父类的属性和方法都能正常使用,子类需要新的需求就自己写,不要直接重写父类的方法,如果为了重写父类已有的方法而继承,对于程序的复用性会大打折扣

依赖倒置原则:面向接口编程,不要面向实现编程

  • 指的是在程序设计中,不应该力求于怎么实现这个功能,应该先思考有那些方法,各自负责什么,实现的细节交由实现类,抽象功能交给接口,更深层次就是面向抽象编程,不要面向实现编程

单一职责原则:控制类的粒度,将对象解耦,提高其内聚性

  • 指的是一个类就专注于实现好一个功能就行了,就像一个方法就实现一个细节一样,如用户登录,想要一个方法负责密码校对又负责检测用户名是否存在,就是一个方法干了多件事,可以把检测用户是否存在抽象为另一个方法,然后调用它,这样类的粒度就低了,粒度越高,代码越可能出现问题

接口隔离原则:为各个类建立它们专需的接口

  • 指的是在程序设计的时候,需要对一个接口对应一个或多个实现类,它们负责的模块可以很小,但是需要专一,不要多个功能都冗余在一个接口内部,应该实现专一功能,然后可以多个实现类来实现更小的细节

迪米特法则:只与你的朋友交谈,不和陌生人说话

  • 指的是两个类需要交流时应该通过一个中间类,不要让它们两个类直接交流,如用户登录时需要密码校对(A类的功能),校对前需要进行用户名检测是否村在(B类的功能),它们之间有耦合关系,但是程序设计中不能将B类塞到A类中,而是需要一个C类,将B类和A类组合,然后实现此功能,好处是,A类B类保持纯粹,坏处是多了一个开销C类

合成复用原则:尽量优先使用组合和聚合的方式实现类之间的关系,其次才考虑继承来实现

  • 指的是在类的关系中多用组合和聚合的方式设计类,组合优于继承,如果你只想使用父类的方法,而很少或根本不再设计新的方法属性,就肯定要使用组合,如果是需要大面积更改父类方法,或者重构父类,则使用继承

注意:OOP的七大原则,多用于设计阶段,需要分清设计和实现的区别

二.工厂模式

实现了创建者和调度者的分离

原来的调度者即是创建者,类就在自己的项目中,且可看源码,所以要使用的时候可以直接new出来,这种方式创建对象需要自己十分的了解这个类,如需要哪些参数,清楚内部的实现细节

在大型项目的设计中,都是面向接口编程,对于调度者,它只知道此接口的内容,和有一些实现类,并不知道实现类的具体细节,如果自己创建对象,很大概率会被抽象接口给整蒙,所以工厂模式出现了,它用于实现对象的创建,创建对象的细节都由工厂模式解决(也就是架构师),普通开发者只用知道自己使用的实现类是那个工厂提供的,然后在工厂内拿取对象,不必自己创建,而只是利用工厂调度

详细工厂的分类:

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

理论上,工厂模式满足:开闭原则,依赖倒转原则,迪米特法则

但是,实际工作中以效率和业务开发为主,不一定完全满足,这取决于效率和理论的冲突

工厂模式的核心本质:

实例化对象不在使用new关键字,用工厂代替

将选择实现类,创建实现类对象统一管理和控制,从而将调用者和实现类解耦

简单工厂模式

简单工厂模式也叫静态工厂模式,指的是工厂中的代码块都是写死的,动态的拓展类需要在工厂中新增代码块来完成对象的创建

接口,Animal:

public interface Animal {
void getName();
}

通过此接口拓展出的实现类:

  • cat类
public class Cat implements Animal{
@Override
public void getName() {
System.out.println("猫类,实现Animal接口");
}
}
  • dog类
public class Dog implements Animal{
@Override
public void getName() {
System.out.println("狗类,实现Animal类");
}
}

普通的创建对象的方式,通过new关键字实现:

//普通的创建对象方式
Cat cat = new Cat();
Dog dog = new Dog();

这种方式使用的前提是创建者对类的内部结构要熟悉,清楚需要什么参数才能创建对象,我们例子的实现类简单,肯定用new关键字很适用,但是这一期主要讲工厂模式,所以我们看看工厂模式怎么创建对象

简单构造一个简单工厂来创建对象(重理解):

public class AnimalFactory {
public static Animal getAnimal(String name){
if (name.equals("cat")){
return new Cat();
} else if (name.equals("dog")) {
return new Dog();
}else {
return null;
}
}
}

如上就是普通工厂的写法,它是讲已有的类先写入工厂中,这就导致工厂的实现类被写死了,如果新增一个拓展类,就需要改变普通工厂的源码,这很显然不符合开闭原则

工厂模式拿取对象:

//工厂模式创建对象
Animal cat1 = AnimalFactory.getAnimal("cat");
Animal dog1 = AnimalFactory.getAnimal("dog");

新建一个Mouse实现类:

public class Mouse implements Animal {
@Override
public void getName() {
System.out.println("老鼠类,实现于Animal类");
}
}

需要改变普通工厂模式的代码:

public class AnimalFactory {
public static Animal getAnimal(String name){
if (name.equals("cat")){
return new Cat();
} else if (name.equals("dog")) {
return new Dog();
}else if (name.equals("mouse")) {
//新增的拓展实现类
return new Mouse();
}else {
return null;
}
}
}

每次新增拓展类都需要改变普通工厂类的原因:普通工厂是拿取对象的必经之路,是和其它实现类的唯一联系

普通工厂模式生产对象略图:

工厂方法模式

工厂方法模式支持实现类的横向拓展,它在普通工厂模式的基础上,增加工厂模式接口,对于每个实现类有专门的接口,

也就是说实现类,实现接口的具体细节,而工厂实现类实现的是工厂模式的创建对象

  • 优点是可以横向拓展业务,不需要改变已经有的工厂模式来融入
  • 缺点是代码量直接翻倍,冗余比较大

接口Animal:

public interface Animal {
void getName();
}

Animal工厂接口:

public interface AnimalFactory {
Animal getAnimal();
}

接口实现类:

public class Cat implements Animal {
@Override
public void getName() {
System.out.println("猫类,实现Animal接口");
}
}

工厂接口实现类:

public class CatFactory implements AnimalFactory{
@Override
public Animal getAnimal() {
return new Cat();
}
}

如上,每个实现类都有它专有的工厂实现类,使得每个实现类都是专门的工厂来加工的,它们各个工厂实现类都是独立存在的互相解耦,所以要创建对象现在就需要去找它们对应的工厂

这样构建工厂的好处是,横向的新增业务,如果现在新增一个业务只需要实现类实现Animal接口,它对应的工厂实现工厂接口,和其它工厂是独立存在的,不需要改变已有的工厂

能实现横向拓展的关键在于,接口和工厂接口都不是关键路径了,而是约束实现类的组成

工厂方法模式创建对象:

//方法工厂模式拿取对象
Animal cat = new CatFactory().getAnimal();
Animal dog = new DogFactory().getAnimal();

工厂方法模式生产对象略图:

三.抽象工厂模式

抽象工厂模式也是工厂模式的一种,但是它的特点和普通工厂模式,工厂方法模式的机制都是不同的

抽象工厂模式围绕一个超级工厂,其它工厂的创建都是由这个超级工厂约束的

定义:抽象工厂模式提供了一个创建一系列相关或相互依赖对象的接口,无需指定它们具体的类

优点:

  • 具体产品在应用层隔离,无需关心创建细节
  • 将一个系列的产品统一到一起实现

缺点:

  • 产品簇新增产品困难
  • 增加了系统抽象性和理解难度

产品接口:

phone

//产品接口,具体的实现细节交给厂商
public interface PhoneProduct {
void getPhoneName();
void getNumber();
void getProduct();
}

router

//产品接口,具体的实现细节交给厂商
public interface RouterProduct {
void getRouterName();
void getRouterNumber();
void getRouterProduct();
}

抽象工厂接口,工厂都需要实现此接口:

//抽象工厂,所有的工厂都需要实现这个超级工厂
public interface AbstractFactory {
PhoneProduct phone();
RouterProduct router();
}

普通工厂:

XiaoMi:

public class MiFactory implements AbstractFactory{
@Override
public PhoneProduct phone() {
return new MiPhone();
} @Override
public RouterProduct router() {
return new MiRouter();
}
}

HuaWei:

public class HWFactory implements AbstractFactory{
@Override
public PhoneProduct phone() {
return new HuaWeiPhone();
} @Override
public RouterProduct router() {
return new HuaWeiRouter();
}
}

抽象工厂模式生产对象略图:

三种工厂模式总结

简单工厂模式:虽然某种程度上不符合设计模式,但是实际应用最多

工厂方法模式:不修改已有类的情况下,通过新增工厂实现类的拓展

抽象工厂模式:不可以新增产品,但是可以新增产品簇或者说,不建议修改已经写好的抽象工厂接口,但是实现抽象工厂接口的普通工厂可以横向拓展

四.单例模式

单例模式指的是在创建对象的时候,只允许全局存在一个对象,从而达到资源共享的目的

实现单例模式的方式一共有两种:

  • 饿汉式单例
  • 懒汉式单例

饿汉式单例

饿汉式单例的特点是将一个类的构造器私有化,不让外部的程序手动的创建对象

而这个类的对象则使用静态方法获取,由程序加载初始化的时候就开始创建,然后伴随程序的结束为止

//饿汉式单例模式
public class HungryInstance {
//私有化构造器,不允许外部类任意创建对象
private HungryInstance(){ }
//创建静态对象,在类初始化时就被创建对象
private static HungryInstance hungry=new HungryInstance();
//外部类利用方法拿取对象,不由外部类自主创建对象
public static HungryInstance getHungry(){
return hungry;
}
}

饿汉式单例模式有一个缺点,也就是此类的对象是静态的,它和程序加载顺序有关系,静态的代码块会和程序初始化一起加载,所以有可能此类如果所需空间很大但是使用不平凡,会白占很多空间

如我们此类需要申请一片内存空间:

private String[] s1=new String[1000];
private String[] s2=new String[1000];
private String[] s3=new String[1000];
private String[] s4=new String[1000];

如上,这片空间会在程序初始化就被占用,且一直存在到程序结束,如果这个单例本身使用很少,内存开销就很不合算

懒汉式单例

懒汉式单例也需要将构造器私有,避免外部类创建对象

懒汉式不是再使用静态属性来创建对象,而是通过方法调用,由方法创建

如果没使用此方法就并不会存在此对象,如果使用了此方法就创建一个对象

然后加一个检测机制,调用此方法时,如果对象存在就直接返回对象,避免创建,如果不存在,则当场创建一个

//懒汉式单例
public class LazyInstance {
//私有化构造器,避免外部类创建对象
private LazyInstance(){ }
private LazyInstance lazy;
//y由调用方法创建对象,被调用才会被创建,没被调用对象就不存在
public LazyInstance getLazy(){
if (lazy==null){
lazy = new LazyInstance();
return lazy;
}else {
return lazy;
}
}
}

懒汉式单例也有自己的一个问题,那就是多线程的情况下,检测机制太简单,单例会被破坏

原因是上面方法创建对象的操作不是原子性,创建对象的过程:1.分配内存空间,2.执行构造方法,初始化对象,3.把对象指向这个空间

创建对象的顺序是123,132都可能,如果多个线程同时来拿对象只有还没进行到第3步,都会默认没有对象,但实际情况是已经有线程正在创建了,所以就会导致多个线程创建了多个对象

解决方式,加锁(synchronized)

    //由调用方法创建对象,被调用才会被创建,没被调用对象就不存在
public static LazyInstance getLazy() {
if (lazy == null) {
//加上线程同步机制,当对象不存在时将此类资源锁住
synchronized (LazyInstance.class) {
if (lazy == null) {
lazy = new LazyInstance();
return lazy;
}
}
}
return lazy;
}

加上同步机制后,在创建对象时,会将类资源锁住,先获得锁的线程就就去创建对象,其它线程只能等待此线程释放锁

当对象创建完成后,其它线程先后获得锁,但是对象此时已经被最先拿到锁的线程创建了,所以其它线程都不能创建对象而是直接返回已经创建好的对象

静态内部类单例

这是使用了Java静态内部类的特点,它可以直接拿到外部类的静态资源,然后又不会直接被初始化加载,它和饿汉式有异曲同工之妙

饿汉式是在程序加载时就初始化一个对象出来,而它需要在被调用时才能拿到对象,由于创建对象的类中,又是final修饰,所以在调用方法的时候不会多创建对象

//静态内部类
public class StaticClass {
//私有化构造器
private StaticClass(){ }
//返回静态内部类的属性
public static StaticClass getInstance(){
return InnerClass.sc;
}
//静态内部类负责创建外部类的对象
public static class InnerClass{
private static final StaticClass sc = new StaticClass();
}
}

上面三种方式的缺点

只要有反射机制存在,以上三种方式创建对象都是不安全的

反射机制使得私有的构造器依旧可以被拿到,反射机制面前就没有私有的属性了,我们可以使用反射机制来创建对象

//通过反射拿取类的构造器
Constructor<LazyInstance> lazy = LazyInstance.class.getDeclaredConstructor(null);
//设置构造器的熟悉为可访问
lazy.setAccessible(true);
//通过反射拿取构造器创建对象
LazyInstance lazy1 = lazy.newInstance();
LazyInstance lazy2 = lazy.newInstance();
//展示hashcode
System.out.println(lazy1);//LazyInstance@4554617c
System.out.println(lazy2);//LazyInstance@74a14482

如上,通过反射机制将构造器再次变为公有属性以后,已经可以通过外部类继续创建对象

所以这种基于类的单例模式大多都是不安全的,关键在于Java的反射机制使得构造器无法真正的私有化

但是如果有能拒绝反射机制的方式,阁下又如何应对呢?接下来的枚举类值得一看

枚举类单例

枚举类的特点:

枚举类的构造器都是私有的(无论是否显式表达,都是私有的),因此枚举类不能对外创建对象

can't deserialize enum" :不能通过反射拿取枚举类
枚举类直接拒绝反射机制,从根本上杜绝了反射更改构造器属性为公有的情况
public enum EnumInstance {
//实例对象
Instance;
//私有构造器,不管是否显示私有化都是私有的,改为公有编译错误
private EnumInstance(){ }
//拿取对象实例方法
public EnumInstance getInstance(){
return Instance;
}
}

试试用反射取改变构造器属性为公有

//枚举的构造器不是无参构造,Idea和JavaP命令都反编译为无参构造,而真正的构造器为参数为String和int
Constructor<EnumInstance> ei = EnumInstance.class.getDeclaredConstructor(String.class, int.class);
//设置构造器为公共属性
ei.setAccessible(true);
//通过构造器创建对象
EnumInstance e1 = ei.newInstance();
EnumInstance e2 = ei.newInstance();
//展示hashcode
System.out.println(e1);
System.out.println(e2);

指向如上代码报错:

意思是不能使用反射创建枚举对象

GOF23--23种设计模式(一)的更多相关文章

  1. java设计模式:概述与GoF的23种设计模式

    软件设计模式的产生背景 设计模式这个术语最初并不是出现在软件设计中,而是被用于建筑领域的设计中. 1977 年,美国著名建筑大师.加利福尼亚大学伯克利分校环境结构中心主任克里斯托夫·亚历山大(Chri ...

  2. 23种设计模式 - 接口隔离(Facade - Proxy - Mediator - Adapter)

    其他设计模式 23种设计模式(C++) 每一种都有对应理解的相关代码示例 → Git原码 ⌨ 接口隔离 在组件构建过程中,某些接口之间直接的依赖常常会带来很多问题.甚至根本无法实现.采用添加一层间接( ...

  3. Java开发中的23种设计模式详解

    [放弃了原文访问者模式的Demo,自己写了一个新使用场景的Demo,加上了自己的理解] [源码地址:https://github.com/leon66666/DesignPattern] 一.设计模式 ...

  4. Java开发中的23种设计模式详解(转)

    设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

  5. Java开发中的23种设计模式(转)

    设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

  6. c#中的23种设计模式

    C# 23种设计模式汇总 创建型模式 工厂方法(Factory Method) 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节.工厂方法模式的核 ...

  7. Java 23种设计模式

    转自: http://zz563143188.iteye.com/blog/1847029 ; i<count; i++){ list.add(new MailSender()); } } pu ...

  8. 从追MM谈Java的23种设计模式(转)

    从追MM谈Java的23种设计模式    这个是从某个文章转载过来的.但是忘了原文链接.如果知道的,我追加一下. 1.FACTORY-追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西 ...

  9. java 23种设计模式及具体例子 收藏有时间慢慢看

    设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代 码可靠性. 毫无疑问,设计模式 ...

  10. JAVA:23种设计模式详解(转)

    设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

随机推荐

  1. 微信的 h5 支付和 jsapi 支付

    目录 申请商户号 申请商户证书 设置APIv3密钥 下载 SDK 开发包 下载平台证书 关联 AppID 账号 开通 H5 支付 H5支付流程 开通 JSAPI 支付 JSAPI 支付流程 通用微信支 ...

  2. wineqq中接收文件的查看与移动

    在Ubuntu等linux系统中安装QQ都需要安装wine支持,而在使用时,会遇到qq接收到的文件无法直接进行操作等问题. 这时,我们发现直接对文件进行复制后,无法在Ubuntu目录中进行粘贴. 其实 ...

  3. Spring Cloud OpenFeign 的使用及踩坑指南

    目录 Feign 和OpenFeign Feign OpenFeign openFeign的优势 OpenFeign应用 1. 导入依赖 2. 使用 3. 日志配置 4. 数据压缩 OpenFeign ...

  4. Airtest遇到模拟器无法输入中文的情况该如何处理?

    此文章来源于项目官方公众号:"AirtestProject" 版权声明:允许转载,但转载必须保留原链接:请勿用作商业或者非法用途 1. 前言 最近有收到同学们的一些提问,使用Air ...

  5. 蚂蚁集团混沌工程 ChaosMeta V0.5 版本发布

    混沌工程 ChaosMeta 的全新版本 V0.5 现已正式发布!该版本包含了许多新特性和增强功能,为用户提供了支撑混沌工程各个阶段的平台能力,以及降低使用门槛的用户界面. ChaosMeta V0. ...

  6. Go学习笔记1

    学习路线 2023-Go全链路工程师课纲 https://www.processon.com/view/link/63594cd97d9c0854f9ac855e 一.搭建环境 https://stu ...

  7. ipmitool配置机器的BMC

    一.设置IP地址 1.确定操作对象 #ipmitool mc info 输出中"Device Revision"是命令的操作对象 2.设置BMC IP # ipmitool -I ...

  8. 如何使用Vite创建Vue3的uniapp项目

    项目结构 my-vue3-project ├─ .env //默认环境变量 ├─ .env.development //开发环境变量 ├─ .eslintrc-auto-import.json //( ...

  9. Go指针探秘:深入理解内存与安全性

    Go指针为程序员提供了对内存的深入管理能力,同时确保了代码的安全性.本文深入探讨了Go指针的基础概念.操作.深层理解及其特性与限制.通过深入了解其设计哲学和应用,我们可以更好地利用Go的强大功能. 关 ...

  10. 在线问诊 Python、FastAPI、Neo4j — 生成 Cypher 语句

    目录 构建节点字典 构建Cypher CQL语句 Test 这边只是为了测试,演示效果和思路,实际应用中,可以通过NLP构建CQL 接上一篇的问题分类 question = "请问最近看东西 ...