一.什么是设计模式

设计模式(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. ATtiny88初体验(一):点灯

    ATtiny88初体验(一):点灯 最近逛淘宝时,发现一块ATtiny88核心板(MH-ET LIVE Tiny88)用完红包后只剩4块钱了,果断下单,准备好好把玩一番. MH-ET LIVE Tin ...

  2. Unity TextMeshPro 添加中文字体遇见的问题以及解决方案

    前言 按标准官方教程为 Unity TextMeshPro 添加中文字体时出现了各种奇奇怪怪的问题,于是有了这篇随笔. 中文字体解决方案 以下步骤适用于 TextMeshPro 3.0.6. 字符数量 ...

  3. 《SQL与数据库基础》18. MySQL管理

    目录 MySQL管理 系统数据库 常用工具 mysql mysqladmin mysqlbinlog mysqlshow mysqldump mysqlimport source 本文以 MySQL ...

  4. 产品代码都给你看了,可别再说不会DDD(五):请求处理流程

    这是一个讲解DDD落地的文章系列,作者是<实现领域驱动设计>的译者滕云.本文章系列以一个真实的并已成功上线的软件项目--码如云(https://www.mryqr.com)为例,系统性地讲 ...

  5. 对比 MyBatis 和 MyBatis-Plus 批量插入、批量更新的性能和区别

    1 环境准备 demo 地址:learn-mybatis · Sean/spring-cloud-alibaba - 码云(gitee.com) 1.1 搭建 MyBatis-Plus 环境 创建 m ...

  6. mybatis数据库字段自动填充

    背景描述 目前,大多数项目的数据库设计,都会添加一些公共字段,比如version(版本号).deleted(逻辑删除标识).create_time.update_time.create_by.upda ...

  7. 银河麒麟使用kickstart二次打包制作安装镜像ISO

    系统:银河麒麟 V10 SP2 服务器:百信恒山 TS02F-F30 安装方式:服务器挂载ISO镜像进行安装 1.安装 mkisofs 软件包: #yum install genisoimage 2. ...

  8. Python比较字符串格式类型时间大小

    已知的格式是 06/24/2021 15:47:01.491 时间比较的思路是,把数据转换成时间戳比较: 第一步是把 06/24/2021 15:47:01.491 格式转换称 2021-06-24 ...

  9. MySQL系列之——索引作用、索引的种类、B树、聚簇索引构建B树、辅助索引(S)构建B+树、辅助索引细分、索引树的高度、索引的基本管理、执行计划获取及分析、索引应用规范、优化器针对索引、问题汇总

    文章目录 一 索引作用 二 索引的种类(算法) 三 B树 基于不同的查找算法分类介绍 B 树 B+树 B*树 四 在功能上的分类 4.1 聚簇索引构建B树(簇就是区) 4.1.1 前提 4.1.2 作 ...

  10. aspnetcore微服务之间grpc通信,无proto文件

    aspnetcore微服务之间通信grpc,一般服务对外接口用restful架构,HTTP请求,服务之间的通信grpc多走内网. 以前写过一篇grpc和web前端之间的通讯,代码如下: exercis ...