如何在不修改子类的情况下添加对现有子类中方法的调用的控制?

时间:2014-02-19 14:45:55

标签: java class methods

我有一些BaseClass方法void doSomething()

foSomething有不同的方式,SubClass1SubClass2SubClass3实施了这些方式。

现在我想向Boolean active添加BaseClass属性,以便在实例上调用doSomething时,它将返回而不执行任何操作。

我知道我可以将BaseClass编码为doSomething(),其内容如下:

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

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

我也可以让每个子类在if (!this.getActive()) return;的实现开始时添加doSomething(),但这似乎也是错误的,因为它的常用功能我更喜欢保持常见。

这样做的常见/最佳做法是什么? 可以在不改变子类的情况下完成吗?

更新

Q的重点不在于设计此类功能的正确方法(这非常简单), 但是如何在不破坏任何内容的情况下添加这样的功能。

默认情况下,

active是真的,但是如果有人打电话给setActive(false),那么在任何所述子类的任何实例上都希望它变为非活动且连续调用{{1}不会做任何事......

3 个答案:

答案 0 :(得分:3)

您想使用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

项目结构

AspectJ Around advice sample code - project structure

<强>的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-参考文献

答案 1 :(得分:1)

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

或者只是用谷歌搜索模式,有非常好的网站。 希望我能提供帮助。

答案 2 :(得分:1)

所以我要做的就是以下内容:

向基类添加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已经有两个签名在开始时相差一个布尔值,那么这不起作用。

即。如果我有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 ..如果它知道doSomething()以及所有上述内容,请让它someThingCanBeDone()优势仍然没有缺点。

所以

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

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

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

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

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