【问题标题】:How to add control over invoking of methods in existing sub classes without modifying the sub classes?如何在不修改子类的情况下添加对现有子类中方法调用的控制?
【发布时间】:2014-02-19 14:45:54
【问题描述】:

我有一些BaseClass 和一些方法void doSomething()

foSomething 有不同的方法,它们由SubClass1SubClass2SubClass3 实现。

现在我想向BaseClass 添加一个Boolean active 属性,这样当在实例上调用doSomething 时,它只会返回而不做任何事情。

我知道我可以将BaseClass 编码为doSomething(),看起来像:

Void doSomething(){
   if (this.getActive()) actuallyDoSomething();
}

然后是@Override actuallyDoSomething(),而不是子类中的@Override doSomething()。 但感觉不对……从某种意义上说,已经同意子类应该为doSomething() 提供一个实现,而他们不知道actuallyDoSomething()

我也可以让每个子类在其实现doSomething() 的开头添加一个if (!this.getActive()) return;,但这似乎也是错误的,因为它的通用功能我希望保持通用。

执行此操作的常用/最佳做法是什么? 不改子类可以吗?

更新

Q 的重点不是设计此类功能的正确方法(这很简单), 但是关于如何在不破坏任何内容的情况下将此类功能添加到现有场景中。

active 默认为 true,但希望在任何上述子类的任何实例上,如果有人调用 setActive(false),那么它将变为非活动状态,并且连续调用 .doSomething() 不会做任何事情。 ..

【问题讨论】:

  • 在您的基类中创建 ActuallyDoSomething() abstractprotected。让它在子类中实现。 DoSomething() 应该是 final。在 Java 中,遵循 Java 命名约定。
  • 所以目的是在所有类中设置active 标志的功能 - 也在派生类中?如果是这种情况,那么 Sotirios Delimanolis 建议的解决方案似乎是最合适的。否则,根据实际意图,您可能会考虑一些“包装器”类,仅在设置标志时委托调用 - 例如if (active) delegate.doSomething()
  • 更改了我的方法的字母大小写(抱歉)。使用 abstract protected actuallyDoSomething() 是一个好主意,如果这是计划开始的方式......我的 Q 是关于改变现有情况。 (也许答案保持不变)......是否没有合理的方法来做到这一点 1)不引入改变类? 2) 不引入新的不同方法?如果 BaseClass 有几十个或几百个子类,你会怎么做?你会改变所有这些吗?以及所有已经使用它们的代码?
  • @epeleg 由于多态性,您不能强制对子类对象的调用首先通过父类的实现。会有相当大的改变要做。您可以通过首先将doSomething() 的方法名称重构为actuallyDoSomething() 来最小化它们,然后通过添加新的doSomething() 方法来更改父类。
  • @Alex,我对 Aspect Oriented Programming 不够熟悉,不知道如何使用您的评论。

标签: java class methods


【解决方案1】:

您想使用来自 AspectJ 的 @Around 建议并执行以下操作:

// Let subClass instances run normally...
cec.setActive(true);
letThemDoSomething("BEFORE", sbc1, sbc2, sbc3);

// Now change existing scenario...
cec.setActive(false);
letThemDoSomething("AFTER", sbc1, sbc2, sbc3);

这将输出:

BEFORE ======
SubClass1: doSomething() called.
SubClass2: doSomething() called.
SubClass3: doSomething() called.

AFTER ======
Blocking instance<1> method: my.first.spring.aop.aspectj.SubClassN#doSomething([]) !!
Blocking instance<2> method: my.first.spring.aop.aspectj.SubClassN#doSomething([]) !!
Blocking instance<3> method: my.first.spring.aop.aspectj.SubClassN#doSomething([]) !!

在下面几行中,我将描述如何通过注释实现这一点。
我也会在这里使用 Spring。它有助于使配置更快更容易。


1- 配置

工具和依赖项

Java 7、AspectJ 1.7.4、Spring 4.0.2

项目结构

pom.xml

<project ...>

  <properties>
     <maven.compiler.source>1.7</maven.compiler.source>
     <maven.compiler.target>1.7</maven.compiler.target> 

     <spring.version>4.0.2.RELEASE</spring.version>
     <aspectj.version>1.7.4</aspectj.version>
  </properties>

  <dependencies>
    <!-- Spring -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!-- AspectJ -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>${aspectj.version}</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>${aspectj.version}</version>
    </dependency>
  </dependencies>

</project>

2 - 现有代码

BaseClass.java

public class BaseClass {
    public void doSomething() {

    }

    public void say(String msg) {
         System.out.println(msg);
    }
}    

SubClassN.java

public class SubClassN extends BaseClass {
    private Integer index;

    public SubClassN(Integer index) {
        this.index = index;
    }

    @Override
    public void doSomething() {
        say("SubClass" + index + ": doSomething() called.");
    }

    public Integer getIndex() {
        return index;
    }
}

3 - 更改现有代码(不破坏任何内容...)

AspectJ 和它的@Around 建议来了。当调用任何doSomething 方法时,我们将首先要求AsjectJ 调用特定方法doSomething 可以位于 BaseClass 或其任何子类中的任何位置。

这个特殊方法被称为changeExistingScenario。它可以有任何名称。这里重要的是放在上面的注解。

关于@Around 值的一句话:

执行(* my.first.spring.aop.aspectj.BaseClass.doSomething(..))

这个表达式只是表示我们想要拦截的方法签名模式。
它会拦截 BaseClass 或子类中的任何 doSomething 方法 多少个参数,返回类型和访问修饰符。

更多详情见:http://guptavikas.wordpress.com/2010/04/15/aspectj-pointcut-expressions/

ChangeExistingCode.java

@Aspect // Mark ChangeExistingCode as the class for modifying the code 
@Component
public class ChangeExistingCode {
    private boolean active;

    public void setActive(boolean active) {
        this.active = active;
    }

    /**
     *
     * This method will be called by AspectJ anytime a `doSomething` method is called.
     *
     * This will give us a chance to decide whether the `doSomething` method should
     * be called or not.
     *
     */
    @Around("execution(* my.first.spring.aop.aspectj.BaseClass.doSomething(..))")
    public void changeExistingScenario(ProceedingJoinPoint joinPoint) throws Throwable {
        // Is active ?
        if (active) { // Yes, let doSomething() run as usual
            joinPoint.proceed();
        } else {// No, block doSomething() invokation
            Signature s = joinPoint.getSignature();

            System.out.format( //
                    "Blocking instance<%d> method: %s#%s(%s) !!\n", //
                    ((SubClassN)joinPoint.getTarget()).getIndex(), //
                    s.getDeclaringTypeName(), //
                    s.getName(), //
                    Arrays.toString(joinPoint.getArgs()) //
                    );
        }
    }
}

4- 让所有的魔法出现......

Main.java

@Configuration // Mark the Main class as the class where Spring will find its configuration
@ComponentScan // Ask Spring to look for other components within the Main class package
@EnableAspectJAutoProxy // Let Spring auto configure AspectJ aspects for us...
public class Main {

    private static int subClassCounter;

    public static void main(String[] args) {
        subClassCounter=0;

        GenericApplicationContext  context = new AnnotationConfigApplicationContext(Main.class);

        SubClassN sbc1 = context.getBean(SubClassN.class);
        SubClassN sbc2 = context.getBean(SubClassN.class);
        SubClassN sbc3 = context.getBean(SubClassN.class);

        ChangeExistingCode cec = context.getBean(ChangeExistingCode.class);

        // Let subClass instances run normally...
        cec.setActive(true);
        letThemDoSomething("BEFORE", sbc1, sbc2, sbc3);

        // Now change existing scenario...
        cec.setActive(false);
        letThemDoSomething("AFTER", sbc1, sbc2, sbc3);

        context.close();
    }

    private static void letThemDoSomething(String prefix, SubClassN... existingClasses) {
        System.out.format("%s ======\n", prefix);
        for (SubClassN subClassInstance : existingClasses) {
            subClassInstance.doSomething();
        }
        System.out.println();
    }

    @Bean // Tell Spring to use this method for creating SubClassN instances
    @Scope(BeanDefinition.SCOPE_PROTOTYPE) // Scope prototype force creation of multiple instances
    private static SubClassN buildSubClassN() {
        subClassCounter++;
        return new SubClassN(subClassCounter);
    }
}

输出

BEFORE ======
SubClass1: doSomething() called.
SubClass2: doSomething() called.
SubClass3: doSomething() called.

AFTER ======
Blocking instance<1> method: my.first.spring.aop.aspectj.SubClassN#doSomething([]) !!
Blocking instance<2> method: my.first.spring.aop.aspectj.SubClassN#doSomething([]) !!
Blocking instance<3> method: my.first.spring.aop.aspectj.SubClassN#doSomething([]) !!

5- 参考文献

【讨论】:

  • 嗨,亚历克斯,首先我要感谢你写得很好。读起来很有趣,可能可以帮助其他人。不幸的是,我无法使用它,因为我使用的是 Java 6,而不是使用 spring。即使不是这种情况,我现在添加这个也有点过头了。也就是说,由于它的高质量,我仍然会接受你的答案,我会添加另一个答案来描述我自己最终做了什么。
  • @epeleg 解决方案与Java 6兼容。只需将maven.compiler.sourcemaven.compiler.target更改为1.6即可。可以在没有 Spring 的情况下使用 AspectJ。这将需要更多的工作来设置所有内容。
  • @epeleg 为了方便起见,这里是完整的源代码:filedropper.com/advicearoundsample
【解决方案2】:

我可能错了,但也许您正在寻找 模板方法 设计模式。 请看: http://www.oodesign.com/template-method-pattern.html

或者只是用谷歌搜索模式,有很多关于它的网站。 希望我能帮上忙。

【讨论】:

  • 它也无济于事,因为它再次要求我提前知道我想要拥有这个功能......
  • 抱歉,我没有在您的问题中看到更新部分。有了这个,我会在你的基类周围说装饰器模式(开闭原则),你可以在其中实现 Marco13 提到的这个新特性。但当然可能会出现更好的灵魂。
【解决方案3】:

所以我会做的事情如下:

向基类添加doSomething 的新签名

public final void doSomthing(boolean testBeforeDoing, ..../*original params*/) {
   if (!testBeforeDoing || this.getIsActive()) {
         doSomething(..../*original params*/)
   }
}

public void doSomething(..../*original params*/) {}

当然还要在基类中添加对isActive 的支持。

所以现在在基类或具有旧签名的任何子类上调用 doSomething 的任何旧代码仍将运行相同(即忽略实例的活动/非活动状态。

任何想要使用新功能停用实例的代码都可以调用.setIsActive(bool),并将true 作为第一个参数添加到他对doSomething 的任何调用中。

这样我获得了以下优势: 1) 向后兼容性 2) 子类完全不需要更改 3)想要使用新功能时的最小更改

但是这种方式有一些限制, 例如,如果 doSomething 在开始时已经有 2 个签名相差一个布尔值,那么这不起作用。

即如果我有doSomething(boolean b,int i)doSomething(boolean a,boolean b,int i) 而不是在第一个签名中添加bool testBeforeDoing 会导致doSomething(boolean testBeforeDoing,boolean b,int i)doSomething(boolean a,boolean b,int i) 无法区分,因此无法完成。

所以我最终做了以下事情:

我不会添加boolean testBeforeDoing,而是将doSomthingIfItCanBeDone 添加到基础类中。如果它知道someThingCanBeDone() 并且所有上述优点仍然没有缺点,则让它调用doSomething()

所以

public final void doSomthingIfItCanBeDone(..../*original params*/) {
   if (this.someThingCanBeDone()) {
         doSomething(..../*original params*/)
   }
}

public boolean someThingCanBeDone() {
  return this.isActive();
}

当然还要在基类中添加对isActive 的支持。

请注意,这种方式someThingCanBeDone() 也可以被子类覆盖,以对何时可以调用 doSomething 以及何时不能调用提供额外的限制。

@Override
public boolean someThingCanBeDone() {
  return base.someThingCanBeDone() && someConditionLocalToTheSubclass;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多