# design-patterns **Repository Path**: xianglf/design-patterns ## Basic Information - **Project Name**: design-patterns - **Description**: 一天一个设计模式 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-03-03 - **Last Updated**: 2023-03-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
每天一个设计模式
## 1. Day 1-简单工厂模式 2023年3月1日 简单工厂模式(Simple Factory Pattern)是一种常见的创建型设计模式,它将对象的创建过程封装到一个工厂类中,从而实现对象的统一管理和维护。在 Java 应用中,简单工厂模式通常应用于以下场景: 1. 当需要创建的对象较少,且这些对象具有相同的父类或接口时,可以考虑使用简单工厂模式。这样可以将对象的创建过程统一管理,降低代码的复杂度和耦合度。 2. 当需要对对象的创建过程进行扩展或修改时,可以考虑使用简单工厂模式。例如,当需要新增一种对象类型时,只需要在工厂类中增加一个创建该对象的方法即可。 下面是一个简单的 Java 应用示例,演示了如何使用简单工厂模式创建不同类型的对象: ``` java // 抽象产品类 interface Product { void show(); } // 具体产品类A class ProductA implements Product { @Override public void show() { System.out.println("This is Product A."); } } // 具体产品类B class ProductB implements Product { @Override public void show() { System.out.println("This is Product B."); } } // 工厂类 class Factory { public static Product createProduct(String type) { switch (type) { case "A": return new ProductA(); case "B": return new ProductB(); default: throw new IllegalArgumentException("Invalid product type."); } } } // 测试类 public class SimpleFactoryPatternDemo { public static void main(String[] args) { Product productA = Factory.createProduct("A"); productA.show(); Product productB = Factory.createProduct("B"); productB.show(); } } ``` 在这个示例中,我们首先定义了一个抽象产品类 `Product`,它有一个 `show()` 方法用于展示产品信息。然后,我们定义了两个具体产品类 `ProductA` 和 `ProductB`,它们实现了 `Product` 接口,并分别实现了 `show()` 方法。 接下来,我们定义了一个工厂类 `Factory`,它有一个静态方法 `createProduct()`,根据传入的产品类型字符串参数,返回相应的产品对象。在这个示例中,我们使用了 switch-case 语句来实现这个方法。 最后,我们在测试类 `SimpleFactoryPatternDemo` 中使用工厂类 `Factory` 创建了两个不同类型的产品对象,并调用它们的 `show()` 方法展示产品信息。 使用简单工厂模式可以帮助我们统一管理对象的创建过程,降低代码的复杂度和耦合度。在 Java 应用中,我们可以使用静态工厂方法、反射、枚举等方式实现简单工厂模式。 除了在 Java 应用中使用静态工厂方法、反射、枚举等方式实现简单工厂模式外,我们还可以通过以下方式更好地使用简单工厂模式: 1. 将工厂类的创建逻辑抽象出来。在实际应用中,我们可能需要根据一些条件来决定创建哪种类型的对象,例如读取配置文件、获取用户输入等。这时,我们可以将工厂类的创建逻辑抽象出来,使得工厂类的创建过程可以更加灵活。 2. 使用依赖注入(Dependency Injection,DI)方式创建工厂类。使用依赖注入方式创建工厂类可以使得工厂类的创建过程更加灵活,便于管理和维护。我们可以通过 DI 框架(如 Spring)来注入工厂类实例,从而实现简单工厂模式。 下面是一个使用依赖注入方式创建工厂类的示例: ```java // 抽象产品类 interface Product { void show(); } // 具体产品类A class ProductA implements Product { @Override public void show() { System.out.println("This is Product A."); } } // 具体产品类B class ProductB implements Product { @Override public void show() { System.out.println("This is Product B."); } } // 工厂类 class Factory { private String type; public Factory(String type) { this.type = type; } public Product createProduct() { switch (type) { case "A": return new ProductA(); case "B": return new ProductB(); default: throw new IllegalArgumentException("Invalid product type."); } } } // 测试类 public class SimpleFactoryPatternDemo { public static void main(String[] args) { Factory factoryA = new Factory("A"); Product productA = factoryA.createProduct(); productA.show(); Factory factoryB = new Factory("B"); Product productB = factoryB.createProduct(); productB.show(); } } ``` 在这个示例中,我们将工厂类的创建逻辑抽象出来,使用构造函数来接收产品类型参数,并在工厂类中实现 `createProduct()` 方法来创建相应类型的产品对象。测试类中,我们通过创建不同类型的工厂类实例来创建不同类型的产品对象。 使用依赖注入方式创建工厂类可以使得工厂类的创建过程更加灵活和可控,便于管理和维护。这种方式也可以和其他设计模式(如工厂方法模式、抽象工厂模式)相结合,实现更复杂的应用场景。 在简单工厂模式中,通常我们会创建一个工厂类(Factory Class),用于创建各种产品对象。使用依赖注入方式创建工厂类可以将工厂类的实例化过程交给容器来完成,从而降低了工厂类的耦合度,增加了代码的可扩展性和可测试性。 下面是一个使用依赖注入方式创建工厂类的示例: 首先,我们需要定义一个产品接口,如: ```java public interface Product { void operate(); } ``` 然后,定义两个产品实现类,如: ```java public class ConcreteProduct1 implements Product { public void operate() { System.out.println("Concrete Product 1 is operating..."); } } public class ConcreteProduct2 implements Product { public void operate() { System.out.println("Concrete Product 2 is operating..."); } } ``` 接下来,定义一个工厂类,如: ```java public class SimpleFactory { private Product product; public SimpleFactory(Product product) { this.product = product; } public Product createProduct() { return product; } } ``` 在这个示例中,我们使用构造函数注入的方式,将产品对象作为参数传入工厂类的构造函数中。这样,在创建工厂类的实例时,我们就可以直接传入具体的产品对象,而不需要再在工厂类中创建产品对象,从而实现了依赖注入的目的。 最后,我们可以通过以下方式使用工厂类来创建产品对象: ```java Product product1 = new SimpleFactory(new ConcreteProduct1()).createProduct(); product1.operate(); Product product2 = new SimpleFactory(new ConcreteProduct2()).createProduct(); product2.operate(); ``` 通过这种方式,我们可以很方便地创建不同的产品对象,而且可以随时更改产品对象,从而实现了更加灵活的设计。同时,依赖注入还可以将对象的创建过程集中到容器中,提高了代码的可维护性和可测试性。 ## 2.Day 2 -策略模式 2023年3月2日 在Java中,策略模式(Strategy Pattern)是一种设计模式,它允许在运行时选择算法的行为,将算法从上下文对象中分离出来,使得算法可以独立于使用它的客户端而变化。 策略模式的基本思想是定义一组算法类(也称为策略类),这些算法类实现了一个共同的接口,然后将算法类的实例作为参数传递给上下文对象(也称为环境对象)。上下文对象根据需要调用不同的算法实例来完成相应的任务。 策略模式的主要角色包括: 1. 策略接口(Strategy Interface):定义算法类的共同接口,规范算法类的实现方式。 2. 具体策略类(Concrete Strategy):实现策略接口,定义具体的算法实现。 3. 上下文对象(Context):持有一个策略接口的引用,并在运行时调用策略实例的算法来完成任务。 策略模式的优点包括: 1. 可以在运行时动态地选择算法实现,使得代码更加灵活和可维护。 2. 将算法与上下文对象解耦,使得算法的变化不会影响到使用算法的客户端。 3. 可以避免使用大量的if-else或switch-case语句,提高代码的可读性和可维护性。 下面是一个简单的Java代码示例,展示了策略模式的实现方式: ```java // 策略接口 interface SortStrategy { void sort(int[] array); } // 具体策略类1 class QuickSortStrategy implements SortStrategy { public void sort(int[] array) { // 实现快速排序算法 } } // 具体策略类2 class BubbleSortStrategy implements SortStrategy { public void sort(int[] array) { // 实现冒泡排序算法 } } // 上下文对象 class Sorter { private SortStrategy strategy; public void setStrategy(SortStrategy strategy) { this.strategy = strategy; } public void sort(int[] array) { strategy.sort(array); } } // 客户端代码 public class Client { public static void main(String[] args) { int[] array = {1, 4, 2, 7, 3, 9}; Sorter sorter = new Sorter(); sorter.setStrategy(new QuickSortStrategy()); sorter.sort(array); // 使用快速排序算法 sorter.setStrategy(new BubbleSortStrategy()); sorter.sort(array); // 使用冒泡排序算法 } } ``` 在上面的示例中,SortStrategy定义了算法类的共同接口,QuickSortStrategy和BubbleSortStrategy实现了具体的算法实现,Sorter持有一个SortStrategy类型的引用,并在sort方法中调用 在策略模式中,我们通常通过传递策略类型参数来选择具体的策略实现。具体实现方式可能因不同的应用场景而异,下面是一种通用的实现方式: 首先,我们需要定义一个策略接口,该接口包含了所有的策略实现方法,例如: ```java public interface Strategy { void execute(); } ``` 接下来,我们定义多个实现了该接口的具体策略类,例如: ```java public class ConcreteStrategyA implements Strategy { @Override public void execute() { // 策略A的具体实现逻辑 } } public class ConcreteStrategyB implements Strategy { @Override public void execute() { // 策略B的具体实现逻辑 } } public class ConcreteStrategyC implements Strategy { @Override public void execute() { // 策略C的具体实现逻辑 } } // ... ``` 接着,我们需要定义一个策略工厂类,该工厂类可以根据传入的策略类型参数来创建对应的策略实现对象,例如: ```java public class StrategyFactory { public static Strategy createStrategy(String strategyType) { if (strategyType.equals("A")) { return new ConcreteStrategyA(); } else if (strategyType.equals("B")) { return new ConcreteStrategyB(); } else if (strategyType.equals("C")) { return new ConcreteStrategyC(); } else { return new DefaultStrategy(); } } } ``` 在该工厂类中,我们根据传入的策略类型参数来创建对应的策略实现对象,如果无法识别该策略类型,则返回一个默认的策略对象。这样,我们就可以根据传入的策略类型选择相应的策略实现了。 最后,我们可以在客户端代码中调用策略工厂类的createStrategy()方法来创建对应的策略实现对象,例如: ```java String strategyType = "A"; Strategy strategy = StrategyFactory.createStrategy(strategyType); strategy.execute(); ``` 在上述代码中,我们根据传入的策略类型参数`strategyType`创建了对应的策略实现对象,并调用该对象的`execute()`方法来执行具体的策略实现。如果我们需要切换到其他的策略实现,只需要修改`strategyType`参数即可,而不需要修改客户端代码。 此外,策略模式还可以通过依赖注入(Dependency Injection,DI)来动态地切换不同的策略实现,从而进一步避免if-else语句的使用。在依赖注入的实现方式中,我们可以将策略实现作为一个对象属性,通过依赖注入的方式来动态地切换不同的策略实现。这种方式可以大大降低代码的耦合度,使得代码更加灵活和可维护。 通过依赖注入(Dependency Injection,DI)方式实现策略模式,可以使用构造函数注入、Setter方法注入和接口注入等方式来实现。 下面以构造函数注入为例来说明: 首先,我们需要定义一个接口,该接口定义了不同策略实现所需要的方法: ```java public interface Strategy { void doSomething(); } ``` 接着,我们定义不同的策略实现类: ```java public class ConcreteStrategyA implements Strategy { @Override public void doSomething() { System.out.println("Strategy A"); } } public class ConcreteStrategyB implements Strategy { @Override public void doSomething() { System.out.println("Strategy B"); } } ``` 接下来,我们定义一个包含策略实现的类Context,该类的构造函数通过依赖注入的方式来动态地注入不同的策略实现: ```java public class Context { private final Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public void execute() { strategy.doSomething(); } } ``` 最后,在客户端代码中,我们可以根据需要来选择具体的策略实现,并将其注入到Context对象中: ```java public static void main(String[] args) { Strategy strategyA = new ConcreteStrategyA(); Strategy strategyB = new ConcreteStrategyB(); Context contextA = new Context(strategyA); Context contextB = new Context(strategyB); contextA.execute(); // 输出 "Strategy A" contextB.execute(); // 输出 "Strategy B" } ``` 在上述代码中,我们通过构造函数注入的方式,将具体的策略实现对象传递给了Context对象,从而实现了动态地切换不同的策略实现。这种方式可以大大提高代码的灵活性和可维护性,避免了大量的if-else语句和switch-case语句的使用。 策略模式本身并不能完全避免if-else语句的使用。在策略模式中,我们通常是通过if-else语句或者switch-case语句来选择具体的策略实现,但是相比于直接使用if-else或者switch-case语句,策略模式将策略的实现与业务逻辑进行了分离,从而使得代码更加易于维护和扩展。 ## 3.Day 3 -装饰模式 2023年3月3日 装饰模式(Decorator Pattern)是一种结构型设计模式,它允许在运行时动态地扩展一个对象的行为。它通过将对象包装在一个装饰器中来实现这一目的,从而为对象提供额外的功能,而无需对对象本身进行修改。 装饰模式的主要思想是,将一个对象包装在一个装饰器中,这个装饰器与原始对象具有相同的接口。装饰器中维护一个指向原始对象的引用,并在调用原始对象的方法时,通过在调用前后添加额外的逻辑来扩展原始对象的行为。 下面以一个简单的咖啡店为例来解释装饰模式的实现。我们假设咖啡店有多种咖啡和调料,每种咖啡和调料都有不同的价格。为了计算订单的价格,我们可以定义一个基础的咖啡类: ```java public class Coffee { private String description; private double price; public Coffee(String description, double price) { this.description = description; this.price = price; } public String getDescription() { return description; } public double getPrice() { return price; } } ``` 然后我们定义一些调料类,例如牛奶、糖浆和摩卡等: ```java public class Milk extends Coffee { public Milk(Coffee coffee) { super(coffee.getDescription() + ", Milk", coffee.getPrice() + 0.5); } } public class Syrup extends Coffee { public Syrup(Coffee coffee) { super(coffee.getDescription() + ", Syrup", coffee.getPrice() + 1.0); } } public class Mocha extends Coffee { public Mocha(Coffee coffee) { super(coffee.getDescription() + ", Mocha", coffee.getPrice() + 1.5); } } ``` 在上面的代码中,我们定义了三个调料类,它们都继承自Coffee类,重写了父类中的getDescription()和getPrice()方法。这些调料类的构造方法都接受一个Coffee对象,将调料的价格添加到原始咖啡的价格上,并将调料的描述添加到原始咖啡的描述中。 最后,我们可以在客户端代码中创建一个咖啡对象,然后通过将这个对象包装在不同的调料类中来扩展咖啡的行为,例如: ```java Coffee coffee = new Coffee("Espresso", 2.0); coffee = new Milk(coffee); coffee = new Syrup(coffee); coffee = new Mocha(coffee); System.out.println(coffee.getDescription() + ": $" + coffee.getPrice()); ``` 在上面的代码中,我们首先创建了一个名为“Espresso”的基础咖啡对象,然后通过将它包装在Milk、Syrup和Mocha对象中来添加调料。最后,我们打印出咖啡的描述和价格。 三个主要角色: 1. Component(抽象构件):抽象构件定义了一个抽象接口,声明了被装饰对象和装饰操作的基本方法。在咖啡的例子中,Component就是咖啡的抽象类,定义了一个getDescription()方法和一个cost()方法。 2. ConcreteComponent(具体构件):具体构件是实现抽象构件接口的类,它定义了基本的功能,也就是被装饰的对象。在咖啡的例子中,Espresso、HouseBlend等都是具体构件。 3. Decorator(装饰者):装饰者是抽象构件的子类,它持有一个被装饰对象的实例,并定义了一个与抽象构件接口一致的接口。装饰者可以在装饰对象的基础上,增加一些额外的行为。在咖啡的例子中,Milk、Soy、Whip等都是装饰者,它们都继承自抽象构件Beverage,并持有一个被装饰对象的引用,即被装饰的咖啡对象。装饰者还可以在自己的getDescription()方法中,增加自己的描述,来说明自己的装饰行为。 装饰模式的核心在于,装饰者与被装饰者实现相同的接口,从而能够互相替代。而具体的装饰行为,则是通过组合方式来实现的,即在装饰者中持有一个被装饰对象的引用。这样,在客户端代码中,可以动态地组合不同的装饰者,从而扩展被装饰对象的功能。 ## 4.Day 4 -代理模式 2023年3月6日 代理模式是一种结构型设计模式,它允许你提供一个代替另一个对象的替身或占位符,以控制对原对象的访问。在代理模式中,代理类和原始类都实现相同的接口,因此代理类可以用来替代原始类,以便在需要时提供额外的功能或控制访问。 代理模式包括以下几个角色: 1. 抽象主题(Subject):定义代理类和原始类的公共接口,这样在任何使用原始类的地方都可以使用代理类。 2. 代理类(Proxy):保存一个引用,使得代理可以访问实体,并提供与 Subject 相同的接口,这样代理就可以用来代替实体。 3. 实体类(Real Subject):定义代理所代表的对象,是我们最终要引用的对象。 在Java中,代理模式常用于动态代理、AOP等方面的实现。Java提供了两种代理方式:基于接口的代理和基于类的代理。基于接口的代理需要实现接口,而基于类的代理则需要继承相应的类。在使用代理模式时,需要考虑代理类和实体类的共同接口,以便代理类可以替代实体类,同时可以提供额外的功能。 下面是一个简单的 Java 代码例子来说明代理模式的实现: ```java // 抽象主题角色 interface Subject { void request(); } // 真实主题角色 class RealSubject implements Subject { public void request() { System.out.println("真实主题角色的request方法被调用!"); } } // 代理主题角色 class ProxySubject implements Subject { private RealSubject realSubject; public void request() { if (realSubject == null) { realSubject = new RealSubject(); } preRequest(); realSubject.request(); postRequest(); } public void preRequest() { System.out.println("代理主题角色的preRequest方法被调用!"); } public void postRequest() { System.out.println("代理主题角色的postRequest方法被调用!"); } } // 测试类 public class ProxyPatternDemo { public static void main(String[] args) { ProxySubject proxySubject = new ProxySubject(); proxySubject.request(); } } ``` 在上面的代码中,我们定义了一个 `Subject` 接口,包含了一个 `request` 方法。`RealSubject` 是真实主题角色,实现了 `Subject` 接口,实现了 `request` 方法。`ProxySubject` 是代理主题角色,也实现了 `Subject` 接口,内部持有一个 `RealSubject` 对象,当调用 `request` 方法时,会先执行代理主题角色的 `preRequest` 方法,然后调用真实主题角色的 `request` 方法,最后执行代理主题角色的 `postRequest` 方法。 在测试类中,我们创建了一个 `ProxySubject` 对象并调用其 `request` 方法,输出结果为: ```shell 代理主题角色的preRequest方法被调用! 真实主题角色的request方法被调用! 代理主题角色的postRequest方法被调用! ``` 从输出结果可以看出,代理主题角色在调用真实主题角色的 `request` 方法前后,增加了一些额外的逻辑。这就是代理模式的作用,通过代理对象来控制对真实对象的访问,并在访问对象前后增加一些额外的操作,比如权限控制、缓存、记录日志等。 代理模式的主要作用是在不改变原有代码的情况下,为对象提供一种代理来控制对其的访问。代理模式在软件开发中有多种用途,主要包括以下几点: 1. 远程代理:为远程的对象提供本地的代理,可以隐藏对象存在于不同地址空间的事实。 2. 虚拟代理:根据需要创建开销很大的对象,通过代理来存储对象的一些基本信息,真正需要时再创建对象。 3. 安全代理:控制对真实对象的访问权限。 4. 智能指引:当调用真实的对象时,代理处理另外一些事情,比如记录日志,计算引用次数等。 在JavaWeb开发中,代理模式可以被广泛地应用于各种场景。以下是一些代理模式的使用场景及好处: 1. 保护目标对象:代理模式可以保护目标对象,使得客户端不能直接访问目标对象。在某些情况下,目标对象可能会受到来自客户端的不良访问,使用代理模式可以对这些访问进行过滤和控制。 2. 缓存目标对象:代理模式可以将一些常用的目标对象缓存起来,减少重复创建和销毁目标对象的开销。 3. 实现懒加载:代理模式可以实现懒加载,当客户端请求某个对象时,代理对象才会真正地创建目标对象,从而避免了不必要的资源浪费。 4. 远程访问:代理模式可以实现远程访问,即客户端可以通过代理对象访问远程的目标对象。 5. 性能优化:代理模式可以在访问目标对象之前或之后执行一些附加操作,从而实现性能优化。 例如,在JavaWeb中,常常使用动态代理来实现事务处理。在进行数据库操作时,开发者需要对事务进行控制,以确保操作的原子性和一致性。使用动态代理可以在方法执行前后自动开启和提交事务,避免了手动编写繁琐的事务代码。同时,代理模式还可以实现AOP(面向切面编程),将一些通用的操作(如日志记录、性能监控等)与业务逻辑分离,提高了代码的可维护性和可扩展性。 ## 4.Day 5 -工厂方法模式 2023年3月7日 工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它提供了一种将创建对象的过程委托给子类的方式,从而让子类决定要实例化的类是哪一个。 在工厂方法模式中,我们不再提供一个统一的工厂来创建对象,而是由具体的工厂子类来创建对象。这样的好处是更容易扩展,当需要添加新的产品对象时,只需要添加一个具体的工厂类即可。 在工厂方法模式中,通常会定义一个抽象工厂类,其中声明了工厂方法用于返回一个产品。而具体的产品则由具体工厂类来创建。 以下是一个简单的工厂方法模式的示例: ```java // 抽象产品类 public abstract class Product { public abstract void use(); } // 具体产品类 public class ConcreteProduct extends Product { @Override public void use() { System.out.println("使用具体产品"); } } // 抽象工厂类 public abstract class Factory { public abstract Product createProduct(); } // 具体工厂类 public class ConcreteFactory extends Factory { @Override public Product createProduct() { return new ConcreteProduct(); } } // 客户端代码 public class Client { public static void main(String[] args) { Factory factory = new ConcreteFactory(); Product product = factory.createProduct(); product.use(); } } ``` 在上述示例中,抽象工厂类 `Factory` 声明了一个工厂方法 `createProduct`,用于返回一个抽象产品类 `Product`。具体工厂类 `ConcreteFactory` 通过实现工厂方法 `createProduct` 来创建具体产品类 `ConcreteProduct`。 客户端代码可以通过创建具体工厂类的实例并调用其工厂方法来获取具体产品对象。这样,如果需要添加新的产品类,只需要添加一个具体产品类和一个对应的具体工厂类即可。 工厂方法模式的优点包括: - 可以在不修改客户端代码的情况下引入新产品。 - 可以将对象的创建和使用分离,降低系统的耦合性。 - 可以更好地符合开闭原则,即对扩展开放,对修改关闭。 工厂方法模式的缺点包括: - 由于需要定义抽象工厂类和抽象产品类,因此代码量可能会增加。 - 每个具体产品类都需要对应一个具体工厂类,这可能会导致类的数量增加。