一、七大设计原则
1.开放-封闭原则 (Open-Closed Principle, OCP)
- 原则描述:软件实体(类、模块、函数等)应当对扩展开放,对修改关闭。也就是说,当需求变化时,应尽量通过添加新代码来实现功能增强,而不是修改现有的代码。
- 举例:在开发图形库时,如果需要支持多种形状的绘制,可以创建一个抽象的
Shape
接口,并为每种形状(如Circle
、Rectangle
)提供具体实现类。当要增加新的形状时,只需新增一个遵循Shape
接口的类即可,无需修改已有代码。
2.单一职责原则 (Single Responsibility Principle, SRP)
- 原则描述:一个类或模块应该有且仅有一个改变它的原因。简单来说,每个类都应该专注于做一件事,并把它做好。
- 举例:在员工管理系统中,
Employee
类只负责处理与员工基本信息相关的操作,而薪资计算的功能则由另一个SalaryCalculator
类负责,这样修改员工信息不会影响到薪资计算逻辑,反之亦然。
3.里氏替换原则 (Liskov Substitution Principle, LSP)
- 原则描述:子类型必须能够替换它们的基类型。即继承体系中的子类在父类出现的地方能够无二义性地替代父类工作,而不改变程序正确性。
- 举例:假设有一个
Shape
基类和派生出的Square
子类,Square
是一个特殊的Rectangle
,但在使用Shape
的地方,无论传入的是Rectangle
还是Square
,都能正常执行面积计算等方法,而不会因多态调用产生错误结果。
4.依赖倒置原则 (Dependency Inversion Principle, DIP)
- 原则描述:高层模块不应该依赖于低层模块,两者都应依赖于抽象。抽象不应依赖于细节,细节应依赖于抽象。
- 举例:在应用程序中,控制器类(高层模块)不直接依赖于数据库访问类(低层模块),而是共同依赖于一个数据访问接口(抽象)。这样,在更换数据库技术时,只需重新实现接口,而不用修改控制器。
5.接口隔离原则 (Interface Segregation Principle, ISP)
- 原则描述:客户端不应该被迫依赖它不需要的方法。接口应该小而专,每个接口代表一种独立的角色或职责。
- 举例:一个大的
UserService
接口可以拆分为更细粒度的UserAuthentication
,UserProfileManagement
,UserPermissionControl
等接口,各个组件根据实际需求选择合适的接口进行依赖,避免接口方法的冗余和浪费。
6.合成/聚合复用原则 (Composition/Aggregate Reuse Principle, CRP)
- 原则描述:优先使用组合或者聚合关系而非继承来达到复用的目的。换句话说,尽量使用“拥有”关系(包含实例)而不是“是”关系(继承)来复用行为。
- 举例:在构建一个系统时,可以创建一个
Car
类,其中包含多个部件对象(如Engine
、Wheel
等),而不是让Car
从这些部件继承。这样,部件的变化不会直接影响到Car
类,增强了系统的灵活性。
7.迪米特法则 (Law of Demeter, LoD)
- 原则描述:也称为最少知识原则,即一个对象应当尽可能少地了解其他对象。一个对象只需要知道与其直接交互的对象即可。
- 举例:在一个订单系统中,
Order
类只需与Customer
类交互获取必要的信息,而不直接访问Customer
的地址信息,而是通过Customer.getAddress()
间接获取,这样减少了对象之间的耦合度。
二、UML类图
UML类图(Unified Modeling Language Class Diagram)是面向对象分析与设计中最为常用的图形化建模工具之一,它主要用于描述系统的静态结构。在UML类图中,主要元素包括类、接口以及它们之间的关系。下面是一些关键概念和组成部分:
-
类(Class):
- 类名:通常以粗体表示,在顶部书写。
- 属性(Attributes):类的成员变量或数据字段,描述类所拥有的状态信息,一般在类名称下方列出,并可标注可见性(+公共、-私有、#保护)和其他属性如数据类型、初始值等。
- 操作(Operations):类的方法或行为,位于类定义的底部,同样可以标记可见性和参数列表。
-
关系(Relationships):
- 继承(Inheritance):用一个带空心三角形箭头的直线表示,箭头指向基类,用于表示子类对父类的继承关系。
- 实现(Realization):类实现接口时,使用虚线箭头,箭头指向接口,表示类实现了接口的所有方法。
- 关联(Association):用来表示类之间的连接,可以通过线条上的角色名和多重性(0.., 1.., 1, etc.)来详细说明关联关系。
- 聚合(Aggregation):特殊的关联关系,表示整体与部分的关系,通常通过空心菱形箭头表示(箭头从整体指向部分)。
- 组合(Composition):比聚合更强的关系,表示部分不能脱离整体存在,用实心菱形箭头表示。
- 依赖(Dependency):表示一种较弱的“使用”关系,即一个元素的变化可能会影响到另一个元素,用带箭头的虚线表示。
-
接口(Interface):
- 在类图中,接口被表示为类似于类的框,但通常会有特殊标记(比如在顶行画三条破折号),并只包含操作声明,不包含属性。
UML类图能够清晰地展示系统中的类结构及其相互间的静态联系,有助于设计者理解系统的组织架构,并作为文档支持后续的开发、维护工作。通过绘制类图,可以更直观地发现和解决设计问题,确保软件设计的一致性和完整性。
三、单例模式
零、介绍
①单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,可以使用单例模式提高系统性能。
②当实例化一个单例类时,必须要记住使用相应的获取对象的方法,而不是使用new。
③使用场景:需要频繁创建和销毁的对象,创建对象时耗时过多或耗费资源过多,但又经常用到的对象;工具类对象,频繁访问数据库或文件的对象(数据源/session工厂)。
一、饿汉式(静态变量)
package com.vanky.design.singleton.type1;
/**
* @author vanky
* @create 2024/3/17 15:29
*/
public class Singleton1 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
for (int i = 0; i < 10; i++){
instance.print();
instance1.print();
}
System.out.println(instance1 == instance);//true
}
}
/**
* 饿汉式(静态变量):在类装载的时候就完成实例化。
* 缺点:如果未用到这个实例,可能造成内存浪费
*/
class Singleton{
private int num = 0;
//1.构造器私有化,外部不能new
private Singleton(){
}
//2.本类内部创建对象实例
private final static Singleton instance = new Singleton();
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
public void print(){
System.out.println(this.num++);
}
}
二、饿汉式(静态代码块)
package com.vanky.design.singleton.type2;
/**
* @author vanky
* @create 2024/3/17 15:41
*/
public class Singleton2 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1 == instance);//true
}
}
/**
* 饿汉式(静态代码块)
*/
class Singleton{
//构造器私有化,外部不能new
private Singleton(){
}
//本类内部创建对象实例
private static Singleton instance;
//静态代码块中创建单例对象
static {
instance = new Singleton();
}
//给外部提供一个获取实例对象的静态方法
public static Singleton getInstance(){
return instance;
}
}
三、懒汉式(线程不安全)
/**
* 懒汉式(线程不安全)
*/
class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
//多线程环境下,可能有多个线程进来实例化对象
instance = new Singleton();
}
return instance;
}
}
四、懒汉式(线程安全)
/**
* 懒汉式(线程安全)
* 缺点:方法同步,效率低
*/
class Singleton{
private static Singleton instance;
private Singleton(){}
//在原有的基础上,给方法加上同步关键字synchronized
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
五、懒汉式(双重检查)【推荐】
/**
* 懒汉式(双重检查)
* 线程安全,同步方法,保证效率
*/
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;
}
}
六、懒汉式(静态内部类)【推荐】
/**
* 懒汉式(静态内部类)
* 线程安全,利用静态内部类特点实现延迟加载,效率高
*
* 1.在需要实例化时,调用getInstance方法才会装载SingletonInstance类,从而完成Singleton的实例化。
* 2.类的静态属性只会在第一次加载类的时候初始化,JVM帮我们保证了线程的安全;
*/
class Singleton{
//构造器私有化
private Singleton(){}
//静态内部类,有一个静态属性:Singleton
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
//静态公有方法,直接返回SingletonInstance.INSTANCE
public static synchronized Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
七、枚举【推荐】
/**
* @author vanky
* @create 2024/3/17 16:35
*/
public class Singleton7 {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance1 = Singleton.INSTANCE;
System.out.println(instance1 == instance);//true
}
}
enum Singleton{
INSTANCE;
public void run(){
System.out.println("hello,world!");
}
}
四、工厂模式
零、介绍
- 意义:将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。
- 三种工厂模式:简单工厂模式、工厂方法模式、抽象工厂模式
- 设计模式的依赖抽象原则:
- 创建对象实例时,不要直接new类,而是把这个new类的动作放在一个工厂的方法中,并返回。(变量不要直接持有具体类的引用)
- 不用让类继承具体类,而是继承抽象类或是实现接口。
- 不用覆盖基类中已经实现的方法。
一、简单工厂模式
简单工厂模式是一种创建型设计模式,它提供了一个统一的接口用于创建对象,而实际实例化哪一个类的工作则由这个工厂类决定。在简单工厂中,客户端不需要知道如何创建一个具体的对象,而是直接调用工厂提供的静态方法来获取所需的对象。
(1)简单工厂模式的角色和结构:
-
工厂类(Factory):
- 这个类负责生产具体的产品对象。
- 通常包含一个静态方法,根据传入的参数(如类型标识符或配置信息)判断应该创建哪个具体产品的实例。
- 它隐藏了产品类的具体实现细节,并负责管理对象的创建过程。
-
抽象产品类(Abstract Product):
- 定义所有具体产品需要实现的公共接口或者抽象方法,这些方法是客户使用产品的入口点。
-
具体产品类(Concrete Product):
- 实现了抽象产品类所定义的接口,每个具体产品都对应一种特定的产品对象。
(2)简单工厂模式的流程:
- 客户端请求一个产品对象时,不直接实例化具体产品类,而是通过调用工厂类的静态方法传递相应的参数。
- 工厂类根据参数值决定应该实例化哪个具体产品类,并返回该产品的实例给客户端。
- 客户端拿到的是抽象产品类型的引用,这样可以针对抽象类型编程,符合“依赖倒置原则”和“开闭原则”。
(3)优缺点:
- 优点:
- 将对象的创建与使用分离,简化了客户端代码,无需了解产品的具体实现。
- 避免了客户端直接接触具体产品类,方便修改和扩展产品结构。
- 缺点:
- 当新增产品类型时,可能需要修改工厂类的逻辑,违反了“开闭原则”,即对扩展开放、对修改关闭的原则。
- 如果产品族过于庞大,工厂类可能会变得复杂且难以维护。
简单工厂模式虽然被称为“工厂模式”,但严格来说,它并不属于GOF(四人帮)所提出的23种经典设计模式之一。后续的工厂方法模式和抽象工厂模式则是更符合“开闭原则”的工厂模式实现方式。
二、工厂方法模式
工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它在面向对象编程中用于将对象的创建过程封装在对应的工厂类中,并且通过定义一个接口来让子类决定实例化哪一个类。该模式的主要目的是为了将对象的创建与使用分离,并且使得系统更容易扩展新的产品类型。
结构与角色:
-
抽象工厂(Abstract Factory):
- 定义了一个创建产品对象的公共接口,声明一个创建产品的工厂方法(factory method),通常是抽象方法。
-
具体工厂(Concrete Factory):
- 是抽象工厂的一个实现,每个具体工厂对应一种产品类型,负责生产相应的产品对象。
- 具体工厂实现了抽象工厂中定义的工厂方法,返回的是具体产品类型的实例。
-
抽象产品(Abstract Product):
- 定义了所有具体产品共有的接口或者抽象类,是工厂方法所返回的对象类型。
-
具体产品(Concrete Product):
- 实现了抽象产品接口的具体类,代表了某种特定的产品对象。
工作流程:
- 客户端请求创建一个产品对象时,不是直接创建产品对象,而是通过调用具体工厂的工厂方法。
- 具体工厂根据客户端的需求选择并创建适当的具体产品对象。
- 客户端只依赖于抽象产品,无需了解实际创建和返回的产品对象的具体类型,这样增加了系统的灵活性和可扩展性。
优点:
- 符合开闭原则,即对扩展开放,对修改关闭。增加新产品时,只需要新增一个具体产品类和相应的具体工厂即可,无需修改已有代码。
- 把对象的创建过程集中管理,降低了模块间的耦合度,提高了代码的可复用性和可维护性。
缺点:
- 如果产品种类过多,会导致类的数目剧增,进而增加系统的复杂性。
- 工厂方法通常需要与if-else或switch-case等逻辑判断配合使用,以决定创建哪种具体产品,这可能导致工厂类变得庞大而难以维护。
例如,在Java中,假设有一个PizzaStore
抽象工厂,它有一个工厂方法createPizza(String type)
,不同的具体工厂如ChicagoPizzaStore
和NYPizzaStore
会根据传入的披萨类型字符串来创建不同口味的Pizza
对象。
三、抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它提供了一种方式来封装一组相关或相互依赖对象的创建过程,而不暴露具体的实现细节。这种模式的核心是为了解决一个系统中可能存在的多个产品族(即相关的产品系列)的问题,允许客户端在不指定具体产品类的情况下创建并使用这些产品的家族。
结构与角色:
-
抽象工厂(Abstract Factory):
- 定义了创建产品家族的一组接口,每个接口用于创建一种特定类型的产品。
- 抽象工厂通常包含一系列创建方法,对应于不同产品族中的各个产品系列。
-
具体工厂(Concrete Factory):
- 实现了抽象工厂接口,负责生产某个产品家族的具体产品实例。
- 每个具体工厂对应一个特定的产品族,根据需要创建该产品族内的不同产品。
-
抽象产品(Abstract Product):
- 定义了产品家族中每类产品所共有的接口或者抽象类。
- 例如,如果有一个汽车产品族,抽象产品可能是“引擎”和“轮胎”,它们都有各自的抽象接口。
-
具体产品(Concrete Product):
- 是抽象产品的实现类,代表了实际可以使用的、具有特定属性的产品对象。
- 在上述汽车产品族的例子中,具体产品可以是“涡轮增压引擎”、“电动汽车引擎”、“夏季高性能轮胎”等。
工作流程:
- 客户端请求创建一个产品家族时,不是直接创建产品对象,而是通过调用具体工厂的方法获取这个家族下的产品。
- 具体工厂根据请求生成相应产品家族中的具体产品实例,并将它们组合在一起。
- 客户端通过抽象产品接口与产品交互,无需知道实际的产品实现细节。
优点:
- 隔离了高层模块与底层实现之间的耦合度,使得更换整个产品族变得容易,只需要改变具体工厂即可。
- 支持产品族的扩展,增加新的产品族时只需新增相应的具体工厂和具体产品,原有代码不受影响。
- 保证了各产品对象间的兼容性,因为它们都是由同一个工厂创建的,符合一致的设计原则。
缺点:
- 当产品族中的产品数量增加时,会增加系统的复杂性和维护成本。
- 如果新添加的产品和现有产品不遵循原有的产品族划分规则,抽象工厂就难以适应这样的变化。
在Java或其他面向对象编程语言中,抽象工厂模式常被应用于图形用户界面库(GUI库),如按钮、文本框等组件的创建;数据库驱动程序,以支持多种数据库类型的连接;或者是不同风格的外观主题切换等场景。
五、原型模式
零、介绍
设计模式中的原型模式是一种创建型设计模式,它提供了一种通过复制现有对象(即原型)来创建新对象的方法。在原型模式中,一个对象可以创建其自身的一个副本而不依赖于特定的类构造器函数或者使用new操作符等传统的创建方式。这种模式适用于当直接实例化对象耗时或复杂,尤其是需要多次创建相似或相同状态的对象时。
原型模式的核心角色包括:
-
抽象原型(Prototype)接口:定义了克隆自身的操作,通常是一个
clone()
方法。所有具体原型类都必须实现这个接口。 -
具体原型(Concrete Prototype)类:实现了抽象原型接口的类,包含将自身深度或浅度拷贝的具体实现。这意味着当调用
clone()
方法时,会创建并返回当前对象的一个复制品。
使用原型模式的优点有:
- 提高性能:通过复用已有的对象结构来减少创建对象的时间和内存开销。
- 简化对象创建过程:客户端不需要知道如何创建复杂对象的内部结构,只需从原型中获取一个拷贝即可。
- 支持动态创建具有相同或部分相同状态的对象。
需要注意的是,在Java等语言中,实现原型模式时通常需要用到对象拷贝技术。拷贝分为两种类型:
- 浅拷贝(Shallow Copy):只复制对象本身以及其中值类型的成员变量,对于引用类型成员,仅复制引用,不复制引用指向的对象内容。
- 深拷贝(Deep Copy):不仅复制对象本身,还递归地复制所有引用类型成员所指向的对象,从而保证原对象与克隆对象之间的独立性。
一、克隆羊
//需要实现Cloneable接口
public class Sheep implements Cloneable{
private int age;
private String name;
private String color = "白色";
public Sheep(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Sheep{" +
"age=" + age +
", name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone(){
Sheep sheep = null;
try{
sheep = (Sheep)super.clone();
}catch (Exception e){
System.out.println(e.getMessage());
}
return sheep;
}
}
public class CloneSheep {
public static void main(String[] args) throws CloneNotSupportedException {
Sheep sheep = new Sheep(18, "肖恩");
Sheep sheep1 = (Sheep) sheep.clone();
Sheep sheep2 = (Sheep) sheep.clone();
Sheep sheep3 = (Sheep) sheep.clone();
Sheep sheep4 = (Sheep) sheep.clone();
Sheep sheep5 = (Sheep) sheep.clone();
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
System.out.println(sheep4);
System.out.println(sheep5);
}
}
二、深拷贝
public class DeepCloneableTarget implements Serializable,Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class DeepProtoType implements Serializable, Cloneable {
public String name;
public DeepCloneableTarget deepCloneableTarget;
public DeepProtoType(){
super();
}
/**
* 方式一
*/
@Override
protected Object clone() throws CloneNotSupportedException {
//完成对基本数据的克隆
DeepProtoType deepProtoType = (DeepProtoType) super.clone();
//单独处理引用类型属性
deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
return deepProtoType;
}
/**
* 方式2
*/
public Object deepClone(){
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try{
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType deepProtoType = (DeepProtoType) ois.readObject();
return deepProtoType;
}catch (Exception e){
System.out.println(e.getMessage());
return null;
}
}
}
六、建造者模式
零、介绍
设计模式中的**建造者模式(Builder Pattern)**是一种创建型设计模式,主要用于解决复杂对象的构建问题。它的核心目的是将一个复杂的对象的构造过程与它的表示(或结果)分离,使得同样的构建过程可以创建不同的表现形式的产品对象。
在建造者模式中,通常包含以下几个关键角色:
-
抽象建造者(Builder):定义了创建产品的各个步骤的接口,并且一般会有一个方法来返回最终的产品。
-
具体建造者(Concrete Builder):实现了抽象建造者的接口,每个具体建造者都负责实现构建过程的一个具体版本,提供指定类型的复杂对象的构造细节。
-
产品(Product):是建造者需要创建的对象的接口或抽象类,它定义了产品的共性属性和方法。
-
导演者(Director)(可选角色):负责安排建造流程,调用具体建造者的相关方法来一步步构造产品,但不涉及具体的构造细节。
通过使用建造者模式,我们可以有以下优点:
- 封装性良好,客户端只需知道建造者的接口,而不必了解内部的具体构造逻辑。
- 面向对象的设计原则之一——单一职责原则得到体现,每个建造者只负责创建对象的一部分。
- 可以按照步骤逐步创建对象,能够很好地应对构造过程较为复杂的场景。
- 便于控制构造过程,支持不同层次的产品配置和定制化。
- 提高代码可读性和可维护性,因为复杂构造过程被分离开来。
例如,在实际应用中,建造者模式可用于构建如XML文档、复杂的数据库查询、配置文件、汽车、电脑配置等具有多个部件组成,且这些部件有不同的组合方式以生成不同形态或配置的产品对象。
一、代码
####①抽象建造者(Builder)
/**
* 抽象建造者Builder
*/
public abstract class HouseBuilder {
protected House house = new House();
//抽象方法
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();
public House buildHouse() {
return house;
}
}
②具体建造者
/**
* 具体建造者:Concrete Builder
*/
public class CommonHouse extends HouseBuilder{
@Override
public void buildBasic() {
System.out.println("CommonHouse 打地基");
}
@Override
public void buildWalls() {
System.out.println("CommonHouse 砌墙");
}
@Override
public void roofed() {
System.out.println("CommonHouse 封顶");
}
}
③产品
/**
* 产品product
*/
public class House {
private String basic;
private String walls;
private String roof;
public String getBasic() {
return basic;
}
public void setBasic(String basic) {
this.basic = basic;
}
public String getWalls() {
return walls;
}
public void setWalls(String walls) {
this.walls = walls;
}
public String getRoof() {
return roof;
}
public void setRoof(String roof) {
this.roof = roof;
}
}
④导演者
/**
* 导演者:Director
*/
public class HouseDirector {
//聚合
HouseBuilder houseBuilder;
//传入方法一:构造器
public HouseDirector(HouseBuilder houseBuilder){
this.houseBuilder = houseBuilder;
}
//传入方法二:setter方法
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
public House constructHouse(){
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
七、适配器模式
零、介绍
设计模式中的适配器模式(Adapter Pattern)是一种结构型设计模式,主要用于解决接口不兼容的问题,使原本因接口不同而不能直接协作的类能够协同工作。适配器模式的核心思想是将一个接口转换为另一个接口,以符合客户端的期望,这样即使目标接口和原接口不匹配,也能通过适配器进行间接交互。
适配器模式的基本组成部分包括:
- Target(目标接口):这是客户所期待的接口,定义了客户端需要的方法。
- Adaptee(被适配者):这是已经存在的接口或类,拥有功能但接口不符合目标接口的需求。
- Adapter(适配器):适配器类实现了目标接口,并在其内部持有对被适配者的引用,负责将目标接口的方法调用转化为对被适配者相应方法的调用。
适配器模式有两种常见的实现方式:
-
类适配器:通过继承来实现适配,适配器类继承自
Adaptee
并实现Target
接口,在适配器类中重写Target
接口方法,调用Adaptee
的原有方法。 -
对象适配器:通过组合来实现适配,适配器类包含一个
Adaptee
对象的引用,同时实现Target
接口,并在实现的目标接口方法中委托给Adaptee
对象的方法。
适配器模式的主要优点包括:
- 允许在不修改原有代码的基础上复用已有类。
- 提高系统的灵活性,可连接不同的类,减少耦合度。
- 解决旧接口与新接口之间的兼容性问题。
实际应用中,适配器模式常用于处理API的变化、第三方库接口的整合以及系统组件间的协同工作等场景。
一、类适配器
目标:220v电压 —-》5v电压
/**
* 适配接口
*/
public interface IVoltage5V {
/**
* 输出5v电压
* @return
*/
int output5V();
}
/**
* 被适配者
*/
public class Voltage220V {
public int output220V(){
int src = 220;
System.out.println("输出电压:" + src + " V");
return src;
}
}
/**
* 适配器
*/
public class VoltageAdapter extends Voltage220V implements IVoltage5V{
@Override
public int output5V() {
int src = output220V();
int des = src / 44; //转出5v输出
return des;
}
}
二、对象适配器
/**
* 适配器
*/
public class VoltageAdapter implements IVoltage5V {
//使用组合替代继承
private Voltage220V voltage220V;
public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int output5V() {
int src = voltage220V.output220V();
int des = src / 44; //转出5v输出
return des;
}
}
三、接口适配器
public interface MyInterface {
void m1();
void m2();
void m3();
}
/**
* 抽象类实现接口,对接口方法进行空实现
*/
public abstract class AbsClass implements MyInterface{
//空实现
@Override
public void m1() {
}
@Override
public void m2() {
}
@Override
public void m3() {
}
}
public class Client {
public static void main(String[] args) {
AbsClass absClass = new AbsClass(){
/**
* 重写需要使用到的方法即可
*/
@Override
public void m1(){
System.out.println("使用了m1方法");
}
};
absClass.m1();
}
}
八、桥接模式
零、介绍
设计模式中的桥接模式(Bridge Pattern)是一种结构型设计模式,它的主要目的是将抽象部分与其实现部分分离,使它们可以独立地变化。在传统的继承关系中,当抽象类的实现需要多种变化维度时,容易导致类的爆炸性增长(即多个维度下的组合会导致大量的子类)。桥接模式通过引入抽象层和实现层两个独立的层次结构,以“组合”而非“继承”的方式来解耦抽象接口与具体实现,使得这两者可以在运行时动态绑定。
模式的主要角色包括:
-
Abstraction(抽象类): 定义了抽象接口,维护一个对Implementor对象的引用,并且它定义了一些操作,这些操作委托给Implementor完成。
-
Refined Abstraction(改进的抽象类或子类): 是Abstraction的一个扩展版本,它可以增加新的行为,同时仍然依赖Implementor进行实现细节。
-
Implementor(实现接口或抽象类): 定义了实现类需要实现的接口,它不依赖于Abstraction的具体实现,而是提供了具体的算法实现。
-
Concrete Implementor(具体实现类): 实现Implementor接口,提供不同类型的实现,而这些不同的实现可以在运行时被Abstraction对象灵活使用。
桥接模式的优点:
- 有效地解耦了抽象和实现,两者可以独立变化。
- 支持多维度的变化,避免了因为类爆炸而导致的设计复杂性。
- 提高了系统的可扩展性和灵活性,新增功能只需添加新类,不需要修改现有代码。
应用场景:
- 当系统需要在多个维度上进行变化时,例如GUI库中窗口组件和渲染引擎是两个独立变化的维度。
- 需要将抽象部分和实现部分分离,以便分别进行独立开发和复用时。
- 想要避免因大量具有特定功能的子类而导致类层次结构过于庞大和复杂的情况。
一、代码
/**
* 抽象接口Implementor
*/
public interface Brand {
void open();
void close();
void call();
}
/**
* 抽象类Abstraction
*/
public abstract class Phone {
//把接口聚合到抽象类中,抽象类可以调用接口的方法
private Brand brand;
public Phone(Brand brand) {
this.brand = brand;
}
protected void open(){
this.brand.open();
}
protected void close(){
this.brand.close();
}
protected void call(){
this.brand.call();
}
}
/**
* 抽象类的子类1 Refined Abstraction
*/
public class UpRightPhone extends Phone {
public UpRightPhone(Brand brand) {
super(brand);
}
public void open() {
super.open();
System.out.println("直立手机");
}
public void close() {
super.close();
System.out.println("直立手机");
}
public void call() {
super.call();
System.out.println("直立手机");
}
}
/**
* 抽象类的子类2 Refined Abstraction
*/
public class FoldedPhone extends Phone{
public FoldedPhone(Brand brand) {
super(brand);
}
public void open(){
super.open();
System.out.println("折叠手机");
}
public void close(){
super.close();
System.out.println("折叠手机");
}
public void call(){
super.call();
System.out.println("折叠手机");
}
}
/**
* 接口的实现类1 Concrete Implementor
*/
public class Vivo implements Brand{
@Override
public void open() {
System.out.println("Vivo 开机");
}
@Override
public void close() {
System.out.println("Vivo 关机");
}
@Override
public void call() {
System.out.println("Vivo 打电话");
}
}
/**
* 接口的实现类2 Concrete Implementor
*/
public class XiaoMi implements Brand{
@Override
public void open() {
System.out.println("XiaoMi 开机");
}
@Override
public void close() {
System.out.println("XiaoMi 关机");
}
@Override
public void call() {
System.out.println("XiaoMi 打电话");
}
}
/**
* 使用
*/
public class Client {
public static void main(String[] args) {
FoldedPhone foldedPhone = new FoldedPhone(new Vivo());
foldedPhone.open();
foldedPhone.call();
foldedPhone.close();
UpRightPhone upRightPhone = new UpRightPhone(new XiaoMi());
upRightPhone.open();
upRightPhone.call();
upRightPhone.close();
}
}
九、装饰者模式
零、介绍
设计模式中的装饰者模式是一种结构型设计模式,它提供了一种在运行时动态地向对象添加新的职责(功能)的方法,而无需通过子类继承来扩展类的行为。装饰者模式的核心思想是通过组合而非继承的方式来增强或修改对象的行为。
在装饰者模式中:
-
角色与结构:
- 抽象组件(Component):定义了一个接口,所有具体组件和装饰者都遵循这个接口,这样客户端就可以一致地使用它们。
- 具体组件(ConcreteComponent):实现了抽象组件的接口,它是需要被装饰的对象。
- 装饰器(Decorator):也实现了抽象组件接口,并包含一个指向抽象组件实例的引用。装饰器可以在其方法中调用被装饰对象的方法,并在此基础上添加额外的操作。
- 具体装饰器(ConcreteDecorator):是装饰器的具体实现,它给具体组件增加了额外的功能。
-
工作原理:
- 客户端创建一个具体组件实例。
- 当需要添加新功能时,不是直接修改或扩展具体组件,而是将具体组件对象传递给装饰器,由装饰器对其进行包裹。
- 通过一系列装饰器的嵌套,可以逐步为原始组件增加多个新功能,形成装饰链。
- 对于客户端而言,装饰后的对象看起来就像是原始组件的简单延伸,这是因为装饰器与组件共享相同的接口,因此对客户端透明。
-
应用场景:
- 在不希望大量增加子类的情况下扩展对象功能。
- 动态、透明地给单个对象添加职责,例如在软件开发中常见的日志记录、性能监控、权限控制等附加行为。
- 当继承层次过深或者由于种种原因不适合使用继承时,如遇到final类不能被继承的情况。
-
优点:
- 保持了开放封闭原则(Open-Closed Principle, OCP),即对扩展开放,对修改关闭。
- 更加灵活,可以通过组合不同的装饰器以不同的方式来装饰同一组件,从而达到各种定制效果。
- 避免了通过继承增加子类数量而导致的系统复杂性增加问题。
-
缺点:
- 如果过度使用装饰器,可能会导致过多的小类,使得设计变得复杂且难以理解。
- 装饰器和组件必须具有共同的接口,这可能意味着接口会过于庞大,包含了实际不需要的方法。
一、代码
①抽象组件Component
public abstract class Drink {
private String des;
private float cost = 0.0f;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public float getCost() {
return cost;
}
public void setCost(float cost) {
this.cost = cost;
}
public abstract float cost();
}
②装饰器 Decorator
/**
* Decorator
*/
public class Decorator extends Drink{
private Drink drink;
public Decorator(Drink drink) {
this.drink = drink;
}
@Override
public float cost() {
return super.getCost() + drink.cost();
}
@Override
public String getDes() {
return super.getDes() + " " + super.getCost() + " " + drink.getDes();
}
}
③具体组件 ConcreteComponent
/**
* 具体组件的一个抽象
*/
public class Coffee extends Drink{
@Override
public float cost() {
return super.getCost();
}
}
/**
* 具体组件1
*/
public class Espresso extends Coffee{
public Espresso() {
setDes("意大利咖啡");
setCost(8.88f);
}
}
/**
* 具体组件2
*/
public class LongBlack extends Coffee{
public LongBlack() {
setDes("黑咖啡");
setCost(9.99f);
}
}
/**
* 具体组件3
*/
public class ShortBlack extends Coffee{
public ShortBlack() {
setDes("ShortBlack");
setCost(5.44f);
}
}
④具体装饰器 ConcreteDecorator
/**
* 具体装饰器1
*/
public class Chocolate extends Decorator{
public Chocolate(Drink drink) {
super(drink);
super.setDes(" 巧克力 ");
super.setCost(1.11f);
}
}
/**
* 具体装饰器2
*/
public class Milk extends Decorator{
public Milk(Drink drink) {
super(drink);
super.setDes(" 牛奶 ");
super.setCost(1.23f);
}
}
/**
* 具体装饰器3
*/
public class Soy extends Decorator{
public Soy(Drink drink) {
super(drink);
super.setDes(" 豆浆 ");
super.setCost(1.44f);
}
}
⑤使用
public class Client {
public static void main(String[] args) {
//生成一杯咖啡
Drink shortBlack = new ShortBlack();
System.out.println(shortBlack.getDes());
System.out.println(shortBlack.cost());
//给咖啡进行装饰(加一个牛奶)
shortBlack = new Milk(shortBlack);
System.out.println(shortBlack.getDes());
System.out.println(shortBlack.cost());
//给咖啡进行装饰(加一个巧克力)
shortBlack = new Chocolate(shortBlack);
System.out.println(shortBlack.getDes());
System.out.println(shortBlack.cost());
//给咖啡进行装饰(加一个巧克力)
shortBlack = new Chocolate(shortBlack);
System.out.println(shortBlack.getDes());
System.out.println(shortBlack.cost());
}
}
十、组合模式
零、介绍
设计模式中的组合模式(Composite Pattern)是一种结构型设计模式,它主要用于解决对象的树形结构问题,使得客户端可以一致地处理单个对象和对象组合。该模式的主要目的是将对象组织成树形结构,允许用户以统一的方式处理单个对象以及对象集合,即无论处理的是单独的对象还是整个组合,其操作方式都相同。
在组合模式中,主要包含以下角色:
-
Component(抽象构件):
- 这是一个抽象类或接口,定义了所有组件共有的接口方法,这些方法包括叶子节点和容器节点都会用到的操作。
- 通过这个接口,客户端可以对单个对象和复合对象进行统一处理。
-
Leaf(叶子构件):
- 实现了
Component
接口的具体类,代表树形结构中的叶子节点,它们没有子节点。 - 叶子节点通常包含了具体的数据或业务逻辑。
- 实现了
-
Composite(复合构件):
- 同样实现
Component
接口,但它还具有管理一组Component
实例的能力,也就是它可以包含多个子构件。 - 提供了用于添加、删除子构件的方法,并且可能重写了一些接口方法来实现递归行为,比如遍历并调用所有子构件的相关方法。
- 同样实现
组合模式的应用场景:
- 文件系统中的目录与文件,一个目录可以包含其他子目录和文件,而文件是叶子节点。
- HTML文档结构中的元素,如DOM节点,其中某些节点可以包含其他节点,形成嵌套结构。
- 组织结构中的员工和部门,部门可以包含多个员工和其他部门。
使用组合模式的优点:
- 客户端可以一致地处理单个对象和对象组合,简化了代码。
- 结构清晰,容易表示对象的部分-整体层次关系。
- 更易于扩展新的容器或者叶子节点类型。
缺点:
- 如果层次结构过于复杂,可能会增加系统的理解难度。
- 对于不需要这种层次结构的场景,使用组合模式会显得冗余。
一、代码
① Component(抽象构件)
public abstract class OrganizationComponent {
private String name;
private String des;
public OrganizationComponent(String name, String des) {
this.name = name;
this.des = des;
}
public void add(OrganizationComponent organizationComponent){
throw new UnsupportedOperationException();
}
public void remove(OrganizationComponent organizationComponent){
throw new UnsupportedOperationException();
}
public abstract void print();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
}
②Composite(复合构件)
/**
* 复合构件1
*/
public class University extends OrganizationComponent{
private List<OrganizationComponent> organizationComponents = new ArrayList<>();
public University(String name, String des) {
super(name, des);
}
@Override
public void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
@Override
public void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
@Override
public void print() {
System.out.println("=================" + this.getName() + "=================");
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
/**
* 复合构件2
*/
public class College extends OrganizationComponent{
private List<OrganizationComponent> organizationComponents = new ArrayList<>();
public College(String name, String des) {
super(name, des);
}
@Override
public void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
@Override
public void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
@Override
public void print() {
System.out.println("=================" + this.getName() + "=================");
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
③ 叶子节点 leaf
public class Department extends OrganizationComponent{
public Department(String name, String des) {
super(name, des);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
@Override
public void print() {
System.out.println(this.getName());
}
}
④使用
public class Client {
public static void main(String[] args) {
OrganizationComponent pu = new University("PekingUniversity","北大");
OrganizationComponent computerCollege = new College("计算机学院","计院");
OrganizationComponent infoEngineerCollege = new College("信息工程学院","信工");
pu.add(computerCollege);
pu.add(infoEngineerCollege);
//pu.print();
computerCollege.add(new Department("软件工程","软工"));
computerCollege.add(new Department("网络工程","网工"));
computerCollege.add(new Department("计算机科学与技术","计科"));
//pu.print();
infoEngineerCollege.add(new Department("信息工程","信工"));
infoEngineerCollege.add(new Department("通信工程","通信"));
pu.print();
}
}
//输出
/**
=================PekingUniversity=================
=================计算机学院=================
软件工程
网络工程
计算机科学与技术
=================信息工程学院=================
信息工程
通信工程
/*
十一、外观模式
零、介绍
设计模式中的外观模式(Facade Pattern)是一种结构型设计模式,其主要目的是为了简化复杂子系统接口的使用。在软件开发中,一个系统通常由多个相互协作的子系统组成,而这些子系统的内部实现往往较为复杂,包含许多类和方法。当客户端直接与这些子系统交互时,可能会面临复杂的调用关系和较高的耦合度。
外观模式通过提供一个统一的高级接口来封装子系统的复杂性,这个接口即“外观”或“门面”,它定义了客户端可以访问的一组操作,并负责与各个子系统进行交互,协调完成请求的工作。这样,客户端只需与外观对象进行交互,而不必了解各个子系统的具体细节,从而降低了系统的耦合度,简化了客户端代码,也使得整个系统的接口更加简洁易用。
以下是一些外观模式的关键特点:
- 简化接口:外观角色为外部用户提供了一个更简单、更易于理解的接口。
- 封装复杂性:隐藏了子系统组件之间的交互细节和复杂逻辑。
- 解耦:减少了客户端与子系统之间的依赖关系,使得子系统的变化不会直接影响到客户端。
- 统一控制:通过外观对象可以更容易地控制对子系统的访问权限和流程控制。
在实际应用中,外观模式的一个典型场景是操作系统提供的图形用户界面(GUI),用户通过简单的点击操作就可以执行复杂的系统任务,而无需了解底层的操作系统服务是如何被调用和组合的。另一个例子是家电控制系统,用户通过一个总的遥控器(外观)就能控制多个电器设备,而不用单独控制每个设备的开关。
一、代码
①门面接口
/**
* 复制复杂的调用
*/
public class HomeTheaterFacade {
private DVDPlayer dvdPlayer;
private Popcorn popcorn;
private Projector projector;
private Screen screen;
public HomeTheaterFacade() {
super();
dvdPlayer = DVDPlayer.getInstance();
popcorn = Popcorn.getInstance();
projector = Projector.getInstance();
screen = Screen.getInstance();
}
public void ready(){
dvdPlayer.on();
popcorn.pop();
projector.on();
screen.down();
}
public void start(){
dvdPlayer.play();
popcorn.pop();
projector.focus();
}
public void end(){
dvdPlayer.off();
popcorn.off();
screen.up();
projector.off();
}
}
②子系统集合
/**
* 子系统1
*/
public class DVDPlayer {
private static DVDPlayer instance = new DVDPlayer();
public static DVDPlayer getInstance(){
return instance;
}
public void on(){
System.out.println(" dvd on ");
}
public void off(){
System.out.println(" dvd off ");
}
public void play(){
System.out.println(" dvd play ");
}
}
/**
* 子系统2
*/
public class Popcorn {
private static Popcorn instance = new Popcorn();
public static Popcorn getInstance(){
return instance;
}
public void on(){
System.out.println(" Popcorn on ");
}
public void off(){
System.out.println(" Popcorn off ");
}
public void pop(){
System.out.println(" Popcorn pop ");
}
}
/**
* 子系统3
*/
public class Projector {
private static Projector instance = new Projector();
public static Projector getInstance(){
return instance;
}
public void on(){
System.out.println(" Projector on ");
}
public void off(){
System.out.println(" Projector off ");
}
public void focus(){
System.out.println(" Projector focus ");
}
}
/**
* 子系统4
*/
public class Screen {
private static Screen instance = new Screen();
public static Screen getInstance(){
return instance;
}
public void up(){
System.out.println(" Screen up ");
}
public void down(){
System.out.println(" Screen down ");
}
}
③客户端调用
public class Client {
public static void main(String[] args) {
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
System.out.println("准备阶段:");
homeTheaterFacade.ready();
System.out.println("开始看电影");
homeTheaterFacade.start();
System.out.println("看完了。。。");
homeTheaterFacade.end();
}
}
十二、享元模式
零、介绍
设计模式中的享元模式(Flyweight Pattern)是一种用于性能优化的结构型设计模式,它通过共享已经创建的对象来有效地支持大量细粒度对象的复用。该模式的主要目的是在系统中减少内存占用和提高运行效率,特别是在处理大量的相似或相同对象时。
基本概念与目的:
- 享元模式的核心是“共享”,即多个对象可以共享一个相同的实例,以减少新对象的创建。
- 这种模式适用于那些大部分状态可共享、而只有少部分状态因环境变化而不同的情况。
- 通过区分内部状态(Intrinsic State)和外部状态(Extrinsic State),内部状态是可以被共享的部分,而外部状态是依赖于具体场景且不能被共享的。
角色组成:
-
抽象享元(Flyweight)角色:定义了所有具体享元类需要实现的公共接口,包括内部状态的操作方法以及可能存在的外部状态设置方法。
-
具体享元(Concrete Flyweight)角色:实现了抽象享元角色所声明的接口,并存储了内部状态。具体享元可以共享。
-
享元工厂(Flyweight Factory)角色:负责创建并管理享元对象池,当请求一个新的享元对象时,如果池中有符合条件的已存在对象,则返回这个已存在的对象;否则创建一个新的具体享元对象并加入到池中。
应用场景举例:
- 文字渲染引擎中,字符的各种字体样式和大小作为内部状态,而字符在页面上的位置、颜色等信息作为外部状态。同一个字符的不同样式只需要创建一次,然后在不同位置重用。
- 在游戏开发中,多种类型的精灵(Sprite)图片可以共享其纹理数据,而各自的坐标、旋转角度等则是外部状态。
- 数据库连接池就是一个典型的享元模式应用,其中每个数据库连接就是享元对象,连接参数等不变的信息为内部状态,而每次实际执行SQL语句时的上下文则为外部状态。
优缺点:
- 优点:
- 减少系统内存开销,提高内存利用率。
- 由于减少了对象的数量,因此提高了系统的整体性能。
- 缺点:
- 使用享元模式会使得程序复杂性增加,因为需要分离内部状态和外部状态,并引入额外的工厂类进行对象管理和状态控制。
- 对于一些无法有效共享或者状态难以划分的问题,不适合使用享元模式。
总结来说,享元模式通过共享技术巧妙地解决了大量相似对象对系统资源造成的压力,是软件设计中的一种重要优化手段。
一、代码
①抽象享元角色
/**
* 抽象享元
*/
public abstract class WebSite {
public abstract void use();
}
②具体享元角色
/**
* 具体享元
*/
public class ConcreteWebSite extends WebSite{
private String type = "";
public ConcreteWebSite(String type) {
this.type = type;
}
@Override
public void use() {
System.out.println(" website type : " + type + " ===>" + this.hashCode());
}
}
③享元工厂角色
/**
* 享元工厂
*/
public class WebSiteFactory {
//集合,充当池的作用
private HashMap<String, ConcreteWebSite> pool = new HashMap<>();
//根据网站类型返回网站,如果没有就创建一个放入池中并返回。
public WebSite getWebSiteCategory(String type){
if (!pool.containsKey(type)){
pool.put(type,new ConcreteWebSite(type));
}
return (WebSite)pool.get(type);
}
public WebSiteFactory() {
}
}
④使用
public class Client {
public static void main(String[] args) {
WebSiteFactory webSiteFactory = new WebSiteFactory();
webSiteFactory.getWebSiteCategory("微信").use();
webSiteFactory.getWebSiteCategory("博客").use();
webSiteFactory.getWebSiteCategory("网站").use();
webSiteFactory.getWebSiteCategory("博客").use();
}
}
/**
输出:
website type : 微信 ===>1078694789
website type : 博客 ===>363771819
website type : 网站 ===>2065951873
website type : 博客 ===>363771819
*/
十三、代理模式
零、介绍
设计模式中的代理模式(Proxy Pattern)是一种结构型设计模式,它提供了一个对象的替代品或代理,这个代理控制着对实际对象(也称为“被代理对象”或“委托对象”)的访问。在软件设计中,代理模式的主要目的是为原对象增加间接层,通过这一层可以附加额外的功能或者进行预处理、后处理操作,同时保持与原始对象接口的一致性。
代理模式的典型应用场景包括但不限于以下几个方面:
-
远程代理:当对象存在于不同地址空间时,例如网络上的另一台计算机上,本地代理对象负责将请求编码并发送到远程主机,再将远程响应解码返回给客户端。
-
虚拟代理:在资源占用较大或者初始化时间较长的对象实例化之前,代理对象可以先创建一个轻量级对象,延迟加载实际对象,直到真正需要时才去创建和初始化。
-
保护代理:用于控制对目标对象的访问,比如权限验证、事务管理等,在调用目标方法前检查调用者的权限或启动事务,在方法执行完成后提交或回滚事务。
-
智能引用:代理对象可以添加额外的操作,如缓存、日志记录、性能统计等。每当客户端请求目标对象的方法时,代理对象会首先检查这些附加行为,然后决定是否以及如何转发请求至实际对象。
-
防火墙代理:在网络编程中,代理对象可以作为一个中介来过滤、监控或者重新定义由外部系统对内部系统的请求。
Java 中实现代理模式通常有两种方式:
-
静态代理:代理类在编译期间就已经确定,代理类和委托类都实现了相同的接口或继承自同一父类,代理类在编译期就完成了对委托类方法的增强。
-
动态代理:
- JDK 动态代理:基于Java反射机制,在运行时动态生成代理类的字节码文件,并创建代理对象。代理类实现InvocationHandler接口,并通过Proxy.newProxyInstance()方法创建代理对象。
- CGLIB代理:采用ASM字节码库,在运行时对指定类生成子类的方式实现代理,如果目标类没有实现接口,则可以使用CGLIB来生成动态代理。
无论是哪种形式的代理,其核心思想都是为了在不修改原有业务逻辑的情况下,对对象的行为进行扩展和控制。
一、静态代理
public interface ITeacherDao {
void teach();
}
/**
* 目标对象
*/
public class TeacherDao implements ITeacherDao{
@Override
public void teach() {
System.out.println("老师授课中");
}
}
/**
* 代理对象
*/
public class TeacherDaoProxy implements ITeacherDao{
//目标对象,聚合关系
private ITeacherDao target;
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
@Override
public void teach() {
System.out.println("开始代理:");
target.teach();
System.out.println("代理介绍。");
}
}
/**
* 客户端使用
*/
public class Client {
public static void main(String[] args) {
//目标对象(被代理对象)
TeacherDao teacherDao = new TeacherDao();
//创建代理对象
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
//代理对象执行方法
teacherDaoProxy.teach();
}
}
二、动态代理
public interface ITeacherDao {
void teach();
void say();
}
/**
* 目标对象
*/
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师授课中");
}
@Override
public void say() {
System.out.println("say方法");
}
}
/**
* 动态代理工厂
*/
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK 代理开始");
Object ret = method.invoke(target, args);
System.out.println("代理结束~");
return ret;
}
});
}
}
/**
* 客户端使用
*/
public class Client {
public static void main(String[] args) {
ITeacherDao teacherDao = new TeacherDao();
ProxyFactory proxyFactory = new ProxyFactory(teacherDao);
ITeacherDao proxyInstance = (ITeacherDao)proxyFactory.getProxyInstance();
proxyInstance.teach();
proxyInstance.say();
}
}
三、Cglib代理
目标对象不用实现接口
/**
* 目标对象
*/
public class TeacherDao{
public void teach() {
System.out.println("老师授课中");
}
public void say() {
System.out.println("say方法");
}
}
/**
* 代理对象工厂
*/
public class ProxyFactory implements MethodInterceptor {
//目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance(){
//1.创建一个工具类
Enhancer enhancer = new Enhancer();
//2.设置父类
enhancer.setSuperclass(target.getClass());
//3.设置回调函数
enhancer.setCallback(this);
//4.创建子类对象,即代理对象
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("准备执行方法:");
long start = System.currentTimeMillis();
try {
// 执行目标方法
Object result = proxy.invokeSuper(obj, args);
long executionTime = System.currentTimeMillis() - start;
System.out.println("执行结束:" + method.getName() + " executed in " + executionTime + "ms");
return result;
} catch (Exception e) {
throw e;
}
}
}
public class Client {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory(new TeacherDao());
TeacherDao proxyInstance = (TeacherDao)proxyFactory.getProxyInstance();
proxyInstance.teach();
proxyInstance.say();
}
}
/**
输出:
准备执行方法:
老师授课中
执行结束:teach executed in 14ms
准备执行方法:
say方法
执行结束:say executed in 0ms
*/
十四、模板模式
零、介绍
设计模式中的模板模式(Template Pattern)是一种行为设计模式,它定义了一个算法的骨架,并允许子类在不改变结构的情况下重写或填充算法的某些步骤。这种模式主要用于代码复用,将通用的处理流程与具体的实现细节相分离。
模板模式的主要组成部分:
-
抽象模板(Abstract Template Class): 这是一个抽象类,它声明了执行算法所需的方法框架,包括一个或多个基本方法和一个模板方法。模板方法是一个具体的方法,该方法定义了算法的骨架,其中调用了基本方法。基本方法可以是抽象的,由子类负责实现;也可以是具体实现的,供所有子类共享。
-
具体模板(Concrete Template Classes): 这些是抽象模板类的子类,它们实现了抽象的基本方法以完成算法中可变的部分。尽管每个子类可以根据需要提供不同的实现,但算法的整体结构由抽象模板类控制。
工作原理:
- 在抽象模板类中,模板方法通常会按照特定顺序调用一些基本操作方法。
- 基本方法可以是抽象方法(Java中的
abstract
方法),要求子类必须提供实现;也可以是钩子方法(Hook Method),即有默认实现但仍允许子类覆盖的方法。 - 子类通过继承抽象模板类并实现那些抽象方法来个性化算法的各个步骤。
应用场景:
- 当多个类具有相同或相似的算法流程,但部分步骤有所不同时,模板模式能很好地封装不变部分,同时让可变部分易于扩展。
- 编译器编译过程、数据库连接与关闭操作(打开连接 -> 执行SQL -> 处理结果 -> 关闭连接)、游戏框架中的角色更新逻辑等都是模板模式的典型应用场景。
通过模板模式,我们可以确保算法的结构一致性,同时又赋予子类足够的灵活性去定制关键的处理环节。
一、代码示例
/**
* 抽象模板
*/
public abstract class SoyBeanMilk {
//模板方法
final void make(){
select();
if (addOther()){
add();
}
soak();
beat();
}
//选材
void select(){
System.out.println("选择好黄豆");
}
//添加不同材料
abstract void add();
//浸泡
void soak(){
System.out.println("浸泡材料");
}
//打碎材料
void beat(){
System.out.println("打碎材料");
}
//钩子方法,有些功能可能不用实现add方法
boolean addOther(){
return true;
}
}
/**
* 具体模板1
*/
public class RedBeanSoya extends SoyBeanMilk{
@Override
void add() {
System.out.println("加入红豆");
}
}
/**
* 具体模板2
*/
public class PeanutSoya extends SoyBeanMilk{
@Override
void add() {
System.out.println("加入花生");
}
}
/**
* 具体模板3
*/
public class PureSoya extends SoyBeanMilk{
@Override
void add() {
}
//重写钩子方法
@Override
boolean addOther() {
return false;
}
}
public class Client {
public static void main(String[] args) {
System.out.println("制作红豆豆浆:");
RedBeanSoya redBeanSoya = new RedBeanSoya();
redBeanSoya.make();
System.out.println("制作花生豆浆:");
PeanutSoya peanutSoya = new PeanutSoya();
peanutSoya.make();
System.out.println("制作纯豆浆:");
PureSoya pureSoya = new PureSoya();
pureSoya.make();
}
}
/**
输出:
制作红豆豆浆:
选择好黄豆
加入红豆
浸泡材料
打碎材料
制作花生豆浆:
选择好黄豆
加入花生
浸泡材料
打碎材料
制作纯豆浆:
选择好黄豆
浸泡材料
打碎材料
进程已结束,退出代码0
*/
十五、命令模式
零、介绍
设计模式中的命令模式(Command Pattern)是一种行为型设计模式,它主要用于将请求封装为一个对象,使得请求可以被发送、排队、记录日志、撤销等。这种模式的核心在于通过解耦请求的发起者与接收者来降低系统的耦合度,提高模块之间的松耦合性,并且方便系统扩展和维护。
在命令模式中,主要包含以下四个角色:
-
抽象命令类(Command):
- 定义了执行命令的接口,通常会声明一个或多个方法,如
execute()
,用于执行命令操作。 - 该接口允许不同的具体命令实现同一接口,从而确保调用者可以通过统一的方式调用不同类型的命令。
- 定义了执行命令的接口,通常会声明一个或多个方法,如
-
具体命令类(Concrete Command):
- 实现了抽象命令类的接口,包含了执行命令所需的具体操作逻辑。
- 具体命令类通常会持有接收者对象的引用,并在执行命令时调用接收者的特定方法来完成实际工作。
-
接收者(Receiver):
- 接收并执行具体命令的对象,负责执行相应的业务操作。
- 任何对象都可以成为接收者,只要它可以响应命令所对应的操作。
-
调用者/请求者(Invoker):
- 负责调用命令对象执行请求的方法的角色。
- 请求者不直接与接收者交互,而是通过命令对象间接触发请求,这样可以对客户端隐藏接收者的具体实现细节。
使用命令模式的优势包括:
- 解耦:通过引入命令对象,降低了请求者与接收者之间的耦合度,它们之间通过命令对象进行交互。
- 可扩展性:容易添加新的命令类型,因为每种新命令只需要创建一个新的具体命令类即可。
- 灵活性:可以对命令进行组合、排队、记录日志、支持事务处理(比如撤销/重做),甚至可以在运行时动态地决定要执行哪个命令。
- 职责清晰:每个类都有明确的责任,命令对象负责封装操作,接收者负责执行操作,请求者则专注于传递请求而不关心如何处理请求。
一、代码
①抽象命令类(Command)
/**
* 抽象命令类(Command)
*/
public interface Command {
//执行
void execute();
//撤销
void undo();
}
②具体命令类(Concrete Command)
/**
* 具体命令类(Concrete Command)
*/
public class LightOnCommand implements Command{
//聚合LightReceiver
LightReceiver lightReceiver;
public LightOnCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
lightReceiver.on();
}
@Override
public void undo() {
lightReceiver.off();
}
}
/**
* 具体命令类(Concrete Command)
*/
public class LightOffCommand implements Command{
//聚合LightReceiver
LightReceiver lightReceiver;
public LightOffCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
lightReceiver.off();
}
@Override
public void undo() {
lightReceiver.on();
}
}
/**
* 具体命令类(Concrete Command)
* 空执行方法,用于初始化每个按钮
* 当调用空命令时,什么都不做
* 可以省掉空判断
*/
public class NoCommand implements Command{
@Override
public void execute() {}
@Override
public void undo() {}
}
③接收者(Receiver)
/**
* 接收者(Receiver)
*/
public class LightReceiver {
public void on(){
System.out.println("电灯打开");
}
public void off(){
System.out.println("电灯关闭");
}
}
④调用者/请求者(Invoker)
/**
* 调用者/请求者(Invoker)
*/
public class RemoteController {
//开-按钮
Command[] onCommands;
//关-按钮
Command[] offCommands;
//撤销-按钮
Command undoCommand;
//构造器初始化
public RemoteController() {
onCommands = new Command[5];
offCommands = new Command[5];
for (int i = 0; i < 5; i++){
//初始化为空实现方法
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}
//设置对应按钮的命令
public void setCommand(int num, Command onCommand, Command offCommand){
onCommands[num] = onCommand;
offCommands[num] = offCommand;
}
//点击on按钮
public void onButton(int num){
//找到对应的按钮,执行方法
onCommands[num].execute();
//记录这次的操作,用于撤销
undoCommand = onCommands[num];
}
//点击off按钮
public void offButton(int num){
//找到对应的按钮,执行方法
offCommands[num].execute();
//记录这次的操作,用于撤销
undoCommand = offCommands[num];
}
//按下撤销
public void undoButton(){
undoCommand.undo();
}
}
⑤使用
public class Client {
public static void main(String[] args) {
//创建灯
LightReceiver lightReceiver = new LightReceiver();
//创建开关命令
LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
//创建遥控器
RemoteController remoteController = new RemoteController();
//设置按钮对应命令
remoteController.setCommand(0,lightOnCommand, lightOffCommand);
//按下按钮
remoteController.onButton(0);
remoteController.offButton(0);
remoteController.undoButton();
}
}
十六、访问者模式
零、介绍
设计模式中的访问者模式(Visitor Pattern)是一种主要用于处理数据结构中多个具有不同接口的对象结构(如组合结构或元素集合)的行为型设计模式。访问者模式的核心目标是在不改变现有类层次结构的基础上,为这些对象结构添加新的操作或者计算逻辑。
访问者模式包含以下几个关键组成部分:
-
抽象访问者(Visitor):这是一个接口或抽象类,它声明了对每一个元素类的操作访问方法。每种元素类型的
visit()
方法对应一种操作。 -
具体访问者(ConcreteVisitor):实现了抽象访问者的各个
visit()
方法,为每个元素类提供了具体的访问操作实现。 -
元素接口(Element):定义了一个接受访问者(
accept()
)的方法,这是所有元素类都要实现的接口。 -
具体元素(ConcreteElement):实现了元素接口,它们可以接受访问者对象的访问,并调用自己的
accept()
方法,执行访问者对象在其上的操作。 -
对象结构(ObjectStructure):能够存储元素对象的集合,可以遍历并调用它们的
accept()
方法,让访问者访问每一个元素。
访问者模式的工作流程大致如下:
- 定义一个表示操作的访问者接口,该接口声明了一组针对元素对象的操作方法。
- 创建具体的访问者类,实现这些操作方法,用于对元素对象进行特定的处理。
- 元素接口声明一个接受访问者的方法。
- 具体元素类实现接受访问者的方法,当被访问时会调用访问者的相应操作方法。
- 对象结构维护元素集合,并提供一个机制供访问者遍历并访问这些元素。
访问者模式的优势在于:
- 将数据结构与算法分离,增强了系统的开放封闭原则(Open/Closed Principle),即对扩展开放,对修改封闭。
- 方便在不修改原有类的基础上添加新的操作。
- 可以集中处理相似的元素,有利于实现一对多的关系。
然而,访问者模式也有其局限性,例如增加了系统的复杂性,且不是所有系统都适合引入访问者模式,尤其是当元素结构稳定,且不需要经常添加新的操作时,使用简单的回调或其他设计模式可能更为简洁有效。
一、代码
完成一个男女生投票的需求
①抽象访问者(Visitor)
/**
* 抽象访问者
*/
public abstract class Action {
//得到男生的结果
public abstract void getManResult(Man man);
//得到女生的结果
public abstract void getWomanResult(Woman woman);
}
②具体访问者(ConcreteVisitor)
//具体访问者1
public class Success extends Action{
@Override
public void getManResult(Man man) {
System.out.println("男生的评价是:success");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println("女生的评价是:success");
}
}
//具体访问者2
public class Fail extends Action{
@Override
public void getManResult(Man man) {
System.out.println("男生的评价是:Fail");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println("女生的评价是:Fail");
}
}
//具体访问者3
public class Wait extends Action{
@Override
public void getManResult(Man man) {
System.out.println("男生的评价是:wait");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println("女生的评价是:wait");
}
}
③元素接口(Element)
public abstract class Person {
public abstract void accept(Action action);
}
④具体元素(ConcreteElement)
public class Man extends Person{
@Override
public void accept(Action action) {
action.getManResult(this);
}
}
public class Woman extends Person{
@Override
public void accept(Action action) {
action.getWomanResult(this);
}
}
⑤对象结构(ObjectStructure)
public class ObjectStructure {
private List<Person> personList = new LinkedList<>();
public void attach(Person p){
personList.add(p);
}
public void detach(Person p){
personList.remove(p);
}
//显示测评情况
public void display(Action action){
for (Person person : personList) {
person.accept(action);
}
}
}
⑥使用
public class Client {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new Man());
objectStructure.attach(new Woman());
objectStructure.display(new Success());
objectStructure.display(new Fail());
objectStructure.display(new Wait());
}
}
/**
输出:
男生的评价是:success
女生的评价是:success
男生的评价是:Fail
女生的评价是:Fail
男生的评价是:wait
女生的评价是:wait
*/
十七、迭代器模式
零、介绍
迭代器模式是一种在软件开发中广泛使用的行为型设计模式,它的核心目标是为不同类型的聚合数据结构(如列表、集合、数组、树等)提供一种统一且抽象的访问接口,允许外部代码以一致的方式遍历这些结构中的元素,而无需关心其内部的具体实现细节或复杂度。
以下是迭代器模式的主要特点、组成、工作原理、适用场景、优缺点以及代码示例:
特点与组成
-
迭代器接口(Iterator):
- 定义了访问和遍历聚合数据的基本操作,如
next()
(获取下一个元素)、hasNext()
(判断是否还有下一个元素)和remove()
(可选,移除当前元素)等方法。
- 定义了访问和遍历聚合数据的基本操作,如
-
具体迭代器(Concrete Iterator):
- 实现迭代器接口,负责跟踪当前遍历位置、管理迭代状态,并通过与具体聚合数据结构的交互来实现上述接口方法。
-
聚合接口(Aggregate):
- 定义创建相应迭代器对象的工厂方法,如
createIterator()
,它返回一个与该聚合类型相匹配的迭代器实例。
- 定义创建相应迭代器对象的工厂方法,如
-
具体聚合(Concrete Aggregate):
- 实现聚合接口,包含元素集合及其实现,并提供必要的支持以供其对应的迭代器正确遍历这些元素。
工作原理
- 客户端代码通过调用聚合对象的
createIterator()
方法获取一个迭代器实例。 - 使用迭代器提供的
next()
方法按顺序访问每个元素,同时借助hasNext()
检查是否有更多元素可供访问。 - 如果需要,可以使用迭代器的
remove()
方法在遍历过程中安全地移除当前元素。
适用场景
- 当需要对不同类型或不同实现的聚合数据结构进行统一遍历时。
- 当希望隐藏聚合数据结构的具体实现,尤其是当这些结构复杂且可能发生变化时。
- 当需要支持多种遍历方式(如正序、逆序、深度优先、广度优先等)时,可以为每种遍历方式提供相应的迭代器。
优点
- 封装性:迭代器封装了遍历集合元素的细节,使客户端代码与具体的集合实现解耦。
- 一致性:为不同类型的集合提供了统一的访问接口,简化了客户端代码的编写和维护。
- 灵活性:新增或修改聚合数据结构的内部表示不影响已有的遍历逻辑,增强了系统的扩展性。
- 迭代多态:通过单一的迭代器接口,可以支持对同一集合的不同遍历方式。
缺点
- 额外的类和接口:引入迭代器模式会增加系统中类的数量,增加一定的复杂性。
- 无法直接访问特定索引:对于需要随机访问或直接按索引访问元素的场景,迭代器模式可能不如直接使用索引来得高效。
- 迭代器实现复杂度:对于某些复杂的聚合结构(如树、图等),迭代器的实现可能较为复杂。
一、代码
①迭代器接口(Iterator)
使用jdk中的Iterator接口
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
②具体迭代器(Concrete Iterator)
//具体迭代器1
public class ComputerCollegeIterator implements Iterator {
Department[] departments;
int position = 0;
public ComputerCollegeIterator(Department[] departments) {
this.departments = departments;
}
@Override
public boolean hasNext() {
if (position >= departments.length || departments[position] == null){
return false;
}else {
return true;
}
}
@Override
public Object next() {
return departments[position++];
}
@Override
public void remove() {
Iterator.super.remove();
}
}
//具体迭代器2
public class InfoCollegeIterator implements Iterator {
List<Department> departments;
int position = -1;
public InfoCollegeIterator(List<Department> departments) {
this.departments = departments;
}
@Override
public boolean hasNext() {
if (position >= departments.size() - 1){
return false;
}else {
position++;
return true;
}
}
@Override
public Object next() {
return departments.get(position);
}
}
③聚合接口(Aggregate)
public interface College {
String getName();
void addDepartment(String name, String desc);
Iterator createIterator();
}
④具体聚合(Concrete Aggregate)
/**
* 数组格式
*/
public class ComputerCollege implements College{
Department[] departments;
public ComputerCollege() {
departments = new Department[5];
departments[0] = new Department("java", "java11");
departments[1] = new Department("php", "php22");
departments[2] = new Department("cpp", "cpp33");
}
@Override
public String getName() {
return "计算机学院";
}
@Override
public void addDepartment(String name, String desc) {
if (departments.length >= 5){
System.out.println("数组已满");
}else {
departments[departments.length] = new Department(name, desc);
}
}
@Override
public Iterator createIterator() {
return new ComputerCollegeIterator(departments);
}
}
/**
* 集合格式
*/
public class InfoCollege implements College{
List<Department> departments;
public InfoCollege() {
departments = new LinkedList<>();
departments.add(new Department("信息工程","信工"));
departments.add(new Department("通信工程","通信"));
}
@Override
public String getName() {
return "信息工程学院";
}
@Override
public void addDepartment(String name, String desc) {
departments.add(new Department(name, desc));
}
@Override
public Iterator createIterator() {
return new InfoCollegeIterator(departments);
}
}
⑤使用
//遍历对象
public class Department {
private String name;
private String desc;
public Department(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
//输出工具类
public class OutputImpl {
//学院集合
List<College> colleges;
public OutputImpl(List<College> colleges) {
this.colleges = colleges;
}
//打印学院信息
public void printCollege(){
Iterator<College> iterator = colleges.iterator();
while (iterator.hasNext()){
College college = iterator.next();
System.out.println("=======" + college.getName() + "======");
printDepartment(college.createIterator());
}
}
//打印专业信息
public void printDepartment(Iterator iterator){
while (iterator.hasNext()){
Department department = (Department) iterator.next();
System.out.println(department.getName() + "---" + department.getDesc());
}
}
}
//客户端
public class Client {
public static void main(String[] args) {
ArrayList<College> colleges = new ArrayList<>();
ComputerCollege computerCollege = new ComputerCollege();
InfoCollege infoCollege = new InfoCollege();
colleges.add(computerCollege);
colleges.add(infoCollege);
OutputImpl output = new OutputImpl(colleges);
output.printCollege();
}
}
/**
输出:
=======计算机学院======
java---java11
php---php22
cpp---cpp33
=======信息工程学院======
信息工程---信工
通信工程---通信
*/
十八、观察者模式
零、介绍
设计模式中的观察者模式(Observer Pattern)是一种行为型设计模式,它旨在定义一种一对多的依赖关系,使得当一个对象(称为主体或被观察者)的状态发生改变时,所有依赖于它的对象(称为观察者)都能得到通知并自动更新自己的状态。这种模式通过解耦观察者与被观察者,允许两者独立地改变和扩展,提高了系统的灵活性和可维护性。
以下是观察者模式的核心组成部分、工作原理、实现方式以及其应用场景和优缺点的详细介绍:
组成部分
-
抽象被观察者(Subject):
- 提供一个接口用于添加、删除观察者对象。
- 定义一个方法(如
notifyObservers()
)用于通知所有已注册的观察者,通常传递状态变化的相关信息。
-
具体被观察者(ConcreteSubject):
- 继承或实现抽象被观察者接口。
- 负责存储观察者对象的列表。
- 在内部状态改变时触发通知方法,调用所有观察者的更新方法。
-
抽象观察者(Observer):
- 定义一个更新接口,通常是一个方法(如
update()
),该方法接收被观察者传递的状态信息作为参数。
- 定义一个更新接口,通常是一个方法(如
-
具体观察者(ConcreteObserver):
- 实现抽象观察者的更新接口,定义如何响应被观察者状态的变化。
- 持有对被观察者的引用,以便在接收到通知时访问其状态。
工作原理
-
注册观察者:具体被观察者通过其提供的接口允许观察者对象注册自己,将其添加到观察者列表中。
-
状态改变:当具体被观察者的内部状态发生改变时,它调用
notifyObservers()
方法。 -
通知观察者:
notifyObservers()
遍历观察者列表,调用每个观察者的update()
方法,传递状态变化的相关数据。 -
更新观察者:每个具体观察者在其
update()
方法中根据接收到的信息更新自身的状态,实现对被观察者状态变化的响应。
实现方式
在很多编程语言中,如Java,已经提供了内置的观察者模式支持。例如,Java的java.util.Observable
类作为被观察者基类,java.util.Observer
接口作为观察者的规范。开发者可以直接继承和实现这些类与接口来快速实现观察者模式。
应用场景
- 用户界面(UI)更新:如数据模型变化时自动更新视图,如MVC(Model-View-Controller)架构中的模型与视图之间的交互。
- 事件驱动系统:如发布/订阅系统,组件间通过事件通知机制进行通信。
- 实时数据同步:如股票价格变动时,多个客户端同时接收到最新报价。
- 日志记录、报警系统:当特定条件满足时,触发日志记录或发送警报给多个订阅者。
- 跨模块通信:在模块化开发中,一个模块的状态变化可以透明地通知其他依赖模块。
优缺点
优点:
- 松耦合:观察者和被观察者之间通过接口而非具体类相联系,降低了它们之间的耦合度。
- 灵活性:增加、删除观察者或者改变观察者的行为对被观察者无影响,反之亦然,增强了系统的可扩展性和可维护性。
- 动态联动:易于实现实时响应和联动更新,一个对象状态变化能自动传播到所有依赖对象。
缺点:
- 开销:如果观察者数量庞大或者通知频繁,可能会导致性能下降,尤其是在需要大量同步更新的场景。
- 循环依赖:不恰当的设计可能导致观察者和被观察者之间形成循环依赖,增加系统的复杂性。
- 过期订阅:如果没有及时移除不再需要的观察者,可能导致不必要的通知和资源浪费。
总的来说,观察者模式通过分离关注点,使得一个对象的变化可以自动地传播给所有依赖于它的对象,适用于需要多个对象对单一对象状态变化作出响应的场景,特别是在需要弱化对象间耦合关系和增强系统可扩展性的设计中。然而,实际应用时需要注意权衡其带来的复杂性和潜在性能开销。
一、代码
①抽象被观察者(Subject)
public interface Observer {
void update(float temperature, float pressure, float humidity);
}
②具体被观察者(ConcreteSubject)
public class WeatherData implements Subject{
private float temperature;
private float pressure;
private float humidity;
private List<Observer> observers;
public void setData(float temperature, float pressure, float humidity){
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
notifyObserver();
}
public WeatherData() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObserver() {
for (int i = 0; i < observers.size(); i++) {
observers.get(i).update(this.temperature,this.pressure,this.humidity);
}
}
}
③抽象观察者(Observer)
public interface Observer {
void update(float temperature, float pressure, float humidity);
}
④具体观察者(ConcreteObserver)
public class CurrentConditions implements Observer{
private float temperature;
private float pressure;
private float humidity;
public void update(float temperature, float pressure, float humidity){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
public void display(){
System.out.println("temperature =====> " + temperature);
System.out.println("pressure =====> " + pressure);
System.out.println("humidity =====> " + humidity);
}
}
⑤使用
public class Client {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditions currentConditions = new CurrentConditions();
weatherData.registerObserver(currentConditions);
weatherData.setData(20,100,30);
}
}
/**
输出:
temperature =====> 20.0
pressure =====> 100.0
humidity =====> 30.0
*/
十九、中介者模式
零、介绍
设计模式中的中介者模式(Mediator Pattern)是一种行为型设计模式,其主要目的是简化多个对象或类之间的复杂交互关系,通过引入一个专门的中介对象来协调这些对象间的通信。中介者模式的核心思想是“封装交互”,即将原来直接在多个对象间进行的多重关联和依赖关系,转移到一个统一的中介者对象中进行管理,从而降低系统的耦合度,提高模块的可复用性和可扩展性。
以下是中介者模式的详细说明:
模式定义
中介者模式定义了一个中介对象(Mediator),它封装了对象间的交互逻辑,使得各个参与交互的个体对象(Colleague)无需显式地相互引用,而是通过与中介者进行交互来间接地与其它对象通信。中介者负责控制和协调各同事对象之间的交互流程,为它们提供一个集中的交互点。
角色组成
1. Mediator(中介者)
- 定义一个接口,用于与各同事对象交互,定义了同事对象之间进行通信的公共方法。
- 实现类封装了同事对象间的复杂交互逻辑,处理来自同事对象的消息,并决定如何转发消息或者执行相应的操作。
2. Colleague(同事类)
- 各个参与交互的对象,它们只知道与中介者打交道,不知道其他同事的存在。
- 每个同事类都持有中介者的引用,通过调用中介者的方法来发送请求或接收响应,而不是直接与其他同事通信。
工作原理
-
交互封装:当同事对象需要与其他同事对象交互时,不直接互相调用方法,而是通过调用中介者的相应方法来实现。这样,同事对象之间的交互细节被中介者隐藏起来,降低了它们之间的耦合度。
-
消息传递:中介者接收到同事对象的请求后,根据请求的性质和系统状态,决定如何转发消息或执行何种操作。它可以同步或异步地通知一个或多个同事对象,也可以根据业务规则进行更复杂的逻辑处理。
-
解耦与复用:由于同事对象仅依赖中介者接口,不直接依赖其他同事,因此修改一个同事对象的行为或添加新的同事对象不会影响到其他对象,增强了系统的灵活性和可扩展性。同时,中介者的逻辑可以被多个同事对象共享,提高了代码的复用性。
-
变更集中化:当系统交互逻辑发生变化时,只需要修改中介者类的实现,而无需改动各个同事类,使得系统行为的变更集中在一个地方,便于管理和维护。
应用场景
中介者模式适用于以下场景:
-
对象间的交互复杂且频繁:当系统中对象间的交互关系呈现出网状结构,有大量多对多的依赖时,中介者模式可以有效地梳理这些关系,将复杂的交互逻辑集中管理。
-
需要降低对象间的耦合度:如果直接的相互引用导致了高耦合度,影响了代码的可读性、可维护性和可扩展性,中介者可以作为通信中枢,解除对象间的直接依赖。
-
希望独立地改变对象间的交互方式:当需要独立地调整或扩展某些对象间的交互行为时,中介者作为一个单独的组件,可以灵活地修改而不影响其他对象。
-
常见示例:
- 用户界面框架(如MVC、MVP、MVVM)中的控制器(Controller)或 presenter,作为视图(View)和模型(Model)之间的中介。
- 多线程编程中的线程池管理器,协调各个线程的任务分配与同步。
- 网络聊天室系统中的聊天服务器,负责转发用户消息。
- 事件驱动系统中的事件总线或调度中心,处理事件的发布与订阅。
优点:
- 减少类间耦合:通过中介者避免了同事类之间的直接依赖,使得对象之间的关系更加清晰。
- 简化系统结构:将原本复杂的网状交互转化为星形结构,简化了系统设计。
- 易于变更与扩展:新增、修改或删除交互行为只需在中介者中进行,不影响同事类。
- 支持复用:中介者的交互逻辑可以被多个同事类共享。
缺点:
- 中介者可能变得庞大而复杂:随着系统规模增大,中介者承担的职责增多,可能导致中介者类过于复杂,成为系统的瓶颈或单点故障。
- 增加系统的抽象层次:引入中介者增加了系统的抽象层次,对于简单系统可能显得过度设计。
总的来说,中介者模式通过引入中介对象来封装和协调对象间的交互,实现了对象间的解耦合,提升了系统的可维护性和可扩展性,尤其适用于处理复杂对象交互的场景。在实际应用中,应根据系统的具体需求权衡是否采用中介者模式以及如何设计中介者以保持其简洁高效。
一、代码
以下是一个基于Java实现的中介者模式示例代码,以一个简单的办公室电器控制系统的场景为例,展示了如何使用中介者来协调电灯、空调和电脑等电器设备的开关操作:
// 抽象中介者接口
interface OfficeElectronicsMediator {
void sendMessage(String message, ElectronicDevice sender);
}
// 具体中介者:办公室电器控制系统
class OfficeControlSystem implements OfficeElectronicsMediator {
private Light light;
private AirConditioner airConditioner;
private Computer computer;
public void setLight(Light light) {
this.light = light;
}
public void setAirConditioner(AirConditioner airConditioner) {
this.airConditioner = airConditioner;
}
public void setComputer(Computer computer) {
this.computer = computer;
}
@Override
public void sendMessage(String message, ElectronicDevice sender) {
if (message.equals("turnOn")) {
if (sender == light) {
System.out.println("Light turned on.");
airConditioner.turnOn();
} else if (sender == airConditioner) {
System.out.println("Air conditioner turned on.");
light.turnOn();
} else if (sender == computer) {
System.out.println("Computer turned on.");
light.turnOn();
airConditioner.turnOn();
}
} else if (message.equals("turnOff")) {
if (sender == light) {
System.out.println("Light turned off.");
airConditioner.turnOff();
} else if (sender == airConditioner) {
System.out.println("Air conditioner turned off.");
light.turnOff();
} else if (sender == computer) {
System.out.println("Computer turned off.");
light.turnOff();
airConditioner.turnOff();
}
}
}
}
// 抽象同事类:电器设备
abstract class ElectronicDevice {
protected OfficeElectronicsMediator mediator;
public ElectronicDevice(OfficeElectronicsMediator mediator) {
this.mediator = mediator;
}
public abstract void turnOn();
public abstract void turnOff();
}
// 具体同事类:电灯
class Light extends ElectronicDevice {
public Light(OfficeElectronicsMediator mediator) {
super(mediator);
}
@Override
public void turnOn() {
mediator.sendMessage("turnOn", this);
}
@Override
public void turnOff() {
mediator.sendMessage("turnOff", this);
}
}
// 具体同事类:空调
class AirConditioner extends ElectronicDevice {
public AirConditioner(OfficeElectronicsMediator mediator) {
super(mediator);
}
@Override
public void turnOn() {
mediator.sendMessage("turnOn", this);
}
@Override
public void turnOff() {
mediator.sendMessage("turnOff", this);
}
}
// 具体同事类:电脑
class Computer extends ElectronicDevice {
public Computer(OfficeElectronicsMediator mediator) {
super(mediator);
}
@Override
public void turnOn() {
mediator.sendMessage("turnOn", this);
}
@Override
public void turnOff() {
mediator.sendMessage("turnOff", this);
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
OfficeControlSystem officeControlSystem = new OfficeControlSystem();
Light light = new Light(officeControlSystem);
AirConditioner airConditioner = new AirConditioner(officeControlSystem);
Computer computer = new Computer(officeControlSystem);
officeControlSystem.setLight(light);
officeControlSystem.setAirConditioner(airConditioner);
officeControlSystem.setComputer(computer);
// 开启电脑,会联动开启电灯和空调
computer.turnOn();
// 关闭电灯,会联动关闭空调
light.turnOff();
}
}
在这个示例中:
OfficeElectronicsMediator
是抽象中介者接口,定义了发送消息的方法。OfficeControlSystem
是具体中介者类,实现了中介者接口,负责协调电灯、空调和电脑之间的交互。ElectronicDevice
是抽象同事类,定义了电器设备的通用行为(如开关)和持有中介者引用。Light
,AirConditioner
,Computer
是具体同事类,代表不同的电器设备,它们通过调用中介者的方法来触发系统内的交互行为。
客户端代码创建了具体的电器设备对象,并通过中介者对象来协调它们的开关操作。当一个设备被开启或关闭时,中介者会根据预设的逻辑决定是否联动开启或关闭其他设备,从而实现了电器设备间的解耦合交互。
二十、备忘录模式
零、介绍
备忘录模式(Memento Pattern)是一种行为型设计模式,旨在提供一种机制来捕获并保存对象的当前状态,以便在未来某个时刻能够恢复到该状态。这种模式允许在不违反封装原则的前提下,安全地备份和恢复对象的内部状态,常用于实现撤销/重做操作、游戏存档、事务回滚等功能。
模式定义
备忘录模式定义了以下角色:
-
Originator(发起人):
- 负责创建备忘录对象(Memento),存储自己的内部状态。
- 提供方法用于获取当前状态的备忘录,以及使用备忘录恢复内部状态。
-
Memento(备忘录):
- 存储发起人对象的内部状态。
- 通常只提供一个接口供发起人访问其内部状态,对外部其他对象(尤其是客户端)隐藏其具体实现和内部数据。
-
Caretaker(管理者):
- 负责保存和管理备忘录对象,但不能对备忘录的内容进行任何操作或访问其内部状态。
- 在需要时选择合适的备忘录返回给发起人,由发起人负责恢复状态。
工作原理
-
状态捕获:发起人对象在需要保存状态时,创建一个备忘录对象,将自身的当前状态(通常是关键属性或数据)复制到备忘录中。
-
状态存储:管理者对象(如一个栈或队列)接收发起人创建的备忘录,将其保存起来。可以保存多个备忘录以支持多次撤销/重做操作。
-
状态恢复:当需要恢复到先前状态时,发起人从管理者那里获取对应的备忘录对象,通过备忘录提供的接口读取并恢复其内部状态。这个过程不需要直接访问备忘录的内部细节,保证了发起人的封装性。
应用场景
备忘录模式适用于以下场景:
- 撤销/重做功能:如文本编辑器、图形编辑器、数据库事务等,用户可以撤销最近的操作并重新执行。
- 游戏存档:玩家可以保存游戏进度并在之后恢复,无需了解游戏内部复杂的状态信息。
- 事务管理:在分布式事务或数据库事务中,回滚到一个已知的稳定状态。
- 配置历史:系统允许用户查看和恢复到之前的配置版本。
优点:
- 封装性:备忘录模式保护了发起人的内部状态,使其免受外部直接访问,维护了对象的封装边界。
- 状态恢复:提供了简单且安全的方式恢复对象到过去的状态,支持非线性操作历史。
- 事务一致性:在需要确保事务原子性和回滚能力的场景中,备忘录模式有助于实现这一目标。
缺点:
- 资源消耗:保存大量备忘录可能会占用较多内存或其他存储资源,特别是在状态数据量大或状态变化频繁的情况下。
- 设计复杂度:如果对象状态复杂,备忘录类的设计和实现可能会较为复杂,需要正确识别和捕获关键状态。
- 过多的备忘录管理:如果撤销/重做层级过深,或者存在大量可撤销操作,对备忘录的管理(如存储、查找、清理等)可能会变得复杂。