简介
设计模式是软件开发过程中共性问题的可重用解决方案。
设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案。面向对象设计模式通常以类别或对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类别或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。
没用的小知识
设计模式 这个术语最初并不是出现在软件设计中,而是被用于建筑领域的设计中。 1977 年,美国著名建筑大师、加利福尼亚大学伯克利分校环境结构中心主任克里斯托夫·亚历山大(Christopher Alexander)在他的著作《建筑模式语言:城镇、建筑、构造(A Pattern Language: Towns Building Construction)中描述了一些常见的建筑设计问题,并提出了 253 种关于对城镇、邻里、住宅、花园和房间等进行设计的基本模式。 1979 年他的另一部经典著作《建筑的永恒之道》(The Timeless Way of Building)进一步强化了设计模式的思想,为后来的建筑设计指明了方向。 1987 年,肯特·贝克(Kent Beck)和沃德·坎宁安(Ward Cunningham)首先将克里斯托夫·亚历山大的模式思想应用在 Smalltalk 中的图形用户接口的生成中,但没有引起软件界的关注。 直到 1990 年,软件工程界才开始研讨设计模式的话题,后来召开了多次关于设计模式的研讨会。
软件设计模式
六大原则
单一职责原则(SRP)
Single Responsibility Principle
一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
开闭原则(OCP)
Open-Closed Principle
一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展
里氏代换原则(LSP)
Liskov Substitution Principle
所有引用基类(父类)的地方必须能透明地使用其子类的对象。
依赖倒置原则(DIP)
Dependence Inversion Principle
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象,其核心思想是:要面向接口编程,不要面向实现编程。
接口隔离原则(ISP)
Interface Segregation Principle
使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
迪米特法则(LoD)/最少知识原则(LKP)
Law of Demeter / Least Knowledge Principle
一个软件实体应当尽可能少地与其他实体发生相互作用。
- 从迪米特法则的定义和特点可知,它强调以下两点:
- 从依赖者的角度来说,只依赖应该依赖的对象。
- 从被依赖者的角度说,只暴露应该暴露的方法。
- 所以,在运用迪米特法则时要注意以下 6 点。
- 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
- 在类的结构设计上,尽量降低类成员的访问权限。
- 在类的设计上,优先考虑将一个类设置成不变类。
- 在对其他类的引用上,将引用其他对象的次数降到最低。
- 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
- 谨慎使用序列化(Serializable)功能。
实例
Sunny软件公司所开发CRM系统包含很多业务操作窗口,在这些窗口中,某些界面控件之间存在复杂的交互关系,一个控件事件的触发将导致多个其他界面控件产生响应,例如,当一个按钮(Button)被单击时,对应的列表框(List)、组合框(ComboBox)、文本框(TextBox)、文本标签(Label)等都将发生改变,在初始设计方案中,界面控件之间的交互关系可简化为如图所示结构:
classDiagram Button --|> List Button --|> ComboBox List -- TextBox Button --|> Label List -- ComboBox Button --|> TextBox TextBox -- ComboBox
在图中,由于界面控件之间的交互关系复杂,导致在该窗口中增加新的界面控件时需要修改与之交互的其他控件的源代码,系统扩展性较差,也不便于增加和删除新控件。 现使用迪米特对其进行重构。
classDiagram Medidator --|> Button Medidator --|> List Medidator --|> ComboBox Medidator --|> Label Medidator --|> TextBox
在本实例中,可以通过引入一个专门用于控制界面控件交互的中间类(Mediator)来降低界面控件之间的耦合度。引入中间类之后,界面控件之间不再发生直接引用,而是 将请求先转发给中间类,再由中间类来完成对其他控件的调用 。当需要增加或删除新的控件时,只需修改中间类即可,无须修改新增控件或已有控件的源代码。
总结
- 单一职责原则:实现类要职责单一
- 里氏替换原则:不要破坏继承体系
- 依赖倒置原则:要面向接口编程
- 接口隔离原则:在设计接口的时候要精简单一
- 迪米特法则:要降低耦合
- 开闭原则:要对扩展开放,对修改关闭
参考
创建型模式
单例模式(Singleton)
单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
-
单例模式特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点。
-
优点:
- 单例模式可以保证内存里只有一个实例,减少了内存的开销。
- 可以避免对资源的多重占用。
- 单例模式设置全局访问点,可以优化和共享资源的访问。
-
缺点:
- 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径, 违背了开闭原则 。
- 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
饿汉式单例
该模式的特点是类一旦加载就创建一个单例,保证在调用getInstance
方法之前单例已经存在了。
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
public class SingletonExample { //private 避免类在外部被实例化 private SingletonExample() { } private static SingletonExample _instance = new SingletonExample(); public static SingletonExample getInstance() { return _instance; }}
懒汉式单例
该模式的特点是类加载时没有生成单例,只有当第一次调用getInstance
方法时才去创建这个单例。
- 先写一个最基础的实现
public class SingletonExample { private SingletonExample() { } private static SingletonExample _instance; public static SingletonExample getInstance() { if (_instance==null) { _instance = new SingletonExample(); } return _instance; }}
如果编写的是多线程程序,上例代码 不能保证线程安全!
。假设在单例类被实例化之前,有两个线程同时在获取单例对象,线程1在执行完 if (instance == null)
后,线程调度机制将 CPU 资源分配给线程2,此时线程2在执行if (instance == null)
时也发现单例类还没有被实例化,这样就会导致单例类被实例化两次。
- 为了防止这种情况发生,需要对
getInstance()
方法同步:
public class SingletonExample { private SingletonExample() { } private static volatile SingletonExample _instance = null; //保证instance在所有线程中同步 public static synchronized SingletonExample getInstance() { //getInstance 方法前加同步 if (_instance == null) { _instance = new SingletonExample(); } return _instance; }}
上例代码中的关键字volatile
和synchronized
可以保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源。
- **双重加锁(double check)**则能解决这个问题。双重加锁实现本质也是一种懒汉模式,相比第2种实现方式将会有较大的性能提升。
public class SingletonExample { private SingletonExample() { } private volatile static SingletonExample _instance; public static SingletonExample getInstance() { if (_instance==null) { synchronized (SingletonExample.class) { if (_instance==null) { _instance = new SingletonExample(); } } } return _instance; }}
- 为什么要使用
volatile
? 不使用volatile
,只能保证第一个线程的安全性,不能保证后面线程的安全性
参考
简单工厂(Simple Factory)
-
概念 由一个工厂类负责创建产品实例,根据参数决定创建哪种具体产品。
-
特点
优点:实现简单,封装了对象创建逻辑。
缺点:违反开闭原则,每增加新产品需要修改工厂代码。 -
代码示例
class Product { void use() { System.out.println("Using a product"); }}
class ConcreteProductA extends Product { void use() { System.out.println("Using Product A"); }}
class ConcreteProductB extends Product { void use() { System.out.println("Using Product B"); }}
class SimpleFactory { static Product createProduct(String type) { return switch (type) { case "A" -> new ConcreteProductA(); case "B" -> new ConcreteProductB(); default -> throw new IllegalArgumentException("Unknown type"); }; }}
public class Main { public static void main(String[] args) { Product product = SimpleFactory.createProduct("A"); product.use(); }}
工厂方法(Factory Method)
工厂方法是一种创建型设计模式,定义了一个创建对象的接口,但由子类决定实例化哪一个类。工厂方法将对象的实例化推迟到子类,让子类决定要实例化的具体类。这种模式通常适用于产品类型较多,且每个产品都有独特的行为时。
- 优点
- 遵循开闭原则:扩展功能时无需修改现有代码,只需添加新的具体工厂类。
- 通过引入工厂类的层次结构,增加了灵活性和可扩展性。
- 允许在子类中定义不同的实例化方式,增强了代码的可维护性。
- 缺点
- 增加了类的数量,使得代码结构更加复杂。
- 客户端可能需要了解更多的类,以便选择合适的工厂。
抽象工厂(Abstract Factory)
抽象工厂是另一种创建型设计模式,它提供了一个接口,用于创建一系列相关或相互依赖的对象,而无需指定具体类。抽象工厂模式通常用于创建多个系列的产品,每个系列中的产品有相互之间的依赖关系,而这些产品的实例化过程被抽象化并封装在工厂中。
- 优点
- 允许在不同的产品系列中交换产品,使得系统更加灵活。
- 遵循开闭原则,新增产品系列时无需修改现有代码。
- 封装了产品的创建过程,客户端不需要了解产品的具体实现。
- 缺点
- 增加了类的数量,代码结构变得更加复杂。
- 难以扩展某些产品类型:如果需要增加产品类型或改变现有产品结构,可能需要修改多个类。
建造者模式(Builder)
建造者模式是一种创建型设计模式,它将一个复杂对象的构建过程分解成多个简单的步骤,从而允许通过相同的构建过程创建不同的产品。通过使用建造者模式,可以在不暴露对象构建的具体细节的情况下,将一个复杂对象的构建过程与表示分离开来。
- 优点
- 可以构建复杂对象而不需要暴露其构建过程。
- 提高了代码的可读性和可维护性。
- 可以灵活地构建不同的产品,通过不同的构建步骤可以得到不同的结果。
- 每个构建步骤都可以单独修改而不影响整体结构。
- 缺点
- 增加了系统中类的数量,尤其是对于每个产品系列,都需要一个建造者类。
- 对于简单对象的构建,建造者模式可能显得过于复杂。
- 需要通过引入多个类来分解构建过程,导致类之间的依赖关系增多。
原型模式(Prototype)
原型模式是用原型实例指定创建对象的种类,并通过拷贝这些原型创建新对象的创建型模式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
- 优点:
- Java 自带的原型模式基于内存二进制流的复制,在性能上比直接new一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
- 缺点:
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码, 违背了开闭原则 。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。
- 原型模式包含以下主要角色。
- 抽象原型类:规定了具体原型对象必须实现的接口。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
浅克隆
在浅克隆中,如果原型对象的成员变量是值类型(byte,short,int,long,char,double,float,boolean).那么就直接复制,如果是复杂的类型,(枚举,String,对象)就只复制对应的内存地址。
- 创建一个盒子类,提供一个添加物品的方法,实现Cloneable接口并重新clone方法
class Box implements Cloneable { private ArrayList<String> items = new ArrayList<String>(); public Box() { } public void addItem(String item) { items.add(item); } @Override protected Box clone() throws CloneNotSupportedException { return (Box) super.clone(); } @Override public String toString() { return String.format("盒子(@%s)[物件列表(@%s)%s]",Integer.toHexString(this.hashCode()),Integer.toHexString(items.hashCode()),items.toString()); }}
- 这样,一个浅克隆就写好了,调用看看
public static void main(String[] args) throws Exception { Box box1 = new Box(); box1.addItem("物件1"); Box box2 = box1.clone(); System.out.println(box1); System.out.println(box2); /*结果: 盒子(@4926097b)[物件列表(@1b70c43)[物件1]] 盒子(@19dfb72a)[物件列表(@1b70c43)[物件1]] */}
- 虽然两个盒子的引用地址已经不相同,但是,上面已经提及,浅克隆 只复制引用对象的内存地址 。也就是说,在这之后调用
box1.addItem("物件2");
也会对box2
造成影响,会变成下面这种情况
盒子(@4926097b)[物件列表(@36e18842)[物件1, 物件2]] 盒子(@19dfb72a)[物件列表(@36e18842)[物件1, 物件2]]
这种情况下,我们需要深克隆。
深克隆
- 在上面的Box的clone方法的基础上进行修改
@Overrideprotected Box clone() throws CloneNotSupportedException { Box clone = (Box) super.clone(); //items也需要克隆 clone.items = (ArrayList<String>) this.items.clone(); return clone;}
- 再进行测试
public static void main(String[] args) throws Exception { Box box1 = new Box(); box1.addItem("物件1"); Box box2 = box1.clone(); box1.addItem("物件2"); System.out.println(box1); System.out.println(box2); /*结果: 盒子(@4926097b)[物件列表(@36e18842)[物件1, 物件2]] 盒子(@19dfb72a)[物件列表(@1b70c43)[物件1]] */}
这样就实现了完全的克隆,两个对象之间没有任何瓜葛,你改你的,我改我的,互不影响,这种克隆就叫做深克隆。
参考
结构型模式
适配器模式(Adapter)
适配器模式是一种结构型设计模式,它通过提供一个适配器类,使得原本由于接口不兼容而无法一起工作的类能够合作。适配器模式主要有两种形式:类适配器和对象适配器。在类适配器中,适配器类通过继承原接口来实现转换;而在对象适配器中,适配器类通过组合原接口来实现转换。
适用场景:
- 当你想要使用某个现有类,但它的接口不符合你的需求时。
- 当系统需要与多个独立开发的组件进行交互,而这些组件的接口不一致时。
- 需要对一个类进行修改,但不能直接修改这个类时。
-
优点
- 提供了灵活的接口转换。
- 增强了系统的可扩展性。
- 可使已有类在不修改的情况下适配到新的环境中。
-
缺点
- 增加了系统的复杂性,增加了类和接口的数量。
- 如果适配器类设计不当,可能会导致额外的性能开销。
-
代码示例
// 目标接口interface Target { void request();}
// 需要适配的类class Adaptee { void specificRequest() { System.out.println("Specific request."); }}
// 适配器类class Adapter implements Target { private Adaptee adaptee;
public Adapter(Adaptee adaptee) { this.adaptee = adaptee; }
@Override public void request() { adaptee.specificRequest(); // 调用被适配类的方法 }}
// 客户端代码public class AdapterPatternExample { public static void main(String[] args) { Adaptee adaptee = new Adaptee(); Target target = new Adapter(adaptee); target.request(); // 输出:Specific request. }}
组合模式(Composite)
组合模式是一种结构型设计模式,它允许你将对象组合成树形结构来表现“部分-整体”的层次关系。组合模式使得客户对单个对象和组合对象的使用具有一致性。组合模式通常用来表示树形结构的数据,如文件系统、目录树等。在组合模式中,叶子节点和组合节点都实现相同的接口,从而使得客户端可以以统一的方式处理单个对象和对象集合。
适用场景:
- 当需要表示对象的部分-整体层次结构时。
- 当客户端需要统一处理单个对象和对象集合时。
- 当你希望以树形结构表示复杂的对象时。
-
优点
- 客户端代码统一简洁,叶子节点和组合节点的处理方式一致。
- 增加新节点类时,不影响客户端的代码。
-
缺点
- 设计较复杂,可能会导致类的数量增加。
- 客户端无法区分单个对象和组合对象,可能会不小心对组合对象调用单独处理的方法。
-
代码示例
// 组件接口interface Component { void operation();}
// 叶子节点类class Leaf implements Component { @Override public void operation() { System.out.println("Leaf operation."); }}
// 组合节点类class Composite implements Component { private List<Component> children = new ArrayList<>();
public void add(Component component) { children.add(component); }
public void remove(Component component) { children.remove(component); }
@Override public void operation() { for (Component child : children) { child.operation(); } }}
// 客户端代码public class CompositePatternExample { public static void main(String[] args) { Leaf leaf1 = new Leaf(); Leaf leaf2 = new Leaf();
Composite composite = new Composite(); composite.add(leaf1); composite.add(leaf2);
composite.operation(); // 输出:Leaf operation. Leaf operation. }}
装饰模式(Decorator)
装饰模式是一种结构型设计模式,它允许动态地向一个对象添加额外的职责。装饰模式通过创建一个装饰器类来包裹原始类,并实现相同的接口,从而在不修改原始类的情况下扩展其功能。装饰模式通常用于需要对对象进行多重扩展,但又不希望创建大量子类的情况。
适用场景:
- 当你需要扩展类的功能,并且扩展功能的组合不固定时。
- 当你希望通过多个装饰器动态地改变对象的行为时。
- 当需要避免使用大量子类进行功能扩展时。
-
优点
- 装饰器可以灵活地添加功能,避免了创建过多的子类。
- 增加了系统的扩展性和可维护性。
-
缺点
- 增加了系统的复杂性,使用过多装饰器可能会使代码难以理解。
- 装饰器之间可能会有层叠依赖,导致链式调用复杂。
-
代码示例
// 基础接口interface Coffee { double cost();}
// 基础类class SimpleCoffee implements Coffee { @Override public double cost() { return 5; }}
// 装饰器抽象类abstract class CoffeeDecorator implements Coffee { protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) { this.decoratedCoffee = coffee; }
@Override public double cost() { return decoratedCoffee.cost(); }}
// 具体装饰器class MilkDecorator extends CoffeeDecorator { public MilkDecorator(Coffee coffee) { super(coffee); }
@Override public double cost() { return decoratedCoffee.cost() + 2; }}
class SugarDecorator extends CoffeeDecorator { public SugarDecorator(Coffee coffee) { super(coffee); }
@Override public double cost() { return decoratedCoffee.cost() + 1; }}
// 客户端代码public class DecoratorPatternExample { public static void main(String[] args) { Coffee coffee = new SimpleCoffee(); System.out.println("Cost of simple coffee: " + coffee.cost());
coffee = new MilkDecorator(coffee); // 加奶 System.out.println("Cost of coffee with milk: " + coffee.cost());
coffee = new SugarDecorator(coffee); // 加糖 System.out.println("Cost of coffee with milk and sugar: " + coffee.cost()); }}
代理模式(Proxy)
代理模式是以扩展目标对象功能为目的、通过代理对象来间接操控目标对象的设计模式。
我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托 者”对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就相当于为厂家 做了一次对客户群体的“过滤”。我们把微商代理和厂家进一步抽象,前者可抽象为代理类,后 者可抽象为委托类(被代理类)。通过使用代理,通常有两个优点,并且能够分别与我们提到的 微商代理的两个特点对应起来: 优点一: 可以隐藏委托类的实现; 优点二: 可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。
代理模式的元素是: 共同接口、代理对象、目标对象。
代理模式的行为: 由代理对象执行目标对象的方法、由代理对象扩展目标对象的方法。
代理模式的宏观特性: 对客户端只暴露出接口,不暴露它以下的架构。
代理模式的微观特性: 每个元由个类构成。
静态代理
什么是静态代理? 很简单,代理类和实现类实现同一个接口,代理类有一个实现类的引用,客户调用代理类的方法时,代理类就调用实现类的方法。称为静态,是因为代理类和实现类是写死的,就是在编译阶段就确定的。
classDiagram 接口 <|.. 实现类 接口 <|.. 代理类 代理类 <-- Client 实现类 <-- 代理类 class 接口{ <<interface>> }
// 接口interface A { public void f();}// 实现类class AImpl implements A { public void f() { System.out.println("A.f()"); }}// 代理类class AProxy implements A { A a; public AProxy(A a) { this.a = a; } public void f() { // do something else a.f(); // do something else }}// Clientpublic static void main() { A aImpl = new AImpl(); AProxy aProxy = new AProxy(aImpl); aProxy.f();}
这个例子好像也没什么用,不过可以在代理类的f()方法调用实现类的方法前后加上一些代码;在构造方法中也可以通过网络类加载器加载别的机器上的类,这样本地调用时,感觉不到远程机器,就像调用本地代码一样。
动态代理
如果有好多类要代理,就要写好几个代理类,这样比较麻烦,这时动态代理就有用了。
动态代理的本质就是用户提供类名、方法名、参数,代理类执行方法,返回结果。
用类加载器可以将类加载到虚拟机,用Class clazz
表示,有这个对象,就可以执行它的方法。(这就是反射)
这就实现了动态代理。
具体实现:
graph RLClient --> P["Proxy实现类<br>Proxy(InvocationHandler)"] --> I["InvocationHandler实现类<br>实现类的引用<br>invoke"] --> 实现类P & 实现类 --> interface["<interface>"]Ptips["这个Proxy实现类是<br>由Proxy的static方法在执行时刻生成的<br>这个类和被代理类有相同的接口<br>并且它的构造函数的参数是InvocaHandler。<br>因为这个类是在运行时刻生成的<br>可以根据传入不同的参数生成不同的代理类<br>所以是动态代理。"] -.-> P
动态代理类并不是程序员写的,而是根据传入的参数,由Proxy类在运行时生成的,所以可以传入不同的参数,这样就可以在运行时产生不同的代理类,所以是动态的。
// InvocationHandler实现类,里面有一个object对象,指向真正的实现类InvocationHandler handler = new MyInvocationHandler();// 代理类,是有Proxy生成的,根据这点代码,已知的是,它实现了被代理类的接口,而且它有个参数为InvocationHandler作为参数的构造函数Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);Foo f = (Foo)proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);
使用时,一般按下面的写法:
Foo f = (Foo)Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class<?>[]{Foo.class}, handler);
Spring的AOP(面向切面编程)就是用的动态代理实现的,可以用于日志,权限控制,缓存等,可以在InvocationHandler中的invoke方法内部调用实际方法前后加上一些有用的代码。
参考
亨元模式/蝇量模式(Flyweight)
亨元模式(Flyweight Pattern)通过共享对象来减少内存使用。它适用于需要大量对象来表示一些数据,而这些数据中的大部分属性可以共享的场景。亨元模式将对象的状态分为“内享状态”和“外享状态”,其中内享状态可以共享,外享状态是独立的。
适用场景:
- 当需要大量对象,并且这些对象占用内存较多时。
- 对象的状态可以分离成共享部分和非共享部分,且共享部分可以被多个对象共享时。
- 客户端不关心对象的创建过程,只需要获取共享的对象实例。
-
优点
- 减少内存使用:通过共享内享状态,降低了内存消耗。
- 提高性能:减少了对象创建的数量,节省了系统开销。
-
缺点
- 设计复杂:需要区分内享状态和外享状态,增加了设计复杂度。
- 对象状态共享可能导致某些不可预见的行为。
-
代码示例
// 内享状态类class Flyweight { private String intrinsicState; // 内享状态
public Flyweight(String state) { this.intrinsicState = state; }
public void operation(String extrinsicState) { System.out.println("Intrinsic: " + intrinsicState + ", Extrinsic: " + extrinsicState); }}
// 亨元工厂类class FlyweightFactory { private Map<String, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(String key) { if (!flyweights.containsKey(key)) { flyweights.put(key, new Flyweight(key)); } return flyweights.get(key); }}
// 客户端代码public class FlyweightPatternDemo { public static void main(String[] args) { FlyweightFactory factory = new FlyweightFactory(); Flyweight flyweight1 = factory.getFlyweight("A"); flyweight1.operation("1"); Flyweight flyweight2 = factory.getFlyweight("B"); flyweight2.operation("2"); }}
外观模式(Facade)
外观模式(Facade Pattern)提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,使得子系统更容易使用。它通过封装复杂的子系统,使客户端与复杂系统的交互变得简单。
适用场景:
- 当系统有复杂的子系统,客户程序不希望与这些复杂的子系统直接交互时。
- 当需要提供统一的接口来简化系统的使用时。
- 当多个子系统的调用顺序较为复杂时,外观模式可以帮助整理和简化调用逻辑。
-
优点
- 简化客户端与复杂子系统之间的交互。
- 降低客户端的耦合度,提高系统的可维护性。
- 有助于子系统之间的松耦合,减少外部对内部实现的依赖。
-
缺点
- 外观模式可能会导致过多的职责集中在一个类中,增加了该类的复杂度。
- 不利于系统的扩展,某些特定需求可能无法通过外观类来提供。
-
代码示例
// 子系统Aclass SubsystemA { public void operationA() { System.out.println("Subsystem A Operation"); }}
// 子系统Bclass SubsystemB { public void operationB() { System.out.println("Subsystem B Operation"); }}
// 子系统Cclass SubsystemC { public void operationC() { System.out.println("Subsystem C Operation"); }}
// 外观类class Facade { private SubsystemA subsystemA; private SubsystemB subsystemB; private SubsystemC subsystemC;
public Facade() { this.subsystemA = new SubsystemA(); this.subsystemB = new SubsystemB(); this.subsystemC = new SubsystemC(); }
public void operation() { subsystemA.operationA(); subsystemB.operationB(); subsystemC.operationC(); }}
// 客户端代码public class FacadePatternDemo { public static void main(String[] args) { Facade facade = new Facade(); facade.operation(); }}
桥接模式(Bridge)
桥接模式是一种结构性设计模式,旨在通过将抽象与实现分离,使得二者可以独立变化。它通过引入抽象类与实现类的分离,使得具体的实现与抽象部分不再绑定,从而增强了系统的灵活性与可扩展性。桥接模式通常用于多个类的实现有不同变化的情况,可以在不修改现有代码的基础上增加新的实现或抽象。
适用场景:
- 当一个类有多个变化维度时,可以将这些维度分离开,独立处理。
- 系统不希望在每次增加新功能时都修改原有代码。
- 需要在运行时决定使用哪种实现。
-
优点
- 可以在不修改原有类的基础上增加新的实现。
- 增加新的抽象和实现类不需要修改已有代码,符合开闭原则。
- 使得系统更具灵活性和可扩展性。
-
缺点
- 增加了系统的复杂性。
- 设计较为抽象,需要合理的设计来实现功能。
-
应用实例
- GUI系统的实现,抽象出图形与具体的操作实现。
- 数据库操作框架,将数据库的操作与具体的数据库类型解耦。
-
代码示例
// 抽象化角色abstract class Shape { protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI){ this.drawAPI = drawAPI; }
public abstract void draw();}
// 具体抽象化角色class Circle extends Shape { private int x, y, radius;
public Circle(int x, int y, int radius, DrawAPI drawAPI) { super(drawAPI); this.x = x; this.y = y; this.radius = radius; }
@Override public void draw() { drawAPI.drawCircle(x, y, radius); }}
// 实现化角色interface DrawAPI { void drawCircle(int x, int y, int radius);}
// 具体实现化角色class RedCircle implements DrawAPI { @Override public void drawCircle(int x, int y, int radius) { System.out.println("Drawing Circle[ color: red, x: " + x + ", y: " + y + ", radius: " + radius + "]"); }}
class GreenCircle implements DrawAPI { @Override public void drawCircle(int x, int y, int radius) { System.out.println("Drawing Circle[ color: green, x: " + x + ", y: " + y + ", radius: " + radius + "]"); }}
// 客户端代码public class BridgePatternExample { public static void main(String[] args) { Shape redCircle = new Circle(100, 100, 10, new RedCircle()); Shape greenCircle = new Circle(200, 200, 20, new GreenCircle());
redCircle.draw(); greenCircle.draw(); }}
- 桥接模式与组合模式的区别:桥接模式强调的是将抽象和实现分离,而组合模式强调的是将对象组合成树形结构,以表示“部分-整体”的层次关系。
- 桥接模式可以避免过多的子类,尤其是在多个维度的变化下,能减少代码的重复性。
行为型模式
模板模式(Template Method)
模板模式是一种行为型设计模式,定义了一个操作中的算法骨架,将一些步骤的实现延迟到子类中。模板方法允许子类在不改变算法结构的情况下,重新定义算法中的某些步骤。模板模式通常用于执行相似操作的多个类,在这些类中,算法的步骤相同,但是某些具体实现可以不同。
适用场景:
- 当多个子类有相同的算法结构,并且其中的某些步骤有不同的实现时。
- 当算法的结构不希望被改变,并且某些步骤允许被具体实现时。
- 在框架设计中,常用于为框架定义固定的操作流程,并允许子类去实现具体操作。
-
优点
- 可以将不变的部分放到父类中,子类只需要关注变化的部分。
- 提高代码复用性,避免重复的算法代码。
- 子类可在不修改算法结构的情况下重定义某些步骤。
-
缺点
- 由于模板方法由父类提供,子类必须遵循父类的设计,可能会限制子类的扩展性。
- 父类的算法修改可能会影响到所有子类,可能引发不必要的变化。
-
应用实例
- 数据处理框架,定义固定的处理流程,子类提供具体的处理方法。
- 测试框架,如JUnit,定义测试流程并允许子类实现具体的测试步骤。
-
代码示例
// 抽象类定义模板方法abstract class DataProcessor { // 模板方法,定义了处理流程 public final void processData() { loadData(); parseData(); processData(); saveData(); }
// 具体方法,子类不需要实现 private void loadData() { System.out.println("Loading data..."); }
// 抽象方法,子类必须实现 protected abstract void parseData();
// 抽象方法,子类必须实现 protected abstract void processData();
// 具体方法,子类不需要实现 private void saveData() { System.out.println("Saving data..."); }}
// 具体类实现具体步骤class CSVDataProcessor extends DataProcessor { @Override protected void parseData() { System.out.println("Parsing CSV data..."); }
@Override protected void processData() { System.out.println("Processing CSV data..."); }}
class JSONDataProcessor extends DataProcessor { @Override protected void parseData() { System.out.println("Parsing JSON data..."); }
@Override protected void processData() { System.out.println("Processing JSON data..."); }}
// 客户端代码public class TemplateMethodExample { public static void main(String[] args) { DataProcessor csvProcessor = new CSVDataProcessor(); DataProcessor jsonProcessor = new JSONDataProcessor();
System.out.println("Processing CSV data:"); csvProcessor.processData();
System.out.println("\nProcessing JSON data:"); jsonProcessor.processData(); }}
在模板模式中,子类通过实现抽象方法来改变算法的某些部分,但不能改变整个算法的结构。若需要完全自定义算法的结构,可以考虑使用策略模式。 模板方法与钩子方法(hook method)的结合使用,使得父类可以给子类提供默认行为或允许子类选择是否覆盖某些步骤。
解释器模式(Interpreter)
解释器模式是一种行为型设计模式,它定义了一种语言的文法表示,并定义一个解释器来解释这个语言中的句子。该模式用于解析和执行特定语言的句子或表达式。
结构
解释器模式主要包含以下几个部分:
- 抽象表达式(Abstract Expression):定义解释操作的接口。
- 终结符表达式(Terminal Expression):实现与文法中的终结符相关联的解释操作。
- 非终结符表达式(Non-terminal Expression):实现与文法中的非终结符相关联的解释操作,通常包含对其他表达式的引用。
- 上下文(Context):包含解释器之外的一些全局信息。
示例代码
// 抽象表达式interface Expression { int interpret();}
// 终结符表达式class Number implements Expression { private int number;
public Number(int number) { this.number = number; }
@Override public int interpret() { return this.number; }}
// 非终结符表达式class Add implements Expression { private Expression leftExpression; private Expression rightExpression;
public Add(Expression leftExpression, Expression rightExpression) { this.leftExpression = leftExpression; this.rightExpression = rightExpression; }
@Override public int interpret() { return this.leftExpression.interpret() + this.rightExpression.interpret(); }}
// 上下文class InterpreterContext { public static void main(String[] args) { Expression expression = new Add(new Number(5), new Number(10)); System.out.println("结果: " + expression.interpret()); }}
在这个示例中,定义了一个简单的数学表达式解释器,可以解释加法操作。Number
类是终结符表达式,Add
类是非终结符表达式,InterpreterContext
类是上下文,负责执行解释操作。
解释器模式适用于以下场景:
- 当有一个语言需要解释执行,并且可以将该语言中的句子表示为一个抽象语法树时。
- 当一个特定类型的问题发生的频率足够高以至于将该问题的各个实例表示为一个简单的语言是值得的。
解释器模式的优点是易于实现和理解,缺点是对于复杂的文法,解释器模式会导致类的数量急剧增加,维护起来比较困难。
策略模式(Strategy)
策略模式是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式使得算法可以独立于使用它的客户端而变化。
结构
策略模式主要包含以下几个部分:
- 策略接口(Strategy):定义所有支持的算法的公共接口。
- 具体策略(Concrete Strategy):实现策略接口的具体算法。
- 上下文(Context):使用一个具体策略对象来配置,维护对策略对象的引用以调用具体策略定义的算法。
示例代码
// 策略接口interface Strategy { int doOperation(int num1, int num2);}
// 具体策略class OperationAdd implements Strategy { @Override public int doOperation(int num1, int num2) { return num1 + num2; }}
class OperationSubtract implements Strategy { @Override public int doOperation(int num1, int num2) { return num1 - num2; }}
class OperationMultiply implements Strategy { @Override public int doOperation(int num1, int num2) { return num1 * num2; }}
// 上下文class Context { private Strategy strategy;
public Context(Strategy strategy) { this.strategy = strategy; }
public int executeStrategy(int num1, int num2) { return strategy.doOperation(num1, num2); }}
public class StrategyPatternDemo { public static void main(String[] args) { Context context = new Context(new OperationAdd()); System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubtract()); System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationMultiply()); System.out.println("10 * 5 = " + context.executeStrategy(10, 5)); }}
在这个示例中,定义了一个简单的策略模式实现,可以执行加法、减法和乘法操作。Strategy
接口定义了一个算法接口,OperationAdd
、OperationSubtract
和OperationMultiply
类实现了具体的算法。Context
类使用一个具体策略对象来配置,并调用具体策略定义的算法。
策略模式适用于以下场景:
- 当有多个算法可以完成相同的工作,并且可以在运行时选择其中一个算法时。
- 当需要避免使用条件语句来选择不同的算法时。
策略模式的优点是可以在不修改客户端代码的情况下更改算法,缺点是客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
状态模式(State)
状态模式是一种行为型设计模式,它允许对象在内部状态改变时改变其行为。状态模式将与状态相关的行为封装到独立的状态类中,使得对象的行为随着内部状态的改变而改变。
结构
状态模式主要包含以下几个部分:
- 状态接口(State):定义一个接口以封装与上下文的一个特定状态相关的行为。
- 具体状态(Concrete State):实现状态接口的具体状态类,每个类封装了与上下文的一个特定状态相关的行为。
- 上下文(Context):维护一个具体状态类的实例,这个实例定义了当前的状态。
示例代码
// 状态接口interface State { void doAction(Context context);}
// 具体状态class StartState implements State { @Override public void doAction(Context context) { System.out.println("玩家处于开始状态"); context.setState(this); }
@Override public String toString() { return "开始状态"; }}
class StopState implements State { @Override public void doAction(Context context) { System.out.println("玩家处于停止状态"); context.setState(this); }
@Override public String toString() { return "停止状态"; }}
// 上下文class Context { private State state;
public Context() { state = null; }
public void setState(State state) { this.state = state; }
public State getState() { return state; }}
public class StatePatternDemo { public static void main(String[] args) { Context context = new Context();
StartState startState = new StartState(); startState.doAction(context); System.out.println("当前状态: " + context.getState().toString());
StopState stopState = new StopState(); stopState.doAction(context); System.out.println("当前状态: " + context.getState().toString()); }}
在这个示例中,定义了一个简单的状态模式实现,可以表示开始和停止状态。State
接口定义了一个状态接口,StartState
和StopState
类实现了具体的状态。Context
类维护一个具体状态类的实例,并定义了当前的状态。
状态模式适用于以下场景:
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变其行为时。
- 当一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
状态模式的优点是将与状态相关的行为局部化,并将不同状态的行为分割开来,缺点是会增加系统类和对象的个数。
观察者模式(Observer)
观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象的状态发生变化时,它会通知所有观察者对象,使它们能够自动更新自己。
结构
观察者模式主要包含以下几个部分:
- 主题(Subject):主题对象持有对所有观察者对象的引用,并提供注册和删除观察者对象的方法。
- 观察者(Observer):观察者对象定义一个更新接口,以便在主题对象状态发生变化时更新自己。
- 具体主题(Concrete Subject):具体主题对象在状态发生变化时通知所有注册的观察者对象。
- 具体观察者(Concrete Observer):具体观察者对象实现更新接口,以便使自身状态与主题对象的状态保持一致。
示例代码
import java.util.ArrayList;import java.util.List;
// 主题接口interface Subject { void registerObserver(Observer o); void removeObserver(Observer o); void notifyObservers();}
// 具体主题class ConcreteSubject implements Subject { private List<Observer> observers; private int state;
public ConcreteSubject() { observers = new ArrayList<>(); }
public int getState() { return state; }
public void setState(int state) { this.state = state; notifyObservers(); }
@Override public void registerObserver(Observer o) { observers.add(o); }
@Override public void removeObserver(Observer o) { observers.remove(o); }
@Override public void notifyObservers() { for (Observer observer : observers) { observer.update(); } }}
// 观察者接口interface Observer { void update();}
// 具体观察者class ConcreteObserver implements Observer { private ConcreteSubject subject;
public ConcreteObserver(ConcreteSubject subject) { this.subject = subject; this.subject.registerObserver(this); }
@Override public void update() { System.out.println("观察者收到更新: " + subject.getState()); }}
public class ObserverPatternDemo { public static void main(String[] args) { ConcreteSubject subject = new ConcreteSubject();
new ConcreteObserver(subject); new ConcreteObserver(subject);
System.out.println("第一次状态变化: 15"); subject.setState(15);
System.out.println("第二次状态变化: 10"); subject.setState(10); }}
在这个示例中,定义了一个简单的观察者模式实现。Subject
接口定义了主题对象的方法,ConcreteSubject
类实现了具体的主题对象,并在状态变化时通知所有观察者。Observer
接口定义了观察者对象的方法,ConcreteObserver
类实现了具体的观察者对象,并在主题对象状态变化时更新自己。
观察者模式适用于以下场景:
- 当一个对象的改变需要同时改变其他对象时,并且它不知道具体有多少对象需要改变时。
- 当一个对象必须通知其他对象,而它又不能假定其他对象是谁时。
观察者模式的优点是观察者和主题之间的耦合度较低,缺点是如果一个主题有很多直接和间接的观察者时,将所有的观察者都通知到会花费很多时间。
备忘录模式(Memento)
备忘录模式是一种行为型设计模式,它允许在不破坏封装的前提下,捕获和恢复对象的内部状态。备忘录模式通过在对象状态改变前后创建备忘录对象来实现状态的保存和恢复。
结构
备忘录模式主要包含以下几个部分:
- 备忘录(Memento):存储原发器对象的内部状态。
- 原发器(Originator):创建一个包含其当前内部状态的备忘录对象,并可以使用备忘录对象恢复其内部状态。
- 负责人(Caretaker):负责保存备忘录对象,但不能对备忘录对象的内容进行操作或检查。
示例代码
// 备忘录class Memento { private String state;
public Memento(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 saveStateToMemento() { return new Memento(state); }
public void getStateFromMemento(Memento memento) { state = memento.getState(); }}
// 负责人class Caretaker { private List<Memento> mementoList = new ArrayList<>();
public void add(Memento state) { mementoList.add(state); }
public Memento get(int index) { return mementoList.get(index); }}
public class MementoPatternDemo { public static void main(String[] args) { Originator originator = new Originator(); Caretaker caretaker = new Caretaker();
originator.setState("State #1"); originator.setState("State #2"); caretaker.add(originator.saveStateToMemento());
originator.setState("State #3"); caretaker.add(originator.saveStateToMemento());
originator.setState("State #4"); System.out.println("当前状态: " + originator.getState());
originator.getStateFromMemento(caretaker.get(0)); System.out.println("第一次保存的状态: " + originator.getState()); originator.getStateFromMemento(caretaker.get(1)); System.out.println("第二次保存的状态: " + originator.getState()); }}
在这个示例中,定义了一个简单的备忘录模式实现。Memento
类存储原发器对象的内部状态,Originator
类创建和恢复备忘录对象,Caretaker
类负责保存备忘录对象。
备忘录模式适用于以下场景:
- 需要保存和恢复对象的状态时。
- 需要防止外界对象访问对象的内部状态时。
备忘录模式的优点是可以在不破坏封装的前提下捕获和恢复对象的内部状态,缺点是如果需要保存的状态过多,会占用较多的内存空间。
中介者模式(Mediator)
中介者模式是一种行为型设计模式,它定义了一个中介者对象来封装一组对象之间的交互。中介者模式通过使对象之间不需要显式地相互引用,从而使其耦合松散,并可以独立地改变它们之间的交互。
结构
中介者模式主要包含以下几个部分:
- 中介者接口(Mediator):定义了同事对象之间通信的接口。
- 具体中介者(Concrete Mediator):实现中介者接口,协调各个同事对象之间的交互。
- 同事类(Colleague):各个同事类通过中介者与其他同事类通信,而不是直接与其他同事类通信。
示例代码
// 中介者接口interface Mediator { void sendMessage(String message, Colleague colleague);}
// 具体中介者class ConcreteMediator implements Mediator { private Colleague1 colleague1; private Colleague2 colleague2;
public void setColleague1(Colleague1 colleague1) { this.colleague1 = colleague1; }
public void setColleague2(Colleague2 colleague2) { this.colleague2 = colleague2; }
@Override public void sendMessage(String message, Colleague colleague) { if (colleague == colleague1) { colleague2.notify(message); } else { colleague1.notify(message); } }}
// 同事类abstract class Colleague { protected Mediator mediator;
public Colleague(Mediator mediator) { this.mediator = mediator; }}
class Colleague1 extends Colleague { public Colleague1(Mediator mediator) { super(mediator); }
public void send(String message) { System.out.println("Colleague1 发送消息: " + message); mediator.sendMessage(message, this); }
public void notify(String message) { System.out.println("Colleague1 收到消息: " + message); }}
class Colleague2 extends Colleague { public Colleague2(Mediator mediator) { super(mediator); }
public void send(String message) { System.out.println("Colleague2 发送消息: " + message); mediator.sendMessage(message, this); }
public void notify(String message) { System.out.println("Colleague2 收到消息: " + message); }}
public class MediatorPatternDemo { public static void main(String[] args) { ConcreteMediator mediator = new ConcreteMediator();
Colleague1 colleague1 = new Colleague1(mediator); Colleague2 colleague2 = new Colleague2(mediator);
mediator.setColleague1(colleague1); mediator.setColleague2(colleague2);
colleague1.send("你好,Colleague2!"); colleague2.send("你好,Colleague1!"); }}
在这个示例中,定义了一个简单的中介者模式实现。Mediator
接口定义了同事对象之间通信的接口,ConcreteMediator
类实现了具体的中介者,协调各个同事对象之间的交互。Colleague
类及其子类通过中介者与其他同事类通信。
中介者模式适用于以下场景:
- 当对象之间存在复杂的引用关系,导致依赖关系结构混乱且难以复用时。
- 当想通过一个中介者对象来封装多个对象之间的交互行为时。
中介者模式的优点是可以降低对象之间的耦合度,使得对象之间的交互更加灵活,缺点是中介者会变得复杂,难以维护。
命令模式(Command)
命令模式是一种行为型设计模式,它将请求封装成对象,从而使你可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
结构
命令模式主要包含以下几个部分:
- 命令接口(Command):定义执行命令的接口。
- 具体命令(Concrete Command):实现命令接口,定义具体的命令行为。
- 接收者(Receiver):执行具体命令相关的操作。
- 调用者(Invoker):持有命令对象,并在需要时执行命令。
- 客户端(Client):创建具体命令对象并设置其接收者。
示例代码
// 命令接口interface Command { void execute();}
// 接收者class Light { public void turnOn() { System.out.println("灯打开了"); }
public void turnOff() { System.out.println("灯关闭了"); }}
// 具体命令class TurnOnLightCommand implements Command { private Light light;
public TurnOnLightCommand(Light light) { this.light = light; }
@Override public void execute() { light.turnOn(); }}
class TurnOffLightCommand implements Command { private Light light;
public TurnOffLightCommand(Light light) { this.light = light; }
@Override public void execute() { light.turnOff(); }}
// 调用者class RemoteControl { private Command command;
public void setCommand(Command command) { this.command = command; }
public void pressButton() { command.execute(); }}
public class CommandPatternDemo { public static void main(String[] args) { Light light = new Light(); Command turnOn = new TurnOnLightCommand(light); Command turnOff = new TurnOffLightCommand(light);
RemoteControl remote = new RemoteControl();
remote.setCommand(turnOn); remote.pressButton();
remote.setCommand(turnOff); remote.pressButton(); }}
在这个示例中,定义了一个简单的命令模式实现。Command
接口定义了执行命令的方法,TurnOnLightCommand
和TurnOffLightCommand
类实现了具体的命令。Light
类是接收者,执行具体的操作。RemoteControl
类是调用者,持有命令对象并在需要时执行命令。
命令模式适用于以下场景:
- 需要对请求排队或记录请求日志时。
- 需要支持可撤销的操作时。
- 需要将一组操作组合在一起执行时。
命令模式的优点是可以将请求封装成对象,使得请求的参数化、排队、记录日志和撤销操作变得更加容易,缺点是可能会导致系统中类的数量增加。
访问者模式(Visitor)
访问者模式是一种行为型设计模式,它允许你在不改变对象结构的情况下,增加新的操作。通过将操作分离到访问者对象中,访问者模式使得你可以在不修改被访问对象的前提下定义新的操作。
结构
访问者模式主要包含以下几个部分:
- 访问者接口(Visitor):为对象结构中的每一个具体元素类声明一个访问操作。
- 具体访问者(Concrete Visitor):实现访问者接口,为每一个具体元素类实现相应的访问操作。
- 元素接口(Element):定义一个接受访问者的方法,该方法通常以访问者作为参数。
- 具体元素(Concrete Element):实现元素接口,定义接受访问者的方法。
- 对象结构(Object Structure):是一个包含元素的集合,可以遍历这些元素并对其进行访问。
示例代码
// 访问者接口interface Visitor { void visit(ConcreteElementA element); void visit(ConcreteElementB element);}
// 具体访问者class ConcreteVisitor implements Visitor { @Override public void visit(ConcreteElementA element) { System.out.println("访问元素A: " + element.operationA()); }
@Override public void visit(ConcreteElementB element) { System.out.println("访问元素B: " + element.operationB()); }}
// 元素接口interface Element { void accept(Visitor visitor);}
// 具体元素Aclass ConcreteElementA implements Element { @Override public void accept(Visitor visitor) { visitor.visit(this); }
public String operationA() { return "具体元素A的操作"; }}
// 具体元素Bclass ConcreteElementB implements Element { @Override public void accept(Visitor visitor) { visitor.visit(this); }
public String operationB() { return "具体元素B的操作"; }}
// 对象结构class ObjectStructure { private List<Element> elements = new ArrayList<>();
public void addElement(Element element) { elements.add(element); }
public void accept(Visitor visitor) { for (Element element : elements) { element.accept(visitor); } }}
public class VisitorPatternDemo { public static void main(String[] args) { ObjectStructure objectStructure = new ObjectStructure(); objectStructure.addElement(new ConcreteElementA()); objectStructure.addElement(new ConcreteElementB());
ConcreteVisitor visitor = new ConcreteVisitor(); objectStructure.accept(visitor); }}
在这个示例中,定义了一个简单的访问者模式实现。Visitor
接口定义了访问操作,ConcreteVisitor
类实现了具体的访问操作。Element
接口定义了接受访问者的方法,ConcreteElementA
和ConcreteElementB
类实现了具体的元素。ObjectStructure
类是对象结构,包含元素的集合,并可以遍历这些元素并对其进行访问。
访问者模式适用于以下场景:
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你不希望这些操作“污染”这些对象的类时。
- 需要在不修改对象结构中的类的前提下,定义对这些类的操作时。
访问者模式的优点是增加新的操作很容易,缺点是增加新的元素类很困难,因为需要在每一个具体访问者类中增加相应的访问操作。
责任链模式(Chain of Responsibility)
责任链模式是一种行为型设计模式,它允许多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
结构
责任链模式主要包含以下几个部分:
- 抽象处理者(Handler):定义一个处理请求的接口,并实现后继链。
- 具体处理者(Concrete Handler):实现抽象处理者的处理请求方法,判断是否能处理请求,如果可以处理请求则处理,否则将请求传递给后继者。
- 客户端(Client):向链上的具体处理者对象提交请求。
示例代码
// 抽象处理者abstract class Handler { protected Handler successor;
public void setSuccessor(Handler successor) { this.successor = successor; }
public abstract void handleRequest(int request);}
// 具体处理者1class ConcreteHandler1 extends Handler { @Override public void handleRequest(int request) { if (request < 10) { System.out.println("ConcreteHandler1 处理请求: " + request); } else if (successor != null) { successor.handleRequest(request); } }}
// 具体处理者2class ConcreteHandler2 extends Handler { @Override public void handleRequest(int request) { if (request >= 10 && request < 20) { System.out.println("ConcreteHandler2 处理请求: " + request); } else if (successor != null) { successor.handleRequest(request); } }}
// 具体处理者3class ConcreteHandler3 extends Handler { @Override public void handleRequest(int request) { if (request >= 20) { System.out.println("ConcreteHandler3 处理请求: " + request); } else if (successor != null) { successor.handleRequest(request); } }}
public class ChainOfResponsibilityPatternDemo { public static void main(String[] args) { Handler handler1 = new ConcreteHandler1(); Handler handler2 = new ConcreteHandler2(); Handler handler3 = new ConcreteHandler3();
handler1.setSuccessor(handler2); handler2.setSuccessor(handler3);
int[] requests = {5, 14, 22, 18, 3, 27};
for (int request : requests) { handler1.handleRequest(request); } }}
在这个示例中,定义了一个简单的责任链模式实现。Handler
类定义了处理请求的方法,并实现了后继链。ConcreteHandler1
、ConcreteHandler2
和ConcreteHandler3
类实现了具体的处理者,判断是否能处理请求,如果可以处理请求则处理,否则将请求传递给后继者。ChainOfResponsibilityPatternDemo
类是客户端,向链上的具体处理者对象提交请求。
责任链模式适用于以下场景:
- 有多个对象可以处理同一个请求,但具体处理者在运行时才确定时。
- 需要在不明确指定接收者的情况下,向多个对象中的一个提交请求时。
责任链模式的优点是降低了对象之间的耦合度,缺点是不能保证请求一定会被处理。
迭代器模式(Iterator)
迭代器模式是一种行为型设计模式,它提供了一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
结构
迭代器模式主要包含以下几个部分:
- 迭代器接口(Iterator):定义访问和遍历元素的接口。
- 具体迭代器(Concrete Iterator):实现迭代器接口,负责遍历聚合对象中的元素。
- 聚合接口(Aggregate):定义创建迭代器对象的接口。
- 具体聚合(Concrete Aggregate):实现聚合接口,返回一个具体迭代器的实例。
示例代码
import java.util.ArrayList;import java.util.List;
// 迭代器接口interface Iterator { boolean hasNext(); Object next();}
// 聚合接口interface Aggregate { Iterator createIterator();}
// 具体聚合class ConcreteAggregate implements Aggregate { private List<Object> items = new ArrayList<>();
public void addItem(Object item) { items.add(item); }
@Override public Iterator createIterator() { return new ConcreteIterator(items); }}
// 具体迭代器class ConcreteIterator implements Iterator { private List<Object> items; private int position = 0;
public ConcreteIterator(List<Object> items) { this.items = items; }
@Override public boolean hasNext() { return position < items.size(); }
@Override public Object next() { if (this.hasNext()) { return items.get(position++); } return null; }}
public class IteratorPatternDemo { public static void main(String[] args) { ConcreteAggregate aggregate = new ConcreteAggregate(); aggregate.addItem("Item 1"); aggregate.addItem("Item 2"); aggregate.addItem("Item 3");
Iterator iterator = aggregate.createIterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } }}
在这个示例中,定义了一个简单的迭代器模式实现。Iterator
接口定义了访问和遍历元素的方法,ConcreteIterator
类实现了具体的迭代器,负责遍历聚合对象中的元素。Aggregate
接口定义了创建迭代器对象的方法,ConcreteAggregate
类实现了具体的聚合,并返回一个具体迭代器的实例。
迭代器模式适用于以下场景:
- 需要访问一个聚合对象中的内容而无需暴露其内部表示时。
- 需要为聚合对象提供多种遍历方式时。
- 需要为遍历不同的聚合结构提供一个统一的接口时。
迭代器模式的优点是它支持以不同的方式遍历一个聚合对象,简化了聚合类,缺点是增加了类的个数。
并发型模式
在软件工程中,并发型模式是用来处理多线程编程范式的一类设计模式。
主动对象模式(Active Object)
主动对象模式是一种并发设计模式,它将方法的执行与方法的调用分离,通过在独立的线程中执行方法来实现异步方法调用。主动对象模式通过引入一个调度器来协调方法的执行,从而避免了多线程编程中的竞争条件。
结构
主动对象模式主要包含以下几个部分:
- 主动对象接口(Active Object):定义主动对象的方法。
- 具体主动对象(Concrete Active Object):实现主动对象接口,并在独立的线程中执行方法。
- 方法请求(Method Request):表示对主动对象方法的调用。
- 调度器(Scheduler):负责调度方法请求,并在适当的时间执行它们。
- 代理(Proxy):提供主动对象的接口,并将方法调用转换为方法请求。
示例代码
import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;
// 主动对象接口interface ActiveObject { void doTask(String taskName);}
// 方法请求class MethodRequest { private final ActiveObject activeObject; private final String taskName;
public MethodRequest(ActiveObject activeObject, String taskName) { this.activeObject = activeObject; this.taskName = taskName; }
public void execute() { activeObject.doTask(taskName); }}
// 调度器class Scheduler extends Thread { private final BlockingQueue<MethodRequest> requestQueue = new LinkedBlockingQueue<>();
public void enqueue(MethodRequest request) { requestQueue.offer(request); }
@Override public void run() { while (true) { try { MethodRequest request = requestQueue.take(); request.execute(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }}
// 具体主动对象class ConcreteActiveObject implements ActiveObject { @Override public void doTask(String taskName) { System.out.println("执行任务: " + taskName); }}
// 代理class ActiveObjectProxy implements ActiveObject { private final Scheduler scheduler; private final ActiveObject activeObject;
public ActiveObjectProxy(Scheduler scheduler, ActiveObject activeObject) { this.scheduler = scheduler; this.activeObject = activeObject; }
@Override public void doTask(String taskName) { MethodRequest request = new MethodRequest(activeObject, taskName); scheduler.enqueue(request); }}
public class ActiveObjectPatternDemo { public static void main(String[] args) { Scheduler scheduler = new Scheduler(); scheduler.start();
ActiveObject activeObject = new ActiveObjectProxy(scheduler, new ConcreteActiveObject()); activeObject.doTask("任务1"); activeObject.doTask("任务2"); activeObject.doTask("任务3");
scheduler.interrupt(); }}
在这个示例中,定义了一个简单的主动对象模式实现。ActiveObject
接口定义了主动对象的方法,ConcreteActiveObject
类实现了具体的主动对象,并在独立的线程中执行方法。MethodRequest
类表示对主动对象方法的调用,Scheduler
类负责调度方法请求,并在适当的时间执行它们。ActiveObjectProxy
类提供主动对象的接口,并将方法调用转换为方法请求。
主动对象模式适用于以下场景:
- 需要将方法的执行与方法的调用分离时。
- 需要在独立的线程中执行方法以实现异步方法调用时。
主动对象模式的优点是可以避免多线程编程中的竞争条件,缺点是引入了额外的复杂性和开销。
阻止模式(Balking)
阻止模式是一种并发设计模式,它用于在某个条件不满足时立即放弃操作,而不是一直等待条件满足。阻止模式通常用于初始化操作或状态转换操作,当对象处于不适当的状态时,直接返回而不执行操作。
结构
阻止模式主要包含以下几个部分:
- 上下文(Context):包含需要保护的操作和状态。
- 保护条件(Guarded Condition):检查操作是否可以执行的条件。
- 阻止操作(Balking Operation):在保护条件不满足时放弃操作。
示例代码
// 上下文class Context { private boolean initialized = false;
// 保护条件 private synchronized boolean canInitialize() { return !initialized; }
// 阻止操作 public void initialize() { if (!canInitialize()) { System.out.println("初始化已被阻止"); return; }
// 执行初始化操作 initialized = true; System.out.println("初始化成功"); }}
public class BalkingPatternDemo { public static void main(String[] args) { Context context = new Context();
// 第一次初始化 context.initialize();
// 第二次初始化将被阻止 context.initialize(); }}
在这个示例中,定义了一个简单的阻止模式实现。Context
类包含需要保护的操作和状态,canInitialize
方法检查操作是否可以执行,initialize
方法在保护条件不满足时放弃操作。
阻止模式适用于以下场景:
- 需要在某个条件不满足时立即放弃操作,而不是一直等待条件满足时。
- 需要保护初始化操作或状态转换操作时。
阻止模式的优点是可以避免不必要的等待,提高系统的响应速度,缺点是可能会导致操作被频繁放弃,需要仔细设计保护条件。
双重检查锁定模式(Double-Checked Locking)
双重检查锁定模式是一种用于减少加锁开销的并发设计模式,通常用于实现单例模式。它通过在加锁前后都进行检查,从而避免每次访问实例时都进行加锁操作。
结构
双重检查锁定模式主要包含以下几个部分:
- 单例类(Singleton Class):包含一个私有的静态实例和一个公有的静态方法用于获取实例。
- 双重检查锁定(Double-Checked Locking):在获取实例的方法中,先检查实例是否已创建,如果未创建则进行加锁操作,再次检查实例是否已创建,如果未创建则创建实例。
示例代码
class Singleton { // 私有的静态实例 private static volatile Singleton instance;
// 私有的构造函数,防止外部实例化 private Singleton() {}
// 公有的静态方法用于获取实例 public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); } } } return instance; }}
public class DoubleCheckedLockingDemo { public static void main(String[] args) { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); // 输出 true,表示两个实例是相同的 }}
在这个示例中,定义了一个简单的双重检查锁定模式实现。Singleton
类包含一个私有的静态实例和一个公有的静态方法用于获取实例。在获取实例的方法中,先检查实例是否已创建,如果未创建则进行加锁操作,再次检查实例是否已创建,如果未创建则创建实例。
双重检查锁定模式适用于以下场景:
- 需要实现单例模式,并且希望减少加锁开销时。
- 需要在多线程环境中安全地延迟初始化对象时。
双重检查锁定模式的优点是可以减少加锁开销,提高系统性能,缺点是实现复杂,需要确保实例变量使用 volatile
关键字以防止指令重排序问题。
保护性挂起模式(Guarded Suspension)
保护性挂起模式(Guarded Suspension)是一种并发设计模式,它用于在某个条件不满足时挂起操作,直到条件满足再继续执行。该模式通过引入条件变量来协调线程之间的交互,从而避免忙等待。
结构
Guarded Suspension 模式主要包含以下几个部分:
- 上下文(Context):包含需要保护的操作和状态。
- 条件变量(Condition Variable):用于挂起和唤醒线程。
- 保护操作(Guarded Operation):在条件不满足时挂起操作,直到条件满足再继续执行。
示例代码
class GuardedSuspension { private boolean condition = false;
// 保护操作 public synchronized void guardedMethod() { while (!condition) { try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } // 执行操作 System.out.println("条件满足,执行操作"); }
// 改变条件并唤醒等待的线程 public synchronized void changeCondition() { condition = true; notifyAll(); }}
public class GuardedSuspensionDemo { public static void main(String[] args) { GuardedSuspension guardedSuspension = new GuardedSuspension();
Thread thread1 = new Thread(() -> { guardedSuspension.guardedMethod(); });
Thread thread2 = new Thread(() -> { try { Thread.sleep(1000); // 模拟一些操作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } guardedSuspension.changeCondition(); });
thread1.start(); thread2.start(); }}
在这个示例中,定义了一个简单的 Guarded Suspension 模式实现。GuardedSuspension
类包含需要保护的操作和状态,guardedMethod
方法在条件不满足时挂起操作,直到条件满足再继续执行,changeCondition
方法改变条件并唤醒等待的线程。
Guarded Suspension 模式适用于以下场景:
- 需要在某个条件不满足时挂起操作,直到条件满足再继续执行时。
- 需要协调线程之间的交互,避免忙等待时。
Guarded Suspension 模式的优点是可以避免忙等待,提高系统的响应速度,缺点是可能会导致线程被长时间挂起,需要仔细设计条件变量。
领导者/追随者模式(Leader/Followers)
领导者/追随者模式是一种并发设计模式,它通过动态地在多个线程之间分配任务来提高系统的吞吐量。该模式将线程分为领导者和追随者,领导者线程负责处理任务并在完成后将自己降级为追随者,而追随者线程则等待成为新的领导者。
结构
领导者/追随者模式主要包含以下几个部分:
- 任务队列(Task Queue):存储待处理的任务。
- 领导者线程(Leader Thread):负责从任务队列中获取任务并处理。
- 追随者线程(Follower Thread):等待成为新的领导者并处理任务。
示例代码
import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;
class Task { private final String name;
public Task(String name) { this.name = name; }
public void execute() { System.out.println(Thread.currentThread().getName() + " 执行任务: " + name); }}
class LeaderFollowers { private final BlockingQueue<Task> taskQueue = new LinkedBlockingQueue<>(); private final BlockingQueue<Thread> followerQueue = new LinkedBlockingQueue<>();
public void addTask(Task task) { taskQueue.offer(task); promoteLeader(); }
public void promoteLeader() { Thread leader = followerQueue.poll(); if (leader != null) { synchronized (leader) { leader.notify(); } } }
public void run() { while (true) { Task task; try { task = taskQueue.take(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } task.execute(); followerQueue.offer(Thread.currentThread()); synchronized (Thread.currentThread()) { try { Thread.currentThread().wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } }}
public class LeaderFollowersPatternDemo { public static void main(String[] args) { LeaderFollowers leaderFollowers = new LeaderFollowers();
for (int i = 0; i < 5; i++) { new Thread(leaderFollowers::run, "线程-" + i).start(); }
for (int i = 1; i <= 10; i++) { leaderFollowers.addTask(new Task("任务" + i)); } }}
在这个示例中,定义了一个简单的领导者/追随者模式实现。Task
类表示待处理的任务,LeaderFollowers
类管理任务队列和追随者队列,并在任务到来时提升新的领导者线程。LeaderFollowersPatternDemo
类创建多个线程并添加任务。
领导者/追随者模式适用于以下场景:
- 需要在多个线程之间动态分配任务以提高系统吞吐量时。
- 需要减少线程之间的竞争,提高系统性能时。
领导者/追随者模式的优点是可以提高系统的吞吐量和性能,缺点是实现复杂,需要仔细管理线程的状态转换。
监视对象模式(Monitor Object)
监视对象模式是一种并发设计模式,它通过将共享资源的访问封装在一个对象中,并提供同步方法来控制对资源的访问,从而确保线程安全。监视对象模式通过使用内置锁和条件变量来协调线程之间的交互。
结构
监视对象模式主要包含以下几个部分:
- 监视对象(Monitor Object):封装共享资源并提供同步方法来控制对资源的访问。
- 条件变量(Condition Variable):用于挂起和唤醒线程,以协调线程之间的交互。
示例代码
class MonitorObject { private boolean condition = false;
// 同步方法 public synchronized void waitForCondition() { while (!condition) { try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } // 执行操作 System.out.println(Thread.currentThread().getName() + " 条件满足,执行操作"); }
// 改变条件并唤醒等待的线程 public synchronized void changeCondition() { condition = true; notifyAll(); }}
public class MonitorObjectPatternDemo { public static void main(String[] args) { MonitorObject monitorObject = new MonitorObject();
Runnable task = () -> { monitorObject.waitForCondition(); };
Thread thread1 = new Thread(task, "线程-1"); Thread thread2 = new Thread(task, "线程-2");
thread1.start(); thread2.start();
try { Thread.sleep(1000); // 模拟一些操作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
monitorObject.changeCondition(); }}
在这个示例中,定义了一个简单的监视对象模式实现。MonitorObject
类封装了共享资源,并提供了同步方法 waitForCondition
和 changeCondition
来控制对资源的访问和协调线程之间的交互。
监视对象模式适用于以下场景:
- 需要确保对共享资源的访问是线程安全的时。
- 需要协调线程之间的交互,避免竞争条件时。
监视对象模式的优点是可以确保线程安全,缺点是可能会导致线程被长时间挂起,需要仔细设计条件变量。
读写锁模式(Read-Write Lock)
读写锁模式是一种并发设计模式,它通过使用不同的锁来区分读操作和写操作,从而提高系统的并发性能。读写锁允许多个读线程同时访问共享资源,但在写线程访问共享资源时,所有的读线程和其他写线程都被阻塞。
结构
读写锁模式主要包含以下几个部分:
- 读写锁(ReadWriteLock):定义获取读锁和写锁的方法。
- 读锁(ReadLock):允许多个读线程同时访问共享资源。
- 写锁(WriteLock):只允许一个写线程访问共享资源,并阻塞其他读线程和写线程。
示例代码
import java.util.concurrent.locks.ReentrantReadWriteLock;import java.util.concurrent.locks.ReadWriteLock;
class SharedResource { private int value = 0; private final ReadWriteLock lock = new ReentrantReadWriteLock();
// 读操作 public void read() { lock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + " 读取值: " + value); } finally { lock.readLock().unlock(); } }
// 写操作 public void write(int newValue) { lock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + " 写入值: " + newValue); value = newValue; } finally { lock.writeLock().unlock(); } }}
public class ReadWriteLockPatternDemo { public static void main(String[] args) { SharedResource sharedResource = new SharedResource();
Runnable readTask = () -> { for (int i = 0; i < 5; i++) { sharedResource.read(); try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } };
Runnable writeTask = () -> { for (int i = 0; i < 5; i++) { sharedResource.write(i); try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } };
Thread thread1 = new Thread(readTask, "读线程-1"); Thread thread2 = new Thread(readTask, "读线程-2"); Thread thread3 = new Thread(writeTask, "写线程");
thread1.start(); thread2.start(); thread3.start(); }}
在这个示例中,定义了一个简单的读写锁模式实现。SharedResource
类包含共享资源,并提供了读操作和写操作的方法。读操作使用读锁,允许多个读线程同时访问共享资源;写操作使用写锁,只允许一个写线程访问共享资源,并阻塞其他读线程和写线程。
读写锁模式适用于以下场景:
- 读操作远多于写操作时。
- 需要提高系统的并发性能,允许多个读线程同时访问共享资源时。
读写锁模式的优点是可以提高系统的并发性能,缺点是实现复杂,需要仔细管理读锁和写锁的获取和释放。
调度者模式(Scheduler)
调度者模式是一种并发设计模式,它通过调度任务的执行来管理线程的使用。调度者模式可以根据不同的策略来调度任务,以提高系统的性能和资源利用率。
结构
调度者模式主要包含以下几个部分:
- 任务(Task):表示需要执行的任务。
- 调度者(Scheduler):负责调度任务的执行。
- 工作线程(Worker Thread):执行调度者分配的任务。
示例代码
import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;
class Task implements Runnable { private final String name;
public Task(String name) { this.name = name; }
@Override public void run() { System.out.println(Thread.currentThread().getName() + " 执行任务: " + name); try { Thread.sleep(1000); // 模拟任务执行时间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }}
class Scheduler { private final BlockingQueue<Task> taskQueue = new LinkedBlockingQueue<>(); private final int numWorkers; private final Worker[] workers;
public Scheduler(int numWorkers) { this.numWorkers = numWorkers; this.workers = new Worker[numWorkers]; for (int i = 0; i < numWorkers; i++) { workers[i] = new Worker(taskQueue); new Thread(workers[i], "工作线程-" + i).start(); } }
public void schedule(Task task) { try { taskQueue.put(task); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }}
class Worker implements Runnable { private final BlockingQueue<Task> taskQueue;
public Worker(BlockingQueue<Task> taskQueue) { this.taskQueue = taskQueue; }
@Override public void run() { while (true) { try { Task task = taskQueue.take(); task.run(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }}
public class SchedulerPatternDemo { public static void main(String[] args) { Scheduler scheduler = new Scheduler(3);
for (int i = 1; i <= 10; i++) { scheduler.schedule(new Task("任务" + i)); } }}
在这个示例中,定义了一个简单的调度者模式实现。Task
类表示需要执行的任务,Scheduler
类负责调度任务的执行,并将任务分配给工作线程,Worker
类执行调度者分配的任务。
调度者模式适用于以下场景:
- 需要管理线程的使用,提高系统的性能和资源利用率时。
- 需要根据不同的策略来调度任务时。
调度者模式的优点是可以提高系统的性能和资源利用率,缺点是实现复杂,需要仔细管理任务的调度和线程的使用。
线程池模式 (Thread Pool)
线程池模式是一种并发设计模式,它通过重用一组固定数量的线程来执行任务,从而减少线程创建和销毁的开销,提高系统的性能和资源利用率。线程池模式适用于需要频繁创建和销毁线程的场景。
结构
线程池模式主要包含以下几个部分:
- 任务(Task):表示需要执行的任务。
- 线程池(Thread Pool):管理一组固定数量的线程,并分配任务给线程执行。
- 工作线程(Worker Thread):从线程池中获取任务并执行。
示例代码
import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;
class Task implements Runnable { private final String name;
public Task(String name) { this.name = name; }
@Override public void run() { System.out.println(Thread.currentThread().getName() + " 执行任务: " + name); try { Thread.sleep(1000); // 模拟任务执行时间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }}
class ThreadPool { private final BlockingQueue<Task> taskQueue = new LinkedBlockingQueue<>(); private final Worker[] workers;
public ThreadPool(int numThreads) { workers = new Worker[numThreads]; for (int i = 0; i < numThreads; i++) { workers[i] = new Worker(taskQueue); new Thread(workers[i], "工作线程-" + i).start(); } }
public void execute(Task task) { try { taskQueue.put(task); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }}
class Worker implements Runnable { private final BlockingQueue<Task> taskQueue;
public Worker(BlockingQueue<Task> taskQueue) { this.taskQueue = taskQueue; }
@Override public void run() { while (true) { try { Task task = taskQueue.take(); task.run(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }}
public class ThreadPoolPatternDemo { public static void main(String[] args) { ThreadPool threadPool = new ThreadPool(3);
for (int i = 1; i <= 10; i++) { threadPool.execute(new Task("任务" + i)); } }}
在这个示例中,定义了一个简单的线程池模式实现。Task
类表示需要执行的任务,ThreadPool
类管理一组固定数量的线程,并分配任务给线程执行,Worker
类从线程池中获取任务并执行。
线程池模式适用于以下场景:
- 需要频繁创建和销毁线程的场景。
- 需要提高系统的性能和资源利用率时。
线程池模式的优点是可以减少线程创建和销毁的开销,提高系统的性能和资源利用率,缺点是实现复杂,需要仔细管理线程池的大小和任务的分配。
线程本地存储(Thread Local Storage)
线程本地存储(Thread Local Storage,TLS)是一种用于线程间数据隔离的技术。它为每个线程提供了独立的存储空间,使得每个线程都可以存储和访问与其相关的数据,而不会与其他线程产生冲突。线程本地存储通常用于存储线程的私有数据,比如用户的会话信息、数据库连接等,避免了多线程程序中共享数据时的竞态条件和同步问题。
适用场景:
- 每个线程都需要存储独立的变量时,如用户会话数据。
- 多线程环境下,不需要同步的线程私有数据存储。
- 性能要求较高的场景,减少同步机制带来的开销。
-
优点
- 每个线程都有独立的数据副本,避免了线程间数据共享带来的同步开销。
- 提高了性能,因为线程间无需共享数据,避免了锁的竞争。
- 数据与线程生命周期绑定,线程结束后数据自动销毁。
-
缺点
- 增加了内存开销,因为每个线程都需要为线程本地存储分配独立的内存。
- 在高并发的情况下,线程数过多可能导致内存泄漏。
- 难以管理,尤其是在涉及多个线程池或任务调度器时,线程本地存储的数据可能会在线程重用时被错误地访问。
-
应用实例
- Web服务器中的每个请求线程可能会有一个唯一的用户身份(session)对象。
- 数据库连接池中的每个线程可能需要访问独立的数据库连接。
- 每个线程中的日志系统记录该线程的日志信息。
-
代码示例
// 使用ThreadLocal为每个线程提供独立的存储空间public class ThreadLocalExample { // 定义一个ThreadLocal变量,每个线程都有独立的存储空间 private static ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);
// 模拟多个线程 public static class Task implements Runnable { @Override public void run() { // 每个线程都可以独立地修改自己线程本地存储的值 int value = threadLocalValue.get(); value++; threadLocalValue.set(value); System.out.println(Thread.currentThread().getName() + ": " + threadLocalValue.get()); } }
public static void main(String[] args) { // 启动多个线程,每个线程都会有独立的ThreadLocal存储 Thread thread1 = new Thread(new Task()); Thread thread2 = new Thread(new Task()); Thread thread3 = new Thread(new Task());
thread1.start(); thread2.start(); thread3.start(); }}
反应堆模式(Reactor)
反应堆模式是一种事件驱动的设计模式,适用于处理高并发的 I/O 操作。它通过一个单独的事件循环(或多线程模型)将事件(例如 I/O 操作)分发到对应的处理器。
核心理念是事件分发和事件处理分离,让主线程专注于监听事件,事件的实际处理由工作线程或处理器完成。
主要组件
- Reactor:负责监听事件并将其分发到相应的处理器。
- Handlers:对事件的实际处理器,分为具体的业务逻辑。
- Event Loop:事件循环,通常由
Selector
或Epoll
实现。 - Non-blocking I/O:采用非阻塞模式的 I/O 机制,如 Java 的 NIO。
适用场景
- 高并发网络应用(如 HTTP 服务器、消息中间件)。
- I/O 密集型操作。
- 延迟敏感型任务。
示例代码
import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.*;import java.util.Iterator;
public class ReactorPatternDemo { public static void main(String[] args) throws IOException { Reactor reactor = new Reactor(8080); new Thread(reactor).start(); }}
// Reactor - 事件分发器class Reactor implements Runnable { private final Selector selector;
public Reactor(int port) throws IOException { selector = Selector.open(); ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.bind(new InetSocketAddress(port)); serverSocket.configureBlocking(false); serverSocket.register(selector, SelectionKey.OP_ACCEPT, new Acceptor(serverSocket, selector)); System.out.println("Server started on port " + port); }
@Override public void run() { try { while (!Thread.interrupted()) { selector.select(); // 等待事件 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key = it.next(); it.remove(); dispatch(key); } } } catch (IOException e) { e.printStackTrace(); } }
private void dispatch(SelectionKey key) { Runnable handler = (Runnable) key.attachment(); if (handler != null) { handler.run(); } }}
// Acceptor - 接收连接class Acceptor implements Runnable { private final ServerSocketChannel serverSocket; private final Selector selector;
public Acceptor(ServerSocketChannel serverSocket, Selector selector) { this.serverSocket = serverSocket; this.selector = selector; }
@Override public void run() { try { SocketChannel client = serverSocket.accept(); if (client != null) { System.out.println("New connection accepted: " + client.getRemoteAddress()); new Handler(selector, client); } } catch (IOException e) { e.printStackTrace(); } }}
// Handler - 处理 I/O 事件class Handler implements Runnable { private final SocketChannel socket; private final SelectionKey key; private final ByteBuffer buffer = ByteBuffer.allocate(1024); private static final int READING = 0, SENDING = 1; private int state = READING;
public Handler(Selector selector, SocketChannel socket) throws IOException { this.socket = socket; socket.configureBlocking(false); key = socket.register(selector, SelectionKey.OP_READ, this); }
@Override public void run() { try { if (state == READING) { read(); } else if (state == SENDING) { send(); } } catch (IOException e) { e.printStackTrace(); key.cancel(); } }
private void read() throws IOException { buffer.clear(); int bytesRead = socket.read(buffer); if (bytesRead > 0) { System.out.println("Received: " + new String(buffer.array(), 0, bytesRead)); state = SENDING; key.interestOps(SelectionKey.OP_WRITE); // 切换到写操作 } }
private void send() throws IOException { buffer.flip(); socket.write(buffer); System.out.println("Response sent to client."); state = READING; key.interestOps(SelectionKey.OP_READ); // 切换到读操作 }}
评论