1. 单例模式(Singleton Pattern)
1.1 定义
Ensure a class has only one instance,and provide a global point of access to it. (确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)
1.2 自己的理解
就像 Windows 系统中的任务管理器,无论在系统的任何地方调用它,都是同一个实例。在一个应用程序中,某些类只需要一个实例,例如数据库连接池、配置信息类等,单例模式可以确保这些类只有一个对象被创建,避免资源的浪费和不一致性。
1.3 类型
创建型模式。
1.4 通用代码
通用代码:(是线程安全的)
public class Singleton{
private static final Singleton singleton = new Singleton();
//限制产生多个对象
private Singleton(){
}
//通过该方法获得实例对象
public static Singleton getSingleton(){
return singleton;
}
//类中其他方法,尽量是static
public static void doSomething(){
}
}
线程不安全实例:
public class Singleton{
private static Singleton singleton = null;
//限制产生多个对象
private Singleton(){
}
//通过该方法获得实例对象
public static Singleton getSingleton(){
if(singleton == null){
singleton = new Singleton();
}
return singleton:
}
}
1.5 注意事项
- 多线程环境下确保线程安全,如上述代码中的实现方式。
- 避免在单例类中使用过多的非静态方法,以免影响单例的唯一性和线程安全性。
1.6 优缺点
- 优点:
- 内存中只有一个实例,节省资源。
- 全局访问点,方便在不同地方获取实例。
- 缺点:
- 违反单一职责原则,既负责创建对象又负责业务逻辑。
- 对测试不友好,因为单例类的状态在全局是共享的,可能影响测试结果的独立性。
1.7 使用场景:
- 要求生成唯一序列号的环境:
- 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的:
- 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源:
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。
- 数据库连接池管理类,确保整个应用程序使用同一个连接池实例。
public class DatabaseConnectionPool {
private static DatabaseConnectionPool instance;
private List<Connection> connections;
private DatabaseConnectionPool() {
// 初始化连接池
connections = new ArrayList<>();
// 模拟创建连接并添加到连接池
for (int i = 0; i < 10; i++) {
try {
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "username", "password");
connections.add(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static synchronized DatabaseConnectionPool getInstance() {
if (instance == null) {
instance = new DatabaseConnectionPool();
}
return instance;
}
public Connection getConnection() {
if (connections.isEmpty()) {
System.out.println("连接池为空,无法获取连接!");
return null;
}
return connections.remove(0);
}
public void releaseConnection(Connection connection) {
connections.add(connection);
}
}
- 配置信息类,在整个应用程序中共享配置数据。
public class AppConfig {
private static AppConfig instance;
private String serverUrl;
private int maxConnections;
private AppConfig() {
// 从配置文件或其他地方加载配置信息
serverUrl = "http://localhost:8080";
maxConnections = 100;
}
public static synchronized AppConfig getInstance() {
if (instance == null) {
instance = new AppConfig();
}
return instance;
}
public String getServerUrl() {
return serverUrl;
}
public int getMaxConnections() {
return maxConnections;
}
}
2. 工厂模式
2.1 定义
Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.(定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。)
2.2 自己的理解
工厂模式就像是一个生产产品的工厂,不同的产品由不同的车间(子类)生产。例如,汽车工厂可以生产不同类型的汽车,如轿车、SUV 等,具体生产哪种汽车由具体的车间来决定。在软件中,当创建对象的过程比较复杂,或者需要根据不同条件创建不同类型对象时,可以使用工厂模式将对象的创建逻辑封装起来,使代码结构更清晰,易于维护和扩展。
2.3 类型
创建型模式。
2.4 通用代码
- Product 为抽象产品类负责定义产品的共性,实现对事物最抽象的定义;
public abstract class Product {
public abstract void use();
}
- Creator 为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。
public abstract class Creator {
public abstract <T extends Product> T createProduct(Class<T> c);
}
- 具体工厂类代码:
public class ConcreteCreator extends Creator {
public <T extends Product> T createProduct(Class<T> c){
Product product = null;
try{
product = (Product)Class.forName(c.getName()).newInstance();
}catch (Exception e){
//异常处理
return (T)product;
}
}
}
2.5 注意事项
- 工厂类的职责要单一,只负责对象的创建,不涉及过多业务逻辑。
- 当产品种类较多时,可能需要创建多个工厂类,避免工厂类过于复杂。
2.6 优缺点
- 优点:
- 解耦对象的创建和使用,使用者不需要了解对象的创建过程。
- 增加新的产品类时,只需要添加具体的产品类和对应的工厂子类,符合开闭原则。
- 缺点:
- 工厂类增多,代码复杂度增加。
- 不符合迪米特法则,因为工厂类需要知道产品类的具体实现。
2.7 使用场景
- Jdbc 连接数据库时,根据不同的数据库类型(如 MySQL、Oracle 等)创建不同的数据库连接对象。
public class DatabaseConnectionFactory {
public static Connection createConnection(String databaseType, String url, String username, String password) throws SQLException {
if ("MySQL".equals(databaseType)) {
return DriverManager.getConnection(url, username, password);
} else if ("Oracle".equals(databaseType)) {
// 创建Oracle连接的逻辑
return null;
} else {
throw new IllegalArgumentException("不支持的数据库类型:" + databaseType);
}
}
}
- 硬件访问,根据不同的硬件设备类型创建相应的驱动对象。
public class HardwareDriverFactory {
public static HardwareDriver createDriver(String deviceType) {
if ("Printer".equals(deviceType)) {
return new PrinterDriver();
} else if ("Scanner".equals(deviceType)) {
return new ScannerDriver();
} else {
throw new IllegalArgumentException("不支持的硬件设备类型:" + deviceType);
}
}
}
tips:
- **简单工厂模式:**一个模块仅需要一个工厂类,没有必要把它产生出来,使用静态的方法
- **多个工厂类:**每个人种(具体的产品类)都对应了一个创建者,每个创建者独立负责创建对应的产品对象,非常符合单一职责原则
- **代替单例模式:**单例模式的核心要求就是在内存中只有一个对象,通过工厂方法模式也可以只在内存中生产一个对象
- **延迟初始化:**ProductFactory负责产品类对象的创建工作,并且通过prMap变量产生一个缓存,对需要再次被重用的对象保留
3. 抽象工厂模式(Abstract Factory Pattern)
3.1 定义
****Provide an interface for creating families of related or dependent objects without specifying their concrete classes. (为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。)
抽象工厂模式通用类图:
抽象工厂模式通用源码类图:
3.2 自己的理解
抽象工厂模式就像是一个超级工厂,它可以生产一系列相关的产品家族。例如,一个家具工厂可以生产不同风格(如现代、欧式、中式)的家具,每种风格的家具都包括桌椅、沙发等产品。在软件中,当需要创建多个相关对象,且这些对象的创建过程相互关联时,可以使用抽象工厂模式,将对象的创建逻辑封装在一个工厂接口中,由具体的工厂子类来创建所需的对象家族,这样可以保证创建出的对象之间的兼容性和一致性。
3.3 类型
创建型模式。
3.4 通用代码
- 抽象工厂类代码:
public abstract class AbstractCreator{
//创建A产品家族
public abstract AbstractProductA createProductA();
//创建B产品家族
public abstract AbstractProductB createProductB();
}
- 抽象产品类 A
public abstract class AbstractProductA {
public abstract void methodA();
}
- 抽象产品类 B。
public abstract class AbstractProductB {
public abstract void methodB();
}
- 具体工厂类。
public class ConcreteCreator1 extends AbstractCreator {
@Override
public AbstractProductA createProductA() {
return new ConcreteProductA1();
}
@Override
public AbstractProductB createProductB() {
return new ConcreteProductB1();
}
}
- 具体产品类 A1。
public class ConcreteProductA1 extends AbstractProductA {
@Override
public void methodA() {
System.out.println("ConcreteProductA1的methodA方法");
}
}
- 具体产品类 B1。
public class ConcreteProductB1 extends AbstractProductB {
@Override
public void methodB() {
System.out.println("ConcreteProductB1的methodB方法");
}
}
3.5 注意事项
- 产品家族的扩展可能需要修改抽象工厂接口和所有具体工厂类,违反开闭原则。
- 不适合创建对象差异较大的产品家族,否则工厂类会变得复杂。
3.6 优缺点
- 优点:
- 保证产品家族的一致性和兼容性,便于切换产品家族。
- 符合迪米特法则,使用者只与抽象工厂类交互,不需要了解对象的创建细节。
- 缺点:
- 不便于扩展产品家族,新增产品家族可能需要修改大量代码。
- 抽象工厂类的职责过重,一旦修改,影响面较大。
3.7 使用场景
- 跨平台应用程序中,根据不同操作系统(如 Windows、Linux、Mac)创建不同风格的界面组件。
public abstract class AbstractWidgetFactory {
public abstract Button createButton();
public abstract TextField createTextField();
}
public class WindowsWidgetFactory extends AbstractWidgetFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public TextField createTextField() {
return new WindowsTextField();
}
}
public class LinuxWidgetFactory extends AbstractWidgetFactory {
@Override
public Button createButton() {
return new LinuxButton();
}
@Override
public TextField createTextField() {
return new LinuxTextField();
}
}
public class MacWidgetFactory extends AbstractWidgetFactory {
@Override
public Button createButton() {
return new MacButton();
}
@Override
public TextField createTextField() {
return new MacTextField();
}
}
- 游戏开发中,根据不同游戏场景(如战斗场景、主菜单场景)创建相应的游戏对象(如敌人、道具、UI 元素等)。
public abstract class AbstractGameObjectFactory {
public abstract Enemy createEnemy();
public abstract Item createItem();
public abstract UIElement createUIElement();
}
public class BattleSceneGameObjectFactory extends AbstractGameObjectFactory {
@Override
public Enemy createEnemy() {
return new BattleEnemy();
}
@Override
public Item createItem() {
return new BattleItem();
}
@Override
public UIElement createUIElement() {
return new BattleUIElement();
}
}
public class MainMenuSceneGameObjectFactory extends AbstractGameObjectFactory {
@Override
public Enemy createEnemy() {
return null;
}
@Override
public Item createItem() {
return null;
}
@Override
public UIElement createUIElement() {
return new MainMenuUIElement();
}
}
- 一个对象族(或是一组没有任何关系的对象)都有相同的约束。
- 涉及不同操作系统的时候,都可以考虑使用抽象工厂模式
4. 模板方法模式(Template Method Pattern)
4.1 定义
Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.(定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。)
4.2 自己的理解
模板方法模式就像是一个制作蛋糕的模板,它定义了制作蛋糕的基本步骤(如准备原料、搅拌、烘焙、装饰),但某些步骤(如装饰)的具体实现可以由不同的蛋糕师傅(子类)根据自己的创意来完成。在软件中,当多个子类有共同的算法框架,但某些步骤的实现细节不同时,可以使用模板方法模式,将通用的算法框架提取到父类中,将可变的步骤定义为抽象方法,由子类实现,这样可以提高代码的复用性,避免重复代码,同时保证算法的稳定性。
4.3 类型
行为型模式。
4.4 通用代码
AbstractClass叫做抽象模板,它的方法分为两类:
- 基本方法
基本方法也叫做基本操作,是由子类实现的方法,并且在模板方法被调用
- 模板方法
可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。
**注意:**为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。
- AbstractClass 为抽象模板类。
public abstract class AbstractClass {
// 模板方法,定义算法框架
public final void templateMethod() {
step1();
step2();
step3();
}
// 基本方法,由子类实现
protected abstract void step1();
protected abstract void step2();
protected abstract void step3();
}
**具体模板:**ConcreteClass1和ConcreteClass2属于具体模板,实现父类所定义的一个或多个抽象方法,也就是父类定义的基本方法在子类中得以实现。
- ConcreteClass1 和 ConcreteClass2 为具体模板类。
public class ConcreteClass1 extends AbstractClass {
@Override
protected void step1() {
System.out.println("ConcreteClass1的step1实现");
}
@Override
protected void step2() {
System.out.println("ConcreteClass1的step2实现");
}
@Override
protected void step3() {
System.out.println("ConcreteClass1的step3实现");
}
}
public class ConcreteClass2 extends AbstractClass {
@Override
protected void step1() {
System.out.println("ConcreteClass2的step1实现");
}
@Override
protected void step2() {
System.out.println("ConcreteClass2的step2实现");
}
@Override
protected void step3() {
System.out.println("ConcreteClass2的step3实现");
}
}
4.5 注意事项
- 为防止子类恶意修改模板方法,通常将模板方法声明为 final。
- 模板方法中的步骤顺序应尽量固定,避免子类依赖特定的步骤顺序。
4.6 优缺点
- 优点:
- 提高代码复用性,将公共算法框架提取到父类。
- 符合开闭原则,子类可以在不改变算法结构的情况下扩展功能。
- 缺点:
- 子类对父类的依赖可能导致代码可读性下降,尤其是当模板方法复杂时。
- 不适合算法步骤经常变动的情况,因为这可能需要修改父类的模板方法。
4.7 使用场景
- 开发框架中,定义一些通用的业务流程模板,如 Web 应用中的用户注册、登录流程,具体的验证规则和数据存储方式可以由子类实现。
public abstract class UserProcessTemplate {
public final void process() {
validateInput();
saveUser();
sendNotification();
}
protected abstract void validateInput();
protected abstract void saveUser();
protected abstract void sendNotification();
}
public class UserRegistrationProcess extends UserProcessTemplate {
@Override
protected void validateInput() {
// 验证注册信息的逻辑
System.out.println("验证注册信息");
}
@Override
protected void saveUser() {
// 将用户信息保存到数据库的逻辑
System.out.println("保存用户信息到数据库");
}
@Override
protected void sendNotification() {
// 发送注册成功通知的逻辑
System.out.println("发送注册成功通知");
}
}
public class UserLoginProcess extends UserProcessTemplate {
@Override
protected void validateInput() {
// 验证登录信息的逻辑
System.out.println("验证登录信息");
}
@Override
protected void saveUser() {}
@Override
protected void sendNotification() {}
}
- 游戏开发中,定义游戏角色的升级算法框架,不同角色的升级属性加成方式可以由子类实现。
public abstract class CharacterLevelUpTemplate {
public final void levelUp() {
checkLevelRequirements();
addAttributePoints();
unlockSkills();
}
protected abstract void checkLevelRequirements();
protected abstract void addAttributePoints();
protected abstract void unlockSkills();
}
public class WarriorLevelUp extends CharacterLevelUpTemplate {
@Override
protected void checkLevelRequirements() {
// 检查战士升级所需经验值等条件的逻辑
System.out.println("检查战士升级条件");
}
@Override
protected void addAttributePoints() {
// 为战士增加力量、体力等属性点的逻辑
System.out.println("为战士增加属性点");
}
@Override
protected void unlockSkills() {
// 解锁战士技能的逻辑
System.out.println("解锁战士技能");
}
}
public class MageLevelUp extends CharacterLevelUpTemplate {
@Override
protected void checkLevelRequirements() {
// 检查法师升级所需经验值等条件的逻辑
System.out.println("检查法师升级条件");
}
@Override
protected void addAttributePoints
- 数据处理流程框架:在数据处理应用中,常常需要对不同类型的数据进行相似的处理流程,如数据读取、清洗、转换、分析和存储。可以使用模板方法模式定义一个抽象的数据处理类,将数据读取、存储等通用步骤在父类中实现,而数据清洗、转换和分析等步骤定义为抽象方法由具体子类实现。
public abstract class DataProcessor {
public final void processData() {
readData();
cleanData();
transformData();
analyzeData();
saveData();
}
protected void readData() {
System.out.println("从数据源读取数据");
}
protected void saveData() {
System.out.println("将处理后的数据保存");
}
protected abstract void cleanData();
protected abstract void transformData();
protected abstract void analyzeData();
}
public class TextDataProcessor extends DataProcessor {
@Override
protected void cleanData() {
System.out.println("清洗文本数据");
}
@Override
protected void transformData() {
System.out.println("转换文本数据格式");
}
@Override
protected void analyzeData() {
System.out.println("分析文本数据内容");
}
}
public class NumericalDataProcessor extends DataProcessor {
@Override
protected void cleanData() {
System.out.println("清洗数值数据");
}
@Override
protected void transformData() {
System.out.println("转换数值数据单位");
}
@Override
protected void analyzeData() {
System.out.println("分析数值数据统计特征");
}
}
- 多个子类有公有的方法,并且逻辑基本相同时。
- 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
- 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数(见“模板方法模式的扩展”)约束其行为。
5. 建造者模式(Builder Pattern)
5.1 定义
Separate the construction of a complex object from its representation so that the same construction process can create different representations.(将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。)
5.2 自己的理解
建造者模式类似于组装一台电脑,电脑的各个组件(如 CPU、主板、内存、硬盘等)的组装过程是固定的,但可以根据用户的需求和预算选择不同品牌和型号的组件,最终组装出不同配置和性能的电脑。在软件中,当创建复杂对象时,将对象的构建过程和表示分离,由建造者负责构建对象的各个部分,导演类负责控制构建过程的顺序,这样可以灵活地创建出具有不同属性和配置的复杂对象,并且代码结构更加清晰,易于维护和扩展。
5.3 类型
创建型模式。
5.4 通用代码
- Product 产品类:通常实现了模板方法模式,包含模板方法和基本方法。
public class Product {
private String partA;
private String partB;
private String partC;
public void setPartA(String partA) {
this.partA = partA;
}
public void setPartB(String partB) {
this.partB = partB;
}
public void setPartC(String partC) {
this.partC = partC;
}
public void showProduct() {
System.out.println("产品组成:PartA = " + partA + ", PartB = " + partB + ", PartC = " + partC);
}
}
- Builder 抽象建造者:规范产品的组建,一般由子类实现。
public abstract class Builder {
protected Product product = new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
public Product getProduct() {
return product;
}
}
- ConcreteBuilder 具体建造者:实现抽象类定义的所有方法,并返回组建好的对象。
public class ConcreteBuilder1 extends Builder {
@Override
public void buildPartA() {
product.setPartA("ComponentA1");
}
@Override
public void buildPartB() {
product.setPartB("ComponentB1");
}
@Override
public void buildPartC() {
product.setPartC("ComponentC1");
}
}
public class ConcreteBuilder2 extends Builder {
@Override
public void buildPartA() {
product.setPartA("ComponentA2");
}
@Override
public void buildPartB() {
product.setPartB("ComponentB2");
}
@Override
public void buildPartC() {
product.setPartC("ComponentC2");
}
}
- Director 导演类:负责安排已有模块的顺序,然后告诉 Builder 开始建造。
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
}
}
5.5 注意事项
- 建造者模式中,导演类和建造者类的职责要明确划分,导演类只负责控制构建过程的顺序,建造者类负责对象的具体构建,避免职责混乱。
- 如果产品的组成部分变化频繁,可能需要频繁修改建造者类,因此在设计时要考虑产品结构的稳定性。
5.6 优缺点
- 优点:
- 可以创建复杂对象,并且将构建过程和表示分离,使得代码结构清晰,易于理解和维护。
- 可以根据不同需求灵活创建不同表示的对象,提高了对象创建的灵活性。
- 缺点:
- 建造者模式需要创建多个类(导演类、建造者类及其子类、产品类),增加了代码量和系统复杂度。
- 如果产品的属性或构建过程相似性较低,使用建造者模式可能会导致代码冗余。
5.7 使用场景
- 创建数据库连接配置对象:数据库连接配置包含多个参数,如 URL、用户名、密码、驱动类等,根据不同的数据库类型和环境,可以使用建造者模式创建不同配置的连接对象。
public class DatabaseConnection {
private String url;
private String username;
private String password;
private String driverClass;
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setDriverClass(String driverClass) {
this.driverClass = driverClass;
}
public void connect() {
System.out.println("连接数据库:URL = " + url + ", 用户名 = " + username + ", 密码 = " + password + ", 驱动类 = " + driverClass);
}
}
public abstract class DatabaseConnectionBuilder {
protected DatabaseConnection connection = new DatabaseConnection();
public abstract void buildUrl();
public abstract void buildUsername();
public abstract void buildPassword();
public abstract void buildDriverClass();
public DatabaseConnection getConnection() {
return connection;
}
}
public class MySQLConnectionBuilder extends DatabaseConnectionBuilder {
@Override
public void buildUrl() {
connection.setUrl("jdbc:mysql://localhost:3306/mydb");
}
@Override
public void buildUsername() {
connection.setUsername("root");
}
@Override
public void buildPassword() {
connection.setPassword("password");
}
@Override
public void buildDriverClass() {
connection.setDriverClass("com.mysql.jdbc.Driver");
}
}
public class OracleConnectionBuilder extends DatabaseConnectionBuilder {
@Override
public void buildUrl() {
connection.setUrl("jdbc:oracle:thin:@localhost:1521:orcl");
}
@Override
public void buildUsername() {
connection.setUsername("system");
}
@Override
public void buildPassword() {
connection.setPassword("oracle");
}
@Override
public void buildDriverClass() {
connection.setDriverClass("oracle.jdbc.driver.OracleDriver");
}
}
public class ConnectionDirector {
private DatabaseConnectionBuilder builder;
public ConnectionDirector(DatabaseConnectionBuilder builder) {
this.builder = builder;
}
public void construct() {
builder.buildUrl();
builder.buildUsername();
builder.buildPassword();
builder.buildDriverClass();
}
}
- 创建复杂的 UI 界面组件:例如创建一个包含多个子组件(如按钮、文本框、标签等)的表单组件,根据不同的页面需求,可以使用建造者模式构建不同布局和样式的表单。
public class Form {
private Button button;
private TextField textField;
private Label label;
public void setButton(Button button) {
this.button = button;
}
public void setTextField(TextField textField) {
this.textField = textField;
}
public void setLabel(Label label) {
this.label = label;
}
public void display() {
System.out.println("表单组件:按钮 = " + button + ", 文本框 = " + textField + ", 标签 = " + label);
}
}
public abstract class FormBuilder {
protected Form form = new Form();
public abstract void buildButton();
public abstract void buildTextField();
public abstract void buildLabel();
public Form getForm() {
return form;
}
}
public class LoginFormBuilder extends FormBuilder {
@Override
public void buildButton() {
form.setButton(new Button("登录"));
}
@Override
public void buildTextField() {
form.setTextField(new TextField("用户名"));
form.setTextField(new TextField("密码"));
}
@Override
public void buildLabel() {
form.setLabel(new Label("登录表单"));
}
}
public class RegistrationFormBuilder extends FormBuilder {
@Override
public void buildButton() {
form.setButton(new Button("注册"));
}
@Override
public void buildTextField() {
form.setTextField(new TextField("姓名"));
form.setTextField(new TextField("邮箱"));
form.setTextField(new TextField("密码"));
form.setTextField(new TextField("确认密码"));
}
@Override
public void buildLabel() {
form.setLabel(new Label("注册表单"));
}
}
public class FormDirector {
private FormBuilder builder;
public FormDirector(FormBuilder builder) {
this.builder = builder;
}
public void construct() {
builder.buildLabel();
builder.buildTextField();
builder.buildButton();
}
}
- 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式。
- 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式。
- 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适。
建造者模式与工厂模式的不同:
建造者模式最主要的功能是基本方法的调用顺序安排,这些基本方法已经实现了,顺序不同产生的对象也不同;
工厂方法则重点是创建,创建零件是它的主要职责,组装顺序则不是它关心的。
6. 代理模式(Proxy Pattern)
6.1 定义
Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问。)
6.2 自己的理解
代理模式就像是一个中介,当你想要访问某个对象时,不是直接与该对象交互,而是通过代理对象来间接访问。例如,你想购买国外的商品,你可能不会直接与国外商家联系,而是通过代购(代理)来完成购买。在软件中,代理模式可以在不改变目标对象接口的前提下,对目标对象的访问进行控制,如权限控制、懒加载、远程访问等,同时还可以在目标对象方法执行前后添加额外的逻辑,如日志记录、性能监控等。
6.3 类型
结构型模式。
6.4 通用代码
- Subject 抽象主题角色:可以是抽象类或接口,定义了目标对象和代理对象共同的业务方法。
public interface Subject {
void request();
}
- RealSubject 具体主题角色:实现了抽象主题角色的业务方法,是真正的业务逻辑执行者。
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject处理请求");
}
}
- Proxy 代理主题角色:实现了抽象主题角色,内部包含对真实主题角色的引用,并在调用真实主题角色方法前后添加额外逻辑。
public class Proxy implements Subject {
private RealSubject realSubject;
@Override
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
System.out.println("Proxy在请求前的预处理");
realSubject.request();
System.out.println("Proxy在请求后的后处理");
}
}
6.5 注意事项
- 代理模式可能会增加系统的复杂性,尤其是在动态代理的情况下,需要理解动态代理的机制和相关的反射知识。
- 在使用代理模式时,要注意代理对象和目标对象之间的一致性,确保代理对象能够正确地代理目标对象的方法,并且不会破坏目标对象的原有功能。
普通代理和强制代理:
普通代理就是我们要知道代理的存在,也就是类似的GamePlayerProxy这个类的存在,然后才能访问:
强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的。
普通代理:
在该模式下,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要你实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合。
强制代理:
强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要产生一个代理出来,代理的管理己经由真实角色自己完成。
动态代理:
根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理会宣称我已经实现该接口下的所有方法了。
两条独立发展的线路。动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者之间没有必然的相互耦合的关系。通知Advice从另一个切面切入,最终在高层模块也就是Client进行耦合,完成逻辑的封装任务。
动态代理调用过程示意图:
动态代理的意图:横切面编程,在不改变我们己有代码结构的情况下增强或控制对象的行为。
**首要条件:**被代理的类必须要实现一个接口。
6.6 优缺点
- 优点:
- 可以实现对目标对象的访问控制,保护目标对象的安全,例如通过权限代理限制某些用户对敏感资源的访问。
- 能够在不修改目标对象的前提下,增强目标对象的功能,如添加日志记录、性能监控等横切关注点,符合开闭原则。
- 对于一些创建开销较大的对象,可以使用代理模式进行懒加载,延迟对象的创建,提高系统性能。
- 缺点:
- 由于增加了代理层,可能会降低系统的性能,尤其是在简单的应用场景中,如果代理逻辑过于复杂,可能会带来额外的开销。
- 代理模式的代码结构相对复杂,增加了代码的理解和维护难度,尤其是在动态代理的情况下,需要掌握更多的技术细节。
6.7 使用场景
- 远程代理:在分布式系统中,当客户端需要访问远程服务器上的对象时,可以使用代理模式创建一个本地代理对象,客户端通过与本地代理对象交互,由代理对象负责与远程服务器进行通信,实现远程方法的调用,就像 RMI(Remote Method Invocation)技术。
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
// 远程接口
public interface RemoteService extends Remote {
String getData() throws RemoteException;
}
// 远程服务的实现类
public class RemoteServiceImpl implements RemoteService {
@Override
public String getData() throws RemoteException {
return "这是远程服务器上的数据";
}
}
// 远程代理类
public class RemoteServiceProxy implements RemoteService {
private RemoteService remoteService;
public RemoteServiceProxy() throws RemoteException {
// 在构造函数中初始化远程服务对象
this.remoteService = (RemoteService) UnicastRemoteObject.exportObject(new RemoteServiceImpl(), 0);
}
@Override
public String getData() throws RemoteException {
System.out.println("远程代理在调用远程方法前的预处理");
String data = remoteService.getData();
System.out.println("远程代理在调用远程方法后的后处理");
return data;
}
}
- 安全代理:在企业级应用中,对于一些敏感资源或操作,如数据库访问、文件操作等,可以使用安全代理进行权限控制,只有授权用户才能访问真实的资源。
public interface Resource {
void access();
}
public class SecureResource implements Resource {
@Override
public void access() {
System.out.println("访问敏感资源");
}
}
public class SecureResourceProxy implements Resource {
private SecureResource secureResource;
private User user;
public SecureResourceProxy(User user) {
this.user = user;
if (user.hasPermission()) {
this.secureResource = new SecureResource();
}
}
@Override
public void access() {
if (secureResource!= null) {
System.out.println("安全代理进行权限验证通过,允许访问");
secureResource.access();
} else {
System.out.println("安全代理进行权限验证失败,禁止访问");
}
}
}
public class User {
private boolean permission;
public User(boolean permission) {
this.permission = permission;
}
public boolean hasPermission() {
return permission;
}
}
- 缓存代理:对于一些计算成本较高或获取数据开销较大的操作,可以使用缓存代理。缓存代理在第一次调用目标对象的方法时,将结果缓存起来,后续相同的调用直接返回缓存结果,提高系统性能。
public interface ExpensiveOperation {
int calculate(int num);
}
public class ExpensiveOperationImpl implements ExpensiveOperation {
@Override
public int calculate(int num) {
System.out.println("执行复杂计算");
return num * 2;
}
}
public class CachedProxy implements ExpensiveOperation {
private ExpensiveOperationImpl expensiveOperation;
private Map<Integer, Integer> cache = new HashMap<>();
@Override
public int calculate(int num) {
if (cache.containsKey(num)) {
System.out.println("从缓存中获取结果");
return cache.get(num);
} else {
int result = expensiveOperation.calculate(num);
cache.put(num, result);
return result;
}
}
}
7. 原型模式(Prototype Pattern)
7.1 定义
Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.(用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。)
7.2 自己的理解
原型模式类似于复印机,当你需要一份文档的副本时,只需将原始文档放入复印机(原型对象),就能快速得到一份内容相同的副本(新对象)。在软件中,当创建对象的过程较为复杂,或者创建一个新对象的成本较高(如需要大量初始化操作、资源消耗大等),且对象之间的差异较小时,可以使用原型模式。通过拷贝已有对象(原型)来创建新对象,避免了重复的初始化过程,提高了创建对象的效率。
7.3 类型
创建型模式。
7.4 通用代码
public class PrototypeClass implements Cloneable {
// 覆写父类Object方法
@Override
public PrototypeClass clone() {
PrototypeClass prototypeClass = null;
try {
prototypeClass = (PrototypeClass) super.clone();
} catch (CloneNotSupportedException e) {
// 异常处理
}
return prototypeClass;
}
}
7.5 注意事项
- 使用原型模式时,需要确保被拷贝的对象的成员变量满足一定条件,引用的成员变量必须是可变的引用对象,且不是方法内变量,否则可能导致拷贝后的对象共享相同的引用,引发意外的结果。
- 如果对象的构造函数中有重要的初始化逻辑,而原型模式是通过拷贝来创建对象,可能会绕过这些初始化逻辑,需要谨慎处理。
原型模式实际上就是实现Cloneable接口,置写clone()方法。
浅拷贝和深拷贝:
浅拷贝:Object类提供的方法clone只是拷贝本对象,其对象内部的数组、引用对象****等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝,其他的原始类型比如int、long、char、string(当做是原始类型)等都会被拷贝。
**注意:**使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:一是类的成员变量,而不是方法内变量;二是必须是一个可变的引用对象,而不是一个原始类型或不可变对象。
**深拷贝:**对私有的类变量进行独立的拷贝。
如:thing.arrayList=(ArrayList)this.arrayListclone();
7.6 优缺点
- 优点:
- 性能优良,尤其是在创建大量相似对象时,通过内存拷贝比直接使用 new 关键字创建对象效率更高,因为避免了重复的初始化过程。
- 可以逃避构造函数的约束,直接在内存中拷贝对象,对于一些特殊情况(如对象初始化需要复杂的资源获取或权限验证等)可能会更加方便。
- 缺点:
- 深拷贝和浅拷贝的选择需要谨慎,如果处理不当,可能会导致对象状态的不一致或内存泄漏等问题。
- 对于复杂对象的拷贝,尤其是包含循环引用的对象,实现深拷贝可能会比较复杂,容易出错。
7.7 使用场景
- 对象池技术:在游戏开发或数据库连接管理中,经常需要创建和销毁大量相同类型的对象,如游戏中的子弹、敌人等对象,或者数据库连接对象。使用原型模式创建对象池,预先创建一定数量的对象(原型),当需要对象时从池中获取,使用完毕后放回池中,避免频繁创建和销毁对象带来的性能开销。
import java.util.ArrayList;
import java.util.List;
public class ObjectPool<T extends Cloneable> {
private List<T> pool;
private T prototype;
public ObjectPool(T prototype) {
this.prototype = prototype;
pool = new ArrayList<>();
// 预先创建一定数量的对象放入池中
for (int i = 0; i < 10; i++) {
pool.add(prototype.clone());
}
}
public T acquire() {
if (pool.isEmpty()) {
System.out.println("对象池为空,创建新对象");
return prototype.clone();
} else {
return pool.remove(0);
}
}
public void release(T object) {
pool.add(object);
}
}
public class Bullet implements Cloneable {
private int damage;
public Bullet(int damage) {
this.damage = damage;
}
@Override
public Bullet clone() {
try {
return (Bullet) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
public int getDamage() {
return damage;
}
}
- 配置对象的复制:在应用程序中,对于一些配置对象,如果需要根据不同的场景创建略有不同的配置副本,可以使用原型模式。例如,一个应用程序有不同的运行模式(如开发模式、测试模式、生产模式),每个模式的配置大部分相同,只有少数参数不同,可以通过拷贝原型配置对象,然后修改部分参数来快速创建不同模式的配置。
public class AppConfig implements Cloneable {
private String databaseUrl;
private String serverAddress;
private boolean debugMode;
public AppConfig(String databaseUrl, String serverAddress, boolean debugMode) {
this.databaseUrl = databaseUrl;
this.serverAddress = serverAddress;
this.debugMode = debugMode;
}
@Override
public AppConfig clone() {
try {
return (AppConfig) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
public void setDatabaseUrl(String databaseUrl) {
this.databaseUrl = databaseUrl;
}
public void setServerAddress(String serverAddress) {
this.serverAddress = serverAddress;
}
public void setDebugMode(boolean debugMode) {
this.debugMode = debugMode;
}
public String getDatabaseUrl() {
return databaseUrl;
}
public String getServerAddress() {
return serverAddress;
}
public boolean isDebugMode() {
return debugMode;
}
}
- 资源优化场景
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
- 性能和安全要求的场景
- 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
- 一个对象多个修改者的场景
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
8. 中介者模式
8.1 定义
Define an object that encapsulates how a set of objects interact.Mediator promotes loose coupling by keeping objects from referring to each other explicitly,and it lets you vary their interaction independently.(用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散。而且可以独立地改变它们之间的交互。)
8.2 自己的理解
中介者模式就像是一个社交聚会的组织者,各个参与者(同事对象)之间不需要直接相互交流所有信息,而是通过组织者(中介者)来传递信息和协调互动。例如,在一个多人在线游戏中,玩家之间的交易、组队、聊天等交互行为都可以通过游戏服务器(中介者)来管理,玩家只需与服务器交互,服务器负责将信息转发给其他相关玩家,这样可以降低玩家之间的耦合度,提高系统的灵活性和可维护性。
8.3 类型
行为型模式。
8.4 通用代码
- Mediator 抽象中介者角色:定义统一的接口,用于各同事角色之间的通信。
public abstract class Mediator {
// 定义同事类
protected ConcreteColleague1 c1;
protected ConcreteColleague2 c2;
// 通过getter/setter方法把同事类注入进来
public ConcreteColleague1 getC1() {
return c1;
}
public void setC1(ConcreteColleague1 c1) {
this.c1 = c1;
}
public ConcreteColleague2 getC2() {
return c2;
}
public void setC2(ConcreteColleague2 c2) {
this.c2 = c2;
}
// 中介者模式的业务逻辑
public abstract void doSomething1();
public abstract void doSomething2();
}
- Concrete Mediator 具体中介者角色:通过协调各同事角色实现协作行为,依赖于各个同事角色。
public class ConcreteMediator extends Mediator {
@Override
public void doSomething1() {
c1.selfMethod();
c2.depMethod();
}
@Override
public void doSomething2() {
c2.selfMethod();
c1.depMethod();
}
}
- Colleague 同事角色:每个同事角色都知道中介者角色,与其他同事通信时通过中介者协作。
每一个同事角色都知道中介者角色,而且与其他的同事角色通信的时候,一定要通过中介者角色协作、每个同事类的行为分为两种:一种是同事本身的行为,比如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为(Self-Method),与其他的同事类或中介者没有任何的依赖:第二种是必须依赖中介者才能完成的行为,叫做依赖方法(Dep-Method)。
public class ConcreteColleague1 {
private Mediator mediator;
public ConcreteColleague1(Mediator mediator) {
this.mediator = mediator;
mediator.setC1(this);
}
public void selfMethod() {
System.out.println("ConcreteColleague1的自发行为");
}
public void depMethod() {
System.out.println("ConcreteColleague1的依赖方法");
mediator.doSomething1();
}
}
public class ConcreteColleague2 {
private Mediator mediator;
public ConcreteColleague2(Mediator mediator) {
this.mediator = mediator;
mediator.setC2(this);
}
public void selfMethod() {
System.out.println("ConcreteColleague2的自发行为");
}
public void depMethod() {
System.out.println("ConcreteColleague2的依赖方法");
mediator.doSomething2();
}
}
- 通用抽象中介者代码:
public abstract class Mediator {
// 定义同事类
protected ConcreteColleague1 c1;
protected ConcreteColleague2 c2;
// 通过getter/setter方法把同事类注入进来
public ConcreteColleague1 getC1() {
return c1;
}
public void setC1(ConcreteColleague1 c1) {
this.c1 = c1;
}
public ConcreteColleague2 getC2() {
return c2;
}
public void setC2(ConcreteColleague2 c2) {
this.c2 = c2;
}
// 中介者模式的业务逻辑
public abstract void doSomething1();
public abstract void doSomething2();
}
**PS:**使用同事类注入而不使用抽象注入的原因是因为抽象类中不具有每个同事类必须要完成的方法。即每个同事类中的方法各不相同。
**问:**为什么同事类要使用构造函数注入中介者,而中介者使用getter/setter方式注入同事类呢?
这是因为同事类必须有中介者,而中介者却可以只有部分同事类。
8.5 注意事项
- 中介者类可能会变得过于复杂,尤其是当同事类之间的交互逻辑较多时,中介者需要协调处理大量的信息,容易导致中介者类的职责过重,违背单一职责原则。
- 中介者模式会使同事类之间的依赖关系转移到中介者类上,如果中介者类出现问题,可能会影响整个系统的正常运行,增加了系统的风险。
8.6 优缺点
- 优点:
- 降低了对象之间的耦合度,使得同事类之间的关系更加松散,易于维护和扩展。各同事类只需要与中介者交互,不需要了解其他同事类的具体实现,符合迪米特法则。
- 可以将复杂的对象交互逻辑集中在中介者类中,提高了系统的可维护性,当交互逻辑发生变化时,只需要修改中介者类,而不需要修改各个同事类。
- 缺点:
- 中介者类的复杂性可能会增加,尤其是在处理大量同事类和复杂交互逻辑时,中介者类可能会变得庞大且难以理解和维护。
- 中介者模式可能会导致系统的性能下降,因为所有的交互都需要通过中介者进行转发,增加了一定的间接性和开销。
8.7 使用场景
- 聊天系统:在即时通讯软件中,多个用户之间的聊天消息传递可以通过中介者(服务器)来实现。用户发送消息时,将消息发送给服务器,服务器根据接收方的信息将消息转发给相应的用户,用户之间不需要直接建立连接,降低了用户之间的耦合度,便于系统的扩展和管理。
public abstract class ChatMediator {
public abstract void sendMessage(User user, String message);
public abstract void addUser(User user);
}
public class ChatServer implements ChatMediator {
private List<User> users = new ArrayList<>();
@Override
public void sendMessage(User user, String message) {
for (User u : users) {
if (u!= user) {
u.receiveMessage(message);
}
}
}
@Override
public void addUser(User user) {
users.add(user);
}
}
public class User {
private String name;
private ChatMediator mediator;
public User(String name, ChatMediator mediator) {
this.name = name;
this.mediator = mediator;
mediator.addUser(this);
}
public void sendMessage(String message) {
mediator.sendMessage(this, message);
}
public void receiveMessage(String message) {
System.out.println(name + "收到消息:" + message);
}
}
- 交通控制系统:在交通路口,车辆、行人等交通参与者之间的交互(如信号灯控制、车辆让行等)可以通过交通控制器(中介者)来协调。交通控制器根据交通规则和各个方向的交通流量,控制信号灯的变化,指挥车辆和行人的通行,避免交通参与者之间直接复杂的交互,提高交通系统的安全性和效率。
public abstract class TrafficMediator {
public abstract void setTrafficLight(TrafficLight light);
public abstract void vehicleArrives(Vehicle vehicle);
public abstract void pedestrianArrives(Pedestrian pedestrian);
}
public class TrafficController implements TrafficMediator {
private TrafficLight trafficLight;
@Override
public void setTrafficLight(TrafficLight light) {
this.trafficLight = light;
}
@Override
public void vehicleArrives(Vehicle vehicle) {
if (trafficLight.isGreen()) {
vehicle.go();
} else {
vehicle.stop();
}
}
@Override
public void pedestrianArrives(Pedestrian pedestrian) {
if (trafficLight.isGreenForPedestrian()) {
pedestrian.cross();
} else {
pedestrian.wait();
}
}
}
public class Vehicle {
private String licensePlate;
private TrafficMediator mediator;
public Vehicle(String licensePlate, TrafficMediator mediator) {
this.licensePlate = licensePlate;
this.mediator = mediator;
}
public void go() {
System.out.println(licensePlate + "车辆通行");
}
public void stop() {
System.out.println(licensePlate + "车辆停止");
}
public void arrive() {
mediator.vehicleArrives(this);
}
}
public class Pedestrian {
private String name;
private TrafficMediator mediator;
public Pedestrian(String name, TrafficMediator mediator) {
this.name = name;
this.mediator = mediator;
}
public void cross() {
System.out.println(name + "行人过马路");
}
public void wait() {
System.out.println(name + "行人等待");
}
public void arrive() {
mediator.pedestrianArrives(this);
}
}
public class TrafficLight {
private boolean green;
private boolean greenForPedestrian;
public TrafficLight(boolean green, boolean greenForPedestrian) {
this.green = green;
this.greenForPedestrian = greenForPedestrian;
}
public boolean isGreen() {
return green;
}
public boolean isGreenForPedestrian() {
return greenForPedestrian;
}
}
- 中介者模式适用于多个对象之间紧密耦合的情况,紧密耦合的标准是:在类图中出现了蜘蛛网状结构,即每个类都与其他的类有直接的联系。
9. 命令模式
9.1 定义
Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.(将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。)
9.2 自己的理解
命令模式就像是一个遥控器,遥控器上的每个按钮(命令对象)都对应着一个具体的操作(如打开电视、切换频道、调节音量等),当你按下按钮时,遥控器就会发送相应的命令给电视(接收者)执行。在软件中,将请求封装成命令对象,可以将请求的发送者和接收者解耦,使得请求可以被参数化、排队、记录日志或撤销等,增加了系统的灵活性和可扩展性。
9.3 类型
行为型模式。
9.4 通用代码
- Receive 接收者角色:执行实际操作的角色,命令传递到这里被执行。
public class Receiver {
public void action() {
System.out.println("执行接收者的操作");
}
}
- Command 命令角色:声明执行命令的接口,包含执行命令的方法。
public interface Command {
void execute();
}
- ConcreteCommand 具体命令角色:实现命令接口,关联接收者,在执行方法中调用接收者的相应方法。
public class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.action();
}
}
- Invoker 调用者角色:负责接收命令并执行命令。
public class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void executeCommand() {
command.execute();
}
}
9.5 注意事项
- 在设计命令模式时,要注意命令对象和接收者对象之间的解耦程度,确保命令对象可以独立于接收者对象进行扩展和维护,同时接收者对象的变化不会影响到命令对象的接口。
- 如果系统中的命令种类繁多,可能会导致命令类的数量过多,增加系统的复杂性。可以考虑使用命令模式的扩展,如命令队列、命令组合等方式来优化设计。
9.6 优缺点
- 优点:
- 实现了请求发送者和接收者之间的解耦,发送者只需要知道如何发送命令,而不需要了解接收者的具体操作,接收者也不需要了解命令的发送者,提高了系统的灵活性和可维护性。
- 可以方便地对命令进行扩展、组合和复用,例如可以创建新的命令类来实现新的功能,或者将多个命令组合成一个复合命令来执行一系列操作。
- 支持命令的排队、记录日志和撤销恢复等功能,为系统提供了更多的控制和管理能力。
- 缺点:
- 每一个命令都需要一个具体的命令类,可能会导致类的数量过多,尤其是在复杂的系统中,过多的类会增加代码的理解和维护难度。
- 命令模式的实现可能会引入一定的性能开销,例如创建命令对象、存储命令对象等操作都需要消耗一定的资源。
9.7 使用场景
- 图形界面操作:在图形用户界面(GUI)中,用户的各种操作(如点击按钮、选择菜单等)都可以看作是命令。例如,一个绘图软件中,“绘制直线”“绘制矩形”“填充颜色” 等操作都可以封装成相应的命令类,当用户点击相应的工具按钮时,就会创建并执行对应的命令,实现图形的绘制和编辑。
import java.awt.Graphics;
public class DrawLineCommand implements Command {
private Graphics graphics;
private int x1, y1, x2, y2;
public DrawLineCommand(Graphics graphics, int x1, int y1, int x2, int y2) {
this.graphics = graphics;
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
@Override
public void execute() {
graphics.drawLine(x1, y1, x2, y2);
}
}
public class DrawRectangleCommand implements Command {
private Graphics graphics;
private int x, y, width, height;
public DrawRectangleCommand(Graphics graphics, int x, int y, int width, int height) {
this.graphics = graphics;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public void execute() {
graphics.drawRect(x, y, width, height);
}
}
public class GraphicsEditor {
private Invoker invoker;
public GraphicsEditor() {
invoker = new Invoker();
}
public void drawLine(Graphics graphics, int x1, int y1, int x2, int y2) {
Command command = new DrawLineCommand(graphics, x1, y1, x2, y2);
invoker.setCommand(command);
invoker.executeCommand();
}
public void drawRectangle(Graphics graphics, int x, int y, int width, int height) {
Command command = new DrawRectangleCommand(graphics, x, y, width, height);
invoker.setCommand(command);
invoker.executeCommand();
}
}
- 游戏操作控制:在游戏中,玩家的各种操作指令(如移动角色、攻击敌人、使用道具等)也可以使用命令模式来实现。每个操作对应一个命令类,游戏引擎作为调用者,接收玩家的输入并执行相应的命令,控制游戏角色的行为。
public class MoveCharacterCommand implements Command {
private Character character;
private int x, y;
public MoveCharacterCommand(Character character, int x, int y) {
this.character = character;
this.x = x;
this.y = y;
}
@Override
public void execute() {
character.move(x, y);
}
}
public class AttackCommand implements Command {
private Character character;
private Enemy enemy;
public AttackCommand(Character character, Enemy enemy) {
this.character = character;
this.enemy = enemy;
}
@Override
public void execute() {
character.attack(enemy);
}
}
public class GameEngine {
private Invoker invoker;
public GameEngine() {
invoker = new Invoker();
}
public void handleInput(String input, Character character, Enemy enemy) {
if ("move".equals(input)) {
Command command = new MoveCharacterCommand(character, 1, 0);
invoker.setCommand(command);
invoker.executeCommand();
} else if ("attack".equals(input)) {
Command command = new AttackCommand(character, enemy);
invoker.setCommand(command);
invoker.executeCommand();
}
}
}
- 认为是命令的地方就可以采用命令模式,例如,在GUI开发中,一个按钮的点击是一个命令,可以采用命令模式;模拟DOS命令的时候,当然也要采用命令模式;触发一反馈机制的处理等。
10. 责任链模式
10.1 定义
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the requestChain the receiving objects and pass the request along the chain until an object handles it.(使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系,将这些对象连成一条链并沿着这条链传递该请求,直到有对象处理它为止。)
10.2 自己的理解
责任链模式就像是一个工作流程中的审批链,一个请求(如请假申请、报销申请等)从链的起始端开始传递,每个环节的审批人(处理者对象)都有机会处理该请求,如果当前审批人能够处理,则处理完毕;如果不能处理,则将请求传递给下一个审批人,直到有合适的审批人处理该请求或者请求到达链的末尾。在软件中,这种模式可以将请求的处理逻辑分散到多个对象中,提高系统的灵活性和可扩展性,同时降低了请求发送者和处理者之间的耦合度。
10.3 类型
行为型模式。
10.4 通用代码
- Handler 抽象处理者:定义处理请求的接口,包含处理请求的方法和设置下一个处理者的方法,同时定义处理者的级别。
public abstract class Handler {
private Handler nextHandler;
public final Response handleMessage(Request request) {
Response response = null;
// 判断是否是自己的处理级别
if (this.getHandlerLevel().equals(request.getRequestLevel())) {
response = this.echo(request);
} else {
// 判断是否有下一个处理者
if (this.nextHandler!= null) {
response = this.nextHandler.handleMessage(request);
} else {
// 没有适当的处理者,业务自行处理
}
}
return response;
}
// 设置下一个处理者是谁
public void setNext(Handler _handler) {
this.nextHandler = _handler;
}
// 每个处理者都有一个处理级别
protected abstract Level getHandlerLevel();
// 每个处理者都必须实现处理任务
protected abstract Response echo(Request request);
}
- ConcreteHandler 具体处理者:实现抽象处理者的接口,根据自身的处理级别决定是否处理请求。
public class ConcreteHandler1 extends Handler {
@Override
protected Level getHandlerLevel() {
return Level.LEVEL1;
}
@Override
protected Response echo(Request request) {
System.out.println("ConcreteHandler1处理请求");
return new Response("ConcreteHandler1处理结果");
}
}
public class ConcreteHandler2 extends Handler {
@Override
protected Level getHandlerLevel() {
return Level.LEVEL2;
}
@Override
protected Response echo(Request request) {
System.out.println("ConcreteHandler2处理请求");
return new Response("ConcreteHandler2处理结果");
}
}
- Request 请求类:包含请求的相关信息和请求级别。
public class Request {
private Level requestLevel;
private String requestData;
public Request(Level requestLevel, String requestData) {
this.requestLevel = requestLevel;
this.requestData = requestData;
}
public Level getRequestLevel() {
return requestLevel;
}
public String getRequestData() {
return requestData;
}
}
- Response 响应类:用于返回处理结果。
public class Response {
private String result;
public Response(String result) {
this.result = result;
}
public String getResult() {
return result;
}
}
- Level 处理级别枚举:定义不同的处理级别。
public enum Level {
LEVEL1,
LEVEL2
}
10.5 注意事项
- 在构建责任链时,要注意处理者的顺序和处理级别设置,确保请求能够按照预期的顺序被正确处理,避免出现请求无法被处理或者处理顺序混乱的情况。
- 责任链过长可能会影响系统性能,因为每个请求都需要依次遍历处理者链,直到找到合适的处理者。可以考虑设置链的最大长度或者采用其他优化策略,如缓存处理结果等。
- 链中节点数量需要控制,通免出现超长链的情况,一般的做法是在Handler中设置一个最大节点数量,在setNext方法中判断是否己经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能。
抽象的处理者实现三个职责:
一是定义一个请求的处理方法handleMessage,唯一对外开放的方法:
二是定义一个链的编排方法setNext,设置下一个处理者:
三是定义了具体的请求者必须实现的两个方法:定义自己能够处理的级别getHandlerLevel和具体的处理任务echo
10.6 优缺点
- 优点:
- 降低了请求发送者和处理者之间的耦合度,发送者不需要知道请求的具体处理者,只需要将请求发送到责任链的起始端即可,符合迪米特法则。
- 可以动态地增加或修改处理者,灵活地调整处理流程,增强了系统的灵活性和可扩展性。
- 每个处理者只关注自己职责范围内的请求处理,符合单一职责原则,提高了代码的可维护性。
- 缺点:
- 责任链过长可能导致性能下降,尤其是在处理复杂请求时,遍历链的开销可能会较大。
- 调试和排查问题可能会比较困难,因为请求在链中的传递过程可能比较复杂,不易追踪和定位问题所在。
10.7 使用场景
- 日志记录级别处理:在日志系统中,可以根据日志的级别(如 DEBUG、INFO、WARN、ERROR 等)设置不同的处理者。例如,DEBUG 级别的日志可以由开发环境中的详细日志处理器处理,INFO 级别的日志可以由普通日志处理器处理,WARN 和 ERROR 级别的日志可以由更高级别的错误日志处理器处理,并且可以将日志记录到不同的目标(如文件、控制台、数据库等)。
public class LogLevel {
public static final LogLevel DEBUG = new LogLevel("DEBUG");
public static final LogLevel INFO = new LogLevel("INFO");
public static final LogLevel WARN = new LogLevel("WARN");
public static final LogLevel ERROR = new LogLevel("ERROR");
private String level;
private LogLevel(String level) {
this.level = level;
}
public String getLevel() {
return level;
}
}
public class LogRecord {
private LogLevel level;
private String message;
public LogRecord(LogLevel level, String message) {
this.level = level;
this.message = message;
}
public LogLevel getLevel() {
return level;
}
public String getMessage() {
return message;
}
}
public abstract class LogHandler {
private LogHandler nextHandler;
public final void handleLog(LogRecord record) {
if (this.getLogLevel().equals(record.getLevel())) {
this.log(record);
} else {
if (this.nextHandler!= null) {
this.nextHandler.handleLog(record);
} else {
System.out.println("未找到合适的日志处理器,日志级别:" + record.getLevel());
}
}
}
public void setNext(LogHandler handler) {
this.nextHandler = handler;
}
protected abstract LogLevel getLogLevel();
protected abstract void log(LogRecord record);
}
public class DebugLogHandler extends LogHandler {
@Override
protected LogLevel getLogLevel() {
return LogLevel.DEBUG;
}
@Override
protected void log(LogRecord record) {
System.out.println("DEBUG日志:" + record.getMessage());
}
}
public class InfoLogHandler extends LogHandler {
@Override
protected LogLevel getLogLevel() {
return LogLevel.INFO;
}
@Override
protected void log(LogRecord record) {
System.out.println("INFO日志:" + record.getMessage());
}
}
public class WarnLogHandler extends LogHandler {
@Override
protected LogLevel getLogLevel() {
return LogLevel.WARN;
}
@Override
protected void log(LogRecord record) {
System.out.println("WARN日志:" + record.getMessage());
}
}
public class ErrorLogHandler extends LogHandler {
@Override
protected LogLevel getLogLevel() {
return LogLevel.ERROR;
}
@Override
protected void log(LogRecord record) {
System.out.println("ERROR日志:" + record.getMessage());
}
}
- 员工请假审批流程:在企业管理系统中,员工请假申请可以按照不同的请假天数和员工级别设置审批流程。例如,普通员工请假 1 天以内由直属上级审批,请假 1 - 3 天由部门经理审批,请假 3 天以上由人力资源部审批;而经理级别的员工请假则可能需要更高层级的领导审批。
public class LeaveRequest {
private int days;
private Employee employee;
public LeaveRequest(int days, Employee employee) {
this.days = days;
this.employee = employee;
}
public int getDays() {
return days;
}
public Employee getEmployee() {
return employee;
}
}
public class Employee {
private String name;
private String position;
public Employee(String name, String position) {
this.name = name;
this.position = position;
}
public String getName() {
return name;
}
public String getPosition() {
return position;
}
}
public abstract class LeaveApprover {
private LeaveApprover nextApprover;
public final void processLeaveRequest(LeaveRequest request) {
if (this.canApprove(request)) {
this.approve(request);
} else {
if (this.nextApprover!= null) {
this.nextApprover.processLeaveRequest(request);
} else {
System.out.println("请假申请未被批准,审批流程结束");
}
}
}
public void setNextApprover(LeaveApprover approver) {
this.nextApprover = approver;
}
protected abstract boolean canApprove(LeaveRequest request);
protected abstract void approve(LeaveRequest request);
}
public class DirectSupervisorApprover extends LeaveApprover {
@Override
protected boolean canApprove(LeaveRequest request) {
return request.getEmployee().getPosition().equals("普通员工") && request.getDays() <= 1;
}
@Override
protected void approve(LeaveRequest request) {
System.out.println(request.getEmployee().getName() + "的请假申请已被直属上级批准");
}
}
public class DepartmentManagerApprover extends LeaveApprover {
@Override
protected boolean canApprove(LeaveRequest request) {
return request.getEmployee().getPosition().equals("普通员工") && request.getDays() > 1 && request.getDays() <= 3;
}
@Override
protected void approve(LeaveRequest request) {
System.out.println(request.getEmployee().getName() + "的请假申请已被部门经理批准");
}
}
public class HRApprover extends LeaveApprover {
@Override
protected boolean canApprove(LeaveRequest request) {
return request.getEmployee().getPosition().equals("普通员工") && request.getDays() > 3;
}
@Override
protected void approve(LeaveRequest request) {
System.out.println(request.getEmployee().getName() + "的请假申请已被人力资源部批准");
}
}
11. 装饰模式(Decorator Pattern)
11.1 定义
Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.(动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。)
11.2 自己的理解
装饰模式就像是给一个基础物品(如手机)添加各种配件(如手机壳、贴膜、耳机等)来增强其功能或改变其外观。在软件中,当需要在不改变现有对象结构的前提下,为对象添加新的功能或行为时,可以使用装饰模式。通过创建装饰类来包装原始对象,装饰类可以在原始对象的基础上添加额外的功能,并且可以根据需要动态地组合多个装饰类,实现不同的功能组合,比使用继承来扩展功能更加灵活。
11.3 类型
结构型模式。
11.4 通用代码
- Component 抽象构件:定义对象的基本接口,可以是抽象类或接口,是被装饰对象和装饰类共同的父类或父接口。
public interface Component {
void operation();
}
- ConcreteComponent 具体构件:实现抽象构件接口,是被装饰的原始对象。
public class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("执行具体构件的操作");
}
}
- Decorator 装饰角色:一般是抽象类,实现抽象构件接口,内部包含一个指向抽象构件的引用,用于装饰原始对象。
public abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
- ConcreteDecorator 具体装饰角色:继承装饰角色,在原始对象的基础上添加额外的功能。
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedFunctionA();
}
private void addedFunctionA() {
System.out.println("添加功能A");
}
}
public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedFunctionB();
}
private void addedFunctionB() {
System.out.println("添加功能B");
}
}
11.5 注意事项
- 装饰类和被装饰类应该具有相同的接口或父类,这样才能保证装饰后的对象可以替代原始对象使用,否则可能会导致类型不匹配的问题。
- 在使用多个装饰类时,要注意装饰的顺序,不同的顺序可能会产生不同的效果,需要根据具体需求进行合理的组合。
11.6 优缺点
- 优点:
- 动态地扩展对象的功能,比继承更加灵活,因为可以在运行时根据需要选择不同的装饰类来组合功能,而继承在编译时就确定了类的层次结构。
- 遵循开闭原则,在不修改原始类代码的基础上,可以通过添加装饰类来扩展功能,对现有系统的影响较小。
- 可以使用多个装饰类对一个对象进行多次装饰,实现复杂的功能组合,提高了代码的复用性和灵活性。
- 缺点:
- 会增加系统中类的数量,如果过度使用装饰模式,可能会导致系统变得复杂,增加代码的理解和维护难度。
- 装饰模式的动态特性可能会使代码的调试和理解变得相对困难,因为装饰类的嵌套可能会隐藏一些对象的真实行为。
11.7 使用场景
- 图形界面组件增强:在 GUI 开发中,对于基本的按钮、文本框等组件,可以使用装饰模式添加额外的功能,如添加边框样式、背景颜色、鼠标悬停效果等。
import javax.swing.JButton;
public class StyledButtonDecorator extends Decorator {
public StyledButtonDecorator(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
JButton button = (JButton) component;
button.setBorder(BorderFactory.createLineBorder(Color.RED));
button.setBackground(Color.YELLOW);
}
}
public class HoverEffectButtonDecorator extends Decorator {
public HoverEffectButtonDecorator(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
JButton button = (JButton) component;
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
button.setBackground(Color.GREEN);
}
@Override
public void mouseExited(MouseEvent e) {
button.setBackground(null);
}
});
}
}
- 数据加密与压缩:在数据处理中,对于原始数据,可以使用装饰模式先进行加密,再进行压缩,或者反之,根据具体需求灵活组合加密和压缩功能。
import java.io.ByteArrayOutputStream;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
public class EncryptionDecorator extends Decorator {
public EncryptionDecorator(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
byte[] data = ((DataComponent) component).getData();
// 简单的异或加密示例
for (int i = 0; i < data.length; i++) {
data[i] ^= 0x55;
}
((DataComponent) component).setData(data);
}
}
public class CompressionDecorator extends Decorator {
public CompressionDecorator(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
byte[] data = ((DataComponent) component).getData();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Deflater deflater = new Deflater();
deflater.setInput(data);
deflater.finish();
byte[] buffer = new byte[1024];
while (!deflater.finished()) {
int count = deflater.deflate(buffer);
outputStream.write(buffer, 0, count);
}
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
byte[] compressedData = outputStream.toByteArray();
((DataComponent) component).setData(compressedData);
}
public byte[] decompress(byte[] compressedData) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Inflater inflater = new Inflater();
inflater.setInput(compressedData);
byte[] buffer = new byte[1024];
try {
while (!inflater.finished()) {
int count = inflater.inflate(buffer);
outputStream.write(buffer, 0, count);
}
outputStream.close();
} catch (IOException | DataFormatException e) {
e.printStackTrace();
}
return outputStream.toByteArray();
}
}
public class DataComponent implements Component {
private byte[] data;
public DataComponent(byte[] data) {
this.data = data;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
@Override
public void operation() {
// 原始数据处理逻辑(可以为空)
}
}
- 需要扩展一个类的功能,或给一个类增加附加功能。
- 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
- 需要为一批的兄弟类进行改装或加装功能,当然是首选装饰模式