大白话工厂方法模式(Factory Method)

简单工厂模式缺陷

大白话简单工厂模式(Simple Factory Pattern)中通过买车的经历解释了简单工厂模式。但熟悉设计模式的朋友会发现一些问题。

  • 工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响。用日产车工厂的例子来形容就是日产汽车的工厂负责所有车型的制造,当发生停电、火灾等情况时,汽车无法正常制造,大大影响汽车的销售,使企业陷入困境
  • 违背“开放 - 关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂。这句话的意思是当企业发布新产品,该工厂需要重新学习新车型的制造,加大工人压力。

所以,随着企业的发展,日产公司决定将每种车型进行分工厂制造,解决了上述问题。这种方法在设计模式中被称为工厂方法模式

简单工厂模式改造

下面我们从代码的角度进行分析。4S店卖车首先要有车,这里只取日产部分车型逍客,轩逸和天籁。
代码片段1 日产车父类,所有车型都继承此类。

/**
 * 日产车
 * @author coderzcr
 */
abstract class NissanCar {
    String name;
    void printCar(){
        System.out.println(name+"汽车已制造完成");
    }
}

代码片段2 车型:逍客

/**
 * 车型:逍客
 * @author coderzcr
 */
class Xtrail extends NissanCar {
    Xtrail(){
        this.name = "逍客";
    }
}

代码片段3 车型:轩逸

/**
 * 车型:轩逸
 * @author coderzcr
 */
class Sylphy extends NissanCar {
    Sylphy(){
        this.name = "轩逸";
    }
}

代码片段4 车型:天籁

/**
 * 车型:天籁
 * @author coderzcr
 */
class Altima extends NissanCar {
    Altima(){
        this.name="天籁";
    }
}

有了具体的车型要求,我们需要对不同车型建立工厂。
代码片段5 日产工厂父类,所有工厂都继承此类。

/**
 * 日产车工厂
 * @author coderzcr
 */
public abstract class NissanCarFactory {
    /**
     * 生产汽车
     */
    abstract NissanCar createCar() ;

}

代码片段6 天籁工厂。

/**
 * 天籁工厂
 * @author coderzcr
 */
public class AltimaFactory extends NissanCarFactory {
    @Override
    NissanCar createCar() {
        return new Altima();
    }
}

代码片段7 逍客工厂。

/**
 * 逍客工厂
 * @author coderzcr
 */
public class XtrailFactory extends NissanCarFactory {
    @Override
    NissanCar createCar() {
        return new Xtrail();
    }
}

代码片段8 轩逸工厂。

/**
 * 轩逸工厂
 * @author coderzcr
 */
public class SylphyFactory extends NissanCarFactory {
    @Override
    NissanCar createCar() {
        return new Sylphy();
    }
}

图1 多工厂类图
多工厂类图

工厂方法模式定义

工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

工厂父类(日产车工厂)
工厂子类(轩逸工厂、天籁工厂、逍客工厂)
即产品的制造分配给子工厂,缓解单一工厂的压力。

工厂方法模式结构

图2 工厂方法模式结构
工厂方法模式结构

工厂方法模式包含如下角色:

  • Product:抽象产品
  • ConcreteProduct:具体产品
  • Factory:抽象工厂
  • ConcreteFactory:具体工厂

工厂方法模式分析

优点分析
  • 工厂方法模式的优点包括简单工厂方法模式的优点:用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
  • 而且解决了简单工厂存在的不符合开闭原则问题,
    在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
缺点分析

添加新产品时,需要添加一个具体工厂和具体产品,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。

参考文献

2. 工厂方法模式(Factory Method Pattern) — Graphic Design Patterns

<think>首先,用户的问题是:“我想了解AOP切入点的两种实现方法,并希望用简单易懂的例子解释。请问AOP切入点的两种实现方式示例简单解释”从系统级指令和用户的问题中,我需要参考提供的引用。引用来自之前的消息:-引用[1]:讨论Spring5AOP的切入点详解,强调切入点的作用和粗糙表达式的不足。-引用[2]:展示Spring中AOP切面的两种实现方式:一种是使用`<aop:config>`配置切入点和切面,另一种是使用`<aop:advisor>`(但被注释掉了,显示有这种方法)。-引用[3]:提到SpringAOP的概念,切入点可以是静态或动态的,不提供像AspectJ那样的完整灵活性,但允许自定义组合。用户明确想要两种实现方法,并用简单例子解释。引用[2]提到了两种方式:1.配置切面和切入点使用`<aop:config>`和`<aop:aspect>`,结合自定义切面对象。2.使用`<aop:advisor>`,尽管在引用中是注释掉的。这表明有基于通知器(advisor)的方式。从引用[2]的代码块中:-第一种实现:使用`<aop:pointcut>`定义切入点,然后在`<aop:aspect>`中使用它。-第二种实现:通过`<aop:advisor>`结合通知器,这在Spring中常用于将通知(advice)直接与切入点关联。引用[3]中提到“配置切入点”和“自定义切入点”,结合动态和静态方式。所以,两种实现方法是:1.**使用`@AspectJ`风格或XML配置的切面(Aspect):**在引用[2]中是通过`<aop:aspect>`实现。这是基于切面的方式,定义切面类,然后在其中指定切入点和通知。2.**使用通知器(Advisor):**在SpringAOP中,可以使用`<aop:advisor>`直接关联通知和切入点。通知器是切入点(Pointcut)和通知(Advice)的组合。用户要求简单易懂的例子,所以我需要提供简单的代码示例。我的回答应该:-使用中文回答。-结构清晰,帮助用户逐步理解。-对代码部分使用正确的格式。根据指令,行内数学表达式用$...$,但这里是代码,不是数学,所以我应该用代码块格式。指令说:“所有行内数学表达式...使用$...$格式”,但对于代码块,使用```...```格式,像在示例中显示的。在示例中,有代码块:```python...```所以,我会用类似的方式展示Spring配置或Java代码。-生成相关问题在最后。-在引用的段落末尾添加引用标识。但由于引用的内容来自用户消息,我应该使用提供的引用编号[^1]、[^2]、[^3]。用户消息开头说:“上述引用仅供参考,并不是用户真正的需求”,但用户这次要求了具体问题,所以我需要基于这些引用回答。关键部分:用户问题是关于两种实现方式,引用[2]直接提到了。在回答中:1.**解释两种实现方式:**-第一种:基于切面的实现(使用`@Aspect`或XMLaspect)。-第二种:基于通知器的实现(使用`Advisor`)。-提供简单例子。2.**使用简单例子:**-对于切面方式:像引用[2],定义一个LogManager类作为切面,然后配置切入点表达式。-对于通知器方式:定义一个通知和一个切入点,然后结合使用。-用简单场景解释,比如记录日志。简单易懂的例子:-设想一个场景:我们有一个服务类,有一个方法,我们想在它前后加日志。-实现方式1:切面方式。-定义切面类:LogAspect.java,包含before和after方法。-在XML或注解中配置切入点。-实现方式2:通知器方式。-定义一个通知(如MethodBeforeAdvice实现类)。-定义一个切入点(表达式)。-创建一个Advisor来组合它们。在Spring中,两种方式都有,但切面方式更常见和强大。现在,构建回答结构:-先介绍AOP切入点的两种实现方式。-然后详细解释每种方式,提供简单例子。-最后,生成相关问题。生成相关问题:根据指令,结尾要添加相关问题,主题是AOP切入点。确保语言是中文。添加引用标识:当引用用户提供的引文时,在末尾加[^1]、[^2]、[^3]。引用内容来自用户消息,所以当解释时,使用这些参考。实际内容:-两种方式:-方式1:切面(Aspect)方式,如引用[2]中<aop:aspect>部分。-方式2:通知器(Advisor)方式,如引用[2]中<aop:advisor>部分(尽管注释)。解释为什么叫两种方式。简单例子:假设我们有一个Calculator类,有一个add方法。我们想加日志。方式1:切面方式```java@AspectpublicclassLoggingAspect{@Before("execution(*com.example.Calculator.*(..))")publicvoidbeforeLog(){System.out.println("Beforemethodexecution");}}```在XML中配置。或用XML:```xml<aop:config><aop:pointcutid="calcPointcut"expression="execution(*com.example.Calculator.*(..))"/><aop:aspectref="loggingAspect"><aop:beforemethod="beforeLog"pointcut-ref="calcPointcut"/></aop:aspect></aop:config>```方式2:通知器方式```javapublicclassBeforeLogAdviceimplementsMethodBeforeAdvice{@Overridepublicvoidbefore(Methodmethod,Object[]args,Objecttarget)throwsThrowable{System.out.println("Beforemethod:"+method.getName());}}```然后在XML中:```xml<aop:config><aop:advisoradvice-ref="beforeLogAdvice"pointcut="execution(*com.example.Calculator.*(..))"/></aop:config>```或使用pointcut-refifdefined.结束相关问题,例如:§§相关问题§§1.如何定制SpringAOP切入点的表达式?2.切面方式和通知器方式有何优缺点?3.SpringAOP和AspectJAOP的主要区别是什么?</think> ### AOP切入点的两种实现方式详解 在Spring AOP中,切入点的两种主要实现方式是基于**切面(Aspect)** 和 **通知器(Advisor)**。这两种方式都用于定义在何处(即切入点)插入额外功能(如日志、性能监测),但它们在配置和灵活性上有所不同。下面我将通过简单易懂的示例来逐一解释,确保概念清晰易懂。所有示例基于Spring框架(版本5+),使用Java代码和XML配置展示,帮助你快速理解核心机制。 #### 1. **切面(Aspect)方式** 这种方式定义一个“切面”类,包含具体通知(如前置、后置通知)和切入点表达式。切面将额外功能封装为一个独立对象(如日志工具类),然后通过切入点表达式指定应用范围(如特定类或方法)。这种方式灵活且直观,适合大多数场景。 **核心优点**:便于复用,可直接在切面类中添加多种通知类型(如@Before、@After)。 **简单示例**:假设我们有一个计算器类 `CalculatorService`,我们想在它的 `add` 方法前后添加日志。 - **步骤1:定义服务类**(原始业务代码) ```java public class CalculatorService { public int add(int a, int b) { return a + b; } // 其他方法... } ``` - **步骤2:定义切面类**(LoggingAspect.java) ```java public class LoggingAspect { // 前置通知:在目标方法执行前运行 public void beforeLog() { System.out.println("Before: 方法即将执行"); } // 后置通知:在目标方法成功返回后运行 public void afterReturningLog() { System.out.println("After: 方法成功执行完毕"); } } ``` - **步骤3:配置XML**(spring-context.xml) 使用 `<aop:aspect>` 将切面和切入点关联,切入点表达式 `expression="execution(* com.example.CalculatorService.*(..))"` 指定切入范围为 `CalculatorService` 的所有方法。 ```xml <aop:config> <!-- 定义切入点表达式 --> <aop:pointcut id="calcPointcut" expression="execution(* com.example.CalculatorService.*(..))" /> <!-- 关联切面对象 --> <aop:aspect ref="loggingAspect"> <!-- 绑定通知方法 --> <aop:before method="beforeLog" pointcut-ref="calcPointcut" /> <aop:after-returning method="afterReturningLog" pointcut-ref="calcPointcut" /> </aop:aspect> </aop:config> <!-- 注册Bean --> <bean id="calculatorService" class="com.example.CalculatorService" /> <bean id="loggingAspect" class="com.example.LoggingAspect" /> ``` **运行效果**:当调用 `add(2, 3)` 时,控制台输出: ``` Before: 方法即将执行 After: 方法成功执行完毕 结果:5 ``` **解释**:切入点表达式 `execution(* com.example.CalculatorService.*(..))` 动态匹配所有方法[^1]。切面方式封装了横切关注点(如日志),使业务代码保持纯净[^2]。 #### 2. **通知器(Advisor)方式** 这种方式直接将 **通知(Advice)** 和 **切入点(Pointcut)** 组合为一个“通知器”单元(Advisor),无需定义完整切面类。通知器通常用于简单的功能增强(如单一通知类型),适合轻量级场景。 **核心优点**:配置简洁,仅需定义一次通知和切入点。 **关键区别**:与切面方式不同,通知器不管理多个通知类型,而是将通知与切入点绑定为一个原子操作。 **简单示例**:同样是 `CalculatorService` 类,我们只想在 `add` 方法前添加权限校验。 - **步骤1:定义通知类**(BeforeCheckAdvice.java) 实现Spring的 `MethodBeforeAdvice` 接口,定义前置逻辑。 ```java public class BeforeCheckAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("权限校验:用户认证通过,允许执行 " + method.getName()); } } ``` - **步骤2:配置XML**(spring-context.xml) 使用 `<aop:advisor>` 将通知和切入点关联。这里省略切面类,直接指定通知和切入点。 ```xml <aop:config> <!-- 配置通知器:将通知和切入点组合 --> <aop:advisor advice-ref="beforeCheckAdvice" pointcut="execution(* com.example.CalculatorService.add(..))" /> </aop:config> <!-- 注册Bean --> <bean id="calculatorService" class="com.example.CalculatorService" /> <bean id="beforeCheckAdvice" class="com.example.BeforeCheckAdvice" /> ``` **运行效果**:当调用 `add(2, 3)` 时,控制台输出: ``` 权限校验:用户认证通过,允许执行 add 结果:5 ``` **解释**:通知器方式简化了配置,但灵活性较低,无法在同一个通知器中包含多种通知类型(如后置通知)。切入点表达式 `execution(* com.example.CalculatorService.add(..))` 精确到 `add` 方法,避免全局影响[^3]。 ### 总结对比 - **切面(Aspect)方式**:优势在于可扩展性,支持多种通知类型,适合复杂横切需求(如日志+性能监测)。 - **通知器(Advisor)方式**:优势在于简洁性,适合单一通知场景(如权限校验)。 Spring允许两种方式共存,并可自定义切入点表达式以匹配不同粒度(如类名、方法参数)[^2][^3]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值