0%

设计模式学习笔记

前言

个人学习设计模式时的学习笔记,开始先介绍了七大设计原则和UML类图的基础知识,接着按照创建型模式、结构型模式和行为型模式的分类来分别介绍23种设计模式,对各种设计模式有了一个基本认识;

1. 七大设计原则

核心思想:

  1. 独立出需要变化的地方,不要和那些不需要变化的部分混在一起;
  2. 针对接口编程而不是针对实现编程;
  3. 交互对象之间松耦合设计;

1.1 单一职责原则(单一功能原则)

含义:对类来说,即一个类应该只负责一项职责;(只有类中方法数量足够少,可在方法级别遵守单一职责原则)

1.2 接口隔离原则

含义:客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上

实现方法:

  • 接口尽量小,但是要有限度。一个接口只服务于一个子模块或业务逻辑。
  • 为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。

1.3 依赖倒置原则

含义:

  1. 高层模块不应该依赖低层模块,二者都应该依赖其抽象;
  2. 抽象不应该依赖细节,细节应该依赖抽象;
  3. 依赖倒置的中心思想是面向接口编程,而不是面向实现编程
  4. 以抽象为基础搭建的架构比细节为基础的架构要稳定得多(在java中抽象指接口或抽象类,细节指具体的实现类);
  5. 使用接口或抽象类的目的是制定好规范,而不涉及具体的操作,具体细节由实现类完成;

实现方法:

  • 每个类尽量提供接口或抽象类,或者两者都具备。

  • 变量的声明类型尽量是接口或者是抽象类。

  • 任何类都不应该从具体类派生。

  • 使用继承时尽量遵循里氏替换原则。

1.4 里氏替换原则

含义:

  1. 所有引用父类的地方必须能透明地使用其子类的对象;
  2. 子类可以扩展父类的功能,但不能改变父类原有的功能,即子类继承父类时,除添加新的方法完成新增功能外,在子类中尽量不要重写父类的方法;
  3. 继承实际上是让两个类耦合性增强,原来的父类和子类应该都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合、组合等关系代替;

实现方法:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
  • 子类中可以增加自己特有的方法
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松
  • 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等

1.5 开闭(ocp)原则

ocp原则含义:(最基础最重要的设计原则)
1.一个软件实体如类、模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方);

2.当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化;

实现方法:可以通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中

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

含义:

  1. 如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。
  2. 一个类对自己依赖的类知道得越少越好,即对于被依赖的类不管多么复杂都尽量将逻辑封装在类的内部,对外除了提供public方法不对外泄露任何信息;
  3. 只与直接朋友通信(出现在成员变量、方法参数、方法返回值中的类为直接朋友,出现在局部变量中的类不是直接朋友)

实现方法:

  • 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。

  • 在类的结构设计上,尽量降低类成员的访问权限。

  • 在对其他类的引用上,将引用其他对象的次数降到最低。

  • 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。

1.7 合成复用原则

含义:在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现(使用继承关系时必须严格遵守里氏替换原则)

1.8 七大设计原则总结

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

依赖倒置原则:高层不应该依赖低层,要面向接口编程

单一职责原则:一个类只干一件事,实现类要单一

接口隔离原则:一个接口只服务一个子模块,接口要精简单一

迪米特法则:一个类应该保持对其它对象最少的了解

里氏替换原则:不要破坏继承体系,子类重写方法功能发生改变,不应该影响父类方法的含义

合成复用原则:尽量使用组合或者聚合关系实现代码复用,少使用继承

七大原则目的:降低对象之间的耦合,增加程序的可复用性、可扩展性和可维护性。

2. 设计模式

2.1 创建型模式

2.1.1 单例模式

含义:一个类只有一个实例,且该类能自行创建这个实例对外提供一个访问该单例的全局访问点

注意:在JDK中,java.lang.Runtime就是经典的单例模式(饿汉式)

使用场景:

  1. 需要频繁地进行创建和销毁的对象(多线程线程池、网络连接池等)
  2. 创建对象时耗时过多或耗费资源过多(重量级对象)但又经常用到的对象;
  3. 某类只要求生成一个对象时;
  4. 频繁访问数据库或文件的对象(数据源、session工厂等)

单例模式的几种写法:

写法一:饿汉式1:静态常量

评价:这种写法可用,在类装载时就完成实例化,虽然可以避免线程同步问题,但没有达到懒加载效果,如果始终没有用过这个实例,可能造成内存浪费;

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton {
1.类的内部创建静态对象实例
private static final Singleton instance = new Singleton( );

2.构造器私有化,外部不能new创建对象
private Singleton( ) { }

3.向外暴露一个静态公共方法getInstance,返回实例对象
public static Singleton getInstance( ) {
return instance;
}
}
写法二:饿汉式2:静态代码块

评价:这种写法可用,在类装载时就完成实例化,虽然可以避免线程同步问题,但没有达到懒加载效果,如果始终没有用过这个实例,可能造成内存浪费;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Singleton {
1.构造器私有化
private Singleton( ) { }

2.类内部创建对象实例
private static Singleton instance;

3.在静态代码块中创建单例对象
static {
instance = new Singleton( );
}

4.向外暴露一个公共的静态的公共方法getInstance,返回实例对象
public static Singleton getInstance( ) {
return instance;
}
}
写法三:懒汉式:线程安全同步

评价:在实际开发中不推荐使用这种方式,虽然解决线程不安全问题,但每次访问时都要同步,会影响性能,且消耗更多的资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Singleton {
//保证instance在所有线程中同步
private static volatile Singleton instance = null;

//private避免雷灾外部被实例化
private Singleton( ) { }

//提供静态同步公有方法,类加载时没有生成单例,只有当第一次调用getlnstance方法时才去创建这个单例
public static synchronized Singleton getInstance( ) {
if (instance == null) {
instance = new Singleton( );
}
return instance;
}
}
写法四:双重检查

评价:实际开发时推荐使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Singleton {
private Singleton( ) { }
private static volatile Singleton instance;

//提供静态公有方法,加入双重检查代码,解决线程安全问题同时解决懒加载问题,同时保证效率;
public static Singleton getInstance( ) {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton( );
}
}
}
return instance;
}
}
写法五:静态内部类

评价:在实际开发时推荐使用

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton {
private Singleton( ) { }

//写一个静态内部类SingletonInstance,该类中有一个静态属性INSTANCE,当外部类加载时静态内部类并不会加载,只有静态方法被调用时才会装载静态内部类,里面的静态属性只会在第一次加载类的时候初始化(JVM类装载机制保证)
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton( );
}

public static Singleton getInstance( ) {
return SingletonInstance.INSTANCE;
}
}
写法六:枚举类

评价:实际开发推荐使用

1
2
3
enum Singleton {
INSTANCE;
}

2.1.2 工厂模式

2.1.2.1 简单工厂模式(静态工厂方法模式)

含义:定义一个工厂类可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类,其创建实例的方法通常为静态方法;(一个工厂类负责所有产品的生产)

简单工厂模式的主要角色如下:

  1. 简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。

  2. 抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。

  3. 具体产品(ConcreteProduct):是简单工厂模式的创建目标。

2.1.2.2 工厂方法模式

含义:基类定义一个创建对象的抽象方法,由多个子类决定分别要实例化的类,工厂方法模式将对象的实例化推迟到子类;(一个具体的子类负责一类产品的生产)

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

  1. 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
  2. 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  3. 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能的公共弄接口。
  4. 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//抽象产品:提供了产品的接口
interface Product {
public void show();
}

//具体产品:实现抽象产品中的抽象方法
class ConcreteProduct implements Product {
public void show() {
System.out.println("具体产品显示");
}
}

//抽象工厂:提供了产品的生成方法
interface AbstractFactory {
public Product newProduct();
}

//具体工厂:实现了产品的生成方法,并返回具体产品(一个工厂只生产一种产品)
class ConcreteFactory implements AbstractFactory {
public Product newProduct() {
System.out.println("具体工厂生成-->具体产品");
return new ConcreteProduct();
}
}
2.1.2.3 抽象工厂模式【难点】

含义:定义一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类;(即定义抽象工厂类和具体实现的工厂子类)(一个具体的子类负责多类产品的生产)

抽象工厂模式的主要角色如下:

  1. 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
  2. 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  3. 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  4. 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//抽象产品:提供了多个产品的接口
interface Product1 {
public void show();
}
interface Product2 {
public void check();
}

//具体产品:实现抽象产品中的抽象方法
class ConcreteProduct1 implements Product1 {
public void show() {
System.out.println("具体产品显示");
}
}
class ConcreteProduct2 implements Product2 {
public void check() {
System.out.println("具体产品检查");
}
}


//抽象工厂:提供了多个产品的生成方法
interface AbstractFactory {
public Product1 newProduct1();
public Product2 newProduct2();
}


//具体工厂:实现了产品的生成方法,并返回具体产品(一个工厂生成多种产品)
class ConcreteFactory implements AbstractFactory {
public Product1 newProduct1() {
System.out.println("具体工厂生成-->具体产品1");
return new ConcreteProduct1();
}
public Product2 newProduct2() {
System.out.println("具体工厂生成-->具体产品2");
return new ConcreteProduct2();
}
}

2.1.3 原型模式

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

原型模式包含以下主要角色:

  1. 抽象原型类:规定了具体原型对象必须实现的接口。
  2. 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  3. 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

原型模式实现:

原型模式的克隆分为浅克隆深克隆

  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。(Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。)
  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

2.1.4 建造者模式

含义:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示;(所创建产品间有较多共同点,如果产品间差异性很大则不适合使用建造者模式

建造者模式的四个角色

  1. Product(产品角色):一个包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件;
  2. Builder(抽象建造者):一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品对象的方法 getResult( )。
  3. ConcreteBuilder(具体建造者):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
  4. Director(指挥者):构建一个使用Builder接口的对象,调用建造者中的方法完成复杂对象的创建;

2.2 结构型模式

2.2.1 装饰器模式

含义:在不改变现有对象结构的情况下,动态地给该对象增加一些职责,即增加其额外功能;(注意装饰器模式会增加许多子类,过度使用会增加程序得复杂性)

装饰器模式主要包含以下角色:

  1. 抽象构件(Component):定义一个抽象接口以规范准备接收附加责任的对象。
  2. 具体构件(ConcreteComponent):实现抽象构件,通过具体装饰角色为其添加一些职责
  3. 抽象装饰(Decorator):实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  4. 具体装饰(ConcreteDecorator):继承抽象装饰的相关方法,并给具体构件对象添加附加的责任

应用场景:

  • 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
  • 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现。
  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//抽象构件
interface Component {
public void operation();
}

//具体构件
class ConcreteComponent implements Component {
public ConcreteComponent() {}

public void operation() {}
}

//抽象装饰(其中包含具体构件的实例)
class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}

public void operation() {
component.operation();
}
}

//具体装饰
class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}

public void addedFunction() {}

public void operation() {
addedFunction();
super.operation();
}
}

2.2.2 代理模式

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

代理模式的主要角色如下:

  1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

根据代理的创建时期,代理模式分为静态代理和动态代理:

  • 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
  • 动态:在程序运行时,运用反射机制动态创建而成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//当被请求时,采用Proxy来获取RealSubject中的对象
public class ProxyTest {
public static void main(String[] args) {

Proxy proxy = new Proxy();
proxy.Request();
}
}


//抽象主题
interface Subject {
void Request();
}

//真实主题
class RealSubject implements Subject {
public void Request() {
System.out.println("访问真实主题方法...");
}
}

//代理类
class Proxy implements Subject {
private RealSubject realSubject;

public void Request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.Request();
postRequest();
}

//可选择添加的原代码前后的增强代码
public void preRequest() {
System.out.println("访问真实主题之前的预处理");
}
public void postRequest() {
System.out.println("访问真实主题之后的后续处理");
}
}

2.2.3 适配器模式(Adapter模式)

含义:将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容;(用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法)

适配器模式(Adapter)包含以下主要角色:

  1. 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  2. 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  3. 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者

应用场景:

  • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
2.2.3.1 类适配器模式

含义:定义一个适配器类来实现当前系统的目标接口,同时继承适配者类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//目标接口
interface Target {
public void request();
}

//适配者类
class Adaptee {
public void specificRequest() {}
}

//类适配器类
class ClassAdapter extends Adaptee implements Target {
public void request() {
specificRequest();
}
}
2.2.3.2 对象适配器模式

含义:将适配者类中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。

1
2
3
4
5
6
7
8
9
10
11
12
//对象适配器类
class ObjectAdapter implements Target {

private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}

public void request() {
adaptee.specificRequest();
}
}

2.2.4 桥接模式【难点】

含义:将抽象与实现分离,使它们可以独立变化。(即将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系,从而降低了抽象和实现这两个可变维度的耦合度)

桥接(Bridge)模式包含以下主要角色:

  1. 抽象化(Abstraction)角色:定义抽象类并包含一个对实现化角色的引用
  2. 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
  3. 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用
  4. 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//实现化角色
interface Implementor {
public void OperationImpl();
}

//具体实现化角色
class ConcreteImplementor implements Implementor {
public void OperationImpl() {}
}

//抽象化角色
abstract class Abstraction {
protected Implementor impl;
protected Abstraction(Implementor impl) {
this.impl = impl;
}

public abstract void Operation();
}

//扩展抽象化角色
class RefinedAbstraction extends Abstraction {
protected RefinedAbstraction(Implementor impl) {
super(impl);
}

public void Operation() {
impl.OperationImpl();
}
}

2.2.5 组合模式【难点】

含义:将对象组合成树状的层次结构,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性;

组合模式包含以下主要角色:

  1. 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)
  2. 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件
  3. 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

组合模式分为透明式的组合模式安全式的组合模式

  • 透明式组合模型

    在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    //抽象构件
    interface Component {
    public void add(Component c);
    public void remove(Component c);
    public Component getChild(int i);
    public void operation();
    }

    //树叶构件
    class Leaf implements Component {
    private String name;
    public Leaf(String name) {
    this.name = name;
    }
    public void add(Component c) {}
    public void remove(Component c) {}
    public Component getChild(int i) {
    return null;
    }
    public void operation() {}
    }

    //树枝构件
    class Composite implements Component {
    private ArrayList<Component> children = new ArrayList<>();
    public void operation() {
    for (Object obj : children) {
    ((Component) obj).operation();
    }
    }
    public void add(Component c) {
    children.add(c);
    }
    public void remove(Component c) {
    children.remove(c);
    }
    public Component getChild(int i) {
    return children.get(i);
    }
    }
  • 安全式组合模型

    在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了透明式组合模型的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    //抽象构建,只保留层次的公共行为
    interface Component {
    public void operation();
    }

    //树叶构件
    class Leaf implements Component {
    private String name;
    public Leaf(String name) {
    this.name = name;
    }
    public void operation() {}
    }

    //树枝构件
    class Composite implements Component {
    private ArrayList<Component> children = new ArrayList<>();
    public void add(Component c) {
    children.add(c);
    }
    public void remove(Component c) {
    children.remove(c);
    }
    public Component getChild(int i) {
    return children.get(i);
    }
    public void operation() {
    for (Object obj : children) {
    ((Component) obj).operation();
    }
    }
    }

2.2.6 外观模式(Facada模式)

含义:通过为多个复杂的子系统提供一个统一的接口,而使这些子系统更加容易被访问;

外观模式包含以下主要角色:

  1. 外观(Facade)角色:为多个子系统对外提供一个共同的接口,它包含了对各个子系统的引用
  2. 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//外观角色
class Facade {
private SubSystem01 obj1 = new SubSystem01();
private SubSystem02 obj2 = new SubSystem02();
public void method() {
obj1.method1();
obj2.method2();
}
}

//子系统角色1
class SubSystem01 {
public void method1() {}
}

//子系统角色2
class SubSystem02 {
public void method2() {}
}

2.2.7 享元模式【难点】

含义:运用共享技术来有效地支持大量细粒度对象的复用(享元模式本质上是缓存共享对象,相同对象只保存一份

享元模式的主要角色有如下:

  1. 抽象享元(Flyweight)角色:是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入
  2. 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
  3. 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中
  4. 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//非享元角色
class UnsharedConcreteFlyweight {
private String info;
UnsharedConcreteFlyweight(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}

//抽象享元角色
interface Flyweight {
public void operation(UnsharedConcreteFlyweight state);
}

//具体享元角色
class ConcreteFlyweight implements Flyweight {
//享元工厂管理的关键字
private String key;
ConcreteFlyweight(String key) {
this.key = key;
}
public void operation(UnsharedConcreteFlyweight state) {}
}

//享元工厂角色
class FlyweightFactory {
private HashMap<String, Flyweight> flyweights = new HashMap<String, Flyweight>();
public Flyweight getFlyweight(String key) {
Flyweight flyweight = (Flyweight) flyweights.get(key);
if (flyweight != null) {
System.out.println("具体享元" + key + "已经存在");
} else {
flyweight = new ConcreteFlyweight(key);
flyweights.put(key, flyweight);
}
return flyweight;
}
}

2.3 行为型模式

2.3.1 观察者模式(Observer模式)

含义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式;

观察者模式的主要角色如下:

  1. 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  2. 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象
  3. 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用
  4. 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//抽象目标
abstract class Subject {
protected List<Observer> observers = new ArrayList<Observer>();
//增加观察者方法
public void add(Observer observer) {
observers.add(observer);
}
//删除观察者方法
public void remove(Observer observer) {
observers.remove(observer);
}
//通知观察者的抽象方法
public abstract void notifyObserver();
}

//具体目标
class ConcreteSubject extends Subject {
public void notifyObserver() {
for (Object obs : observers) {
((Observer) obs).response();
}
}
}

//抽象观察者
interface Observer {
//更新状态
void response();
}

//具体观察者
class ConcreteObserver1 implements Observer {
public void response() {}
}

2.3.2 模板方法模式【难点】

含义:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。(即提取公共部分代码,不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现

模板方法模式包含以下主要角色:

  1. 抽象类/抽象模板(Abstract Class)

抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下:

① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

② 基本方法:是整个算法中的一个步骤,包含以下几种类型:

  • 抽象方法:在抽象类中声明,由具体子类实现。
  • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
  • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
  1. 具体子类/具体实现(Concrete Class)

具体实现类,用于实现抽象类中所定义的抽象方法和钩子方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//抽象模板类
abstract class AbstractClass {
//模板方法
public void TemplateMethod() {
SpecificMethod();
abstractMethod1();
abstractMethod2();
}
//具体方法
public void SpecificMethod() {}
//抽象方法1
public abstract void abstractMethod1();
//抽象方法2
public abstract void abstractMethod2();
}


//具体子类
class ConcreteClass extends AbstractClass {
public void abstractMethod1() {}
public void abstractMethod2() {}
}

2.3.3 命令模式【难点】

含义:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开,两者之间通过命令对象进行沟通,可以提供命令的撤销和恢复功能。

命令模式包含以下主要角色:

  1. 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
  2. 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作
  3. 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者
  4. 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//接收者
class Receiver {
public void action() {}
}

//调用者
class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
public void setCommand(Command command) {
this.command = command;
}
public void call() {
command.execute();
}
}

//抽象命令
interface Command {
public abstract void execute();
}

//具体命令
class ConcreteCommand implements Command {
private Receiver receiver;
ConcreteCommand() {
receiver = new Receiver();
}
//调用者call()中command.execute()根据多态性,实际调用了具体命令类execute()中的receiver.action(),间接访问接收者
public void execute() {
receiver.action();
}
}

2.3.4 访问者模式【难点】

含义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。(将对数据的操作与数据结构进行分离)

访问者模式包含以下主要角色:

  1. 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
  2. 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么
  3. 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数
  4. 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  5. 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//抽象访问者
interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}

//具体访问者A类
class ConcreteVisitorA implements Visitor {
public void visit(ConcreteElementA element) {
System.out.println("具体访问者A访问-->" + element.operationA());
}
public void visit(ConcreteElementB element) {
System.out.println("具体访问者B访问-->" + element.operationB());
}
}

//抽象元素类
interface Element {
void accept(Visitor visitor);
}

//具体元素A类
class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationA() {
return "具体元素A的操作。";
}
}
//具体元素B类
class ConcreteElementB implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationB() {
return "具体元素B的操作。";
}
}

//对象结构角色
class ObjectStructure {
private List<Element> list = new ArrayList<>();
public void accept(Visitor visitor) {
Iterator<Element> i = list.iterator();
while (i.hasNext()) {
((Element) i.next()).accept(visitor);
}
}
public void add(Element element) {
list.add(element);
}
public void remove(Element element) {
list.remove(element);
}
}

2.3.5 迭代器模式

含义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。(即通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据)

迭代器模式主要包含以下角色:

  1. 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
  2. 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例
  3. 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
  4. 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//抽象聚合
interface Aggregate {
public void add(Object obj);
public void remove(Object obj);
//创建迭代器对象
public Iterator getIterator();
}

//具体聚合
class ConcreteAggregate implements Aggregate {
private List<Object> list = new ArrayList<>();
public void add(Object obj) {
list.add(obj);
}
public void remove(Object obj) {
list.remove(obj);
}
//返回具体迭代器实例
public Iterator getIterator() {
return (new ConcreteIterator(list));
}
}

//抽象迭代器
interface Iterator {
boolean hasNext();
Object first();
Object next();
}

//具体迭代器
class ConcreteIterator implements Iterator {
private List<Object> list = null;
private int index = -1;
public ConcreteIterator(List<Object> list) {
this.list = list;
}
public boolean hasNext() {
if (index < list.size() - 1) {
return true;
} else {
return false;
}
}
public Object first() {
index = 0;
Object obj = list.get(index);
return obj;
}
public Object next() {
Object obj = null;
if (this.hasNext()) {
obj = list.get(++index);
}
return obj;
}
}

2.3.6 中介者模式

含义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。

中介者模式包含以下主要角色:

  1. 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法
  2. 具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  3. 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  4. 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//抽象中介者
abstract class Mediator {
//同事对象注册
public abstract void register(Colleague colleague);
//转发同时对象信息
public abstract void relay(Colleague cl);
}

//具体中介者
class ConcreteMediator extends Mediator {
private List<Colleague> colleagues = new ArrayList<>();
public void register(Colleague colleague) {
if (!colleagues.contains(colleague)) {
colleagues.add(colleague);
colleague.setMedium(this);
}
}
public void relay(Colleague cl) {
for (Colleague ob : colleagues) {
if (!ob.equals(cl)) {
((Colleague) ob).receive();
}
}
}
}

//抽象同事类
abstract class Colleague {
protected Mediator mediator;
public void setMedium(Mediator mediator) {
this.mediator = mediator;
}
public abstract void receive();
public abstract void send();
}

//具体同事类
class ConcreteColleague1 extends Colleague {
public void receive() {}
public void send() {
//请中介者转发
mediator.relay(this);
}
}

2.3.7 备忘录模式(快照模式)

含义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态

备忘录模式的主要角色如下:

  1. 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  2. 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人
  3. 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//备忘录
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
//存储发起人内部状态
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
}

//发起人
class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
//创建备忘录
public Memento createMemento() {
return new Memento(state);
}
//恢复备忘录
public void restoreMemento(Memento m) {
this.setState(m.getState());
}
}

//管理者
class Caretaker {
private Memento memento;
public void setMemento(Memento m) {
memento = m;
}
public Memento getMemento() {
return memento;
}
}

2.3.8 解释器模式【不常用】

含义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。

解释器模式包含以下主要角色:

  1. 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
  2. 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
  3. 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
  4. 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//抽象表达式类
interface AbstractExpression {
//解释方法
public void interpret(String info);
}

//终结符表达式类
class TerminalExpression implements AbstractExpression {
//对终结符表达式的处理
public void interpret(String info) {}
}

//非终结符表达式类
class NonterminalExpression implements AbstractExpression {
private AbstractExpression exp1;
//非对终结符表达式的处理
public void interpret(String info) {}
}

//环境类
class Context {
private AbstractExpression exp;
//数据初始化
public Context() {}
//调用相关表达式类的解释方法
public void operation(String info) {}
}

2.3.9 状态模式【难点】

含义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

状态模式包含以下主要角色:

  1. 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换
  2. 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为
  3. 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//环境类
class Context {
private State state;
//定义环境类的初始状态
public Context() {
this.state = new ConcreteStateA();
}
//设置新状态
public void setState(State state) {
this.state = state;
}
//读取状态
public State getState() {
return state;
}
//对请求做处理
public void Handle() {
state.Handle(this);
}
}

//抽象状态类
abstract class State {
public abstract void Handle(Context context);
}

//具体状态A类
class ConcreteStateA extends State {
public void Handle(Context context) {
System.out.println("当前状态是 A.");
context.setState(new ConcreteStateB());
}
}
//具体状态B类
class ConcreteStateB extends State {
public void Handle(Context context) {
System.out.println("当前状态是 B.");
context.setState(new ConcreteStateA());
}
}

2.3.10 策略模式【难点】

含义:定义一组算法,并将每个算法封装到一系列的策略类中,并且使它们之间可以互换。作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法。

策略模式的主要角色如下:

  1. 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  2. 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
  3. 环境(Context)类:持有一个策略类的引用,最终给客户端调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//抽象策略类
interface Strategy {
//策略方法
public void strategyMethod();
}

//具体策略类A
class ConcreteStrategyA implements Strategy {
public void strategyMethod() {}
}
//具体策略类B
class ConcreteStrategyB implements Strategy {
public void strategyMethod() {}
}

//环境类
class Context {
private Strategy strategy;
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void strategyMethod() {
strategy.strategyMethod();
}
}

2.3.11 责任链模式(职责链模式)

含义:将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

职责链模式主要包含以下角色:

  1. 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接
  2. 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  3. 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//客户类
public class ChainOfResponsibilityPattern {
public static void main(String[] args) {
//组装责任链,先创建头节点和后续节点
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
handler1.setNext(handler2);
//向链头提交请求创建处理链
handler1.handleRequest("two");
}
}

//抽象处理者
abstract class Handler {
//创建后续节点
private Handler next;
public void setNext(Handler next) {
this.next = next;
}
public Handler getNext() {
return next;
}
//处理请求的方法
public abstract void handleRequest(String request);
}

//具体处理者
class ConcreteHandler2 extends Handler {
public void handleRequest(String request) {
if (request.equals("two")) {
System.out.println("具体处理者2负责处理该请求!");
} else {
if (getNext() != null) {
getNext().handleRequest(request);
} else {
System.out.println("没有人处理该请求!");
}
}
}
}

2.4 设计模式总结

2.4.1 创建型模式(简单来说就是用来创建对象的)

1. 单例模式

保证一个类仅有一个实例,并且提供一个全局访问点

2. 工厂模式

不同条件下创建不同实例

3. 原型模式

通过拷贝原型创建新的对象

4. 建造者模式

用来分步按顺序创建复杂的复合对象

2.4.2 结构型模式(关注类和对象的组合)

1. 代理模式

为其他对象提供一种代理以控制对这个对象的访问

2. 外观模式

要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行,即对外提供一个统一的接口用来访问子系统

3. 装饰器模式

动态地给一个对象添加一些额外的职责

4. 享元模式

使用对象池来减少重复对象的创建

5. 组合模式

将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性

6. 适配器模式

将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作

7. 桥接模式

将两个能够独立变化的部分分离开来

2.4.3 行为型模式(关注对象之间的通信)

1. 模板方法模式

定义一套流程模板,根据需要实现模板中的操作

2. 策略模式

封装不同的算法,算法之间能互相替换,组织这些算法的使用

3. 责任链模式

拦截的类都实现统一接口,每个接收者都包含对下一个接收者的引用。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

4. 迭代器模式

提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节

5. 命令模式

将请求封装成命令,并记录下来,能够撤销与重做

6. 状态模式

当一个对象内在状态改变时允许其改变行为,根据不同的状态做出不同的行为

7. 备忘录模式

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样有需要时就可将该对象恢复到原先保存的状态

8. 中介者模式

将对象之间的通信关联关系封装到一个中介类中单独处理,从而使其耦合松散

9. 解释器模式

给定一个语言,定义它的语法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子

10. 观察者模式

定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于 它的对象都会得到通知并被自动更新

11. 访问者模式

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新操作