设计模式学习历程

62 阅读35分钟

设计模式的分类

类图(UML)

类图概述:

显示了模型的静态结构,特别是模型中存在的类,类的内部结构以及他们与其他类的关系等,类图不显示暂时性的信息,类图是面向对象建模的主要组成部分

类图的作用:

在软件工程中,类图是一种静态的结构图,描述了系统的类的集合,类的属性和类之间的关系,可以简化人们对系统的理解;

类图是系统分析和设计阶段的重要产物,是系统编码和测试的重要模型

类图的表示法:

类的表示方式:

在UML类图中,类使用包含类名,属性(field)和方法(method)且带有分割线的矩形来表示,如下表示一个Employee类,他包含name,age,address这三个属性,以及work()方法

属性/方法名前面的-和+表示这个属性、方法的可见性,uml类图中表示可见性的符号有三种

  • +:表示public
  • -:表示private
  • #:表示protected

属性的完整表示方法是:可见性 名称 : 类型 [=初始化值]

方法的完整表示方式是:可见性 名称(参数列表)【 :返回类型】

注意:

  1. 中括号中的内容表示是可选的
  2. 也有将类型放在变量名前面,返回值类型放在方法名前面

举例:

  • method()方法:修饰public,没有参数,没有返回值
  • method1()方法:修饰private,没有参数,返回字符串
  • method2()方法:修饰protected,接受类型int和string,返回类型为int

类与类之间的表示方式

关联关系

关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系;关联关系是类与类之间最常见的一种关系,分为一般关联关系、耦合关联关系,组合关系

关联有可以分为单项关联,双向关联,自关联

单项关联

在uml类图中单向关联用一个带箭头的实线表示,上边表示每个顾客都有一个地址,这通过让Customer类持有一个类型的Address的成员变量类实现

双向关联

在uml类图中,双向关联用一个不带箭头的直线表示,上图中在Customer类中维护了一个List,表示一个顾客可以购买多个商品;在Product类中维护一个Customer类型的成员变量表示这个产品被那个顾客所购买

自管理

自关联在UML类图中用一个带有箭头且指向自身的线表示,上图的意思就是Node类包含类型为Node的成员变量,也就是自己包含自己

聚合关系

聚合关系也是关联关系的一种,是强关联关系,是整体和部分之间的关系

聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在

在UML类图中,聚合关系可以用带空心菱形的实现来表示,菱形指向整体,谢图是大学与教师的关系图

组合关系

组合表示类之间的整体与部分的关系,但他是一种更强烈的聚合关系

在组合关系中,整体对象可以控制部分对象的声明周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在,例如,头和嘴的关系,没有了头,嘴也就不存在了

在UML类图中,组合关系用带实心菱形的实现来表示,菱形指向整体。

依赖关系

依赖关系是一种使用关系,他是对象之间耦合度最弱的一种关联关系,是临时性的关联,在代码中,某个类的方法通过局部变量,方法的参数或者对静态方法的调用来访问另一类(被依赖类)中的某些方法来完成一些职责

在UML类图中,依赖关系使用带线头的虚线来表示,箭头从使用类指向被依赖的类,

继承关系

继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类和子类之间的关系

在类图中,泛化关系也表示继承关系,在代码实现时,使用面向对象的继承机制来实现泛化关系

实现关系

实现关系是接口与实现类之间的关系,在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作

在类图中箭头从实现类指向接口

类间关系单元总结

  1. 自关联
    • 定义:类包含自身类型的成员变量,即自己包含自己。
    • 表示:用带有箭头且指向自身的线表示。
  1. 聚合关系
    • 本质:关联关系的一种,强关联,体现整体与部分关系。
    • 实现:通过成员对象实现,成员对象是整体对象一部分。
    • 特点:成员对象可脱离整体对象独立存在。
    • 表示:用带空心菱形的实线表示,菱形指向整体。
  1. 组合关系
    • 本质:更强烈的聚合关系,体现整体与部分关系。
    • 特点:整体对象控制部分对象生命周期,整体不存在部分也不存在,部分不能脱离整体存在。
    • 表示:用带实心菱形的实线表示,菱形指向整体。
  1. 依赖关系
    • 本质:使用关系,对象间耦合度最弱的临时性关联。
    • 实现:类的方法通过局部变量、方法参数或对静态方法调用访问另一类方法。
    • 表示:用带线头的虚线表示,箭头从使用类指向被依赖类。
  1. 继承关系
    • 本质:对象间耦合度最大的关系,体现一般与特殊关系,即父类和子类关系。
    • 表示:在类图中用泛化关系表示,代码实现用面向对象继承机制。
  1. 实现关系
    • 本质:接口与实现类之间的关系,类实现接口中所有抽象操作。
    • 表示:箭头从实现类指向接口 。

软件设计原则

开闭原则

对扩展开放,对修改关闭:在程序需要拓展的时候,不能修改原有的代码,实现一个热插拔的效果,简而言之,是为了使程序的扩展性好,已于维护和升级,要想达到这个效果,我们要使用接口和抽象类,软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件发生变化的时候,只需要根据需求重新派生一个实现类来扩展就行了

eg:

📎AbstractSkin.java📎Client.java📎DefausltSkin.java📎shuaiSkin.java📎SougouInput.java

里氏代换原则

任何基类可以出现的地方子类一定可以出现:子类可以拓展父类的功能但是不能改变父类原有的功能;

如果通过重写父类的方法来完成新的功能更,这样写起来虽然简单,但是整个继承体系的可复用性比较差,特别是运用多态比较频繁时。程序运行出错的概率也会非常大

例子:正方形不是长方形--正方形继承长方形

📎Rectangle.java📎RectangleDemo.java📎Square.java

运行上述代码发现:当输入是长方形的时候可以看到宽度逐渐增长的过程,当宽度大于长度将不在增长,代码停止,符合预期;但是当我们传入的是正方形的时候,发现长款都在不断增长,

所以我得出结论:在resize方法中,Rectangle类型的参数 是不能被Square类型的参数代替,因此,square和Rectangle类型之间继承关系违反了里氏代换原则,他们的继承关系不成立,所以正方形不是长方形

改进:

📎Quadrilateral.java📎Rectangle.java📎RectangleDemo.java📎Square.java

依赖倒转原则

高层模块不应该依赖底层原则,两者都应该依赖于其抽象类,抽象不应该依赖细节,细节应该依赖抽象,简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合

eg:组装一个电脑,需要配件cpu,硬盘,内存条,只有有了这些配置电脑才能正常运行.选择cpu,硬盘,内存条都有很多.

接口隔离原则

客户端不应该被迫依赖于他不适用的方法--应对方法: 一个类对另一个类的依赖应该建立在最小的接口上

比如:A类有方法1和方法2; B类继承A类

好处:B类继承了A类,就拥有了方法1的功能

问题:B类现在只需要方法1的功能,不需要方法2的功能(客户端不应该被迫依赖于他不适用的方法)

解决方法: 方法流出一个接口:一个类对另一个类的依赖应该建立在最小的接口上

迪米特法则(最少知识法则)

如果两个软件实体无须直接通信,那么就不应该发生直接的互相调用,可以通过第三方转发调用,目的是降低类之间的的耦合度,提高模块的相对独立性(只和你的直接朋友交谈,不和"陌生人说话" )

合成复用原则

尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现;通常复用分为:继承复用和合成复用两种.

继承复用有点是简单易实现缺点:

  1. 继承复用破坏了类的封装性,因为继承会将父类的实现细节暴露给子类,父类对于子类来说是透明的.
  2. 子类与父类的耦合度高,父类实现的任何改变都会导致子类的实现发生变化,这不利于类的拓展与维护
  3. 复用的灵活性高,这种复用可以在运行时态进行,新对象可以动态的进行与成分对象类型相同的对象

设计模型

创建者模型

创建型模式的主要关注点是"怎样创建对象",他的特点是:将对象的创建与使用分离

这样可以降低系统的耦合度,使用者不需要关注对象的创建细节.

创建型模式分为:

  • 单例模式
  • 工厂方法模式
  • 抽象工程模式
  • 原型模式
  • 创建者模式

单例设计模式

单例模式是java中最简单的设计模式之一,这种类型的设计模式属于创建型模式,他提供了一种创建对象的最佳方式

这种模式设计到一个单一的类,该类负责创建自己的对象,同事确保只有单个对象被创建,这个类提供了一种访问其唯一的对象的方法可以直接访问,不需要实例化该类的对象

单例模式的结构

单例模式主要有一下角色:

  • 单例类:只能创建一个实例的类
  • 访问类:使用单例类
单例模式的实现

单例设计模式的实现

  • 饿汉式:类加载就会导致该单实例对象被创建
  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
  • 会导致内存的浪费
/**
 * 饿汉式:静态成员变量
 *
 * @author lijialin
 * @since 2025/6/4
 */
public class Singleton {

    //1.私有构造方法
    private Singleton(){}

    //2.在本类中创建本类对象
    private static Singleton instance=new Singleton();
    
    //3.提供公共的访问方式让外界获取该对象
    public static Singleton getInstance(){
        return instance;
    }
}



    public static void main(String[] args) {
        //    创建Singleton类对象
        Singleton instance = Singleton.getInstance();

        Singleton instance1 = Singleton.getInstance();
        //    判断两个获取到的是否是同一个对象
        System.out.println(instance == instance1);
        //    运行结果显示是同一个对象
    }
package com.itheima.principles.demo2;

/**
 * @Author: lijialin
 * @Date: 2025/6/7 18:55
 * 饿汉式:静态代码块
 * @Version 1.0
 */
public class Singleton {
    //私有构造方法
    private Singleton(){}

    //声明Singleton类型的变量
    private static Singleton instance; //null

    //在静态代码块中进行赋值
    static {
        instance = new Singleton();
    }

    //对外提供获取该类对象访问方式
    public static Singleton getInstance(){
        return instance;
    }

}

package com.itheima.principles.demo2;

/**
 * @Author: lijialin
 * @Date: 2025/6/7 19:01
 * @Version 1.0
 */
public class Client {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();

        Singleton instance1=Singleton.getInstance();

//        判断两次获取到的Singleton是不是同一个对象
        System.out.println(instance==instance1);

    }
}
package com.itheima.principles.demo3;

/**
 * @Author: lijialin
 * @Date: 2025/6/7 19:07
 * 懒汉式:线程不安全
 * @Version 1.0
 */
public class Singleton {

    //私有构造方法
    private Singleton(){}

    //声明Singleton类型的变量Instance
    private static Singleton instance; //只是声明了该类型的变量,并没有进行赋值

    //对外提供访问方式
    public static Singleton getInstance() {
        //判断instance是否为null,如果为null说明还没有创建Singleton类对象,则创建
        //如果没有创建,则创建,如果有则返回
        if (instance==null){
//           如果多线程
//            线程1等待的话,此时线程2执行创建时instance==null为true,此时线程2也在等待,也会进入等待依次类推
            instance=new Singleton();
        }
        return instance;
    }
}


package com.itheima.principles.demo3;

/**
 * @Author: lijialin
 * @Date: 2025/6/7 19:31
 * @Version 1.0
 */
public class Client {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        //        判断两次获取到的Singleton是不是同一个对象
        System.out.println(instance==instance1);
    }
}
package com.itheima.principles.demo3;

/**
 * @Author: lijialin
 * @Date: 2025/6/7 19:07
 * 懒汉式:线程不安全
 * @Version 1.0
 */
public class Singleton {

    //私有构造方法
    private Singleton(){}

    //声明Singleton类型的变量Instance
    private static Singleton instance; //只是声明了该类型的变量,并没有进行赋值

    //对外提供访问方式  
    // synchronized关键字是Java中用于实现线程同步的核心机制,
    // 主要作用是确保多线程环境下对共享资源的互斥访问,同时保证操作的原子性、可见性和有序性‌。
    // 它通过获取对象的锁(Monitor锁)来控制线程对同步代码块或方法的访问,同一时刻仅允许一个线程执行被修饰的代码
    public static synchronized Singleton getInstance() {
        //判断instance是否为null,如果为null说明还没有创建Singleton类对象,则创建
        //如果没有创建,则创建,如果有则返回
        if (instance==null){
//           如果多线程
//            线程1等待的话,此时线程2执行创建时instance==null为true,此时线程2也在等待,也会进入等待依次类推
            instance=new Singleton();
        }
        return instance;
    }
}


/**
 * @Author: lijialin
 * @Date: 2025/6/7 19:31
 * @Version 1.0
 */
public class Client {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        //        判断两次获取到的Singleton是不是同一个对象
        System.out.println(instance==instance1);
    }
}

懒汉式模型中加锁的问题,对于getInstance()方法大多数为读操作,大部分读操作都是线程安全的,所以我们没有必要让每个线程都必须持有锁才能调用该方法,因为这样会造成性能低下,所以延申出第三种方式

package com.itheima.principles.demo4;

/**
 * @Author: lijialin
 * @Date: 2025/6/7 19:44
 * 双重检查锁方式
 * @Version 1.0
 */
public class Singleton {
    //私有构造方法
    private Singleton(){}

    //声明Singleton类型的变量Instance
    private static Singleton instance;

//    对外提供公共访问当时
public static Singleton getInstance() {
//    第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
    if (instance==null){

//        同步代码块
        synchronized (Singleton.class){
//            第二次判断
            if (instance==null){
                instance=new Singleton();
            }
        }

    }
    return instance;
}
}

双重检查锁模式是一种很好的单例实现模式,解决了单例 性能 线程安全问题但是还是存在一个问题,在多线程模式下有可能出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作,要解决这个问题,只需使用volatile关键字,这个关键字能保证可见性和有序性---‌volatile关键字的主要作用是确保变量的可见性和防止指令重排序,但不保证操作的原子性‌。

package com.itheima.principles.demo4;

/**
 * @Author: lijialin
 * @Date: 2025/6/7 19:44
 * 双重检查锁方式
 * @Version 1.0
 */
public class Singleton {
    //私有构造方法
    private Singleton(){}

    //声明Singleton类型的变量Instance
    private static volatile Singleton instance;

//    对外提供公共访问当时
public static Singleton getInstance() {
//    第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
    if (instance==null){

//        同步代码块
        synchronized (Singleton.class){
//            第二次判断
            if (instance==null){
                instance=new Singleton();
            }
        }

    }
    return instance;
}
}

-------------------------------------小结-------------------------------------------

添加volatile关键字之后双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全不会有性能问题

静态内部类单例模式中实例由内部类创建,由于jvm在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性、方法被调用时才会被加载,并初始化其静态实行。静态属性由于被static修饰没摆正只被实例化了一次,并且严格保证实例化顺序

package com.itheima.principles.demo5;

/**
 * @Author: lijialin
 * @Date: 2025/6/8 14:41
 * 静态内部类方式
 * @Version 1.0
 */
public class Singleton {

//    私有构造方法
    private Singleton(){}

//    定义一个静态内部类
    private static class SingletonHoler{
//        在内部类中声明并初始化外部类的对象
    private static final Singleton INSTANCE=new Singleton();
}
//提供外部共用调用方式
    public static Singleton getInstance(){
        return SingletonHoler.INSTANCE;
    }
}


package com.itheima.principles.demo5;



/**
 * @Author: lijialin
 * @Date: 2025/6/7 19:31
 * @Version 1.0
 */
public class Client {
    public static void main(String[] args) {
        Singleton instance =Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        //        判断两次获取到的Singleton是不是同一个对象
        System.out.println(instance==instance1);
    }
}

-------------------------------------小结-------------------------------------------

静态内部类单例模式时一种优秀的单例模式,是开源项目中比较常用的一种单例模式,在没有加任何锁的情况下保证了多线程下的安全,并且没有任何性能影响和空间的浪费


枚举方法:是单例模式极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式。并且是所有单例实现中唯一一种不会被破坏的单例实现模式

package com.itheima.principles.demo6;

/**
 * @Author: lijialin
 * @Date: 2025/6/8 14:57
 * 枚举实现方式
 * @Version 1.0
 */
public enum Singleton {
    INSTANCE;
}


package com.itheima.principles.demo6;


/**
 * @Author: lijialin
 * @Date: 2025/6/7 19:31
 * @Version 1.0
 */
public class Client {
    public static void main(String[] args) {
        Singleton instance = Singleton.INSTANCE;
        Singleton instance1 =  Singleton.INSTANCE;
        //        判断两次获取到的Singleton是不是同一个对象
        System.out.println(instance==instance1);
    }
}
存在问题
破坏单例模式:

使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外,有两种方式,分别是序列化和反射

  • 序列化反序列化
package com.itheima.principles.demo7;

import java.io.Serializable;

/**
 * @Author: lijialin
 * @Date: 2025/6/8 14:41
 * 静态内部类方式
 * @Version 1.0
 */
public class Singleton implements Serializable {

//    私有构造方法
    private Singleton(){}

//    定义一个静态内部类
    private static class SingletonHoler{
//        在内部类中声明并初始化外部类的对象
    private static final Singleton INSTANCE=new Singleton();
}
//提供外部共用调用方式
    public static Singleton getInstance(){
        return SingletonHoler.INSTANCE;
    }
}


package com.itheima.principles.demo7;


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * @Author: lijialin
 * @Date: 2025/6/7 19:31
 * @Version 1.0
 */
public class Client {
    public static void main(String[] args)throws Exception {
//        writeObject2File();
        readObject2File();
        readObject2File();
    }

//    从文件中读取数据(对象)
public static void readObject2File() throws Exception {
//        创建对象输入流对象
    ObjectInputStream ois=new ObjectInputStream(new FileInputStream("D:\桌面\a.txt"));
//    读取对象
    Singleton instance = (Singleton) ois.readObject();

    System.out.println(instance);
//    释放资源
    ois.close();
}

//    向文件中写数据(对象)
public static void writeObject2File() throws Exception {
//    获取Singleton对象
    Singleton instance = Singleton.getInstance();
//    创建对象输出流对象
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\桌面\a.txt"));

//    写对象
    oos.writeObject(instance);

//    释放资源
    oos.close();
}
}

结果不一致说明破坏了单例模式

  • 反射
package com.itheima.principles.demo8;

/**
 * @Author: lijialin
 * @Date: 2025/6/8 14:41
 * 静态内部类方式
 * @Version 1.0
 */
public class Singleton  {

//    私有构造方法
    private Singleton(){}

//    定义一个静态内部类
    private static class SingletonHoler{
//        在内部类中声明并初始化外部类的对象
    private static final Singleton INSTANCE=new Singleton();
}
//提供外部共用调用方式
    public static Singleton getInstance(){
        return SingletonHoler.INSTANCE;
    }
}

package com.itheima.principles.demo8;


import java.lang.reflect.Constructor;

/**
 * @Author: lijialin
 * @Date: 2025/6/7 19:31
 * 测试反射破坏单例模式
 * @Version 1.0
 */
public class Client {
    public static void main(String[] args)throws Exception {
//获取Singleton的字节码对象
        Class clazz = Singleton.class;
//        获取无参构造方法对象
        Constructor cons = clazz.getDeclaredConstructor();
//        取消访问检查
        cons.setAccessible(true);
//        通过反射创建Singleton对象
        Singleton s1 =(Singleton) cons.newInstance();
        Singleton s2 =(Singleton) cons.newInstance();
//        如果返回的是true则说明没有破坏单例模式否则说明破坏的单例模式
        System.out.println(s1==s2);

    }


}
解决方式
  • 序列化反序列化破坏单例模式解决方式

在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方的值如果没有定义就返回新的new的对象

package com.itheima.principles.demo9;

import java.io.Serializable;

/**
 * @Author: lijialin
 * @Date: 2025/6/8 14:41
 * 静态内部类方式
 * @Version 1.0
 */
public class Singleton implements Serializable {

//    私有构造方法
    private Singleton(){}

//    定义一个静态内部类
    private static class SingletonHoler{
//        在内部类中声明并初始化外部类的对象
    private static final Singleton INSTANCE=new Singleton();
}
//提供外部共用调用方式
    public static Singleton getInstance(){
        return SingletonHoler.INSTANCE;
    }

//    序列化反序列化破坏单例模式解决方式
//当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
    public Object readResolve(){
        return SingletonHoler.INSTANCE;
    }
}
  • 反射模式破解单例模式解决方案
package com.itheima.principles.demo5;


/**
 * 饿汉式:静态成员变量
 *
 * @author lijialin
 * @since 2025/6/4
 */
public class Singleton {

    private static boolean flag = false;

    //1.私有构造方法
    private Singleton() {
        synchronized (Singleton.class) {
            //    判断flag的值是否是true,如果是true,说明非第一次访问,直接抛出异常,如果是flase,说明是第一次访问,
            if (flag) {
                throw new RuntimeException("不能创建多个对象");
            }
            //    将flag的值设置成true
            flag = true;
        }
    }

    //2.定义一个静态内部类
    private static class SingletonHolder {
        //    在内部类中声明并初始化外部类对象
        public static final Singleton INSTANCE = new Singleton();

    }

    //3.提供公共的访问方式让外界获取该对象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

package com.itheima.principles.demo5;


import java.lang.reflect.Constructor;

/**
 * 描述
 *
 * @author lijialin
 * @since 2025/6/4
 */
public class Client {
    public static void main(String[] args) throws Exception {
        //    获取Singleton的字节码对象
        Class clazz = Singleton.class;

        //获取无参构造方法对象
        Constructor cons = clazz.getDeclaredConstructor();
        //取消访问检查
        cons.setAccessible(true);

        //创建Singleton对象
        Singleton instance = (Singleton) cons.newInstance();
        Singleton instance1 = (Singleton) cons.newInstance();

        //    判断两个获取到的是否是同一个对象
        System.out.println(instance == instance1);

    }
}

JDK源码解析-Runtime类

package com.itheima.principles.demp9;

import java.io.IOException;
import java.io.InputStream;

/**
 * 描述
 *
 * @author lijialin
 * @since 2025/6/10
 */
public class RuntimeDemo {
    public static void main(String[] args)throws IOException {
    //    获取Runtime类的对象
        Runtime runtime = Runtime.getRuntime();
    //    调用runtime的exec方法,参数要的是一个命令
        Process pconfig = runtime.exec("ipconfig");
    //    调用Process对象的获取输入流的方法
        InputStream is = pconfig.getInputStream();

        byte[] arr=new byte[1024*1024*100];
    //    读取数据
        int read = is.read(arr);//返回读到的字节个数
    //    将字节数组转化成字符串输出到控制台
        System.out.println( new String(arr,0,read,"GBK"));
    }
}

工厂模式

概述:

需求:设计一个咖啡厅点餐系统

设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡(AmericanCoffee)和拿铁(LatteCoffee));在设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能

package com.itheima.pattern.factor.befor;

/**
 * 描述 咖啡类
 *
 * @author lijialin
 * @since 2025/6/10
 */
public abstract class Coffee {
    public abstract String getName();
    //    加糖
    public void addSugar() {
        System.out.println("加糖");
    }
    //加奶
    public void addMilk() {
        System.out.println("加奶");
    }
}

package com.itheima.pattern.factor.befor;

/**
 * 描述 美食咖啡
 *
 * @author lijialin
 * @since 2025/6/10
 */
public class AmreicanCoffee extends Coffee{
    @Override
    public String getName(){
        return "美式咖啡类";
    }
}


package com.itheima.pattern.factor.befor;

/**
 * 描述 拿铁
 *
 * @author lijialin
 * @since 2025/6/10
 */
public class LatteCoffee extends Coffee{
    @Override
    public String getName(){
        return "拿铁";
    }
}

package com.itheima.pattern.factor.befor;

/**
 * 描述
 *
 * @author lijialin
 * @since 2025/6/10
 */
public class CoffeeStore {
    public Coffee orderCoffee(String type){
    //    声明一个Coffee类型变量,根据不同类型创建不同的Coffee子类对象
       Coffee coffee=null;
       if ("american".equals(type)){
           coffee = new AmreicanCoffee();
       }else if ("latte".equals(type)){
           coffee = new LatteCoffee();
       }else {
           throw new RuntimeException("对不起,你所点的咖啡没有");
       }
    //   加配料
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}

package com.itheima.pattern.factor.befor;

/**
 * 描述 测试
 *
 * @author lijialin
 * @since 2025/6/10
 */
public class Client {
    public static void main(String[] args) {
    //    1.创建咖啡店类
        CoffeeStore store = new CoffeeStore();
    //    2.点咖啡
        Coffee coffee = store.orderCoffee("latte");
        System.out.println(coffee.getName());

    }
}

在java中万物皆对象,这些对象都需要创建,如果创建的是后直接new该对象,就会对该对象耦合严重,假如我们需要更换对象,则所有的地方都需要修改一遍,这显然违反了软件设计的开闭原则,如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果更换对象,直接在工厂里更换对象即可,达到了与对象解耦的目的,所以说工厂模式最大的有点就是:解耦

简单工厂模式

结构:

  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能
  • 具体产品:实现或者继承产品的子类
  • 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品

实现:

使用简单工厂模式优化上述案例


package com.itheima.pattern.factor.sinple_factory;

/**
 * 描述 咖啡类
 *
 * @author lijialin
 * @since 2025/6/10
 */
public abstract class Coffee {
    public abstract String getName();
    //    加糖
    public void addSugar() {
        System.out.println("加糖");
    }
    //加奶
    public void addMilk() {
        System.out.println("加奶");
    }
}


package com.itheima.pattern.factor.sinple_factory;

/**
 * 描述 美食咖啡
 *
 * @author lijialin
 * @since 2025/6/10
 */
public class AmreicanCoffee extends Coffee{
    @Override
    public String getName(){
        return "美式咖啡类";
    }
}

package com.itheima.pattern.factor.sinple_factory;

/**
 * 描述 拿铁
 *
 * @author lijialin
 * @since 2025/6/10
 */
public class LatteCoffee extends Coffee{
    @Override
    public String getName(){
        return "拿铁";
    }
}

package com.itheima.pattern.factor.sinple_factory;

/**
 * 描述 生产咖啡
 *
 * @author lijialin
 * @since 2025/6/10
 */
public class CoffeeStore {
    public Coffee orderCoffee(String type) {

        SinpleCoffeeFacyor factory = new SinpleCoffeeFacyor();
        //调用生产咖啡的方法
        Coffee coffee = factory.creatCoffee(type);
        //   加配料
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}



package com.itheima.principles.demo10;

/**
 * @Author: lijialin
 * @Date: 2025/6/14 15:43
 * @Version 1.0
 */
public class SinpleCoffeeFacyor {
    public Coffee createCoffee(String type){
//        声明Coffee类型的变量,根据不同类型创建不同的Coffee子类对象
        Coffee coffee=null;
        if ("american".equals(type)){
            coffee =new AmreicanCoffee()
        }else if ("latte".equals(type)){
            coffee =new LatteCoffee();
        }else {
            throw new RuntimeException("没有这种咖啡")
        }
        return coffee;
    }
}

package com.itheima.pattern.factor.sinple_factory;



/**
 * 描述 测试
 *
 * @author lijialin
 * @since 2025/6/10
 */
public class Client {
    public static void main(String[] args) {
    //    1.创建咖啡店类
        CoffeeStore store = new CoffeeStore();
    //    2.点咖啡
        Coffee coffee = store.orderCoffee("latte");
        System.out.println(coffee.getName());

    }
}

这样写就摆脱了咖啡店与制作咖啡的耦合

简单工厂的优缺点:

优点:封装了创建对象的过程,可以通过参数直接获取对象,把对象的创建和业务逻辑层分开,这样就避免了修改客户代码,如果要实现新的产品可以直接修改工厂类,而不需要在源码中修改,这样就降低了客户修改的可能性,更加容易拓展

缺点:增加新产品时,还是需要修改工厂类的代码,违背了开闭原则

静态工厂

在开发中有部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,他也不是23种设计模式中的

package com.itheima.principles.demo10;

public class AmreicanCoffee extends Coffee{
    @Override
    public String getName(){
        return "美式咖啡类";
    }
}

package com.itheima.principles.demo10;

public class LatteCoffee extends Coffee{
    @Override
    public String getName(){
        return "拿铁";
    }
}

package com.itheima.principles.demo10;

public abstract class Coffee {
    public abstract String getName();
    //    加糖
    public void addSugar() {
        System.out.println("加糖");
    }
    //加奶
    public void addMilk() {
        System.out.println("加奶");
    }
}

package com.itheima.principles.demo10;

/**
 * 描述 生产店
 *
 * @author lijialin
 * @since 2025/6/10
 */
public class CoffeeStore {
    public  Coffee orderCoffee(String type) {

//        SinpleCoffeeFacyor factory = new SinpleCoffeeFacyor();
        //调用生产咖啡的方法
//        Coffee coffee = factory.creatCoffee(type);
        Coffee coffee = SinpleCoffeeFacyor.creatCoffee(type);
        //   加配料
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}

package com.itheima.principles.demo10;

/**
 * @Author: lijialin
 * @Date: 2025/6/14 15:43
 * @Version 1.0
 */
public class SinpleCoffeeFacyor {
    public static Coffee creatCoffee(String type){
//        声明Coffee类型的变量,根据不同类型创建不同的Coffee子类对象
        Coffee coffee=null;
        if ("american".equals(type)){
            coffee =new AmreicanCoffee();
        }else if ("latte".equals(type)){
            coffee =new LatteCoffee();
        }else {
            throw new RuntimeException("没有这种咖啡");
        }
        return coffee;
    }
}

package com.itheima.principles.demo10;

/**
 * 描述 测试
 *
 * @author lijialin
 * @since 2025/6/10
 */
public class Client {
    public static void main(String[] args) {
    //    1.创建咖啡店类
        CoffeeStore store = new CoffeeStore();
    //    2.点咖啡
        Coffee coffee = store.orderCoffee("latte");
        System.out.println(coffee.getName());

    }
}
工厂方法模式

针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则;

概念:

定义一个用于创建对象的接口,让子类决定实例化那个产品类对象;工厂方法使一产品类的实例化延迟到其工厂的子类。

结构:

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

  • 抽象工厂(abstruct factory):提供了创建产品的接口,调用者通过他访问具体工厂的工厂方法来创建产品
  • 具体工厂(ConcreatFactory):主要时实现抽象工厂中的抽象方法,完成具体产品的创建
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,他同具体工厂之间一一对应
实现:

📎AmericanCoffeeFactory.java📎AmreicanCoffee.java📎Client.java📎Coffee.java📎CoffeeFactory.java📎CoffeeStore.java📎LatteCoffee.java📎LatterCofferFactory.java

优点:

  • 用户只需要知道具体工厂的名称就可以得到所要的产品,无须知道产品的具体创建过程
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则

缺点:

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度
抽象工厂模式

概念:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构

结构:

  • 抽象工厂(Abstruct Factory):提供创建产品的接口,他包含了多个创建产品的方法,可以创建多个不同级别的产品
  • 具体工厂(Concrete Factory):主要实现抽象工厂中的多个抽象方法,完成具体产品的创建
  • 抽象产品(Product):定义了产品的规范,描述了茶农的具体功能特性,抽象工厂模式有多个抽象产品
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,他同具体工厂之间是一对多的关系

实现:

咖啡店不仅要生产咖啡还要生产:提拉米苏,抹茶,甜品.咖啡等等

📎AmericanDessertFactory.java📎AmreicanCoffee.java📎Client.java📎Coffee.java📎Dessert.java📎DessertFactory.java📎ItalyDesserFactory.java📎LatteCoffee.java📎MatchaMousse.java📎Trimisu.java

如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类

优点:当一个产品族中的多个对象背设计成一起工作时,他能保证客户端时终止使用同一个产品族中的对象

缺点:当产品族需要增加一个新的产品时,所有的工厂类都需要进行修改

使用场景:
  • 当需要创建的对象是一系列相互关联或者相互依赖的产品族时,如电器工厂中的电视机,空调
  • 系统中有多个产品族,但每次只是用其中的某一族产品,
  • 系统中提供了产品的类库,且所有产品的接口相同,产品端不依赖产品实例的创建细节和内部结构-一整套的搞
模式扩展
简单工厂+配置文件解除耦合

可以通过工厂模式+配置文件的方式接触工厂对象和产品对象的耦合,再工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可

📎Client.java📎LatteCoffee.java📎CoffeeFactory.java📎Coffee.java📎AmreicanCoffee.java

创建者模式-原型模式

概述: 用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象

结构:

  • 抽象原型类: 规定了具体原型对象必须实现的Clone()方法
  • 具体原型类: 实现抽象原型类中的Clone()方法,他可被复制的对象
  • 访问类: 使用原型类中的clone()方法来复制新的对象

实现:

  • 浅克隆: 创建一个新对象,新对象的属性与原来对象属性完全相同,对于非基本类型属性,扔指向原有属性指向的对象的内存地址
  • 深克隆: 创建一个新对象,属性中引用的其他对象也会被克隆,不在指向原有对象地址

在java中用clone()提供了浅克隆的. Cloneable接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现就是具体的圆形类

📎client.java📎Rwalizetype.java

案例:

📎CitaionTest.java📎Citation.java

使用场景:

  • 对象的创建非常复杂,可以使用原型模式快捷的创建对象
  • 性能和安全要求非常高

拓展(深克隆):

📎CitaionTest.java📎Citation.java📎Student.java

📎CitaionTest.java📎Citation.java📎Student.java

创建者模式-建造者模式

概述: 将一个复杂对象的创建与标示分离,使得同样的构建过程可以创建不同的表示

  • 分离了部件的构造,和装配,从而可以构造出复杂的对象,这个模型适用于:某个对象的构造过程复杂的情况
  • 由于实现了构造和装配的解耦,不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象,也就是实现了构建算法,装配算法的解耦,更好的实现了复用
  • 构造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象,用户只需要指定复杂对象的类型就可以得到该对象,而无需知道其内部的具体构造细节

结构:

  • 抽象建造者类(Builder):这个接口规定要实现复杂对象的哪些部分的构建,并不设计具体的对象部件的构建
  • 具体建造者类(ConcreteBuilder):实现Builder接口,完成复杂产品的各个部件的具体创建方法.在构建过程完成后,提供产品的实例
  • 产品类(Product):要创建的复杂对象
  • 指挥者类(Director):调用具体构造者来创建复杂对象的各个部分,在指挥者中不涉及具体的产品的信息,只负责保证对象各部分完整郭建或按照某种顺序构建

实例:生产自行车

📎Bike.java📎Builder.java📎Clinet.java📎Director.java📎MobileBuilder.java📎OfoBuilder.java

优化:可以把指挥者和抽象建造者相结合,但是这样做可以简化结构,但是也加重了抽象建造者的职责,还是建议用上述方法

优点:

  • 封装性好,使用建造者模式可以很好的变化建造者,在使用建造者模式场景中,一半产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对于整体而言可以取得比较好的稳定性
  • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的构建过程解耦,使得相同的创建过程可以创建不同的产品对象
  • 可以更加精细的控制产品的创建过程,将复杂的产品创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
  • 创建者模式很容易进行拓展,如果有新的需求,通过实现一个心的建造者就可以完成,基本不用修改之前已经测试通过的代码,因此就不会对原有的功能引入风险,符合开闭原则

缺点:

  • 建造者所创建的产品一般具有相同的共同点,其组成部分相似,如果产品之间差异很大,则不适用构造者模式,因此其使用范围受到一定的限制

使用场景:

建造者模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但他们组合在一起的算法却相对稳定,所以他们通常在一下场景使用

  • 创建的对象复杂,由多个部件构造,各部件面临着复杂的变化,但构建间的构造顺序是稳定的
  • 创建复杂对象的算法独立于该对象的组成部分以及他们的装配方式,即产品的构建过程和最终的表示都是独立的
模式扩展

建造者模式除了上面的用途外,在开发中还有一个常见的使用方法,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码的可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构

package com.itheima.principles.demo11;

import com.itheima.principles.demo10.Builder;

import java.util.Base64;

/**
 * 描述:手机类
 *
 * @author lijialin
 * @since 2025/6/23
 */
public class Phone {

    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;

    //私有构造方法
    private Phone(Builder builder) {
        this.cpu=builder.cpu;
        this.screen=builder.screen;
        this.memory=builder.memory;
        this.mainboard=builder.mainboard;

    }

    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + ''' +
                ", screen='" + screen + ''' +
                ", memory='" + memory + ''' +
                ", mainboard='" + mainboard + ''' +
                '}';
    }

    public static final class Builder {
        private String cpu;
        private String screen;
        private String memory;
        private String mainboard;

        public Builder cpu(String cpu) {
            this.cpu = cpu;
            return this;
        }

        public Builder screen(String screen) {
            this.screen = screen;
            return this;
        }

        public Builder memory(String memory) {
            this.memory = memory;
            return this;
        }

        public Builder mainboard(String mainboard) {
            this.mainboard = mainboard;
            return this;
        }

        //使用构建者创建Phone对象
        public Phone build(){
            return new Phone(this);
        }
    }
}
package com.itheima.principles.demo11;

/**
 * 描述
 *
 * @author lijialin
 * @since 2025/6/23
 */
public class Client {
    public static void main(String[] args) {

    //    创建手机对象  通过构建者对象获取手机对象
        Phone phone = new Phone.Builder()
                .cpu("intel")
                .screen("三星")
                .memory("金士顿")
                .mainboard("华硕")
                .build();
        System.out.println(phone);
    }
}

重构后的代码用起来更加方便,同时也增加了开发效率,但是在软件设计上,对程序员的要求比较高

创建者模式各个模式的对比

工厂模式VS创造者模式

工厂方法模式注重的是整体对象的创建方法,而建造者模式注重的是部件建造的过程,意在通过一步一步的精确构造出一个复杂的对象

举例说明: 创造一个超人,使用工厂模式,直接生产出来的就是一个力大无穷,内裤外穿,可以飞翔的超人,但是建造者模式的化,先建造头,腿,胳膊,躯干,组装,然后内裤外穿

抽象工厂模式VS建造者模式

抽象工厂模式实现对产品家族的创建,一个产品家族是这样一系列产品:具有不同维度的产品组合,而抽象工厂模式则不需要关心构造过程,只关心什么产品由什么工厂产出即可

建造者模式则是要求按照指定的蓝图构造产品,他的主要目的是通过组装零配件而产出一个产品

举例说明: 如果将抽象工厂模式看作汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车

结构性模式

概念:结构型模式,描述如何将类或者对象按照某种布局组成更大的结构,他分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者采用组合或者聚合在组合对象

由于组合关系或者聚合关系比继承关系耦合度低,满足"合成复用",所以对象结构比类结构模式具有更大的灵活性

结构模式分为一下7种

  1. 代理模式
  2. 适配器模式
  3. 装饰着模式
  4. 桥接模式
  5. 外观模式
  6. 组合模式
  7. 享元模式

代理模式

概念:由于某种原因需要给某对象提供一个代理以控制该对象的访问,这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介

java种的代理按照代理类生成时机不同又分为静态代理和动态代理,静态代理类在编译器就能生成,而动态代理类则在java运行时动态生成,动态代理又有JDK和CGLid代理两种

结构:

  • 抽象主题(Subject): 通过接口或抽象类声明真实主题和代理对象实现的业务方法
  • 真实主题(Real Subject): 实现抽象主题种的具体业务,是代理对象所代表的真实对象,是最终要引用的对象
  • 代理(Proxy): 提供了与真实主题相同的接口,其每部含有对真实主题的引用,他可以访问,控制或拓展真实主题的功能
静态代理

[例]:火车站买票

如果要买火车票,需要去火车站,但是一半火车站都又代售点这样就很方便,火车站是目标对象,代售点是代理对象

package com.itheima.principles.demo12;

/**
 * 描述:代售点-代理类
 *
 * @author lijialin
 * @since 2025/6/23
 */
public class ProxyPoint implements SellTickets{
    //声明火车站类对象
    private TrainStation trainStation=new TrainStation();

    @Override
    public void sell() {
        System.out.println("代售点收取服务费");
        trainStation.sell();
    }
}
package com.itheima.principles.demo12;

/**
 * 描述:卖火车票的接口-抽象主题类
 *
 * @author lijialin
 * @since 2025/6/23
 */
public interface SellTickets {
    void sell();
}
package com.itheima.principles.demo12;

/**
 * 描述:火车站类定义-具体主题类
 *
 * @author lijialin
 * @since 2025/6/23
 */
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火车站买票");
    }
}
package com.itheima.principles.demo12;

/**
 * 描述
 *
 * @author lijialin
 * @since 2025/6/23
 */
public class Clint {
    public static void main(String[] args) {
    //    创建代售点类对象
        ProxyPoint proxyPoint = new ProxyPoint();
    //    调用方法进行买票
        proxyPoint.sell();

    }
}