23种设计模式-程序员宅基地

技术标签: intellij-idea  设计模式  23种设计模式  

目录

一.设计模式的概述

1.1 不同维度分析代码质量的好坏

1.1.1 可读性

1.1.2 可维护性

1.1.3 可拓展性

1.1.4 可复用性

1.1.5 可测试性

1.1.6 灵活性

1.1.7 简洁性

1.2 编程方法论

1.2.1 面向对象

1.2.2 设计原则

1.2.3 设计模式

1.2.4 编程规范

1.2.5 重构技巧

1.3 设计模式概述

1.4 产生的背景

1.5 设计模式分类   

​编辑

二.UML图

2.1 UML类图的概述

2.2 UML类图的作用

2.3 UML类图的表示

2.3.1 UML类图具体表示

2.3.2 UML类图种的关系

三.创建型设计模式

3.1 单例模式

3.1.1 饿汉式单例模式 

3.1.2 懒汉式单例模式

3.1.3 静态内部类单例模式

3.1.4 反射对单例模式的破坏

3.1.5 反序列化对单例模式的破坏

3.1.6 枚举式单例模式

3.2 工厂方法模式

3.3 抽象工厂模式

3.4 建造者模式

3.5 原形模式

由于源代码太多,后续代码我将放到gitee上。

四.结构型设计模式

五.行为型设计模式

六.设计模式在框架中的应用

七.参考网址


一.设计模式的概述

1.1 不同维度分析代码质量的好坏

程序员一般评论其他人的代码写的好烂,好low,垃圾代码,都是一个笼统的称谓,代码写的好或者坏都要看代码的7个特性。

1.1.1 可读性

code view  

代码要易于理解,写代码要遵循编码规范。

代码不应该冗余,函数代码过多,代码依赖性很强。  

1.1.2 可维护性

    在原有的代码设计下,不破坏原有的代码设计以及不引入新bug,能快速新增代码,或者修改原有代码。

1.1.3 可拓展性

  在不动或者少动原来的代码的基础上,添加新的功能代码。

1.1.4 可复用性

  写的共性代码抽取出来,做到代码的复用。

  不应在同一项目类,编写重复的代码逻辑。

1.1.5 可测试性

  编写代码应该要易于测试,不应写出测试困难重重的代码。

  在开发过程中,测试是避免不了的。

1.1.6 灵活性

  指的是在新增代码时,已有代码不受影响,不冲突,不产生排斥。

  我们通常会把共性的代码抽取出来,并且留出接口,目的就是为了易于拓展。

1.1.7 简洁性

 写代码应该要做到别人理解它所需的时间最小化。

 例如方法的见名知意。

1.2 编程方法论

        编程方法论(Programming methodology)是指软件开发中用于规范、管理和优化整个开发过程的方法、技术和流程。良好的编程方法论可以提高软件开发的效率和质量,减少开发成本和时间。

1.2.1 面向对象

面向对象是软件开发方法,一种编程范式。

1.2.2 设计原则

设计原则是指导我们代码设计的一些经验总结。

  1. 单一职责原则
  2. 开发封闭原则
  3. 里氏替换原则
  4. 依赖倒置原则
  5. 接口隔离原则
  6. 迪米特法则

1.2.3 设计模式

设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思 路。

1.2.4 编程规范

编程规范主要解决的是代码的可读性问题。

1.2.5 重构技巧

不改变原有的代码外部行为情况下,对代码进行重构。

1.3 设计模式概述

        设计模式(Design Pattern)是经过总结、优化的、反复使用的、被广泛接受的、可用来解决特定问题的程序设计经验的总结。它是一种被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式的目的是促进软件开发人员的共同语言、思维方式和理解能力,使编程任务变得更加容易。

        设计模式既是一种思想,也是一种解决问题的方案,它关注于如何通过面向对象的方式去解决系统中的一些常见问题。它提供了一套通用的解决方案,由于这些解决方案是经过验证的、经过了实践证明的,可以用于大多数情况,从而可以提高软件开发的效率和质量。

1.4 产生的背景

        设计模式,最开始是使用在建筑领域,而不是软件设计中的。

在软件开发的历史中,人们发现不同的系统之间存在着很多共性,例如设计问题、代码重复等。为了解决这些共性问题,人们将经验和知识总结出来,形成了一些通用的解决方案,这些解决方案被称为设计模式。

        20世纪80年代,四位著名的计算机科学家(埃里克·伽玛、理査德·海尔姆、拉尔夫·约翰逊和约翰·威利斯)在《设计模式:可复用面向对象软件的基础》一书中定义了23种经典的设计模式,并将它们分为创建型、结构型和行为型三大类。这些设计模式被广泛接受,并被认为是提高软件设计和开发质量的有效手段。

设计模式的出现,极大地促进了软件开发的进步,使得开发人员可以更加高效地解决复杂的设计问题。

1.5 设计模式分类   

设计模式可以按照它们解决的问题或要达成的目标进行分类,常见的分类包括以下几类:

  1. 创建型模式:用于处理对象的创建,提供了创建对象的最佳实践。常见的创建型模式有简单工厂模式、工厂方法模式、抽象工厂模式、单例模式、建造者模式和原型模式。

  2. 结构型模式:用于描述如何将类或对象组合成更大的系统。结构型模式关注类和对象的组合,通过继承和组合形成更大的结构。常见的结构型模式有适配器模式、桥接模式、组合模式、装饰器模式、外观模式、享元模式和代理模式。

  3. 行为型模式:用于描述对象之间的通信方式及其行为的分配。行为型模式描述了对象之间的通信方式,以及它们如何相互协作以完成任务。常见的行为型模式有命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。

二.UML图

        UML(Unified Modeling Language)是一种用于软件开发的图形化语言。它的主要目的是帮助开发人员在不同的阶段设计、分析和构建软件系统。UML图包括以下几种类型:

  1. 用例图(Use Case Diagram):用于描述系统的功能和用户之间的交互。

  2. 类图(Class Diagram):用于描述系统中的类、对象、属性和方法之间的关系。

  3. 序列图(Sequence Diagram):用于描述对象之间的交互,以及消息和操作之间的时间顺序。

  4. 活动图(Activity Diagram):用于描述系统中的业务流程和工作流程。

  5. 状态图(State Diagram):用于描述对象的状态和状态之间的转换。

  6. 组件图(Component Diagram):用于描述系统中的组件、接口和依赖关系。

  7. 部署图(Deployment Diagram):用于描述系统的物理部署,包括硬件、软件和网络设备等。

通过使用这些UML图,开发人员可以更加清晰地理解系统的结构和行为,从而更加高效地进行软件开发。

在研究设计模式时,我们通常使用UML类图去描述更加可靠。下面来介绍一下类图。

2.1 UML类图的概述

        UML类图是一种可视化的模型,用于描述软件系统中的类、接口、关系和属性等信息。它是UML(统一建模语言)的一部分,可以帮助软件开发人员更好地理解系统的结构和设计,从而更方便地进行开发和维护。

        类图是UML中最基本的图之一,它显示了系统中的类、接口、关系和属性等元素之间的关系。类图中的类通常用矩形表示,类名写在矩形中间,属性和方法则写在矩形下面和上面。类之间的关系常用的有继承、实现和关联等。

2.2 UML类图的作用

        UML类图是一种用于描述面向对象系统中类、对象、属性、方法和关系的统一建模语言。它可以帮助软件开发人员更好地理解系统的结构和功能,从而更好地进行软件设计和开发。具体来说,UML类图的作用包括:

        1. 用于设计系统结构:UML类图可以帮助开发人员设计系统的基本结构,包括类、属性和方法等。

        2. 用于说明系统功能:UML类图可以通过类之间的关联关系和继承关系,说明系统的基本功能。

        3. 用于开发流程控制:UML类图可以通过状态机图和活动图等其他图形来表示流程控制逻辑。

        4. 用于文档编写:UML类图可以用于生成系统设计文档,对项目的开发过程具有指导作用。

总之,UML类图是软件开发的一种重要工具,它可以使开发人员更好地理解和设计系统,从而提高开发效率和软件质量。

2.3 UML类图的表示

UML类图中包含的元素主要有:

        1.类:表示系统中的一个对象或者一组对象,通常用矩形表示。

        2.接口:定义一组方法,但不实现它们,通常用圆角矩形表示。

        3.属性:类中的数据成员,表示类的状态。

        4.方法:类中的函数成员,表示类的行为。

        5.关系:表示不同类之间的关系,包括继承、实现、关联、聚合和组合等。

        6.泛化:表示一种类是另一种类的特殊形式,通常用箭头表示。

        7.实现:表示一个类实现了一个接口,通常用带三角箭头的虚线表示。

        8.关联:表示不同类之间的关系,通常用带箭头的实线表示。

        9.聚合:表示一种弱的拥有关系,即一个类拥有另一个类的实例,通常用带空心菱形的实线表示。

        10.组合:表示一种强的拥有关系,即一个类拥有另一个类的实例,并对其生命周期负责,通常用带实心菱形的实线表示。

2.3.1 UML类图具体表示

简单类

public class SimpleHuman {
    public String name;
    private Integer age;
    protected String sex;
    String number;
    public void eat(){
    }
    private String play(){
        return "玩的很好";
    }
}

可以看到,public 开锁的状态,private 为锁状态, protected 需要钥匙,默认如下number表示。

抽象类

public abstract class SimpleAbstractHuman {
    public String name;
    private Integer age;
    protected String sex;
    String number;
    public abstract void eat();
    private String play(){
        return "玩的很好";
    }
}

接口

public interface SimpleInterfaceHuman {

    public static final String NAME = "张三";

    public void eat();

    String run();

    void play();

    Integer read();
}

2.3.2 UML类图种的关系

        UML类图主要用于表示系统或软件的静态结构,它描述了系统中的类、接口、属性和方法之间的关系。类图的关系包括:

        1. 继承关系:表示一个类继承自另一个类。

        2. 实现关系:表示一个类实现了一个接口。

        3. 关联关系:表示两个类之间有某种类型的关联关系,如一对一、一对多、多对一和多对多。

        4. 聚合关系:表示一个对象包含多个其他对象,子对象可以独立于父对象存在。

        5. 组合关系:表示一个对象被另一个对象包含,子对象不能独立于父对象存在。

        6. 依赖关系:表示一个类的实现依赖于另一个类。

类图中,类之间的关系非常重要,它们可以帮助开发人员更好地理解系统结构,并指导实现和维护的工作。

UML类图的关系
关系 符号表示
继承关系 空心三角形+实线
实现关系 空心三角形+虚线
关联关系 实线箭头
聚合关系 空心菱形+实线箭头
组合关系 实心菱形+实线箭头
依赖关系 虚线箭头

三.创建型设计模式

创建型设计模式是一种创建对象的设计模式,它关注于对象的创建过程,旨在通过创建对象的过程来提高系统的灵活性和可扩展性。构造型设计模式包括如下几种:

  1. 工厂方法模式(Factory Method Pattern):定义了一个用于创建对象的接口,但由子类决定要实例化的类是哪一个。

  2. 抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或依赖对象的接口,而无需指定具体类。

  3. 建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

  4. 原型模式(Prototype Pattern):用原型实例指定创建对象的种类,并通过复制这些原型创建新的对象。

  5. 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点来访问该实例。

这些设计模式在实际开发中都有非常广泛的应用,可以帮助开发者更加灵活地创建对象,并提高代码的可扩展性和可维护性。

3.1 单例模式

3.1.1 饿汉式单例模式 

实现如下

public class Hungry {
    private Hungry(){}

    private static Hungry hungry = new Hungry();

    public static Hungry getInstance(){
        return hungry;
    }
}

测试如下

    /**
     * 测试饿汉式单例模式
     *   方法:看实例是否为同一个
     */
    @Test
    public void testHungryModel(){
        Hungry h1 = Hungry.getInstance();
        Hungry h2 = Hungry.getInstance();
        // 直接比较对象地址是否为同一个
        // true
        if(h1 == h2){
            System.out.println("h1 == h2");
        }
    }

结果如下

饿汉式创建的实例是线程安全的。

因为每次调用获取实例的方法,拿到的实例是在对象初始化阶段就已经创建出来的

3.1.2 懒汉式单例模式

实现如下

public class Lazy {
    private Lazy(){}
    private static Lazy lazy;
    public static Lazy getInstance(){
        if (lazy == null){
            return lazy = new Lazy();
        }
        return lazy;
    }
}

测试如下

    /**
     * 测试懒汉式单例模式
     *   方法:看实例是否为同一个
     */
    @Test
    public void testLazyModel(){
        for (int i = 0; i < 100; i++){
            new Thread(() -> {
                Lazy instance = Lazy.getInstance();
                System.out.println(Thread.currentThread().getName() + "------" + instance);
            }).start();
        }
    }
}

结果如下

不是说好了单例模式吗?为什么还是出现了不同的实例呢。

原因很简单:因为上面的代码在单线程的情况下是线程安全的

但是多线程的情况下,是不安全的

假设一个线程进入到获取实例内部,还没出来,目前实例为空

第二个线程进入到实例内部,因为是空值,他也创建一个实例

最终导致有不同的实例出现。

改进如下

public class Lazy02 {
    private Lazy02(){}

    private static Lazy02 lazy;

    public static synchronized Lazy02 getInstance(){
        if (lazy == null){
            return lazy = new Lazy02();
        }
        return lazy;
    }
}

问题

代码只是在获取实例方法上多加了一个 synchronize 同步锁 

问题就得以解决了,但是这样的做法和我单线程创建单例的效率有何区别?

这样每个线程获取实例就要去竞争锁资源,没拿到锁资源只能阻塞,大大影响效率

下面在对代码进行改进,缩小锁的范围。

再次改进 --双重检验

public class Lazy03 {

    /**
     * 双重校验
     */
    static class DoubleCheck{

        private DoubleCheck(){};

        private static volatile DoubleCheck instance;

        public static DoubleCheck getInstance(){
            if( instance == null){
                synchronized (DoubleCheck.class){
                    /*
                    这里是为了防止,线程刚刚释放锁资源,
                    其余线程进入判断获得锁资源加的校验
                     */
                    if(instance == null){
                        instance = new DoubleCheck();
                    }
                }
            }
            return instance;
        }
    }
}

3.1.3 静态内部类单例模式

实现如下

public class StaticInnerClass {

    private StaticInnerClass(){};

    private static class InnerClass{
        private static volatile StaticInnerClass instance
                = new StaticInnerClass();
    }

    public static StaticInnerClass getInstance(){
        return InnerClass.instance;
    }

}

测试如下

    /**
     * 测试静态内部类单例模式
     *   方法:看实例是否为同一个
     */
    @Test
    public void testStaticInnerClassModel(){
        StaticInnerClass s1 = StaticInnerClass.getInstance();
        StaticInnerClass s2 = StaticInnerClass.getInstance();
        if(s1 == s2){
            System.out.println("s1 == s2");
        }
    }

结果如下

3.1.4 反射对单例模式的破坏

测试如下

    @Test
    public void testReflectDestroy(){
        try {
            Class<StaticInnerClass> clazz = StaticInnerClass.class;
            Constructor<StaticInnerClass> c = clazz.getDeclaredConstructor();
            //设置为true后,就可以对类中的私有成员进行操作
            c.setAccessible(true);
            
            StaticInnerClass s1 = c.newInstance();
            StaticInnerClass s2 = c.newInstance();
            System.out.println(s1 == s2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

结果如下

问题

单例模式的目的是确保一个类只有一个实例,并且提供全局访问点。然而,反射允许我们在运行时动态地访问和修改一个类的属性和方法,包括私有构造函数和私有变量。如果在一个单例类中使用反射,我们可以通过调用私有构造函数来创建多个实例,这违反了单例模式的原则。

此外,反射还可以访问和修改单例类中的私有变量和方法,这可能会导致单例对象的状态不一致或不安全,从而破坏了单例模式的目的。

所以,在单例模式中使用反射,需要特别小心,因为它可能会导致单例实例的破坏或者状态不一致。

解决方法

public class StaticInnerClass {

    private StaticInnerClass(){
        if(InnerClass.instance != null){
            throw new RuntimeException("不允许非法访问");
        }
    };

    private static class InnerClass{
        private static volatile StaticInnerClass instance
                = new StaticInnerClass();
    }

    public static StaticInnerClass getInstance(){
        return InnerClass.instance;
    }

}

结果如下

3.1.5 反序列化对单例模式的破坏

测试如下

public class SerializationDestroy {
    /**
     * 单例类实现序列化接口
     */
    public static class Singleton implements Serializable {

        private volatile static Singleton singleton;

        private Singleton() {

        }

        public static Singleton getInstance() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }


    }

    /**
     * 序列化对单例模式的破坏
     */
    public static void serializationDestroy() throws Exception {
        //序列化对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.obj"));
        oos.writeObject(Singleton.getInstance());

        //序列化对象输入流
        File file = new File("a.obj");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        Singleton Singleton = (Singleton) ois.readObject();

        System.out.println(Singleton);
        System.out.println(SerializationDestroy.Singleton.getInstance());

        //判断是否是同一个对象 //false
        System.out.println(SerializationDestroy.Singleton.getInstance() == Singleton);
    }

}

结果如下

解决方案

    public static class Singleton implements Serializable {

        private volatile static Singleton singleton;

        private Singleton() {

        }

        public static Singleton getInstance() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }

        /**
         * 解决方案:只要在Singleton类中定义readResolve就可以解决该问题
         * 程序会判断是否有readResolve方法,如果存在就在执行该方法,如果不存在--就创建一个对象
         */
        private Object readResolve() {
            return singleton;
        }

    }

原因

    /**
    * ObjectInputStream.readObject方法
    */    
    public final Object readObject()
        throws IOException, ClassNotFoundException
    {
        if (enableOverride) {
            return readObjectOverride();
        }

        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
	        // 重点看此处方法
            Object obj = readObject0(false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }

/**
* 上述方法的代码片段
*/        
switch (tc) {
      // 判断如果为对象              
      case TC_OBJECT:
          return checkResolve(readOrdinaryObject(unshared));
}

    // 进入到此处方法
    private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {

        Object obj;
        try {
	        // 如果一个对象在序列化过程可以被实例化
	        // 那么通过反射 new 一个对象...
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        return obj;
    }

通过上面反序列化的源码我们可以知道,对象在反序列化过程中,通过反射,创建了实例,所以才会导致单例失效。

为什么 加了readResolve()能解决问题?

        在Java中,当一个对象被序列化并反序列化回来时,它会变成一个全新的对象,其状态与原来的对象不同。考虑到单例模式的实现方式,它的构造器是私有的,单例对象通过一个静态方法(如getInstance())返回,因此无法通过普通的反射方式创建新的实例。

        然而,如果一个类实现了Serializable接口并且被序列化后,反序列化过程会创建一个新的实例,不同于原始的单例对象。这就导致了单例模式的破坏。为了避免这种情况,可以借助Java提供的readResolve()方法来解决单例破坏问题。

        当一个序列化对象被反序列化时,Java运行时系统会检查该类是否实现了readResolve()方法,如果有实现,会在反序列化对象之前调用该方法,并将readResolve()返回的对象替换反序列化后的对象。通过实现readResolve()方法,可以在反序列化过程中控制对象的创建和替换。因此,我们可以在该方法中返回原始的单例对象,确保反序列化后的对象与原始单例对象是同一个实例。

if (obj != null &&
            handles.lookupException(passHandle) == null &&
            // 如果实现了serializable 接口的类中包含readResolve则返回true
            desc.hasReadResolveMethod())
        {
            // 调用我们上面解决问题中新建的方法
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                handles.setObject(passHandle, obj = rep);
            }
        }

3.1.6 枚举式单例模式

实现如下

public enum EnumSingleton {
    INSTANCE,
    ;

    private Object data;


    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public EnumSingleton getInstance(){
        return INSTANCE;
    }

}

推荐使用枚举类创建单例模式的原因如下:

  1. 线程安全:枚举类在Java中是线程安全的,在多线程环境下可以避免线程安全问题。

  2. 序列化安全:枚举类会自动处理序列化和反序列化,可以避免单例模式在序列化和反序列化过程中出现的问题。

  3. 简单明了:使用枚举类创建单例模式代码简单明了,可以避免单例模式中出现的复杂代码。

  4. 防止反射攻击:枚举类在Java中是无法通过反射创建对象的,可以防止反射攻击。

因此,使用枚举类创建单例模式是一种简单、安全、高效的方式。

3.2 工厂方法模式

工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它定义一个用于创建对象的接口,但是由子类决定需要创建的具体对象类。工厂方法让一个类的实例化延迟到其子类。这样,子类可以通过重写工厂方法,修改其所创建的对象的类型。

工厂方法模式的主要角色有:

  1. 抽象工厂(Creator):定义一个工厂接口,包含创建产品对象的抽象方法。
  2. 具体工厂(Concrete Creator):实现抽象工厂接口,返回一个具体产品类的实例。
  3. 抽象产品(Product):定义产品的接口,描述产品的属性、方法等。
  4. 具体产品(Concrete Product):实现抽象产品接口,具体产品与具体工厂一一对应。

工厂方法模式的优点:

  1. 可以很好地封装对象的创建过程,降低了系统的耦合度。
  2. 可以添加新的产品类,无需修改现有的代码,只需要增加一个具体产品类和对应的具体工厂类即可。
  3. 可以让客户端程序在不必知道具体产品类名的情况下,创建新的对象。

工厂方法模式的缺点:

  1. 需要额外定义很多工厂类或者工厂方法,增加了系统的抽象性和理解难度。
  2. 增加了系统的类和对象个数,增加了系统的复杂度和开销。
  3. 客户端必须知道所使用的工厂类,如果使用了错误的工厂类,就会导致运行时错误。

需求: 为了让我们的案例更加贴近实际开发, 模拟一下互联网电商中促销拉新下的业务场景, 新用户注册立即参与抽奖活动 ,奖品的种类有: 打折券, 免费爱奇艺会员,小礼品.

下面不使用工厂方来实现 -- 展示免费爱奇艺会员逻辑

接口层

public class Client {

    public void sendByType(String type){
        //奖品类型: 1 打折券 ,2 爱奇艺会员,3 小礼品
        if("1".equals(type)){
            DiscountService discountService = new DiscountService();
            DiscountInfo discountInfo = new DiscountInfo();
            discountService.sendDiscount(discountInfo);
        } else if ("2".equals(type)) {
            AiQiYiService aiQiYiService = new AiQiYiService();
            AiQiYiMember aiQiYiMember = new AiQiYiMember();
            aiQiYiMember.setUserName("张三");
            aiQiYiMember.setUserPhone("13812015488");
            aiQiYiMember.setOrderId("1");
            aiQiYiMember.setRelEmail("[email protected]");
            aiQiYiService.sendMember(aiQiYiMember);
        } else if ("3".equals(type)) {
            SmallGiftService smallGiftService = new SmallGiftService();
            smallGiftService.giveSmallGift(new SmallGiftInfo());
        }
    }

}

实现层

public class AiQiYiService {

    public AiQiYiResult sendMember(AiQiYiMember aiQiYiMember){
        System.out.println("向"+aiQiYiMember.getUserName() +"发送优酷会员");
        System.out.println("已发送到邮箱"+ aiQiYiMember.getRelEmail()+",请注意查收");
        return new AiQiYiResult("200","OK");
    }
}

public class DiscountService {
    public Boolean sendDiscount(DiscountInfo discountInfo){
        System.out.println("打折卷发送成功");
        return true;
    }
}

public class SmallGiftService {

    public Boolean giveSmallGift(SmallGiftInfo smallGiftInfo){
        System.out.println("小礼品发放成功");
        return true;
    }
}

数据层

@Data
public class AiQiYiMember {

    /**
     * 用户姓名
     */
    private String userName;
    /**
     * 用户手机
     */
    private String userPhone;
    /**
     * 订单ID
     */
    private String orderId;
    /**
     * 收货邮箱
     */
    private String relEmail;
}

@Data
@AllArgsConstructor
public class AiQiYiResult {
    /**
     *  状态码
     */
    private String status;
    /**
     * 信息
     */
    private String message;
}

public class AwardInfo {
    /**
     * 用户唯一ID
     */
    private String uid;
    /**
     * 奖品类型: 1 打折券 ,2 爱奇艺会员,3 小礼品
     */
    private Integer awardType;
    /**
     * 奖品编号
     */
    private String awardNumber;
    /**
     * 额外信息
     */
    Map<String, String> extMap;
}

public class DiscountInfo {

    // 省略.....
}

public class SmallGiftInfo {
    // 省略.....
}

上述的业务代码,看起来没什么问题,就是一个根据不同的类型发奖品。

但是有没有想过,新增一种获奖方式的时候就要更改Client代码,又要多加一组if-else代码

这样违反开闭原则,而且这样过程也十分的繁琐

下面我们对代码再次改进

接口层

public class Client2 {

    public Result sendAward(AwardInput input){
        // 拿到工厂引用
        AwardFactory awardFactory = new AwardFactory();
        // 发放奖品
        return awardFactory.sendAward(input);
    }

}

服务层

public interface AwardInterface {

    Result sendAward();
}

public class AiQiYiInfo implements AwardInterface {
    @Override
    public Result sendAward() {
        System.out.println("爱奇艺会员发放成功");
        return new Result();
    }
}

public class DiscountInfo implements AwardInterface {
    @Override
    public Result sendAward() {
        System.out.println("打折卷发放成功");
        return new Result();
    }
}

public class SmallGiftInfo implements AwardInterface {

    @Override
    public Result sendAward() {
        System.out.println("小礼品发放成功");
        return new Result();
    }
}

工厂

public class AwardFactory {

    public Result sendAward(AwardInput input){
        // 1 打折券 ,2 爱奇艺会员,3 小礼品
        AwardInterface awardInterface = null;
        if(input.getType().equals("1")){
            awardInterface = new DiscountInfo();
            awardInterface.sendAward();
        }

        if(input.getType().equals("2")){
            awardInterface = new AiQiYiInfo();
            awardInterface.sendAward();
        }

        if(input.getType().equals("3")){
            awardInterface = new SmallGiftInfo();
            awardInterface.sendAward();
        }

        return new Result(awardInterface,"200","ok");
    }


}

VO

@Data
@AllArgsConstructor
public class AwardInput {

    private String type;

}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {

    private Object data;

    private String code;

    private String message;
}

上面的代码已经用了工厂调用抽象工厂接口去创建,看起来没什么问题了。

也使用到了简单工厂模式(不属于23种设计模式),但是真的没问题了吗?

从工厂的代码中我们还是用到了很多if-else去判断,新增一个发放奖品的服务,又要多加一组if-else,违反了开闭原则。

下面使用工厂方法模式,对代码进行重构

抽象工厂 

public interface AwardFactory {

    AwardInterface getInstance();
}

具体工厂

public class AiQiYiFactory implements AwardFactory {
    @Override
    public AwardInterface getInstance() {
        return new AiQiYiInfo();
    }
}
public class DiscountFactory implements AwardFactory {
    @Override
    public AwardInterface getInstance() {
        return new DiscountInfo();
    }
}
public class SmallGiftFactory implements AwardFactory {
    @Override
    public AwardInterface getInstance() {
        return new SmallGiftInfo();
    }
}

业务接口

public class Client3 {

    public Result sendAward(AwardInput input){
        // 拿工厂实例
        AwardFactory factory = null;
        //  1 打折券 ,2 爱奇艺会员,3 小礼品
        if(input.getType().equals("1")){
            factory = new DiscountFactory();
        }
        if(input.getType().equals("2")){
            factory = new AiQiYiFactory();
        }
        if(input.getType().equals("3")){
            factory = new SmallGiftFactory();
        }
        // 通过工厂拿到实例
        AwardInterface instance = factory.getInstance();
        // 发奖
        return instance.sendAward();
    }
}

上述方法使用到了工厂方法模式,但是业务层接口又出现了一堆的if-else,所以在把工厂抽象出来,代码如下

public class AwardFactoryFactory {


    public static final HashMap<String, AwardFactory> FACTORY_MAP = new HashMap<>();

    static {
        FACTORY_MAP.put("1",new DiscountFactory());
        FACTORY_MAP.put("2",new AiQiYiFactory());
        FACTORY_MAP.put("3",new SmallGiftFactory());
    }

    public static AwardFactory getFactory(String type){
        return FACTORY_MAP.get(type);
    }

}
public class Client4 {

    public Result sendAward(AwardInput input){
        AwardFactory factory = AwardFactoryFactory.getFactory(input.getType());
        return factory.getInstance().sendAward();
    }
}

上述代码,如果要多加产品的话,只需要改动少量代码即可,遵循了开闭原则。

3.3 抽象工厂模式

抽象工厂模式(Abstract Factory Pattern)是指提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。

在抽象工厂模式中,有一个抽象工厂(Abstract Factory)接口,定义了可以创建多个产品族(product family)的方法,每个产品族包含多个产品(Product)。具体的工厂类(Concrete Factory)实现了抽象工厂接口,并且负责实现具体的产品。在客户端使用时,使用抽象工厂接口定义的方法来创建具体产品,而无需关心具体工厂类和产品类的实现细节。

优点:

  1. 便于扩展产品族:增加产品族时,只需要增加对应的具体工厂和产品类即可,不需要修改已有代码;
  2. 保证产品配套性:同一工厂生产的产品配合使用,可以保证产品配套性;
  3. 降低耦合度:客户端只需要知道抽象工厂接口和抽象产品接口,不需要关心具体的实现细节。

缺点:

  1. 增加新产品会比较麻烦,需要修改所有的工厂类;
  2. 容易增加系统的复杂性。

适用场景:

  1. 同一产品族的产品在一起使用;
  2. 系统需要保证产品配套性,如家电、汽车产品的零配件等;
  3. 系统需要提供一个产品的多种实现,客户端只需要知道抽象产品接口即可。

控制层

public class Client5 {

    public ChinaProductService getChinaProduct(AbstractFactory abstractFactory){
        // 中国奔驰 中国阿迪鞋
        return abstractFactory.getChinaProduct();
    }

    public USAProductService getUsaProduct(AbstractFactory abstractFactory){
        // 美国宝马 美国耐克鞋
        return abstractFactory.getUSAProduct();
    }


}

业务层

public interface ChinaProductService {
    void getInstance();
}
public interface USAProductService {
    void getProduct();
}
public class ChinaAdidasShoeImpl implements ChinaProductService {
    @Override
    public void getInstance() {
        System.out.println(new ChinaAdidasShoe().getName());
    }
}
public class ChinaBanZImpl implements ChinaProductService {

    @Override
    public void getInstance() {
        System.out.println(new ChinaBanZ().getName());
    }
}
public class USABaoMaImpl implements USAProductService {

    @Override
    public void getProduct() {
        System.out.println(new USABaoMa().getName());
    }
}
public class USANikeShoeImpl implements USAProductService {

    @Override
    public void getProduct() {
        System.out.println(new USANikeShoe().getName());
    }
}

数据层

@Data
public class ChinaAdidasShoe {
    private String name = "中国阿迪达斯鞋子";
}
@Data
public class ChinaBanZ {
    private String name = "中国奔驰汽车";
}
@Data
public class USABaoMa {
    private String name = "美国宝马汽车";
}
@Data
public class USANikeShoe {

    private String name = "美国耐克鞋子";
}

工厂

public interface AbstractFactory {

    ChinaProductService getChinaProduct();

    USAProductService getUSAProduct();

}
public class CarFactory implements AbstractFactory {

    @Override
    public ChinaProductService getChinaProduct() {
        return new ChinaBanZImpl();
    }

    @Override
    public USAProductService getUSAProduct() {
        return new USABaoMaImpl();
    }
}

由上可见,抽象工厂模式,可以生成一类的工厂。

如上方,分为中国产品,美国产品,而工厂有汽车工厂,鞋子工厂。

3.4 建造者模式

建造者模式是一种创建型设计模式,用于将复杂对象的创建过程分解为多个简单的步骤,以便更好地进行控制和管理。

在该模式中,由一个指导者类来控制建造过程,而具体对象的构建由若干个建造者类来实现,每个建造者类只负责构建自己负责的部分,最终由指导者将这些部分组装在一起形成一个完整的对象。

以下是建造者模式的一个示例代码:

// 产品类
class Product {
    private String partA;
    private String partB;
    private String partC;

    public void setPartA(String partA) {
        this.partA = partA;
    }

    public void setPartB(String partB) {
        this.partB = partB;
    }

    public void setPartC(String partC) {
        this.partC = partC;
    }

    public String toString() {
        return "PartA: " + partA + " PartB: " + partB + " PartC: " + partC;
    }
}

// 抽象建造者
abstract class Builder {
    protected Product product = new Product();

    public abstract void buildPartA();

    public abstract void buildPartB();

    public abstract void buildPartC();

    public Product getResult() {
        return product;
    }
}

// 具体建造者
class ConcreteBuilder extends Builder {

    public void buildPartA() {
        product.setPartA("A");
    }

    public void buildPartB() {
        product.setPartB("B");
    }

    public void buildPartC() {
        product.setPartC("C");
    }

}

// 指导者
class Director {
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    public void construct() {
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        Builder builder = new ConcreteBuilder();
        Director director = new Director(builder);
        director.construct();
        Product product = builder.getResult();
        System.out.println(product);
    }
}

在上面的示例中,产品类 Product 包含了三个部分,具体建造者 ConcreteBuilder 实现了对三个部分的构建,而指导者 Director 负责控制建造过程,最终得到一个完整的产品对象。测试类中,通过建造者来构造产品,并输出结果。

3.5 原形模式

原型模式(Prototype Pattern)是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而不需要从头开始创建。这个模式主要是用于对象的复制,它的核心是就是对象复制和对象复制的实现方法。

使用原型模式的时候,我们需要先创建好一个原型对象,然后通过复制原型对象来创建多个新的对象。新对象可以根据需要进行修改,而原型对象保持不变。

下面是一个简单的java代码示例,其中我们创建了一个简单的Shape类用于表示图形,并通过继承来创建了不同类型的图形类。然后我们创建了一个ShapeCache类来缓存不同类型的图形对象,并通过复制来创建新对象。

import java.util.HashMap;

abstract class Shape implements Cloneable {
    private String id;
    protected String type;
    
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    
    public abstract void draw();
    
    public Object clone() {
        Object clone = null;
        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
}

class Circle extends Shape {
    public Circle() {
        type = "Circle";
    }
    public void draw() {
        System.out.println("Inside Circle::draw() method.");
    }
}

class Rectangle extends Shape {
    public Rectangle() {
        type = "Rectangle";
    }
    public void draw() {
        System.out.println("Inside Rectangle::draw() method.");
    }
}

class Square extends Shape {
    public Square() {
        type = "Square";
    }
    public void draw() {
        System.out.println("Inside Square::draw() method.");
    }
}

class ShapeCache {
    private static HashMap<String, Shape> shapeMap = new HashMap<String, Shape>();

    public static Shape getShape(String shapeId) {
        Shape cachedShape = shapeMap.get(shapeId);
        return (Shape) cachedShape.clone();
    }

    public static void loadCache() {
        Circle circle = new Circle();
        circle.setId("1");
        shapeMap.put(circle.getId(),circle);

        Square square = new Square();
        square.setId("2");
        shapeMap.put(square.getId(),square);

        Rectangle rectangle = new Rectangle();
        rectangle.setId("3");
        shapeMap.put(rectangle.getId(),rectangle);
    }
}

public class PrototypePatternDemo {
    public static void main(String[] args) {
        ShapeCache.loadCache();

        Shape clonedShape = (Shape) ShapeCache.getShape("1");
        System.out.println("Shape : " + clonedShape.getType());        

        Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
        System.out.println("Shape : " + clonedShape2.getType());        

        Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
        System.out.println("Shape : " + clonedShape3.getType());        
    }
}

运行结果如下:

Inside Circle::draw() method.
Shape : Circle
Inside Square::draw() method.
Shape : Square
Inside Rectangle::draw() method.
Shape : Rectangle

在以上代码中,我们首先定义了一个Shape类,它有一个类型属性和一个抽象方法draw()。然后我们通过继承创建了三个不同的图形类Circle、Rectangle、Square,并实现了它们的draw()方法。

接着,我们创建了一个ShapeCache类,用于缓存不同类型的图形对象。在loadCache()方法中,我们创建了三个图形对象,并将它们缓存到一个HashMap中。在getShape()方法中,我们根据传入的参数获取对应的图形对象,并通过调用对象的clone()方法来返回一个新的对象。

最后,在main()方法中,我们测试了三种不同类型的图形对象的获取、复制和输出。这也展示了原型模式的核心思想:通过复制原型对象来创建新的对象。

由于源代码太多,后续代码我将放到gitee上。

四.结构型设计模式

结构型设计模式是一种软件设计模式,旨在提高程序的组织性和可维护性。它们关注的是类和对象的组成方式,以及它们相互之间的关系,以实现更加灵活和可扩展的代码结构。

下面是一些常见的结构型设计模式:

  1. 适配器模式(Adapter Pattern):将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。

  2. 桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们可以独立地变化。

  3. 组合模式(Composite Pattern):把对象组合成树形结构,以表示“部分-整体”的层次结构。

  4. 装饰器模式(Decorator Pattern):动态地给一个对象添加一些额外的职责,同时又不改变其结构。

  5. 外观模式(Facade Pattern):为子系统中的一组接口提供一个一致的界面,以方便客户端使用。

  6. 享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度的对象。

  7. 代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。

这些模式都是在不同的场景下提高代码结构和可维护性的有效工具。

五.行为型设计模式

行为型设计模式是一种软件设计模式,它关注对象之间的通信和交互,旨在提高对象之间的灵活性和可维护性。其中一些模式包括:

  1. 观察者模式:定义对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动收到通知。

  2. 策略模式:定义一系列算法,并将它们封装起来,使它们可以相互替换,从而使算法的变化独立于使用算法的客户。

  3. 命令模式:将请求封装成一个对象,从而使您可以使用不同的请求、队列或日志记录来参数化客户端,并支持可撤销操作。

  4. 迭代器模式:提供一种方法来访问集合对象,而无需暴露其底层结构。

  5. 责任链模式:将请求的发送者和接收者解耦,并将多个对象依次链接起来处理请求。

  6. 模板方法模式:定义一个操作中的算法框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

  7. 状态模式:允许对象在其内部状态改变时改变其行为,使得对象看起来似乎已经改变了其类。

  8. 中介者模式:定义一个封装对象之间交互的对象,从而使对象之间相互独立。

  9. 访问者模式:定义一组与对象结构分离的操作,从而可以为一个对象集合中的各个对象透明地添加新的操作。

六.设计模式在框架中的应用

gitee持续更新

七.参考网址

1.6大设计原则参考网址

2.UML类图参考网址 

3.设计模式类图参考

3.设计模式类图参考网址2

4.gitee代码保持更新

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_59291331/article/details/133245039

智能推荐

hdu 1229 还是A+B(水)-程序员宅基地

文章浏览阅读122次。还是A+BTime Limit: 2000/1000 MS (Java/Others)Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 24568Accepted Submission(s): 11729Problem Description读入两个小于10000的正整数A和B,计算A+B。...

http客户端Feign——日志配置_feign 日志设置-程序员宅基地

文章浏览阅读419次。HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息。FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。BASIC:仅记录请求的方法,URL以及响应状态码和执行时间。NONE:不记录任何日志信息,这是默认值。配置Feign日志有两种方式;方式二:java代码实现。注解中声明则代表某服务。方式一:配置文件方式。_feign 日志设置

[转载]将容器管理的持久性 Bean 用于面向服务的体系结构-程序员宅基地

文章浏览阅读155次。将容器管理的持久性 Bean 用于面向服务的体系结构本文将介绍如何使用 IBM WebSphere Process Server 对容器管理的持久性 (CMP) Bean的连接和持久性逻辑加以控制,使其可以存储在非关系数据库..._javax.ejb.objectnotfoundexception: no such entity!

基础java练习题(递归)_java 递归例题-程序员宅基地

文章浏览阅读1.5k次。基础java练习题一、递归实现跳台阶从第一级跳到第n级,有多少种跳法一次可跳一级,也可跳两级。还能跳三级import java.math.BigDecimal;import java.util.Scanner;public class Main{ public static void main(String[]args){ Scanner reader=new Scanner(System.in); while(reader.hasNext()){ _java 递归例题

面向对象程序设计(荣誉)实验一 String_对存储在string数组内的所有以字符‘a’开始并以字符‘e’结尾的单词做加密处理。-程序员宅基地

文章浏览阅读1.5k次,点赞6次,收藏6次。目录1.串应用- 计算一个串的最长的真前后缀题目描述输入输出样例输入样例输出题解2.字符串替换(string)题目描述输入输出样例输入样例输出题解3.可重叠子串 (Ver. I)题目描述输入输出样例输入样例输出题解4.字符串操作(string)题目描述输入输出样例输入样例输出题解1.串应用- 计算一个串的最长的真前后缀题目描述给定一个串,如ABCDAB,则ABCDAB的真前缀有:{ A, AB,ABC, ABCD, ABCDA }ABCDAB的真后缀有:{ B, AB,DAB, CDAB, BCDAB_对存储在string数组内的所有以字符‘a’开始并以字符‘e’结尾的单词做加密处理。

算法设计与问题求解/西安交通大学本科课程MOOC/C_算法设计与问题求解西安交通大学-程序员宅基地

文章浏览阅读68次。西安交通大学/算法设计与问题求解/树与二叉树/MOOC_算法设计与问题求解西安交通大学

随便推点

[Vue warn]: Computed property “totalPrice“ was assigned to but it has no setter._computed property "totalprice" was assigned to but-程序员宅基地

文章浏览阅读1.6k次。问题:在Vue项目中出现如下错误提示:[Vue warn]: Computed property "totalPrice" was assigned to but it has no setter. (found in <Anonymous>)代码:<input v-model="totalPrice"/>原因:v-model命令,因Vue 的双向数据绑定原理 , 会自动操作 totalPrice, 对其进行set 操作而 totalPrice 作为计..._computed property "totalprice" was assigned to but it has no setter.

basic1003-我要通过!13行搞定:也许是全网最奇葩解法_basic 1003 case 1-程序员宅基地

文章浏览阅读60次。十分暴力而简洁的解决方式:读取P和T的位置并自动生成唯一正确答案,将题给测点与之对比,不一样就给我爬!_basic 1003 case 1

服务器浏览war文件,详解将Web项目War包部署到Tomcat服务器基本步骤-程序员宅基地

文章浏览阅读422次。原标题:详解将Web项目War包部署到Tomcat服务器基本步骤详解将Web项目War包部署到Tomcat服务器基本步骤1 War包War包一般是在进行Web开发时,通常是一个网站Project下的所有源码的集合,里面包含前台HTML/CSS/JS的代码,也包含Java的代码。当开发人员在自己的开发机器上调试所有代码并通过后,为了交给测试人员测试和未来进行产品发布,都需要将开发人员的源码打包成Wa..._/opt/bosssoft/war/medical-web.war/web-inf/web.xml of module medical-web.war.

python组成三位无重复数字_python组合无重复三位数的实例-程序员宅基地

文章浏览阅读3k次,点赞3次,收藏13次。# -*- coding: utf-8 -*-# 简述:这里有四个数字,分别是:1、2、3、4#提问:能组成多少个互不相同且无重复数字的三位数?各是多少?def f(n):list=[]count=0for i in range(1,n+1):for j in range(1, n+1):for k in range(1, n+1):if i!=j and j!=k and i!=k:list.a..._python求从0到9任意组合成三位数数字不能重复并输出

ElementUl中的el-table怎样吧0和1改变为男和女_elementui table 性别-程序员宅基地

文章浏览阅读1k次,点赞3次,收藏2次。<el-table-column prop="studentSex" label="性别" :formatter="sex"></el-table-column>然后就在vue的methods中写方法就OK了methods: { sex(row,index){ if(row.studentSex == 1){ return '男'; }else{ return '女'; }..._elementui table 性别

java文件操作之移动文件到指定的目录_java中怎么将pro.txt移动到design_mode_code根目录下-程序员宅基地

文章浏览阅读1.1k次。java文件操作之移动文件到指定的目录_java中怎么将pro.txt移动到design_mode_code根目录下

推荐文章

热门文章

相关标签