设计模式

设计模式

学习推荐设计模式目录:22种设计模式 (refactoringguru.cn) 图说设计模式 — Graphic Design Patterns (design-patterns.readthedocs.io)

UML类图初见

什么是统一建模语言(UML)? (visual-paradigm.com)

介绍

UML 是统一建模语言的简称,它是一种由一整套图表组成的标准化建模语言。UML用于帮助系统开发人员阐明,展示,构建和记录软件系统的产出。UML代表了一系列在大型而复杂系统建模中被证明是成功的做法,是开发面向对象软件和软件开发过程中非常重要的一部分。UML主要使用图形符号来表示软件项目的设计,使用UML可以帮助项目团队沟通、探索潜在的设计和验证软件的架构设计。以下我们将向您详细介绍什么是UML、UML的历史以及每个UML图类型的描述,辅之以UML示例。

类图

什么是类图?

类图是一切面向对象方法的核心建模工具。该图描述了系统中对象的类型以及它们之间存在的各种静态关系。

关系

主要的关系:

  1. 关联 (Association)- 代表类型之间的关系(一个人为公司工作,一间公司有多个办事处)(一对多关系,一对一)。表示是一个带有箭头的实线
  2. 继承 (Generalization) - 专为将实例关系图 (ERD) 应用于面向对象设计而设的一种关系。它在面向对象设计中的继承概念互相呼应继承关系用一条带空心箭头
  3. 聚合 (Aggregation) - 面向对象设计中的一种对象组合 也表示整体与部分的关系(整体与部分可以分开)比如A类里面有一个成员变量是属于B类的,这是A类不使用时,B类还是会有意义,A聚合了B。使用SetXXX将B类实列化 聚合关系用一条带空心菱形箭头的直线表示
  4. 实现(Realization)一般用于接口之间的关系。实现关系用一条带空心箭头的虚线表示
  5. 依赖(Dependency)表示依赖(使用),比如A类需要使用B类里面的某些方法,需要传入B类的实列化对象调用方法。依赖关系是用一套带箭头的虚线表示的
  6. 组合(Composite)表示A类里面的一个成员变量,并且这个变量属于B类,与聚合不同的是,在A类里面B xx=new B(),在A类实列化成功之后B类也会实列化成功 组合关系用一条带实心菱形箭头直线表示

类图示例

类图

class结构

类名


成员变量 -:表示是private 修饰 +:表达使用public修饰 #:表示protected修饰 ~表示package/default


成员方法

UML图六种箭头含义 - 简书 (jianshu.com)

设计模式的目的

  1. 代码重用性(即:相同功能的代码,不用多次的编写)
  2. 可读性:几编程的规范性,便于其他的程序员的阅读和理解
  3. 可扩展性:当需要增加新的功能时,非常的方便
  4. 可靠性,当我们增加新的功能后,对原来的功能没有影响
  5. 是程序呈现高内聚,低耦合的特性

设计模式原则(OOP七大原则)

  • 开闭原则:对扩展开放,对修改关闭 ocp
  • 里氏替换原则:继承必须确保超类(父类)所拥有的性质在子类中仍然成立,就是尽量不要重写父类的方法,而是使用新的方法去实现新的功能。
  • 依赖倒置原则:要面向接口编程,不要面向实现编程
  • 单一职责原则:控制类的粒度大小,将对象解耦,提高其内聚性。一个类或者是一个方法主要是干好一个功能(原子性)
  • 接口隔离原则:要为各个类建立它们需要的专用接口,
  • 迪米特法则:只与你的朋友直接交谈,不跟“陌生人”说话
  • 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现

设计模式分类

设计模式总结之模式分类_迷死特兔的博客-CSDN博客

image-20230822180027879

创建性

它的主要特点是“将对象的创建与使用分离”

单例模式

介绍

单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法

单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。

实现单例模式步骤

  1. 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
  2. 提供一个自身的静态私有成员变量;
  3. 提供一个公有的静态工厂方法。该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象

../_images/Singleton.jpg

如类图,将构造器私有使用getInstance()方法获得实列对象

饿汉式单列模式

通过名字简单了解一下,饿汉式一定会很饿 ,在类初始化的时候就会创建该类的实列化对象

public class SingleObject {
 
   //创建 SingleObject 的一个对象
   private static SingleObject instance = new SingleObject();
 
   //让构造函数为 private,这样该类就不会被实例化
   private SingleObject(){}
 
   //获取唯一可用的对象
   public static SingleObject getInstance(){
      return instance;
   }
 
   public void showMessage(){
      System.out.println("Hello World!");
   }
}

存在弊端:可能会占用大量资源,比如该类里面的成员变量会比较大,在我不需要使用该类之前,这个资源应该是想要空闲。

懒汉式单例模式

在我需要使用该类之后才创建该类的实列对象

基本懒汉式

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

在多个进程同时调用getInstance时会出现创建了多个实列对象。解决办法我们可以想到使用锁(synchronized)来实现多线程安全

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    //当需要的时候创建。
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

加锁了的懒汉式

描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

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

DCL懒汉式(双检锁/双重校验锁)

描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

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

注意:这里的singleton对象是加了Volatile,因为singleton=new Singleton();不是一个原子性操作,主要分为3步来实现

  1. 给 singleton 分配内存

  2. 调用 Singleton 的构造函数来初始化成员变量,形成实例

  3. 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了)

    这就又可能会发生指令重排,造成了132顺序,如果是这个顺序的话,就会出现线程A已经执行了3,准备执行2之前,有一个线程来执行getSingleton()方法,判断singleton是否等于NULL,这是线程A已经执行了3,singleton就不为NULL,于是线程B就返回singleton,造成线程B并获取到singleton的实列对象。

    但是上面的方法并不是安全的,我们知道反射下面没有private.如果我们通过反射机制直接使用静态方法来创建的话,是可以破坏单列模式的。

枚举

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

工厂模式

OOP原则

  • 开闭原则:一个软件的实体应当对扩展开放,对修改关闭
  • 依赖倒转原则:要针对接口编程,不要针对实现编程
  • 迪米特法则:只与你直接的朋友通信,而避免和陌生人通信

实质介绍

  • 实例化对象不使用new,思工厂方法代替
  • 将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦
  • 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。

简单工厂模式

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式(方法都是static修饰),它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例(工厂类(factory)),被创建的实例通常都具有共同的父类。

简单工厂模式包含如下角色:

  • Factory:工厂角色

    工厂角色负责实现创建所有实例的内部逻辑

  • Product:抽象产品角色

    抽象产品角色是所创建的所有对象的父类,负责描述所有实例所共有的公共接口

  • ConcreteProduct:具体产品角色

    具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。

../_images/SimpleFactory.jpg

下面这种方式违反了单一职责原则:

package Factroy;

public class SimpleFactory {
    public static Car getCar(String name){
        if(name.equals("wuling")){
            return new wuling();
        }else if(name.equals("Tesla")){
            return new Tesla();
        }
        return null;
    }
}
public class Tesla implements Car {
}
public class wuling implements Car {
}
public class Customer {
    public static void main(String[] args) {
            Car car=SimpleFactory.getCar("wuling");
    }
}

进行简单优化,虽然也并不完全满足单一职责原则,违反了类上面的单一职责原则,但是符合方法上面的单一职责原则。

public class SimpleFactory {
    public static Car getWulingCar(){   
            return new wuling();
    }
    public static Car getTeslaCar(){
        return new Tesla();
    }
}
简单工厂模式的优点
  • 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。
  • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
简单工厂模式的缺点
  • 由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响
  • 使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度。
  • 违反了开闭原则:系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
  • 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

工厂方法模式

模式结构

工厂方法模式包含如下角色:

  • Product:抽象产品
  • ConcreteProduct:具体产品
  • Factory:抽象工厂
  • ConcreteFactory:具体工厂

../_images/FactoryMethod.jpg

实列

日志记录器

某系统日志记录器要求支持多种日志记录方式,如文件记录、数据库记录等,且用户可以根据要求动态选择日志记录方式, 现使用工厂方法模式设计该系统。

结构图:

../_images/loger.jpg

工厂方法优点
  • 你可以避免创建者和具体产品之间的紧密耦合
  • 单一职责原则。 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。
  • 开闭原则。 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。
工厂方法缺点
  • 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。
使用环境

在以下情况下可以使用工厂方法模式:

  • 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
  • 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
  • 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。

抽象工厂模式

它能创建一系列相关的对象, 而无需指定其具体类,

与简单工厂模式和工厂方法模式区别

抽象工厂模式是创建一个产品群,而简单工厂和工厂方法是创建一个产品类型。

  1. 一系列相关产品, 例如 椅子Chair 、 沙发Sofa和 咖啡桌Coffee­Table 。
  2. 系列产品的不同变体。 例如, 你可以使用 现代Modern 、 维多利亚Victorian 、 装饰风艺术Art­Deco等风格生成 椅子沙发咖啡桌

生成不同风格的系列家具。

模式结构

抽象工厂模式包含如下角色:

  • AbstractFactory:抽象工厂
  • ConcreteFactory:具体工厂 小米工厂,华为工厂
  • AbstractProduct:抽象产品 AbstractProductA:手机 AbstractProductB:路由器
  • Product:具体产品 分别是各个厂商自己品牌的实现。

../_images/AbatractFactory.jpg

代码实现:

AbstractFactory

package AbstractFactory;

public interface IFactory {
    IPhoneinterface Phone();
    IRouterinterface Router();
}

ConcreteFactory1

package AbstractFactory;

public class xiaomiFactory implements IFactory {
    @Override
    public IPhoneinterface Phone() {
        return new xiaomiPhone();
    }

    @Override
    public IRouterinterface Router() {
        return new xiaomiRouter();
    }
}

ConcreteFactory2

package AbstractFactory;

public class huaweiFactory implements IFactory {
    @Override
    public IPhoneinterface Phone() {
        return new huaweiPhone();
    }

    @Override
    public IRouterinterface Router() {
        return new huaweiRouter();
    }
}

AbstractProductA

package AbstractFactory;

public interface IPhoneinterface {
    void startUp();
    void stop();
    void takePhone();
}

AbstractProductB

package AbstractFactory;

public interface IRouterinterface {
    void startup();
    void stop();
    void run();
}

ProductA1 ProductA2

package AbstractFactory;

public class xiaomiPhone implements IPhoneinterface {
    @Override
    public void startUp() {
        System.out.println("小米手机开机");
    }

    @Override
    public void stop() {
        System.out.println("小米手机关机");
    }

    @Override
    public void takePhone() {
        System.out.println("小米手机打电话");
    }
}

package AbstractFactory;

public class huaweiPhone implements IPhoneinterface {
    @Override
    public void startUp() {
        System.out.println("华为手机开机");
    }

    @Override
    public void stop() {
        System.out.println("华为手机关机");
    }

    @Override
    public void takePhone() {
        System.out.println("华为手机打电话");
    }
}

ProductB1 ProductB2

package AbstractFactory;

public class xiaomiRouter implements IRouterinterface {
    @Override
    public void startup() {
        System.out.println("小米路由器开机");
    }

    @Override
    public void stop() {
        System.out.println("小米路由器关机");
    }

    @Override
    public void run() {
        System.out.println("小米路由运行");
    }
}
package AbstractFactory;

public class huaweiRouter implements IRouterinterface {
    @Override
    public void startup() {
        System.out.println("华为路由器开机");
    }

    @Override
    public void stop() {
        System.out.println("华为路由器关机");
    }

    @Override
    public void run() {
        System.out.println("华为路由运行");
    }
}

适用环境

在以下情况下可以使用抽象工厂模式:

  • 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
  • 系统中有多于一个的产品族,而每次只使用其中某一产品族。
  • 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
  • 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
抽象工厂模式优点
  • 你可以确保同一工厂生成的产品相互匹配。
  • 你可以避免客户端和具体产品代码的耦合。
  • 单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
  • 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。
抽象工厂模式缺点
  • 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂
  • 开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)。
  • 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。

原型模式

使你能够复制已有对象, 而又无需使代码依赖它们所属的类。

使用场景

  1. 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
  2. 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
  3. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
  4. 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。

Spring Bean使用了原型模式和单例模式

getMergedBeanDefinition方法.cloneBeanDefinition()

if (mbd == null || mbd.stale) {
    previous = mbd;
    if (bd.getParentName() == null) {
        if (bd instanceof RootBeanDefinition) {
            mbd = ((RootBeanDefinition)bd).cloneBeanDefinition();
        } else {
            mbd = new RootBeanDefinition(bd);
        }
    } 
    

Arrays toArrAy

@Override
public Object[] toArray() {
    return a.clone();
}

注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。我们在后面进行研究。

模型结构

img

如图当我们已经拥有了实列对象obj1之后,在调用clone()方法。拷贝一份

代码实现

  1. 将原型类 implements Cloneable接口
  2. 重写clone()方法。
package prototype;
import java.util.Date;
//①实现Cloneable接口
public class Video implements Cloneable {
    private String name;
    private Date date;
//②重写clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Video{" +
                "name='" + name + '\'' +
                ", date=" + date +
                '}';
    }
    public Video(String name, Date date) {
        this.name = name;
        this.date = date;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Date getDate() {
        return date;
    }
    public void setDate(Date date) {
        this.date = date;
    }
}
package prototype;

import javax.swing.*;
import java.util.Date;

public class PrototypeDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Date date=new Date();
        Video v1 = new Video("某UP主", date);
        Video v2 = (Video) v1.clone();
        System.out.println("V1的hashCode值"+v1.hashCode());
        System.out.println("V2的hashCode值"+v2.hashCode());
        System.out.println("V1"+v1.toString());
        System.out.println("V2"+v2.toString());
    }
}
/*
V1的hashCode值356573597   
V2的hashCode值1735600054  
看出V1和V2是两个对象。
V1Video{name='某UP主', date=Wed Aug 23 20:28:48 CST 2023}
V2Video{name='某UP主', date=Wed Aug 23 20:28:48 CST 2023}
*/

浅拷贝与深拷贝

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的

深拷贝和浅拷贝的示意图大致如下:

img

浅拷贝只复制指向某个对象的指针(重新在堆中申请了一快内存,之后又重新指向了同一个成员变量),而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象

判断我们上面的实列是属于浅拷贝还是深拷贝

package prototype;

import javax.swing.*;
import java.util.Date;

public class PrototypeDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Date date=new Date();
        Video v1 = new Video("某UP主", date);
        Video v2 = (Video) v1.clone();
        date.setDate(123);
        System.out.println("V1的hashCode值"+v1.hashCode());
        System.out.println("V2的hashCode值"+v2.hashCode());
        System.out.println("V1.Date的hashCode"+v1.getDate().hashCode());
        System.out.println("V2.Date的hashCode"+v2.getDate().hashCode());
    }
}
运行结果:
V1的hashCode值356573597
V2的hashCode值1735600054
V1.Date的hashCode123
V2.Date的hashCode123
//观察到V1和V2的Date成员变量是同一对象,所以我们上面的实列是属于浅拷贝

如何实现深拷贝

  • 由于指向的是同一成员对象,那么我们可以改写clone方法将我们的成员变量也进行拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
    Object clone = super.clone();
    Video V1 = (Video) clone;
    V1.date= (Date) this.date.clone();
    return clone;
}
package prototype;

import javax.swing.*;
import java.util.Date;

public class PrototypeDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Date date=new Date();
        Video v1 = new Video("某UP主", date);
        Video v2 = (Video) v1.clone();
        date.setTime(123);
        System.out.println("V1的hashCode值"+v1.hashCode());
        System.out.println("V2的hashCode值"+v2.hashCode());
        System.out.println("V1.Date的hashCode"+v1.getDate().hashCode());
        System.out.println("V1.Date的hashCode"+v2.getDate().hashCode());
    }
}
V1的hashCode值356573597
V2的hashCode值1735600054
V1.Date的hashCode123
V1.Date的hashCode579090841
  • 同构序列化和反序列化实现深拷贝

序列化和反序列化是什么?

序列化:就是将对象转化成字节序列的过程。

反序列化:就是讲字节序列转化成对象的过程。

对象序列化成的字节序列会包含对象的类型信息、对象的数据等,说白了就是包含了描述这个对象的所有信息,能根据这些信息“复刻”出一个和原来一模一样的对象。这和我们的原型模式很像

为什么需要序列化

  1. 持久化:对象是存储在JVM中的堆区的,但是如果JVM停止运行了,对象也不存在了。序列化可以将对象转化成字节序列,可以写进硬盘文件中实现持久化。在新开启的JVM中可以读取字节序列进行反序列化成对象。
  2. 网络传输:网络直接传输数据,但是无法直接传输对象,可在传输前序列化,传输完成后反序列化成对象。所以所有可在网络上传输的对象都必须是可序列化的。

注意:

  1. 一个对象要进行序列化,如果该对象成员变量是引用类型的,那这个引用类型也一定要是可序列化的,否则会报错
  2. 同一个对象多次序列化成字节序列,这多个字节序列反序列化成的对象还是一个(使用==判断为true)(因为所有序列化保存的对象都会生成一个序列化编号,当再次序列化时回去检查此对象是否已经序列化了,如果是,那序列化只会输出上个序列化的编号)
  3. 如果序列化一个可变对象,序列化之后,修改对象属性值,再次序列化,只会保存上次序列化的编号(这是个坑注意下)
  4. 对于不想序列化的字段可以再字段类型之前加上transient关键字修饰(反序列化时会被赋予默认值)

代码实现

  // 实际的数据流向:ObjectOutputStream->ByteArrayOutputStream->ByteArrayInputStream ->ObjectInputStream,深度复制从序列化对象又转为序列化对象
    public User copy() throws IOException, ClassNotFoundException {
        // 序列化
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);   // 将this对象 转变为 二进制字节流

        // 反序列化
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        return (User) objectInputStream.readObject();
    }

建造者模式

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

建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。根据中文翻译的不同,建造者模式又可以称为生成器模式。

模型结构

建造者模式包含如下角色:

  • Builder:抽象建造者
  • ConcreteBuilder:具体建造者
  • Director:指挥者 指挥这指挥具体的建造者(ConcreteBuilder)的执行步骤。
  • Product:产品角色

../_images/Builder.jpg

应用生成器模式

将一个复杂的产品分配好步骤,最终得到一个完整的产品。与工厂模式不同的是工厂模式是生产相应的零件(一个实现类只是一个零件),而建造者模式会组合好相应的零件最后生产出完整的产品。

往往实列中需要将工厂模式和建造者模式相结合。

实例:KFC套餐

建造者模式可以用于描述KFC如何创建套餐:套餐是一个复杂对象,它一般包含主食(如汉堡、鸡肉卷等)和饮料(如果汁、 可乐等)等组成部分,不同的套餐有不同的组成部分,而KFC的服务员可以根据顾客的要求,一步一步装配这些组成部分,构造一份完整的套餐,然后返回给顾客。

../_images/KFCBuilder.jpg

代码实现

package Builder;

public class Worker extends Builder {
    Product product;

    public Worker() {
        this.product = new Product();
    }
   @Override
    public Product getProduct() {
        return product;
    }

    @Override
    Builder A(String msg) {
        product.setA(msg);
        return this;
    }

    @Override
    Builder B(String msg) {
        product.setB(msg);
        return this;
    }

    @Override
    Builder C(String msg) {
        product.setC(msg);
        return this;
    }

    @Override
    Builder D(String msg) {
        product.setD(msg);
        return this;
    }
}

package Builder;

public abstract class Builder {
    abstract Builder A(String msg);
    abstract Builder B(String msg);
    abstract Builder C(String msg);
    abstract Builder D(String msg);
    abstract Product getProduct();
}


package Builder;

public class Product {
    private String A;
    private String B;
    private String C;
    private String D;

    public String getA() {
        return A;
    }

    public void setA(String a) {
        A = a;
    }

    public String getB() {
        return B;
    }

    public void setB(String b) {
        B = b;
    }

    public String getC() {
        return C;
    }

    public void setC(String c) {
        C = c;
    }

    public String getD() {
        return D;
    }

    public void setD(String d) {
        D = d;
    }

    @Override
    public String toString() {
        return "Product{" +
                "A='" + A + '\'' +
                ", B='" + B + '\'' +
                ", C='" + C + '\'' +
                ", D='" + D + '\'' +
                '}';
    }
}

package Builder;

public class Customer {
    public static void main(String[] args) {
        Worker watier = new Worker();
       Product product = watier.A("汉堡").B("可乐").C("炸鸡").getProduct();
        System.out.println(product);
    }
}

在该案列中顾客相当于是一个指挥者,指挥着套餐如生产。套餐是那些。

实列2:造车

生成器模式结构示例

该实例里面有专门的指挥者来指挥着建造的具体步骤

4.8. 优点

  • 在建造者模式中, 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
  • 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象
  • 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”。

缺点

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

模式扩展

建造者模式的简化:

  • 省略抽象建造者角色:如果系统中只需要一个具体建造者的话,可以省略掉抽象建造者。
  • 省略指挥者角色:在具体建造者只有一个的情况下,如果抽象建造者角色已经被省略掉,那么还可以省略指挥者角色,让

Builder角色扮演指挥者与建造者双重角色。

建造者模式与抽象工厂模式的比较:
  • 与抽象工厂模式相比, 建造者模式返回一个组装好的完整产品 ,而 抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。
  • 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象。
  • 如果将抽象工厂模式看成 汽车配件生产工厂 ,生产一个产品族的产品,那么建造者模式就是一个 汽车组装工厂 ,通过对部件的组装可以返回一辆完整的汽车。

结构性

简单介绍

结构型模式(Structural Pattern) 描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。

结构型模式可以分为 类结构型模式对象结构型模式

  • 类结构型模式(采用继承机制来组织接口和类),在类结构型模式中一般只存在继承关系和实现关系
  • 对象结构型模式(釆用组合或聚合来组合对象),通过关联关系使得在一 个类中定义另一个类的实例对象,然后通过该对象调用其方法。

适配器模式

定义

适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

模式结构

适配器模式包含如下角色:

  • Target:目标抽象类
  • Adapter:适配器类
  • Adaptee:适配者类
  • Client:客户类

适配器模式有对象适配器和类适配器两种实现:

对象适配器:

../_images/Adapter.jpg

类适配器:

../_images/Adapter_classModel.jpg

代码实现

package Adapter;

public class Target {
    public void request(Adapter adapter){
        adapter.Apadter();
    }

    public static void main(String[] args) {
      Target target = new Target();
      Adaptee adaptee = new Adaptee();
      Adapter adapter = new Adapter(adaptee);
      target.request(adapter);
    }
}

public class Adapter implements NetToUSB {
    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void Apadter() {
        adaptee.request();
    }
}
public class Adaptee  {
        public void request(){
            System.out.println("网线接入可以上网");
        }
}

public interface NetToUSB {
    void Apadter();
}

Java 核心程序库中有一些标准的适配器:

优点

  • 类适配器模式还具有如下优点:

    由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

  • 对象适配器模式还具有如下优点:

    一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。

缺点

  • 类适配器模式的缺点如下:

    对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。

  • 对象适配器模式的缺点如下:

    与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

桥接模式

模式定义

桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

模式结构

桥接模式包含如下角色:

  • Abstraction:抽象类
  • RefinedAbstraction:扩充抽象类
  • Implementor:实现类接口
  • ConcreteImplementor:具体实现类

../_images/Bridge.jpg

桥接模式的解决方案

代码实现

Abstraction

package Bridge;

public abstract class Shape {
    //简历联系 (建好桥梁)
    private Color color;

    public Shape(Color color) {
        this.color = color;
    }

    public  void shape(){
        color.color(); 
    };
}
class Circle extends Shape{

    public Circle(Color color) {
        super(color);
    }

    @Override
    public void shape() {
        super.shape();
        System.out.print("圆形");
    }
}
class Rectangle extends Shape{
    public Rectangle(Color color) {
        super(color);
    }

    @Override
    public void shape() {
        super.shape();
        System.out.println("矩形");
    }

}

InterfaceImp

package Bridge;

public interface Color {

    void color();
}
class red implements Color{
    @Override
    public void color() {
        System.out.print("红色的");
    }
}
class blue implements Color{

    @Override
   public void color() {
        System.out.print("蓝色的");
    }
}

package Bridge;

public class Test {
    public static void main(String[] args) {
           Circle circle = new Circle(new red());
           circle.shape();
    }
}

在Abstraction中使用组合将Interface作为成员变量建立连接,

代理模式

模式定义

代理模式(Proxy Pattern) :给某一个对象提供一个代 理,并由代理对象控制对原对象的引用。代理模式的英 文叫做Proxy或Surrogate,它是一种对象结构型模式。Spring AOP

代理模式分类

静态代理: 所谓静态代理也就是在程序运行前就已经存在代理类的字节码文件(在真正结婚之前,就已经做好了相关工作,婚庆公司和结婚的人就做好了相关联系)。代理模式就是在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。简单来说静态代理就是在不改变源代码的基础上增加新的功能。

模式结构

代理模式包含如下角色:

  • Subject: 抽象主题角色 租房,结婚 (抽象类或接口)
  • Proxy: 代理主题角色 中介,婚介所
  • RealSubject: 真实主题角色 房主 ,需要结婚的人

../_images/Proxy.jpg

代码实现

package Proxy;

public interface Rent {
    void renct();
}


package Proxy;

public class Proxy implements Rent {
    private Host host;

    public void setHost(Host host) {
        this.host = host;
    }

    @Override
    public void renct() {
        //租房前
        before();
         host.renct(); //房东租房
        after();
    }
    public void before(){
        System.out.println("租房前需要确定的一些事情");
    }

    public void after(){
        System.out.println("租房后需要做的一些事情");
    }
}


package Proxy;
//房东需要租房
public class Host implements Rent {
    @Override
    public void renct() {
        System.out.println("房东成功的将房子出租");
    }
}


package Proxy;

import Builder.Product;

public class Customer {
    public static void main(String[] args) {
        Host host=new Host();
        Proxy proxy=new Proxy();
        proxy.setHost(host);
        proxy.renct();
    }
}

优点

代理模式的优点

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系 统的耦合度。(一直是Proxy类建立的联系)
  • 远程代理使得客户端可以访问在远程机器上的对象,远程机器 可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
  • 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系 统资源的消耗,对系统进行优化并提高运行速度。
  • 保护代理可以控制对真实对象的使用权限。

缺点

代理模式的缺点

  • 由于在客户端和真实主题之间增加了代理对象,因此 有些类型的代理模式可能会造成请求的处理速度变慢。(一个新的实体类就可能需要增加一个代理类),增加了代码的复杂性
  • 实现代理模式需要额外的工作,有些代理模式的实现 非常复杂。。

动态代理

基于反射实现,没有明确的代理类

动态代理分类:

  1. 基于接口的——jdk动态代理 Proxy InvocationHandle
  2. 基于类的 cglib
  3. javasist

jdk动态代理 代码实现

了解Proxy InvocationHandle

Proxy 获取代理对象

Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class<?>[] { Foo.class },
handler);
proxy class 是在运行时创建的类,它实现了指定的接口列表,称为 proxy interfaces 。 proxy instance 是代理类的一个实例。每个代理实例都有一个关联的调用处理程序对象,它实现了接口 InvocationHandler 。通过其代理接口之一对代理实例的方法调用将被分派到实例调用处理程序的 invoke 方法,传递代理实例、标识被调用方法的 java.lang.reflect.Method 对象以及包含参数的 Object 类型数组.调用处理程序适当地处理编码的方法调用,并且它返回的结果将作为对代理实例的方法调用的结果返回。

InvocationHandle处理代理实例上的方法

Object  invoke(Object  proxy, Method  method, Object [] args) throws Throwable 
            
处理代理实例上的方法调用并返回结果。当在与其关联的代理实例上调用方法时,将在调用处理程序上调用此方法。

动态代理类

package DynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Objects;

public class InvocationHalderImp implements InvocationHandler {
    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }
    //获取代理对象
    public Object getTarget(){
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),
               rent.getClass().getInterfaces(),
               this);
    }
    //执行代理方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(rent, args);
        after();
        return result;
    }
    public void before(){
        System.out.println("租房前需要确定的一些事情");
    }

    public void after(){
        System.out.println("租房后需要做的一些事情");
    }
}

测试

package DynamicProxy;

public class Client {
    public static void main(String[] args) {
        Rent host=new Host();
        InvocationHalderImp invoke=new InvocationHalderImp();
        invoke.setRent(host);
        Rent target = (Rent) invoke.getTarget();
        target.renct();
    }
}

可以使用Arthus观察底层源码

执行流程如下:

  • 1.在测试类中通过代理对象调用rent()方法
  • 2.根据多态的特性,执行的是代理类(PrOXy0)中的rent()方法
  • 3.代理类(​Proxy0)中的rent()方法中又调用了InvocationHandl.er接口的子实现类对象的invoke方法
  • invoke方法通过反射执行了真实对象所属类(Host)中的rent()方法

Cglib代理

jdk动态代理的缺陷是基于接口,必须要实现接口才可以使用,如果不实现接口,可以使用基于类的Cglib动态代理。

Cglib是第三方提供的需要导入jar包

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

package DynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Objects;

public class InvocationHalderImp implements MethodInterceptor {
    private Host rent;

    public void setRent(Host rent) {
        this.rent = rent;
    }
    //获取代理对象
    public Object getTarget(){
//       return Proxy.newProxyInstance(this.getClass().getClassLoader(),
//               rent.getClass().getInterfaces(),
//               this);
        //创建Enhancer对象,类似于JDK代理中的Proxy类
        Enhancer enhancer=new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperClass(Host.class);
        //设置回调函数,参数是MethoInterceptor
        enhancer.setCallback(this);
        //创建代理
        Host host=(Host) enhancer.create();
        return host
    }
        @Override
        public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("======插入前置通知======");
            Object object = methodProxy.invokeSuper(sub, objects);
            System.out.println("======插入后者通知======");
            return object;
        }
    }
    public void before(){
        System.out.println("租房前需要确定的一些事情");
    }

    public void after(){
        System.out.println("租房后需要做的一些事情");
    }
}

装饰者模式

模式定义

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。

模式结构

装饰模式包含如下角色:

  • Component: 抽象构件
  • ConcreteComponent: 具体构件
  • Decorator: 抽象装饰类
  • ConcreteDecorator: 具体装饰类

../_images/Decorator.jpg

代码实现

Component抽象构建

package Decorator;

public abstract class FactFood {
    private Integer price;
    private String desc;
    abstract Integer coat();

    public FactFood(Integer price, String desc) {
        this.price = price;
        this.desc = desc;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }
}

ComCreteComponent具体组件

package Decorator;

public class Factrice extends FactFood {


    public Factrice() {
        super(10,"炒饭");
    }

    @Override
    Integer coat() {
        return getPrice();
    }

}

Decorator抽象装饰类

package Decorator;

public abstract class Decorator extends  FactFood {
    private FactFood fact;

    public Decorator(FactFood fact, Integer price, String desc) {
        super(price, desc);
        this.fact=fact;
    }

    public FactFood getFact() {
        return fact;
    }

    public void setFact(FactFood fact) {
        this.fact = fact;
    }

}

ConcreteDecorator具体装饰类

package Decorator;

public class Egg extends Decorator {

    public Egg(FactFood factFood) {
        super(factFood,1, "鸡蛋");
    }

    @Override
    Integer coat() {
        return getPrice()+getFact().getPrice();
    }

    @Override
    public String getDesc() {
        return super.getDesc()+getFact().getDesc();
    }
}
优点
  • 你无需创建新子类即可扩展对象的行为。
  • 你可以在运行时添加或删除对象的功能。
  • 你可以用多个装饰封装对象来组合几种行为。
  • 单一职责原则。 你可以将实现了许多不同行为的一个大类拆分为多个较小的类。
缺点
  • 在封装器栈中删除特定封装器比较困难。
  • 实现行为不受装饰栈顺序影响的装饰比较困难。
  • 各层的初始化配置代码看上去可能会很糟糕。

外观模式

模式定义

外观模式(Facade Pattern):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。

模式结构

外观模式包含如下角色:

  • Facade: 外观角色
  • SubSystem:子系统角色

../_images/Facade.jpg

优点

外观模式的优点

  • 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。
  • 实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。
  • 降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
  • 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。

缺点

外观模式的缺点

  • 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
  • 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

外观模式适合应用场景

如果你需要一个指向复杂子系统的直接接口, 且该接口的功能有限, 则可以使用外观模式。

子系统通常会随着时间的推进变得越来越复杂。 即便是应用了设计模式, 通常你也会创建更多的类。 尽管在多种情形中子系统可能是更灵活或易于复用的, 但其所需的配置和样板代码数量将会增长得更快。 为了解决这个问题, 外观将会提供指向子系统中最常用功能的快捷方式, 能够满足客户端的大部分需求。

如果需要将子系统组织为多层结构, 可以使用外观。

创建外观来定义子系统中各层次的入口。 你可以要求子系统仅使用外观来进行交互, 以减少子系统之间的耦合。

JDK源码中的运用

组合模式

模型定义

组合模式是一种结构型设计模式, 你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们

组合模式建议使用一个通用接口来与 产品盒子进行交互

模式结构

  1. 组件 (Component) 接口描述了树中简单项目和复杂项目所共有的操作。

  2. 叶节点 (Leaf) 是树的基本结构, 它不包含子项目。一般情况下, 叶节点最终会完成大部分的实际工作, 因为它们无法将工作指派给其他部分。

  3. 容器 (Container)——又名 “组合 (Composite)”——是包含叶节点或其他容器等子项目的单位。 容器不知道其子项目所属的具体类, 它只通过通用的组件接口与其子项目交互。容器接收到请求后会将工作分配给自己的子项目, 处理中间结果, 然后将最终结果返回给客户端。

  4. 客户端 (Client) 通过组件接口与所有项目交互。 因此, 客户端能以相同方式与树状结构中的简单或复杂项目交互。

组合设计模式的结构

实列

打印文件名称

image-20230827102603905

Component类

package Combine;

public abstract class Menucomponent {
     String name;//文件名称
     int level;//文件等级

    public Menucomponent(String name, int level) {
        this.name = name;
        this.level = level;
    }
    public void addMenu(Menucomponent menucomponent){
        throw new UnsupportedOperationException();
    }
    public void RemoveMenu(Menucomponent menucomponent){
        throw new UnsupportedOperationException();
    }
    public Menucomponent GetMenu(int index){
        throw new UnsupportedOperationException();
    }
    abstract void print();
}

Composite

package Combine;

import java.util.ArrayList;
import java.util.List;

public class Menu extends Menucomponent {
    private List<Menucomponent> list=new ArrayList<>();

    public Menu(String name, int level) {
        super(name, level);
    }

    @Override
    public void addMenu(Menucomponent menucomponent) {
           list.add(menucomponent);
    }

    @Override
    public void RemoveMenu(Menucomponent menucomponent) {
       list.remove(menucomponent);
    }

    @Override
    public Menucomponent GetMenu(int index) {
        return list.get(index);
    }

    @Override
    void print() {
        for(int i=0;i<level;i++){
            System.out.print("--");
        }
        System.out.println(name);
        for (Menucomponent menucomponent : list) {
             menucomponent.print();
        }
    }
}

Leaf叶子类

package Combine;

public class MenuIntem extends Menucomponent {

    public MenuIntem(String name, int level) {
        super(name, level);
    }

    @Override
    void print() {
        for (int i=0;i<this.level;i++){
            System.out.print("--");
        }
        System.out.println(this.name);
    }
}

Client

package Combine;

public class Client {
    public static void main(String[] args) {
        Menucomponent menu1=new Menu("菜单管理",2);
        menu1.addMenu(new MenuIntem("页面访问",3));
        menu1.addMenu(new MenuIntem("展开菜单",3));
        menu1.addMenu(new MenuIntem("删除彩单",3));
        menu1.addMenu(new MenuIntem("新增菜单",3));
        Menucomponent menu2=new Menu("权限管理",2);
        menu2.addMenu(new MenuIntem("页面访问",3));
        menu2.addMenu(new MenuIntem("提交保存",3));
        Menucomponent menu3=new Menu("角色管理",2);
        menu3.addMenu(new MenuIntem("页面访问",3));
        menu3.addMenu(new MenuIntem("新增角色",3));
        menu3.addMenu(new MenuIntem("修改角色",3));
        Menucomponent menu=new Menu("系统管理",1);
        menu.addMenu(menu1);
        menu.addMenu(menu2);
        menu.addMenu(menu3);
        menu.print();
    }
}

优点

  • 你可以利用多态和递归机制更方便地使用复杂树结构。
  • 开闭原则。 无需更改现有代码, 你就可以在应用中添加新元素, 使其成为对象树的一部分。

缺点

  • 对于功能差异较大的类, 提供公共接口或许会有困难。 在特定情况下, 你需要过度一般化组件接口, 使其变得令人难以理解。

享元模式

模式定义

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

池化技术。

模式结构

享元模式包含如下角色:

  • Flyweight: 抽象享元类
  • ConcreteFlyweight: 具体享元类
  • UnsharedConcreteFlyweight: 非共享具体享元类
  • FlyweightFactory: 享元工厂类 使用单列模式创建好实列对象,并且保存在缓存中(map,数组)。

../_images/Flyweight.jpg

代码实现

Flyweight

package FlyWeight;

public abstract class AbstractShap {
    abstract String getShape();
    public void getDesc(String Color){
        System.out.println("形状="+getShape()+",颜色"+Color);
    }
}

ConcreteFlyweight

package FlyWeight;

public class IBox extends AbstractShap{
    @Override
    String getShape() {
        return "i";
    }
}

FlyweightFactory

package FlyWeight;


import java.util.HashMap;
import java.util.Map;

public class ShapeFactory  {
    private static ShapeFoactory shapeFoactory=new ShapeFoactory();
    private Map<String,AbstractShap> map;

    public ShapeFoactory() {
        map=new HashMap<>();
        map.put("I",new IBox());
        map.put("L",new LBox());
        map.put("T",new TBox());
    }

    public static ShapeFoactory getInstance(){
        return shapeFoactory;
    }

    public AbstractShap getShape(String name){
        return map.get(name);
    }
}

享元模式在JDK中的运用,如Integer

Integer在-128到127范围内,使用==作比较得到的答案是true,表示是用一对象,在范围之外使用 ==比较得到的答案是false表示不是同一对对象。所以在Integer使用了享元模式让Integer在范围-128到127里面的对象得到了多次使用

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

优点

享元模式的优点

  • 享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。
  • 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。

缺点

享元模式的缺点

  • 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
  • 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。

行为型

行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。

行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。

通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象 之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。

行为型模式分为类行为型模式和对象行为型模式两种:

  • 类行为型模式:类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。
  • 对象行为型模式:对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。

模板方法模式

模板方法模式是一种行为设计模式, 它在超类中(父类)定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。

模板方法模式结构

  1. 抽象类 (Abstract­Class) 会声明作为算法步骤的方法, 以及依次调用它们的实际模板方法。 算法步骤可以被声明为 抽象类型, 也可以提供一些默认实现。
  2. 具体类 (Concrete­Class) 可以重写所有步骤, 但不能重写模板方法自身。

模板方法设计模式的结构

实列

代码实现

AbstractClass

package template;

public abstract class AbstractTemplateClass {

    public final void Implenment(){
        A();
        B();
        C();
        D();
        E();
    }

    public void A(){
        System.out.println("第一步");
    }
    public void B(){
        System.out.println("第二步");
    }
    abstract  void C();
    abstract  void D();
    public void E(){
        System.out.println("第五步");
    }
}

ConcreteClassA

package template;

public class ConcreteA extends AbstractTemplateClass {
    @Override
    void C() {
        System.out.println("具体A类实现的C方法");
    }

    @Override
    void D() {
        System.out.println("具体A类实现的D方法");
    }
}

ConcreteClassB

package template;

public class ConcreteB extends AbstractTemplateClass {
    @Override
    void C() {
        System.out.println("具体B类实现的C方法");
    }

    @Override
    void D() {
        System.out.println("具体B类实现的D方法");
    }
}

策略模式

模式定义

策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。

模式结构

策略模式包含如下角色:

  • Context: 环境类
  • Strategy: 抽象策略类
  • ConcreteStrategy: 具体策略类

../_images/Strategy.jpg

伪代码

// 策略接口声明了某个算法各个不同版本间所共有的操作。上下文会使用该接口来
// 调用有具体策略定义的算法。
interface Strategy is
    method execute(a, b)

// 具体策略会在遵循策略基础接口的情况下实现算法。该接口实现了它们在上下文
// 中的互换性。
class ConcreteStrategyAdd implements Strategy is
    method execute(a, b) is
        return a + b

class ConcreteStrategySubtract implements Strategy is
    method execute(a, b) is
        return a - b

class ConcreteStrategyMultiply implements Strategy is
    method execute(a, b) is
        return a * b

// 上下文定义了客户端关注的接口。
class Context is
    // 上下文会维护指向某个策略对象的引用。上下文不知晓策略的具体类。上下
    // 文必须通过策略接口来与所有策略进行交互。
    private strategy: Strategy

    // 上下文通常会通过构造函数来接收策略对象,同时还提供设置器以便在运行
    // 时切换策略。
    method setStrategy(Strategy strategy) is
        this.strategy = strategy

    // 上下文会将一些工作委派给策略对象,而不是自行实现不同版本的算法。
    method executeStrategy(int a, int b) is
        return strategy.execute(a, b)


// 客户端代码会选择具体策略并将其传递给上下文。客户端必须知晓策略之间的差
// 异,才能做出正确的选择。
class ExampleApplication is
    method main() is

        创建上下文对象。

        读取第一个数。
        读取最后一个数。
        从用户输入中读取期望进行的行为。

        if (action == addition) then
            context.setStrategy(new ConcreteStrategyAdd())

        if (action == subtraction) then
            context.setStrategy(new ConcreteStrategySubtract())

        if (action == multiplication) then
            context.setStrategy(new ConcreteStrategyMultiply())

        result = context.executeStrategy(First number, Second number)

        打印结果。

优点

策略模式的优点

  • 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
  • 策略模式提供了管理相关的算法族的办法。
  • 策略模式提供了可以替换继承关系的办法。
  • 使用策略模式可以避免使用多重条件转移语句。

缺点

策略模式的缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

命令模式

模式定义:

命令模式(Command Pattern):将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。

模式结构

命令模式包含如下角色:

  • Command: 抽象命令类
  • ConcreteCommand: 具体命令类
  • Invoker: 调用者
  • Receiver: 接收者
  • Client:客户类

../_images/Command.jpg

代码实现

Command

package Command;

public interface Command {
    void execute();
}

ConcreteCommand,必须要关联上执行值(Receiver)

package Command;

import java.util.Set;

public class OrderCommand implements Command {
     private Receiver receiver;
     private Order order;
    public OrderCommand(Receiver receiver,Order order) {
          this.receiver=receiver;
          this.order=order;
    }

    @Override
    public void execute() {
        System.out.println("新的订单来了");
        Set<String> keys = order.getMap().keySet();
        for (String key : keys) {
            Integer integer = order.getMap().get(key);
            receiver.active(key,integer);
        }
        System.out.println(order.getTable()+"订单完成");
    }
}

Receiver命令执行者

package Command;

public class Receiver {
     public void active(String name,Integer num){
         System.out.println(num+"份的"+name+"制作完成");
     }
}

Invoke命令的调用者

package Command;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Invoke {
    private List<OrderCommand> list=new ArrayList<>();

    public void add(OrderCommand orderCommand){
        list.add(orderCommand);
    }
   //发送命令执行
    public void call(){
        for (OrderCommand orderCommand : list) {
             orderCommand.execute();
        }

    }
}

优点

命令模式的优点

  • 降低系统的耦合度。
  • 新的命令可以很容易地加入到系统中。
  • 可以比较容易地设计一个命令队列和宏命令(组合命令)。
  • 可以方便地实现对请求的Undo和Redo。

缺点

命令模式的缺点

  • 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。

在JDK中的实列

以下是在核心 Java 程序库中的一些示例:

Runable是一个典型命令模式,Runnable担当命令的角色,,Thread充当的是调用者,start方法就是其执行方法

职责链模式

责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理,或将其传递给链上的下个处理者

模式结构

  1. 处理者 (Handler) 声明了所有具体处理者的通用接口。 该接口通常仅包含单个方法用于请求处理, 但有时其还会包含一个设置链上下个处理者的方法。
  2. 基础处理者 (Base Handler) 是一个可选的类, 你可以将所有处理者共用的样本代码放置在其中.通常情况下, 该类中定义了一个保存对于下个处理者引用的成员变量。 客户端可通过将处理者传递给上个处理者的构造函数或设定方法来创建链。 该类还可以实现默认的处理行为: 确定下个处理者存在后再将请求传递给它。
  3. 具体处理者 (Concrete Handlers) 包含处理请求的实际代码。 每个处理者接收到请求后, 都必须决定是否进行处理, 以及是否沿着链传递请求。处理者通常是独立且不可变的, 需要通过构造函数一次性地获得所有必要地数据。

责任链设计模式的结构

代码实现

Handler

package Chain;

public abstract class Handler {
    public static final Integer HOLIDAY_ONE = 1;
    public static final Integer HOLIDAY_THREE = 3;
    public static final Integer HOLIDAY_SEVEN = 7;
    private Note note;
    private Handler nextHandler;
    private Integer start;
    private Integer end;
    public Note getNote() {
        return note;
    }

    public void setNote(Note note) {
        this.note = note;
    }

    public Handler getNextHandler() {
        return nextHandler;
    }

    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }

    public abstract void hanler();

    public Handler(Integer start, Integer end) {
        this.end = end;
        this.start = start;
    }

    public final void submit() {
        this.hanler();
        if(this.nextHandler!=null&&this.note.getNum()>this.end){
            this.nextHandler.setNote(this.note);
            this.nextHandler.submit();
        }else{
            System.out.println("完毕");
        }

    }
}

ConcreteHandler

package Chain;

public class Groupmanager extends Handler {
    public Groupmanager() {
        super(0,Handler.HOLIDAY_ONE);
    }

    @Override
    public void hanler() {
        System.out.println(this.getNote().getName()+"因为"+this.getNote().getContent()+"需要请加"+this.getNote().getNum());
        System.out.println("小组部长批准");
    }
}
package Chain;

public class Manager extends Handler {
    public Manager() {
        super(Handler.HOLIDAY_THREE, Handler.HOLIDAY_SEVEN);
    }

    @Override
    public void hanler() {
        System.out.println(this.getNote().getName()+"因为"+this.getNote().getContent()+"需要请加"+this.getNote().getNum());
        System.out.println("副经理批准");
    }
}
package Chain;

public class GeneralManager extends Handler {
    public GeneralManager() {
        super(Handler.HOLIDAY_SEVEN,9);
    }

    @Override
    public void hanler() {
        System.out.println(this.getNote().getName()+"因为"+this.getNote().getContent()+"需要请假"+this.getNote().getNum());
        System.out.println("总经理批准");
    }
}

Client

package Chain;

public class Client {
    public static void main(String[] args) {
        Note note=new Note("小明",1,"身体不舒服");
        Groupmanager group=new Groupmanager();
        Manager manager=new Manager();
        GeneralManager generalManager=new GeneralManager();
        //建立责任链
        group.setNextHandler(manager);
        manager.setNextHandler(generalManager);
        group.setNote(note);
        group.submit();
    }
}

Event事件类

package Chain;

public class Note {
     private String name;
     private Integer num;
     private String content;

    public String getName() {
        return name;
    }

    public Integer getNum() {
        return num;
    }

    public String getContent() {
        return content;
    }

    public Note(String name, Integer num, String content) {
        this.name = name;
        this.num = num;
        this.content = content;
    }
}

优点:

  • 你可以控制请求处理的顺序。
  • 单一职责原则。 你可对发起操作和执行操作的类进行解耦。
  • 开闭原则。 你可以在不更改现有代码的情况下在程序中新增处理者。

缺点:

  • 部分请求可能未被处理。

职责链模式在Tomcat中的应用

posted @ 2023-09-20 00:32  zL66  阅读(22)  评论(0编辑  收藏  举报