当前位置: 首页 > news >正文

23种设计模式(持续更新中)

参考链接干货分享 | 《设计模式之美》学习笔记 - 知乎 (zhihu.com)

总体来说设计模式分为三大类:

创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共7种:适配器模式、装饰器模式、代理模式、桥接模式、外观(门面)模式、组合模式、享元模式。

行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、状态模式、命令模式、备忘录模式、访问者模式、中介者模式、解释器模式。

ps:标红为常用模式,其余不常用。常用的也就4+4+6=14种模式。

学习设计模式前了解一些设计原则和思想

 设计原则和思想

当然,这些设计原则和哲学是软件设计中非常重要的概念。它们旨在提高代码的可维护性、可扩展性和可读性。以下是每个原则的详细解释:

 1. SRP - Single Responsibility Principle (单一职责原则)


**定义**:一个类应该只有一个引起它变化的原因。
**解释**:每个类都应该只有一个职责,只有一个导致其变化的原因。如果一个类承担了多个职责,当需求变化时就会导致类的多处修改,难以维护。
**示例**:假设一个类既负责用户数据的保存又负责用户数据的显示,这违反了SRP。可以拆分成两个类,一个负责数据的保存,另一个负责显示。

 2. OCP - Open/Closed Principle (开放封闭原则)


**定义**:软件实体(类、模块、函数等)应该是对扩展开放的,但对修改封闭的。
**解释**:应通过扩展系统的行为来实现变化,而不是通过修改已有的代码。这减少了修改现有代码带来的风险。
**示例**:通过使用抽象类和接口,可以在不修改现有类的情况下扩展新功能。

 3. LSP - Liskov Substitution Principle (里氏替换原则)


**定义**:子类型必须能够替换掉它们的基类型
**解释**:任何使用基类型的地方都应该可以透明地使用其子类型。如果子类不能替代父类,则可能会破坏程序的正确性。
**示例**:假设有一个基类`Shape`和其子类`Rectangle`和`Square`,子类的方法不应该违反基类的方法行为。

 4. ISP - Interface Segregation Principle (接口隔离原则)


**定义**:客户端不应该被迫依赖于它们不使用的方法。
**解释**:应当为客户端提供专用接口,而不是使用一个包含所有方法的庞大接口。这样可以防止客户端依赖不需要的方法。
**示例**:将一个庞大的接口拆分成多个小接口,每个接口只包含一个客户端需要的方法。

 5. DIP - Dependency Inversion Principle (依赖倒置原则)


**定义**:高层模块不应该依赖低层模块,二者都应该依赖于抽象。抽象不应该依赖细节,细节应该依赖抽象。
**解释**:通过依赖抽象(如接口或抽象类)而不是具体实现,可以减少代码的耦合性,使系统更加灵活和可维护。
**示例**:使用接口来定义行为,具体实现类依赖接口,而不是直接依赖具体类。

6. KISS - Keep It Simple, Stupid (保持简单,愚蠢)


**定义**:系统应该尽可能保持简单。
**解释**:简单的系统更容易理解、维护和扩展。复杂性应当尽量避免。
**示例**:避免过度设计,不引入不必要的复杂性。

7. DRY - Don't Repeat Yourself (不要重复自己)


**定义**:每一块知识在系统中应该有唯一的、明确的、权威的表示。
**解释**:避免代码重复,重复的代码会导致难以维护和更新。如果一个地方的逻辑改变了,需要修改所有相关的地方。
**示例**:使用函数、类和模块来封装重复代码逻辑

 8. LOD - Law of Demeter (迪米特法则)


**定义**:一个对象应该对其他对象有最少的了解。
**解释**:一个对象不应该知道太多其他对象的内部细节。它只应该和直接相关的对象交流,减少类之间的耦合。
**示例**:在方法中避免调用链(如`a.getB().getC().doSomething()`),而是通过中介者或传递方法减少耦合。

 9. YAGNI - You Aren't Gonna Need It (你不会需要它)


**定义**:不要实现当前不需要的功能。
**解释**:避免过早优化或加入不必要的功能,只实现当前需求的功能。这样可以减少代码的复杂性和维护成本。
**示例**:在项目初期,不要为未来可能的需求实现功能,只关注当前的需求

### 总结
这些设计原则和哲学是为了帮助开发者编写高质量、易维护和可扩展的代码。理解并应用这些原则可以显著提高软件开发的效率和质量。

创建型设计模式

创建型模式:用于解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码 

1 单例模式

单例模式用来创建全局唯一的对象,一个类只允许创建一个对象实例。有几种经典的实现方式,分别是饿汉式,懒汉式、双重检测、静态内部类、枚举

单例模式是一种设计模式,确保一个类只有一个实例,并提供对该实例的全局访问点。在Java中,实现单例模式的几种经典方式有饿汉式、懒汉式、双重检测、静态内部类和枚举。以下是每种方式的实现示例:

1. 饿汉式(Eager Initialization)


饿汉式在类加载时就创建实例,线程安全但可能造成资源浪费。

public class SingletonEager {private static final SingletonEager INSTANCE = new SingletonEager();private SingletonEager() {// 私有构造方法,防止外部实例化}public static SingletonEager getInstance() {return INSTANCE;}
}

 2. 懒汉式(Lazy Initialization)


懒汉式在第一次需要实例时才创建,非线程安全。

public class SingletonLazy {private static SingletonLazy instance;private SingletonLazy() {// 私有构造方法,防止外部实例化}public static SingletonLazy getInstance() {if (instance == null) {instance = new SingletonLazy();}return instance;}
}

 3. 双重检测(Double-Checked Locking)


双重检测在保证线程安全的同时提高了性能。

public class SingletonDoubleChecked {private static volatile SingletonDoubleChecked instance;private SingletonDoubleChecked() {// 私有构造方法,防止外部实例化}public static SingletonDoubleChecked getInstance() {if (instance == null) {// 这段代码用于在实例为空时,锁定当前类以确保只有一个线程能进入同步块创建实例。 // synchronized关键字确保了同步块内的代码在多线程环境下是安全的。 // 这个锁是为了防止多个线程同时进入创建实例的代码块。synchronized (SingletonDoubleChecked.class) {if (instance == null) {instance = new SingletonDoubleChecked();}}}return instance;}
}

 4. 静态内部类(Static Inner Class)


静态内部类方式利用类加载机制保证线程安全,延迟加载且性能高。

public class SingletonStaticInnerClass {private SingletonStaticInnerClass() {// 私有构造方法,防止外部实例化}private static class Holder {private static final SingletonStaticInnerClass INSTANCE = new SingletonStaticInnerClass();}public static SingletonStaticInnerClass getInstance() {return Holder.INSTANCE;}
}

 5. 枚举(Enum)


枚举方式是实现单例模式最简洁和安全的方式,天生保证线程安全和防止反序列化创建新对象。

// 定义枚举类型
public enum SingletonEnum {// 唯一实例INSTANCE;// 可以在这里添加其他方法public void doSomething() {System.out.println("Doing something...");}// 示例方法,可以保存一些状态private int value;public int getValue() {return value;}public void setValue(int value) {this.value = value;}
}// 使用枚举单例
public class Main {public static void main(String[] args) {// 访问单例实例SingletonEnum singleton = SingletonEnum.INSTANCE;// 调用单例的方法singleton.doSomething();// 设置和获取值singleton.setValue(42);System.out.println("Singleton value: " + singleton.getValue());}
}

这几种方式各有优缺点,选择适合自己项目需求的方式实现单例模式是关键。

2 工厂模式

工厂模式包括简单工厂、工厂方法、抽象工厂3种细分模式,抽象工厂模式不常用。工厂模式用来创建不同但相关类型的对象,由给定的参数决定来创建哪种类型的对象。

好的,下面是使用Java实现的简单工厂模式和工厂方法模式的示例。

1. 简单工厂模式


简单工厂模式通过一个工厂类,根据传入的参数决定创建哪种具体类型的对象。它将对象的创建逻辑集中在一个地方。

#### 示例:
假设我们要创建不同类型的产品,例如`Car`和`Bike`。

// 产品接口
interface Vehicle {void drive();
}// 具体产品类:Car
class Car implements Vehicle {@Overridepublic void drive() {System.out.println("Driving a car.");}
}// 具体产品类:Bike
class Bike implements Vehicle {@Overridepublic void drive() {System.out.println("Riding a bike.");}
}// 简单工厂类
class VehicleFactory {public static Vehicle createVehicle(String type) {if (type.equalsIgnoreCase("Car")) {return new Car();} else if (type.equalsIgnoreCase("Bike")) {return new Bike();} else {throw new IllegalArgumentException("Unknown vehicle type.");}}
}// 使用简单工厂模式
public class SimpleFactoryDemo {public static void main(String[] args) {Vehicle car = VehicleFactory.createVehicle("Car");car.drive();Vehicle bike = VehicleFactory.createVehicle("Bike");bike.drive();}
}

2. 工厂方法模式


工厂方法模式通过定义一个创建对象的接口,将实际创建工作推迟到子类中。它让一个类的实例化延迟到其子类。

#### 示例:
我们用相同的例子,但这次使用工厂方法模式。

// 产品接口
interface Vehicle {void drive();
}// 具体产品类:Car
class Car implements Vehicle {@Overridepublic void drive() {System.out.println("Driving a car.");}
}// 具体产品类:Bike
class Bike implements Vehicle {@Overridepublic void drive() {System.out.println("Riding a bike.");}
}// 抽象工厂接口
interface VehicleFactory {Vehicle createVehicle();
}// 具体工厂类:CarFactory
class CarFactory implements VehicleFactory {@Overridepublic Vehicle createVehicle() {return new Car();}
}// 具体工厂类:BikeFactory
class BikeFactory implements VehicleFactory {@Overridepublic Vehicle createVehicle() {return new Bike();}
}// 使用工厂方法模式
public class FactoryMethodDemo {public static void main(String[] args) {VehicleFactory carFactory = new CarFactory();Vehicle car = carFactory.createVehicle();car.drive();VehicleFactory bikeFactory = new BikeFactory();Vehicle bike = bikeFactory.createVehicle();bike.drive();}
}

### 解释

#### 简单工厂模式
- **工厂类** (`VehicleFactory`):包含一个静态方法,用于根据传入的参数创建不同类型的对象。
- **优点**:使用简单,集中管理对象创建。
- **缺点**:增加新的产品类型时需要修改工厂类代码,违反开闭原则。

#### 工厂方法模式
- **抽象工厂接口** (`VehicleFactory`):定义一个创建对象的方法。
- **具体工厂类** (`CarFactory` 和 `BikeFactory`):实现创建对象的方法,每个具体工厂负责创建一种具体产品。
- **优点**:符合开闭原则,可以通过增加新的具体工厂类来扩展系统而不修改现有代码。
- **缺点**:增加代码复杂性,需要创建更多的类。

这两种模式都有各自的适用场景,根据实际需求选择合适的模式是关键。

当然可以!简单工厂模式和工厂方法模式各自有其适用的场景和优缺点。了解这些场景可以帮助我们在实际开发中做出更合适的选择。

### 简单工厂模式适用场景

1. **对象创建逻辑简单且稳定**:
   - 如果对象创建逻辑比较简单,不会经常变化,那么简单工厂模式是合适的选择。因为简单工厂模式将所有对象创建逻辑集中在一个地方,这样的集中管理非常方便。

2. **客户端只需要知道工厂类**:
   - 客户端不需要关心具体产品类的实现,只需传入参数给工厂类即可获得产品对象。这种场景下,简单工厂模式能够降低客户端的复杂度。

3. **产品类型较少**:
   - 当产品种类较少且变化不频繁时,简单工厂模式能够有效管理这些产品的创建。

#### 示例
适用于一个简单的图形绘制工具,支持创建不同类型的形状(例如圆形、矩形)。

public class ShapeFactory {public static Shape createShape(String type) {if (type.equalsIgnoreCase("Circle")) {return new Circle();} else if (type.equalsIgnoreCase("Rectangle")) {return new Rectangle();} else {throw new IllegalArgumentException("Unknown shape type.");}}
}

### 工厂方法模式适用场景

1. **对象创建逻辑复杂多变**:
   - 当对象的创建过程比较复杂,或者对象的创建依赖于具体的配置、参数或外部资源时,工厂方法模式可以将这些复杂的创建过程封装在具体工厂类中。

2. **系统需要拓展新的产品类型**:
   - 如果系统需要在未来拓展新的产品类型,而不希望修改已有代码,工厂方法模式是理想的选择。它遵循开闭原则,通过增加新的具体工厂类来创建新的产品类型。

3. **需要提供一系列相关的产品**:
   - 当需要创建一系列相关的产品对象,且这些产品有一个共同的接口或父类时,工厂方法模式可以提供一种灵活且可扩展的方式来创建这些产品。

#### 示例
适用于一个文档处理系统,可以创建不同类型的文档(例如Word文档、PDF文档)。

// 抽象产品接口
interface Document {void open();void save();
}// 具体产品类:WordDocument
class WordDocument implements Document {@Overridepublic void open() {System.out.println("Opening Word document.");}@Overridepublic void save() {System.out.println("Saving Word document.");}
}// 具体产品类:PdfDocument
class PdfDocument implements Document {@Overridepublic void open() {System.out.println("Opening PDF document.");}@Overridepublic void save() {System.out.println("Saving PDF document.");}
}// 抽象工厂接口
interface DocumentFactory {Document createDocument();
}// 具体工厂类:WordDocumentFactory
class WordDocumentFactory implements DocumentFactory {@Overridepublic Document createDocument() {return new WordDocument();}
}// 具体工厂类:PdfDocumentFactory
class PdfDocumentFactory implements DocumentFactory {@Overridepublic Document createDocument() {return new PdfDocument();}
}// 使用工厂方法模式
public class FactoryMethodDemo {public static void main(String[] args) {DocumentFactory wordFactory = new WordDocumentFactory();Document wordDoc = wordFactory.createDocument();wordDoc.open();wordDoc.save();DocumentFactory pdfFactory = new PdfDocumentFactory();Document pdfDoc = pdfFactory.createDocument();pdfDoc.open();pdfDoc.save();}
}

### 总结

- **简单工厂模式**适用于产品种类少且创建逻辑简单、稳定的场景。它通过一个工厂类集中管理对象创建,简化客户端代码,但不利于扩展新产品。
- **工厂方法模式**适用于产品种类多且创建逻辑复杂、多变的场景。它通过定义抽象工厂接口和具体工厂类,实现了创建逻辑的封装和扩展,符合开闭原则,利于系统的扩展和维护。

选择哪种模式取决于系统的复杂性和对扩展性的需求。

3.抽象工厂模式

抽象工厂模式是一种创建型设计模式,它提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。抽象工厂模式使得客户端可以通过接口创建产品对象,从而实现产品族的创建和互换。

### 适用场景
- **系统要独立于它的产品创建、组合和表示时**。
- **系统要由多个产品系列中的一个来配置时**。
- **当需要强调一组相关的产品对象的设计以便进行联合使用时**。
- **系统提供一个产品类库,只有产品接口而不是实现时**。

### 抽象工厂模式的结构

- **抽象产品(Abstract Product)**:定义了产品的接口。
- **具体产品(Concrete Product)**:实现了抽象产品接口。
- **抽象工厂(Abstract Factory)**:声明了一组创建抽象产品的方法。
- **具体工厂(Concrete Factory)**:实现了创建具体产品对象的方法。

### 示例:GUI 工具包

假设我们有一个跨平台的 GUI 工具包,需要创建一系列的按钮和文本框,这些按钮和文本框在不同操作系统(如 Windows 和 MacOS)上有不同的实现。

#### 抽象产品接口

// 抽象产品:按钮
interface Button {void paint();
}// 抽象产品:文本框
interface TextField {void render();
}

#### 具体产品类

// 具体产品:Windows 按钮
class WindowsButton implements Button {@Overridepublic void paint() {System.out.println("Painting a Windows button.");}
}// 具体产品:MacOS 按钮
class MacOSButton implements Button {@Overridepublic void paint() {System.out.println("Painting a MacOS button.");}
}// 具体产品:Windows 文本框
class WindowsTextField implements TextField {@Overridepublic void render() {System.out.println("Rendering a Windows text field.");}
}// 具体产品:MacOS 文本框
class MacOSTextField implements TextField {@Overridepublic void render() {System.out.println("Rendering a MacOS text field.");}
}

#### 抽象工厂接口

// 抽象工厂
interface GUIFactory {Button createButton();TextField createTextField();
}

#### 具体工厂类

// 具体工厂:Windows 工厂
class WindowsFactory implements GUIFactory {@Overridepublic Button createButton() {return new WindowsButton();}@Overridepublic TextField createTextField() {return new WindowsTextField();}
}// 具体工厂:MacOS 工厂
class MacOSFactory implements GUIFactory {@Overridepublic Button createButton() {return new MacOSButton();}@Overridepublic TextField createTextField() {return new MacOSTextField();}
}

#### 客户端代码

客户端代码通过使用抽象工厂接口来创建产品,而无需直接依赖具体产品类。

public class AbstractFactoryDemo {private static Application configureApplication() {Application app;GUIFactory factory;String osName = System.getProperty("os.name").toLowerCase();if (osName.contains("win")) {factory = new WindowsFactory();} else if (osName.contains("mac")) {factory = new MacOSFactory();} else {throw new UnsupportedOperationException("Unsupported OS: " + osName);}app = new Application(factory);return app;}public static void main(String[] args) {Application app = configureApplication();app.paint();}
}// 客户端类:应用程序
class Application {private Button button;private TextField textField;public Application(GUIFactory factory) {button = factory.createButton();textField = factory.createTextField();}public void paint() {button.paint();textField.render();}
}

### 解释

1. **抽象产品接口**:`Button` 和 `TextField` 定义了产品的公共接口。
2. **具体产品类**:`WindowsButton`, `MacOSButton`, `WindowsTextField`, `MacOSTextField` 实现了抽象产品接口,提供具体产品的实现。
3. **抽象工厂接口**:`GUIFactory` 定义了一组创建抽象产品的方法。
4. **具体工厂类**:`WindowsFactory` 和 `MacOSFactory` 实现了抽象工厂接口,负责创建具体产品。
5. **客户端代码**:`Application` 类通过抽象工厂接口创建具体产品,并且在 `main` 方法中配置应用程序的工厂,使用工厂创建产品。

### 总结

抽象工厂模式通过定义创建一系列相关或相互依赖的对象的接口,能够有效地分离具体产品的创建逻辑,提供灵活的产品族创建方式,从而提高系统的可扩展性和可维护性。

总结

  • 简单工厂模式:适用于对象创建逻辑简单且稳定,产品种类较少的场景。它通过一个工厂类集中管理对象创建,但不利于扩展新产品。
  • 工厂方法模式:适用于对象创建逻辑复杂且多变,系统需要扩展新的产品类型时。它通过定义抽象工厂接口和具体工厂类,实现创建逻辑的封装和扩展,符合开闭原则。
  • 抽象工厂模式:适用于系统需要独立于产品创建、组合和表示,系统要由多个产品系列中的一个来配置,或者需要创建一系列相关的产品对象时。它通过定义创建一系列相关或相互依赖对象的接口,实现产品族的创建和互换。就像这种GUI库,不同类型平台下包含多个组件,就适合抽象工厂模式。

3 建造者模式

### 建造者模式 (Builder Pattern)

**定义**:
建造者模式是一种创建型设计模式,它用于将一个复杂对象的构建过程与它的表示分离,使得同样的构建过程可以创建不同的表示。

**优点**:
1. **将复杂对象的构建过程封装起来**:使客户端代码简化,不需要关心构建过程的细节。
2. **支持不同的表示**:可以通过不同的建造者来创建不同表示的对象。
3. **逐步构建对象**:允许通过分步骤构建复杂对象。

**适用场景**:
1. **需要生成的对象具有复杂的内部结构**:对象的构建步骤是稳定的,但各部分的细节可能经常变化。
2. **需要生成的对象有不同的表示**:对象的构建步骤相同,但对象的具体表示可能不同。

### 结构
1. **Product(产品类)**:要创建的复杂对象。
2. **Builder(抽象建造者)**:定义了创建Product的各个子部件的抽象接口。
3. **ConcreteBuilder(具体建造者)**:实现Builder接口,构建和装配各个部件。
4. **Director(指挥者)**:负责安排复杂对象的建造顺序。它知道如何构建复杂对象。

### 示例:建造一辆汽车#### 产品类(Car)

public class Car {private String engine;private String wheels;private String body;public void setEngine(String engine) {this.engine = engine;}public void setWheels(String wheels) {this.wheels = wheels;}public void setBody(String body) {this.body = body;}@Overridepublic String toString() {return "Car [engine=" + engine + ", wheels=" + wheels + ", body=" + body + "]";}
}

#### 抽象建造者(CarBuilder)

abstract class CarBuilder {protected Car car;public Car getCar() {return car;}public void createNewCar() {car = new Car();}public abstract void buildEngine();public abstract void buildWheels();public abstract void buildBody();
}

#### 具体建造者(SportsCarBuilder 和 SUVCarBuilder)

class SportsCarBuilder extends CarBuilder {@Overridepublic void buildEngine() {car.setEngine("Sports Engine");}@Overridepublic void buildWheels() {car.setWheels("Sports Wheels");}@Overridepublic void buildBody() {car.setBody("Sports Body");}
}class SUVCarBuilder extends CarBuilder {@Overridepublic void buildEngine() {car.setEngine("SUV Engine");}@Overridepublic void buildWheels() {car.setWheels("SUV Wheels");}@Overridepublic void buildBody() {car.setBody("SUV Body");}
}

#### 指挥者(Director)

class Director {private CarBuilder carBuilder;public void setCarBuilder(CarBuilder carBuilder) {this.carBuilder = carBuilder;}public Car getCar() {return carBuilder.getCar();}public void constructCar() {carBuilder.createNewCar();carBuilder.buildEngine();carBuilder.buildWheels();carBuilder.buildBody();}
}

#### 客户端代码

public class BuilderPatternDemo {public static void main(String[] args) {Director director = new Director();// 建造一辆运动型汽车CarBuilder sportsCarBuilder = new SportsCarBuilder();director.setCarBuilder(sportsCarBuilder);director.constructCar();Car sportsCar = director.getCar();System.out.println("Car built:\n" + sportsCar);// 建造一辆SUV汽车CarBuilder suvCarBuilder = new SUVCarBuilder();director.setCarBuilder(suvCarBuilder);director.constructCar();Car suvCar = director.getCar();System.out.println("Car built:\n" + suvCar);}
}

### 解释

1. **产品类(Car)**:包含汽车的组成部分以及设置这些部分的方法。
2. **抽象建造者(CarBuilder)**:定义了构建汽车的步骤。
3. **具体建造者(SportsCarBuilder 和 SUVCarBuilder)**:实现了构建汽车的具体步骤。
4. **指挥者(Director)**:负责组织建造步骤,确保按照正确的顺序构建产品。
5. **客户端代码**:通过指挥者来构建具体的产品。

### 总结

建造者模式非常适合用来构建复杂对象,尤其是在对象的构建过程涉及多个步骤并且这些步骤可以灵活地配置或组合时。通过使用建造者模式,可以将对象的构建过程与其表示分离,从而实现代码的可读性、可维护性和扩展性。

4 原型模式

不用过多了解,知道即可。

**定义**:
原型模式是一种创建型设计模式,它允许一个对象再创建另外一个可定制的对象,而无需知道如何创建的细节。原型模式通过克隆已有的实例创建新的对象

**适用场景**:
1. **类初始化消耗较多资源**:当类的初始化过程比较复杂或消耗大量资源时,通过克隆现有实例可以节省开销。
2. **大量相似对象的创建**:在需要创建大量相似对象时,通过克隆现有实例可以简化创建过程。
3. **对象的差异化定制**:在需要不同配置的对象时,可以通过克隆并修改部分属性来实现。

### 结构
1. **Prototype(抽象原型类)**:定义一个克隆自身的接口。
2. **ConcretePrototype(具体原型类)**:实现克隆自身的方法。
3. **Client(客户端类)**:通过调用原型对象的克隆方法来创建新的对象。

### 示例:图形的克隆#### 抽象原型类(Shape)

public abstract class Shape implements Cloneable {private String id;protected String type;abstract void draw();public String getType() {return type;}public String getId() {return id;}public void setId(String id) {this.id = id;}@Overridepublic Object clone() {Object clone = null;try {clone = super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return clone;}
}

#### 具体原型类(Rectangle 和 Circle)

public class Rectangle extends Shape {public Rectangle() {type = "Rectangle";}@Overridepublic void draw() {System.out.println("Inside Rectangle::draw() method.");}
}public class Circle extends Shape {public Circle() {type = "Circle";}@Overridepublic void draw() {System.out.println("Inside Circle::draw() method.");}
}

#### 原型管理器(ShapeCache)

import java.util.Hashtable;public class ShapeCache {private static Hashtable<String, Shape> shapeMap = new Hashtable<String, Shape>();public static Shape getShape(String shapeId) {Shape cachedShape = shapeMap.get(shapeId);return (Shape) cachedShape.clone();}public static void loadCache() {Circle circle = new Circle();circle.setId("1");shapeMap.put(circle.getId(), circle);Rectangle rectangle = new Rectangle();rectangle.setId("2");shapeMap.put(rectangle.getId(), rectangle);}
}

#### 客户端代码

public class PrototypePatternDemo {public static void main(String[] args) {ShapeCache.loadCache();Shape clonedShape1 = ShapeCache.getShape("1");System.out.println("Shape : " + clonedShape1.getType());clonedShape1.draw();Shape clonedShape2 = ShapeCache.getShape("2");System.out.println("Shape : " + clonedShape2.getType());clonedShape2.draw();}
}

### 代码解释

1. **抽象原型类(Shape)**:
    - 包含一个 `clone()` 方法,用于克隆对象。
    - 包含一些属性和方法,这些属性和方法将被具体原型类继承和实现。

2. **具体原型类(Rectangle 和 Circle)**:
    - 实现抽象原型类的 `draw()` 方法,定义具体原型的行为。
    - 通过构造函数设置对象的类型。

3. **原型管理器(ShapeCache)**:
    - `loadCache()` 方法预加载一些对象实例,并存储到一个哈希表中。
    - `getShape(String shapeId)` 方法通过对象的 ID 从哈希表中获取对象,并返回其克隆。

4. **客户端代码(PrototypePatternDemo)**:
    - 加载缓存对象,通过克隆方法获取新对象并使用。

### 优点
1. **性能提高**:通过克隆已有实例来创建对象,而不是通过构造函数重新创建,减少了开销。
2. **隐藏创建逻辑**:客户端不需要知道具体对象的创建细节,只需使用原型对象的克隆方法。

### 缺点
1. **深拷贝和浅拷贝**:需要考虑对象的拷贝是浅拷贝还是深拷贝。
2. **复杂性增加**:克隆方法的实现可能会比较复杂,尤其是当对象有复杂的引用关系时。

### 总结

原型模式是一种强大的创建型模式,它通过克隆现有对象来创建新的对象,适用于需要大量相似对象的场景。通过使用原型模式,可以提高性能并隐藏对象的创建逻辑,使代码更加简洁和易于维护。

结构型设计模式

结构式设计模式主要总结了一些类或对象组合在一起的经典结构,这些经典的结构可以解决特定应用场景的问题。

1. 代理模式

代理模式(Proxy Pattern)是设计模式中的一种结构型模式,它提供了对象的代理以控制对这个对象的访问。代理对象可以在对象的实际操作前后添加额外的功能(如控制访问、延迟初始化、日志记录等)。代理模式主要有三种形式:静态代理动态代理和CGLIB代理。以下是代理模式的基本介绍以及Java版本的实现示例。

代理模式基本概念

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

动机

  • 控制对原对象的访问
  • 增强对象的功能
  • 懒加载或延迟初始化

代理模式的参与者

  1. 抽象主题(Subject):定义代理和真实对象的共同接口。
  2. 真实主题(RealSubject):实现了抽象主题的类。
  3. 代理(Proxy):持有一个对真实主题对象的引用,并实现抽象主题接口。

Java版本的实现示例

1. 静态代理

步骤

  1. 创建接口。
  2. 创建真实主题类实现接口。
  3. 创建代理类实现接口,并持有真实主题类的引用。
// 1. 定义接口
public interface Subject {void request();
}// 2. 真实主题类
public class RealSubject implements Subject {@Overridepublic void request() {System.out.println("RealSubject: Handling request.");}
}// 3. 代理类
public class Proxy implements Subject {private RealSubject realSubject;@Overridepublic void request() {if (realSubject == null) {realSubject = new RealSubject();}System.out.println("Proxy: Logging before request.");realSubject.request();System.out.println("Proxy: Logging after request.");}
}// 使用代理模式
public class Client {public static void main(String[] args) {Subject proxy = new Proxy();proxy.request();}
}
2. 动态代理

步骤

  1. 创建接口。
  2. 创建真实主题类实现接口。
  3. 创建动态代理处理类,利用Java的Proxy类生成代理对象。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 1. 定义接口
public interface Subject {void request();
}// 2. 真实主题类
public class RealSubject implements Subject {@Overridepublic void request() {System.out.println("RealSubject: Handling request.");}
}// 3. 动态代理处理类
public class DynamicProxyHandler implements InvocationHandler {private Object realSubject;public DynamicProxyHandler(Object realSubject) {this.realSubject = realSubject;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Proxy: Logging before request.");Object result = method.invoke(realSubject, args);System.out.println("Proxy: Logging after request.");return result;}
}// 使用动态代理模式
public class Client {public static void main(String[] args) {RealSubject realSubject = new RealSubject();Subject proxyInstance = (Subject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),realSubject.getClass().getInterfaces(),new DynamicProxyHandler(realSubject));proxyInstance.request();}
}

CGLIB(Code Generation Library)代理是一种在运行时生成子类来实现方法拦截的技术。它是动态代理的一种,常用于无法直接使用Java内置动态代理的场景,例如代理没有实现接口的类。

### CGLIB代理的基本概念

CGLIB代理通过生成目标类的子类并覆盖其方法来实现代理。由于它不需要目标类实现任何接口,因此可以代理那些没有实现接口的类。但CGLIB代理有一个限制:目标类不能是final类,因为Java不允许对final类进行子类化。

### CGLIB代理的应用场景

- **Spring框架**:Spring AOP在使用注解或配置进行切面编程时,如果目标类没有实现接口,Spring默认使用CGLIB代理。
- **无法使用Java内置动态代理的类**:例如没有实现接口的类或需要对类的所有方法进行代理。

### 使用CGLIB代理的示例

要使用CGLIB代理,首先需要引入CGLIB库。在Maven中,你可以添加以下依赖:

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

#### 示例代码

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;// 目标类,没有实现任何接口
public class RealSubject {public void request() {System.out.println("RealSubject: Handling request.");}
}// CGLIB MethodInterceptor 实现
public class CglibProxy implements MethodInterceptor {private Object target;public CglibProxy(Object target) {this.target = target;}// 创建代理对象public Object createProxy() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("CGLIB Proxy: Logging before request.");Object result = proxy.invokeSuper(obj, args);  // 调用父类的方法System.out.println("CGLIB Proxy: Logging after request.");return result;}
}// 测试类
public class Client {public static void main(String[] args) {RealSubject realSubject = new RealSubject();CglibProxy proxy = new CglibProxy(realSubject);RealSubject proxyInstance = (RealSubject) proxy.createProxy();proxyInstance.request();}
}

### CGLIB代理的原理

1. **动态生成子类**:CGLIB通过字节码生成技术在运行时生成目标类的子类。
2. **方法拦截**:通过重写子类的方法,在方法调用前后添加自定义逻辑(如日志记录、权限验证等)。
3. **调用父类方法**:使用`MethodProxy`对象调用被代理的父类方法,确保原有逻辑的执行。

### CGLIB代理的优缺点

**优点**:
- **不依赖接口**:可以代理没有实现接口的类
- **灵活性高**:可以对类的所有方法进行代理。

**缺点**:
- **性能开销**:由于需要生成子类,性能开销较大。
- **限制**:目标类不能是final类,目标方法不能是final方法或静态方法。

### 总结

CGLIB代理提供了一种强大的方法来为没有实现接口的类添加代理逻辑。它广泛应用于AOP、缓存、延迟加载等领域。虽然CGLIB代理性能略低于Java内置动态代理,但在某些场景下是不可替代的。理解和使用CGLIB代理可以帮助开发者在实际项目中更加灵活地控制对象的行为。

代理模式的优点

  1. 职责分离:代理模式将调用过程中的附加职责(如日志记录、访问控制)从主业务逻辑中分离出来。
  2. 透明性:客户端不需要知道代理对象和真实对象的区别,它们可以互换使用。
  3. 控制访问:代理可以控制对真实对象的访问,比如在访问前进行权限验证、延迟加载等。

代理模式的缺点

  1. 开销:代理模式在一定程度上增加了系统的复杂性和调用开销。
  2. 代码复杂:尤其是动态代理,理解和维护起来较为复杂。

通过以上示例,你可以了解到如何在Java中实现代理模式,并理解其基本原理和应用场景。代理模式广泛应用于很多实际开发中,例如RPC框架、AOP(面向切面编程)等。

 2. 桥接模式

桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立地变化。桥接模式的主要意图是通过组合的方式使两个维度的变化独立开来,从而达到解耦的目的。

在桥接模式中,会涉及到以下几个角色:
1. **抽象(Abstraction)**:定义抽象的接口,并持有一个实现接口的引用。
2. **扩展抽象(Refined Abstraction)**:扩展抽象接口,通常会在这里调用实现接口的功能。
3. **实现(Implementor)**:定义实现的接口,该接口不一定要与抽象接口一致,通常提供基本操作。
4. **具体实现(Concrete Implementor)**:实现Implementor接口,提供具体的实现。

### 桥接模式的类图

            Abstraction|RefinedAbstraction|Implementor|ConcreteImplementorA|ConcreteImplementorB

### 案例实现

我们以一个简单的图形绘制为例,通过桥接模式实现不同颜色的图形绘制。这里抽象部分是图形,具体的扩展抽象是不同类型的图形(如圆形和方形),实现部分是颜色,具体实现是不同的颜色(如红色和绿色)。

#### 抽象部分(Abstraction)

// Abstraction
abstract class Shape {protected Color color;protected Shape(Color color) {this.color = color;}public abstract void draw();
}

#### 扩展抽象(Refined Abstraction)

// RefinedAbstraction
class Circle extends Shape {private int radius;protected Circle(Color color, int radius) {super(color);this.radius = radius;}@Overridepublic void draw() {System.out.print("Drawing Circle with radius " + radius + " and ");color.applyColor();}
}// RefinedAbstraction
class Rectangle extends Shape {private int width, height;protected Rectangle(Color color, int width, int height) {super(color);this.width = width;this.height = height;}@Overridepublic void draw() {System.out.print("Drawing Rectangle with width " + width + " and height " + height + " and ");color.applyColor();}
}

#### 实现部分(Implementor)

// Implementor
interface Color {void applyColor();
}

#### 具体实现(Concrete Implementor)

// ConcreteImplementorA
class RedColor implements Color {@Overridepublic void applyColor() {System.out.println("applying Red color.");}
}// ConcreteImplementorB
class GreenColor implements Color {@Overridepublic void applyColor() {System.out.println("applying Green color.");}
}

#### 客户端代码`

public class Client {public static void main(String[] args) {Shape redCircle = new Circle(new RedColor(), 10);redCircle.draw();Shape greenRectangle = new Rectangle(new GreenColor(), 20, 30);greenRectangle.draw();}
}

### 运行结果

Drawing Circle with radius 10 and applying Red color.
Drawing Rectangle with width 20 and height 30 and applying Green color.

桥接模式的优点


1. **分离抽象接口及其实现部分**:可以分别对它们进行修改,而不会对另一部分产生影响。
2. **提高扩展性**:通过组合的方式,可以自由地扩展抽象部分和实现部分,而不会导致类层次结构过于复杂。
3. **符合开闭原则**:对扩展开放,对修改关闭。增加新的抽象部分和实现部分,不需要修改现有代码。

 桥接模式的使用场景


1. **需要跨多个平台的图形和窗口系统**:例如,Java的AWT和Swing。
2. **需要更高的灵活性,能够独立地扩展和变化**:例如,一个系统需要支持不同类型的数据库连接。
3. **系统不希望在抽象和实现部分之间有强耦合**:例如,文件的不同操作(打开、关闭、读取、写入)和文件的不同存储(本地存储、云存储、网络存储)。

3. 装饰器模式

装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。装饰器模式是通过创建一个装饰类来包裹原始类,从而在保持类方法签名完整性的前提下,提供额外的功能。

装饰器模式通常有以下几个角色:
1. **组件(Component)**:这是一个接口或抽象类,定义了要被装饰的对象的行为。
2. **具体组件(ConcreteComponent)**:这是一个具体的类,实现了组件接口,表示被装饰的对象。
3. **装饰(Decorator)**:这是一个抽象类,实现了组件接口,并持有一个组件对象。它主要是为了给具体装饰角色提供一个公共的接口。
4. **具体装饰(ConcreteDecorator)**:这是一个具体的类,继承装饰类,并为组件对象添加额外的行为。

### 类图

           Component|-----------------|               |
ConcreteComponent   Decorator|-----------------|               |ConcreteDecoratorA   ConcreteDecoratorB

### 代码实现

我们以一个简单的例子来说明装饰器模式。假设我们有一个基本的消息发送功能,可以向消息添加加密和压缩功能。

#### 组件(Component)

// Component
public interface Message {String getContent();
}

#### 具体组件(ConcreteComponent)

// ConcreteComponent
public class TextMessage implements Message {private String content;public TextMessage(String content) {this.content = content;}@Overridepublic String getContent() {return content;}
}

#### 装饰(Decorator)

// Decorator
public abstract class MessageDecorator implements Message {protected Message message;public MessageDecorator(Message message) {this.message = message;}@Overridepublic String getContent() {return message.getContent();}
}

#### 具体装饰(ConcreteDecorator)

// ConcreteDecoratorA
public class Base64EncodedMessage extends MessageDecorator {public Base64EncodedMessage(Message message) {super(message);}@Overridepublic String getContent() {return Base64.getEncoder().encodeToString(message.getContent().getBytes());}
}// ConcreteDecoratorB
public class CompressedMessage extends MessageDecorator {public CompressedMessage(Message message) {super(message);}@Overridepublic String getContent() {return compress(message.getContent());}private String compress(String content) {// A simple mock compression, real compression logic can be more complexreturn content.substring(0, content.length() / 2);}
}

#### 客户端代码

public class Client {public static void main(String[] args) {Message message = new TextMessage("Hello, World!");// Add base64 encodingMessage base64Message = new Base64EncodedMessage(message);System.out.println(base64Message.getContent());// Add compressionMessage compressedMessage = new CompressedMessage(message);System.out.println(compressedMessage.getContent());// Add both encoding and compressionMessage encodedAndCompressedMessage = new CompressedMessage(base64Message);System.out.println(encodedAndCompressedMessage.getContent());}
}

### 运行结果

SGVsbG8sIFdvcmxkIQ==
Hello
U0dWc2JHVT0=

### 装饰器模式的优点
1. **更灵活**:相比于继承,装饰器模式提供了一种更灵活的方式来动态地扩展对象的功能。
2. **遵循开闭原则**:可以在不修改现有代码的情况下,向对象添加新的功能。比如这个例子里,不改变原有的消息类,但可以加密,压缩等。
3. **职责分离**:每个装饰器都有其特定的功能,不同的装饰器可以相互组合,来实现复杂的功能。

### 装饰器模式的缺点
1. **复杂性增加**:如果使用过多的装饰器,系统会变得复杂,难以理解和维护。
2. **调试困难**:由于装饰器的多层嵌套,调试时需要逐层跟踪,可能会比较困难。

### 装饰器模式的使用场景
1. **需要动态地为对象添加功能**:例如,Java I/O 类库中的各种流处理类(如 `BufferedReader`、`InputStreamReader` 等)。
2. **需要在不影响其他对象的情况下,为特定对象添加功能**。
3. **需要对一个对象的功能进行多个组合**。

4. 适配器模式

适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。适配器模式可以分为对象适配器和类适配器两种形式。

### 类图

            Target|----------------|              |Adapter         Adaptee|ConcreteAdapter

### 适配器模式的角色
1. **目标接口(Target)**:定义客户端使用的接口。
2. **适配者类(Adaptee)**:定义一个已经存在的接口,这个接口需要适配。
3. **适配器(Adapter)**:实现目标接口,并通过在内部包装一个适配者对象,将目标接口与适配者接口连接起来。

类适配器模式

类适配器模式使用多继承来实现,适配器继承了目标接口和适配者类。由于Java不支持多继承,类适配器模式在Java中并不常见,但我们仍然可以通过其他方式来实现。

// Target interface
public interface Target {void request();
}// Adaptee class
public class Adaptee {public void specificRequest() {System.out.println("Adaptee's specific request.");}
}// Adapter class
public class Adapter extends Adaptee implements Target {@Overridepublic void request() {specificRequest();}
}// Client code
public class Client {public static void main(String[] args) {Target target = new Adapter();target.request();}
}

对象适配器模式

对象适配器模式通过组合(即在适配器内部持有一个适配者对象)来实现。相比于类适配器模式,对象适配器模式在Java中更为常见。

// Target interface
public interface Target {void request();
}// Adaptee class
public class Adaptee {public void specificRequest() {System.out.println("Adaptee's specific request.");}
}// Adapter class,直接实现目标接口,不用继承 待适配的类
public class Adapter implements Target {private Adaptee adaptee;public Adapter(Adaptee adaptee) {this.adaptee = adaptee;}@Overridepublic void request() {adaptee.specificRequest();}
}// Client code
public class Client {public static void main(String[] args) {Adaptee adaptee = new Adaptee();Target target = new Adapter(adaptee);target.request();}
}

### 适配器模式的优点
1. **单一职责原则**:可以将接口或数据转换代码从业务逻辑中分离出来。
2. **开闭原则**:新的适配器可以随时被添加到程序中,使得新的适配者类可以无缝地融入系统。

### 适配器模式的缺点
1. **复杂性增加**:系统中引入了额外的适配器层,增加了代码的复杂性。
2. **性能开销**:由于增加了一层间接调用,可能会导致性能的轻微下降。

### 适配器模式的使用场景
1. **希望复用一些现有的类,但接口又与复用环境要求不一致**。
2. **需要统一多个类的接口时**,可以通过适配器模式将它们的接口统一起来,供外部使用。
3. **需要使用一些现有的子系统,但这些子系统的接口并不符合需求**。

通过适配器模式,我们可以在不修改现有代码的情况下,使得原本由于接口不兼容而不能一起工作的类可以一起工作。这种模式非常适用于系统集成和第三方库的使用场景。

类适配器 vs 对象适配器

  1. 实现方式

    • 类适配器通过继承来实现,它继承了适配者类并实现了目标接口。
    • 对象适配器通过组合来实现,它在内部包含一个适配者类的实例。
  2. 灵活性

    • 类适配器不够灵活,因为它是通过继承来实现的,所以无法适配适配者类的子类。
    • 对象适配器更灵活,因为它是通过组合来实现的,可以适配适配者类及其子类。
  3. 复用性

    • 类适配器复用性较低,因为它是通过继承实现的,需要为每个适配者类创建一个适配器。
    • 对象适配器复用性较高,因为它是通过组合实现的,可以复用一个适配器类来适配多个适配者类。

5. 门面模式(Facade Pattern)

门面模式(Facade Pattern)是一种结构型设计模式,提供了一个简化的接口来访问复杂系统中的子系统。它通过定义一个高层接口,让子系统更容易使用,简化了客户端的交互。

门面模式的主要目的是降低系统的复杂性,提供一个统一的接口来访问子系统的一群接口,从而使得系统更加易用和便于维护。

门面模式的优点

1. **简化使用**:隐藏子系统的复杂性,使得客户端可以更简单地使用子系统。
2. **松耦合**:客户端与子系统的实现解耦,客户端只需要知道门面的接口,而不需要了解子系统的具体实现。
3. **更好的分层**:可以在系统的不同层次之间创建一个清晰的分界线。

示例代码

假设我们有一个复杂的家庭影院系统,包括电视、音响、DVD播放器、灯光等子系统。我们可以通过门面模式来简化家庭影院系统的操作。

1. 子系统类
// 子系统类之一:电视
public class TV {public void on() {System.out.println("TV is ON");}public void off() {System.out.println("TV is OFF");}
}// 子系统类之二:音响
public class SoundSystem {public void on() {System.out.println("SoundSystem is ON");}public void off() {System.out.println("SoundSystem is OFF");}public void setVolume(int volume) {System.out.println("SoundSystem volume set to " + volume);}
}// 子系统类之三:DVD播放器
public class DVDPlayer {public void on() {System.out.println("DVDPlayer is ON");}public void off() {System.out.println("DVDPlayer is OFF");}public void play(String movie) {System.out.println("Playing movie: " + movie);}
}// 子系统类之四:灯光
public class Lights {public void dim() {System.out.println("Lights are dimmed");}public void on() {System.out.println("Lights are ON");}
}
 2. 门面类
// 门面类:家庭影院门面
public class HomeTheaterFacade {private TV tv;private SoundSystem soundSystem;private DVDPlayer dvdPlayer;private Lights lights;public HomeTheaterFacade(TV tv, SoundSystem soundSystem, DVDPlayer dvdPlayer, Lights lights) {this.tv = tv;this.soundSystem = soundSystem;this.dvdPlayer = dvdPlayer;this.lights = lights;}public void watchMovie(String movie) {System.out.println("Get ready to watch a movie...");lights.dim();tv.on();soundSystem.on();soundSystem.setVolume(20);dvdPlayer.on();dvdPlayer.play(movie);}public void endMovie() {System.out.println("Shutting movie theater down...");lights.on();tv.off();soundSystem.off();dvdPlayer.off();}
}
3. 客户端代码
// 客户端代码
public class Client {public static void main(String[] args) {TV tv = new TV();SoundSystem soundSystem = new SoundSystem();DVDPlayer dvdPlayer = new DVDPlayer();Lights lights = new Lights();HomeTheaterFacade homeTheater = new HomeTheaterFacade(tv, soundSystem, dvdPlayer, lights);homeTheater.watchMovie("Inception");homeTheater.endMovie();}
}

### 解释

1. **子系统类**:包括 `TV`、`SoundSystem`、`DVDPlayer` 和 `Lights`,它们分别表示家庭影院系统的不同部分,每个类都有自己的方法来控制设备的操作。
2. **门面类**:`HomeTheaterFacade` 封装了子系统的复杂性,提供了简化的接口(`watchMovie` 和 `endMovie`)来控制整个家庭影院系统。
3. **客户端代码**:`Client` 只需要与 `HomeTheaterFacade` 交互,而不需要了解家庭影院系统的内部实现细节。

门面模式通过提供一个统一的接口,使得客户端代码可以更加简单直观使用复杂子系统,从而提高了系统的可维护性和易用性。

6. 组合模式(Composite Pattern)

组合模式(Composite Pattern)是一种结构型设计模式,它将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

 组合模式的优点

1. **清晰的层次结构**:定义了包含基本对象和组合对象的树形结构。
2. **一致性**:用户可以一致地对待单个对象和组合对象。
3. **易于扩展**:新增叶子节点和组合节点都很容易,无需修改现有类。

### 示例代码

假设我们有一个文件系统,其中包含文件和文件夹。文件夹可以包含文件和其他文件夹。我们可以使用组合模式来表示这种结构。

1. 组件接口
import java.util.ArrayList;
import java.util.List;// 抽象组件
abstract class FileSystemComponent {protected String name;public FileSystemComponent(String name) {this.name = name;}public abstract void show();public void add(FileSystemComponent component) {throw new UnsupportedOperationException();}public void remove(FileSystemComponent component) {throw new UnsupportedOperationException();}public FileSystemComponent getChild(int i) {throw new UnsupportedOperationException();}
}
 2. 叶子节点类
// 叶子节点:文件
class File extends FileSystemComponent {public File(String name) {super(name);}@Overridepublic void show() {System.out.println("File: " + name);}
}
 3. 组合节点类
// 组合节点:文件夹
class Folder extends FileSystemComponent {private List<FileSystemComponent> components = new ArrayList<>();public Folder(String name) {super(name);}@Overridepublic void show() {System.out.println("Folder: " + name);for (FileSystemComponent component : components) {component.show();}}@Overridepublic void add(FileSystemComponent component) {components.add(component);}@Overridepublic void remove(FileSystemComponent component) {components.remove(component);}@Overridepublic FileSystemComponent getChild(int i) {return components.get(i);}
}
 4. 客户端代码
// 客户端代码
public class Client {public static void main(String[] args) {FileSystemComponent file1 = new File("File1.txt");FileSystemComponent file2 = new File("File2.txt");FileSystemComponent file3 = new File("File3.txt");FileSystemComponent folder1 = new Folder("Folder1");FileSystemComponent folder2 = new Folder("Folder2");folder1.add(file1);folder1.add(file2);folder2.add(folder1);folder2.add(file3);folder2.show();}
}

### 解释

1. **组件接口**:`FileSystemComponent` 是一个抽象类,定义了文件系统组件的基本行为,包括 `show`、`add`、`remove` 和 `getChild` 方法。默认情况下,`add`、`remove` 和 `getChild` 方法会抛出 `UnsupportedOperationException`,因为叶子节点不支持这些操作。

2. **叶子节点类**:`File` 类是 `FileSystemComponent` 的子类,表示文件。在 `show` 方法中,它仅输出文件的名称。

3. **组合节点类**:`Folder` 类也是 `FileSystemComponent` 的子类,表示文件夹。它包含一个 `List<FileSystemComponent>` 来存储其子组件。在 `show` 方法中,它输出文件夹的名称并递归地调用每个子组件的 `show` 方法。它还实现了 `add`、`remove` 和 `getChild` 方法来管理其子组件。

4. **客户端代码**:在 `Client` 类中,我们创建了一些文件和文件夹,并将文件添加到文件夹中。通过调用 `folder2.show()` 方法,输出整个文件系统的层次结构。

这意味着客户端代码不需要区分 FileFolder,可以统一对待它们。这种一致性使得代码更加简洁和灵活。通过抽象类 FileSystemComponent,客户端只需要调用组件的接口方法,而不需要关心组件的具体实现类型。 

### 总结

组合模式通过将对象组合成树形结构来表示“部分-整体”的层次结构,允许客户端以一致的方式处理单个对象和组合对象。它适用于需要处理树形结构的场景,如文件系统、UI组件树等。

 7. 享元模式

享元模式(Flyweight Pattern)是一种结构型设计模式,主要用于减少程序中对象的数量,从而节省内存和提高性能。它通过共享细粒度对象,将那些相似或相同的对象共享起来,从而避免对象的重复创建。

### 核心思想

享元模式的核心思想是通过共享减少内存的使用,将对象中的可变部分和不可变部分分开,只将不可变部分进行共享,从而减少内存消耗。这个模式特别适合用于有大量重复对象的场景。

### 适用场景

1. **大量相似对象**:当程序中有大量相似或相同的对象时,享元模式可以减少内存开销。
2. **对象的创建代价高**:对象的创建代价较高,而对象之间的差异非常小,可以通过共享对象减少创建次数。

### 组成部分

享元模式主要包括以下几个部分:

1. **Flyweight(享元接口)**:定义了对象的接口,通过这个接口可以接受外部状态并进行操作。
2. **ConcreteFlyweight(具体享元类)**:实现了享元接口,存储对象的内部状态。
3. **UnsharedConcreteFlyweight(非共享具体享元类)**:并非所有的享元对象都可以共享,非共享对象可以通过这个类来实现。
4. **FlyweightFactory(享元工厂)**:负责创建和管理享元对象,确保可以共享的对象被适当地复用。
5. **Client(客户端)**:使用享元对象的客户端。

### Java 实现

以下是一个使用享元模式的Java代码示例,模拟一个简单的文字处理器,其中的字符对象会被共享。

import java.util.HashMap;
import java.util.Map;// Flyweight 接口
interface CharacterFlyweight {void display(int fontSize);
}// ConcreteFlyweight 实现享元接口
class ConcreteCharacter implements CharacterFlyweight {private final char character;public ConcreteCharacter(char character) {this.character = character;}@Overridepublic void display(int fontSize) {System.out.println("Character: " + character + ", Font size: " + fontSize);}
}// FlyweightFactory 享元工厂
class CharacterFactory {private final Map<Character, CharacterFlyweight> characterMap = new HashMap<>();public CharacterFlyweight getCharacter(char character) {CharacterFlyweight flyweight = characterMap.get(character);if (flyweight == null) {flyweight = new ConcreteCharacter(character);characterMap.put(character, flyweight);}return flyweight;}
}// Client 客户端
public class FlyweightPatternDemo {public static void main(String[] args) {CharacterFactory factory = new CharacterFactory();String document = "hello world";int[] fontSizes = {12, 14, 16, 18, 20};for (int i = 0; i < document.length(); i++) {CharacterFlyweight character = factory.getCharacter(document.charAt(i));character.display(fontSizes[i % fontSizes.length]);}}
}

### 代码解释

- **CharacterFlyweight**:享元接口,定义了一个`display`方法,用于展示字符和字体大小。
- **ConcreteCharacter**:具体享元类,实现了`CharacterFlyweight`接口,内部保存了字符这个不可变状态。
- **CharacterFactory**:享元工厂类,负责创建和管理享元对象。通过这个工厂,客户端可以获取共享的字符对象。
- **FlyweightPatternDemo**:客户端,通过工厂获取字符对象并展示它们,减少了字符对象的重复创建。

### 优点

1. **减少内存消耗**:通过共享对象,避免了大量相似对象的重复创建,从而减少内存的使用。
2. **提高性能**:减少了创建对象的开销,特别是在需要大量对象的场景中,性能提升显著。

### 缺点

1. **增加复杂性**:引入共享对象的管理和区分外部状态与内部状态的逻辑,增加了系统的复杂性。
2. **非共享部分的处理**:如果对象之间的差异较大,享元模式可能会变得复杂且不易维护。

### 总结

享元模式是一种通过共享来减少内存使用的模式,适用于那些有大量相似对象的场景。通过享元模式,可以有效地优化内存的使用,提高程序的性能。不过,这种模式也会带来一定的复杂性,需要根据具体场景权衡使用。

享元模式和单例模式对比

享元模式(Flyweight Pattern)和单例模式(Singleton Pattern)确实有一些相似之处,但它们解决的问题和应用场景是不同的。以下是两者的主要区别:

1. **目的不同**

- **单例模式**的目的是**确保一个类只有一个实例**,并提供全局访问点。它用于控制全局状态或资源访问的场景。例如,日志管理器、配置管理器、线程池等通常使用单例模式。

- **享元模式**的目的是**通过共享多个相似对象来减少内存开销**。它用于大量相似对象的场景中,主要目的是减少对象的数量,优化内存使用。例如,文字处理器中的字符对象、图形应用中的形状对象等。

 2. **实现方式不同**

- **单例模式**确保某个类只有一个实例,通常通过私有构造函数和静态方法来实现。    

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

- **享元模式**则通过工厂方法返回共享的对象实例。这个工厂方法会管理实例的创建和共享。   

public class FlyweightFactory {private Map<String, Flyweight> flyweightMap = new HashMap<>();public Flyweight getFlyweight(String key) {if (!flyweightMap.containsKey(key)) {flyweightMap.put(key, new ConcreteFlyweight(key));}return flyweightMap.get(key);}}
 3. **应用场景不同**

- **单例模式**适用于那些需要全局唯一的对象,比如配置管理类、连接池等。
- **享元模式**适用于那些需要大量相似对象的场景,比如文字处理、游戏开发中的图形元素等。它通过共享对象,避免了创建大量相似的对象。

 4. **实例数量**

- **单例模式**限制一个类只能有**一个实例**,整个应用程序中共享这一个实例。
- **享元模式**允许创建多个实例,但这些实例是**有限的**,并且**共享使用**。比如例子里的字符类,是可以有多个实例的。

 5. **状态管理**

- **单例模式**通常管理的是全局的、不可变的状态。
- **享元模式**将对象的状态分为**内部状态**和**外部状态**。内部状态是共享的,外部状态可以由客户端来传递或管理。

举个例子:

- **单例模式**:假设我们有一个日志管理器,整个系统中只需要一个日志管理器实例来写日志。这个日志管理器就可以使用单例模式来实现。

- **享元模式**:假设我们在开发一款游戏,其中有许多树木对象,每棵树的种类、颜色是相同的,但它们的位置不同。我们可以使用享元模式来共享树的种类和颜色,而位置则由外部传入。

### 总结

- **单例模式**强调的是一个类只有一个实例,全局共享该实例。
- **享元模式**强调的是共享对象,通过共享相同的对象实例来减少内存消耗。

两者虽然都有“共享”的概念,但其目的是不同的,一个是为了控制实例数量,另一个是为了优化资源使用。

 

行为型设计模式

相关文章:

23种设计模式(持续更新中)

参考链接干货分享 | 《设计模式之美》学习笔记 - 知乎 (zhihu.com) 总体来说设计模式分为三大类&#xff1a; 创建型模式&#xff0c;共5种&#xff1a;工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式&#xff0c;共7种&#xff1a;适配器模式、…...

Linux文件系统详解

Linux的一切皆文件 Linux 中的各种事物比如像文档、目录&#xff08;Mac OS X 和 Windows 系统下称之为文件夹&#xff09;、键盘、监视器、硬盘、可移动媒体设备、打印机、调制解调器、虚拟终端&#xff0c;还有进程间通信&#xff08;IPC&#xff09;和网络通信等输入/输出资…...

大数据面试SQL(五):查询最近一笔有效订单

文章目录 查询最近一笔有效订单 一、题目 二、分析 三、SQL实战 四、样例数据参考 查询最近一笔有效订单 一、题目 现有订单表t5_order&#xff0c;包含订单ID&#xff0c;订单时间&#xff0c;下单用户&#xff0c;当前订单是否有效。 请查询出每笔订单的上一笔有效订…...

OpenCV图像滤波(8)getGaborKernel()函数的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 该函数返回 Gabor 滤波器系数。 Gabor 滤波器在图像处理中非常有用&#xff0c;特别是在纹理分析、特征提取和边缘检测等领域。 函数原型 Mat c…...

门店收银系统源码+同城即时零售多商户入驻商城源码

一、我们为什么要开发这个系统&#xff1f; 1. 商户经营现状 “腰尾部”商户&#xff0c;无小程序运营能力&#xff1b;自营私域商城流量渠道单一&#xff1b;无法和线下收银台打通&#xff0c;库存不同步&#xff0c;商品不同步&#xff0c;订单不同步&#xff1b; 2.平台服…...

MaxKB:基于 LLM大语言模型的知识库问答系统实操

1.MaxKB介绍 MaxKB 是一款基于 LLM&#xff08;Large Language Model&#xff09;大语言模型的知识库问答系统。MaxKB 的产品命名内涵为 “Max Knowledge Base”&#xff0c;为用户提供强大的学习能力和问答响应速度&#xff0c;致力于成为企业的最强大脑。与同类基于 LLM 的知…...

linux文件命令:更新文件时间戳的工具touch详解

目录 一、概述 二. touch 命令的基本用途 三. touch 命令的语法 3.1、语法 3.2、touch 命令的选项 3.3、时间字符串格式 四. 常用场景 4.1 创建空文件 4.2 同时创建多个文件 4.3 更新文件的时间戳 4.4 只更新访问时间 4.5 只更新修改时间 4.6 设置特定时间戳 4.7 使用另…...

Docker学习(6):Docker Compose部署案例

一、docker-compose部署mysql 1、准备镜像 2、编写my.cnf配置文件 # 服务端参数配置 [mysqld] usermysql # MySQL启动用户 default-storage-engineINNODB # 创建新表时将使用的默认存储引擎 character-set-serverutf8mb4 # 设置mysql服务端默认字符集…...

4章3节:处理医学类原始数据的重要技巧,R语言中的宽长数据转换,tidyr包的使用指南

在数据分析中,数据的存储方式直接影响分析过程的效率和准确性。常见的数据存储形式有宽型数据(wide format)和长型数据(long format)。宽型数据适合人类查看和理解,而长型数据则更适合计算机处理和分析。为此,R语言提供了tidyr包,用于在这两种数据格式之间进行转换。本…...

[Web安全架构] HTTP协议

文章目录 前言1. HTTP1 . 1 协议特点1 . 2 URL1 . 3 Request请求报文1 . 3 .1 请求行1 . 3 .2 请求头1 . 3 .3 请求正文1 . 3 .4 常见传参方式 1 . 4 Response响应报文1 . 4 .1 响应行1 . 4 .2 响应头1 . 4 .3 响应正文 2. Web会话2 .1 Cookie2 .2 Session2 .3 固定会话攻击 前…...

mysql数据库之运算符

安全等于运算符(<=>) 这个操作符和 = 操作符执行相同的比较操作,不过<=>可以用来判断NULL值。在两个操作数均为NULL时,其返回值为1而不为NULL;而当一个操作数为NULL时,其返回值为0而不为NULL。 下面分别是 SELECT NULL <=>1 SELECT 1<=>0 SEL…...

Spark轨迹大数据高效处理_计算两经纬度点间的距离_使用Haversine formula公式

开发背景 接上文我求的两经纬度点之间的方位角&#xff0c;我的需求里还提到了要计算距离&#xff0c;当然这个距离也是为后面的需求做铺垫的&#xff0c;因此需要求两个经纬度电之间的距离。 不要妄想用勾股定理求出来&#xff0c;实际上距离的计算还是稍微复杂些。这里使用的…...

[C++] : std::copy_n

std::copy_n 是 C 标准库中的一个算法&#xff0c;用于将指定数量的元素从一个输入范围复制到一个输出范围。那这就提供了很灵活的用法了。下面我们举例说明。 语法 template< class InputIt, class Size, class OutputIt > OutputIt copy_n(InputIt first, Size count…...

centos上传工具

yum install lrzsz 安装完成之后 作用是 输入 rz 可以本地上传文件...

【C++】vector习题

一、杨辉三角 class Solution { public:vector<vector<int>> generate(int numRows) {} }; 这里给你一个vector<vector<int>>类型 也就是说vector中的各个数据&#xff0c;存的是各个不同的vector 思路&#xff1a;先给vector开空间&#xff0c;然后…...

Webpack Bundle Analysis:减少包体积的技巧

Webpack 是一个流行的 JavaScript 模块打包器&#xff0c;它能够将项目中各种类型的模块和资源打包成一个或多个 bundle。然而&#xff0c;随着项目的复杂性增加&#xff0c;bundle 的体积也会随之增大&#xff0c;这可能导致加载时间变长&#xff0c;影响用户体验。 Webpack …...

如何利用 ChatGPT 提高工作效率?

内容创作与总结&#xff1a; 写作辅助&#xff1a;可以帮助撰写文章、报告、邮件等各种文本&#xff0c;如为招商银行写宣传文案、写论文、写故事等。学习材料生成&#xff1a;能够生成学习材料&#xff0c;如摘要、抽认卡和测验&#xff0c;帮助学生复习和学习课程。评估和考核…...

使用 Redisson 、Redis实现分布式锁

Redisson 是基于 Redis 实现的一个 Java 框架&#xff0c;为开发者提供了更方便、更高级的 API 和功能。 Redisson 优点&#xff1a; 简单易用的 API&#xff1a;简化了 Redis 操作的代码编写&#xff0c;使开发者能够更专注于业务逻辑。 分布式特性支持&#xff1a;如分布式…...

Typro + PicGo 图床 + Docsify + GitHub Pages,玩转个人知识库搭建,写给小白的建站入门课

自动开了这个号以后&#xff0c;陆陆续续写了很多干货文章&#xff0c;一方面是可以帮助自己梳理思路&#xff0c;另一方面也方便日后查找相关内容。 但是&#xff0c;我想检索某个关键词是在之前哪篇文章写过的&#xff0c;就有点捉急了。CSDN 还好&#xff0c;可以检索到相关…...

多角度文字识别:应对复杂环境的智能解决方案

多角度文字识别&#xff08;Multi-Angle Text Recognition&#xff09;是指在不同视角、不同光照条件和不同背景下对文本进行识别的技术。这项技术在许多应用场景中都非常重要&#xff0c;例如自动驾驶、智能监控、文档数字化等。以下是关于多角度文字识别的一些关键点和摘要&a…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

接口自动化测试:HttpRunner基础

相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具&#xff0c;支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议&#xff0c;涵盖接口测试、性能测试、数字体验监测等测试类型…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

【Linux】自动化构建-Make/Makefile

前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具&#xff1a;make/makfile 1.背景 在一个工程中源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…...