Java 扩展功能的最佳方式是什么?

Java 扩展功能的最佳方式是什么?,java,Java,我遇到过这样一种情况:我要扩展给定类的功能,但我不确定最好的方法是什么。我从“向上”调用功能开始,现在切换到“向下”,但我发现两者都有问题。让我解释一下我的意思。第一,“向上”的方法: public class ParentValidator { public void validate() { // Some code } } public class ChildValidator extends ParentValidator { @Override

我遇到过这样一种情况:我要扩展给定类的功能,但我不确定最好的方法是什么。我从“向上”调用功能开始,现在切换到“向下”,但我发现两者都有问题。让我解释一下我的意思。第一,“向上”的方法:

public class ParentValidator
{
    public void validate() {
        // Some code
    }
}

public class ChildValidator extends ParentValidator
{
    @Override
    public void validate() {
        super.validate();
        // Some code
    }
}

public class GrandchildValidator extends ChildValidator
{
    @Override
    public void validate() {
        super.validate();
        // Some code
    }
}
这个函数的功能非常好,但它要求我始终记住在方法体中放置super.validate(),否则父类中的逻辑将不会执行。此外,由于子类实际上可以替换/修改父类中定义的代码,因此以这种方式进行的扩展可能被视为“不安全”。这就是我所谓的“向上”调用方法,因为我在调用更高级别的类的方法

为了弥补这些不足,我决定将ParentValidator.validate()设为final,并让它调用另一个方法。以下是我的代码被修改的内容:

public class ParentValidator
{
    public final void validate() {
        // Some code

        subValidate();
    }

    protected void subValidate() {}
}

public class ChildValidator extends ParentValidator
{
    @Override
    public final void subValidate() {
        // Some code

        subSubValidate();
    }

    protected void subSubValidate() {}
}

public class GrandchildValidator extends ChildValidator
{
    @Override
    public void subSubBalidate() {
        // Some code

        subSubSubValidate();
    }

    protected void subSubSubValidate();
}
这就是我所说的向下调用,因为每个类调用继承链中“向下”的类上的方法

使用这种方法,我可以保证父类中的逻辑将被执行,这是我喜欢的。然而,它不能很好地扩展。我继承的遗产层次越多,就越难看。在某种程度上,我认为这是非常优雅的。在两个层面上,它开始显得劣质。在三个或更多的时候,这是可怕的

此外,正如我必须记住调用super.validate()作为我孩子的任何验证方法的第一行一样,我现在必须记住在我父母的任何验证方法的末尾调用一些“subValidate”方法,因此这似乎没有得到任何改善


有没有更好的方法来做这种类型的扩展,我甚至没有提到过。这两种方法都有一些严重的缺陷,我想知道是否有更好的设计模式可以使用。

如果您的subsubvalidate与一般功能相关,我更喜欢使用组合而不是继承。您可以提取新类并将其移动到那里,而不必在其他类中继承即可使用它

还有

“偏爱‘对象合成’而不是 “类继承”。“(四人帮) 1995:20)


也许浏览一下访问者模式可以帮助您开发自己的模式


这里有一些信息:

在您描述的第一种方法中,您使用的是简单继承,第二种方法更接近于[GoF]所称的a,因为您的父类使用的是所谓的“不要调用我们,我们将调用您”

但是,在父类中将subvalidate()方法声明为抽象方法会使您受益匪浅,这样可以确保强制所有子类实现它。那么它将是一个真正的模板方法

public abstract class ParentValidator
{
    public final void validate() {
        //some code
        subValidate();
    }
    protected abstract void subValidate() {}
}
根据您正在做的事情,还有其他模式可以帮助您以不同的方式完成这项工作。例如,如前所述,您可以使用PEF来形成验证,通过这种方式,组合优于继承,但结果是您将需要更多的验证类

public abstract class ParentValidator
    {
        private final ValidatorStrategy validator;

        protected ParentValidator(ValidatorStrategy validator){
           this.validator = validator;
        }

        public final void validate() {
            //some code
            this.validator.validate();
        }
    }
然后,您可以为您拥有的每种类型的验证器提供特定的验证策略

如果你想两全其美,你可以考虑把这个解决方案实现为一个子类可以扩展父类的功能并且仍然坚持一个公共接口的解决方案

public abstract class ValidatorDecorator implements Validator
        {
            private final Validator validator;

            protected ParentValidator(Validator validator){
               this.validator = validator;
            }

            public final void validate() {
                //some code
                super.validate(); //still forced to invoke super
                this.validator.validate();
            }
        }

所有模式都有后果和优缺点,你必须仔细考虑。

< P>我更喜欢1个程序,而不是接口,2)选择继承而不是继承。这就是我所做的。有些人喜欢,有些人不喜欢。它起作用了

// java pseudocode below, you'll need to work the wrinkles out

/**
 * Defines a rule or set of rules under which a instance of T
 * is deemed valid or invalid
 **/
public interface ValidationRule<T>
{
    /**
     * @return String describing invalidation condition, or null 
     * (indicating then that parameter t is valid */
     **/
    String apply(final T t);
}

/**
 * Utility class for enforcing a logical conjunction 
 * of zero or more validatoin rules on an object.
 **/
public final class ValidatorEvaluator
{
    /**
     * evaluates zero or more validation rules (as a logical
     * 'AND') on an instance of type T.
     **/
    static <T> String apply(final T t, ValidationRule<T> ... rules)
    {
       for(final ValidationRules<T> v : rules)
       {
          String msg = v.apply(t);
          if( msg != null )
          {
             return msg; // t is not valid
          }
       }
       return null;
    }
}

// arbitrary dummy class that we will test for
// i being a positive number greater than zero
public class MyFoo
{
   int i;
   public MyFoo(int n){ i = n; }
   ///
}

public class NonZeroValidatorRule implements ValidatorRule<MyFoo>
{
   public String apply(final MyFoo foo)
   {
      return foo.i == 0 ? "foo.i is zero!" : null;
   }
}

// test for being positive using NonZeroValidatorRule and an anonymous
// validator that tests for negatives

String msg = ValidatorEvaluator.apply( new MyFoo(1),
                                       new NonZeroValidatorRule(),
                                       new ValidatorRule<MyFoo>()
                                       {
                                         public String apply(final MyFoo foo)
                                         {
                                           return foo.i < 0 ? "foo.i is negative!" : null;
                                         }
                                       }
                                      );

if( msg == null )
{
   \\ yay!
   ...
}
else
{
   \\ nay...
   someLogThingie.log("error: myFoo now workie. reason=" + msg );
}

保持事情简单化,不要试图让它变得不可能或愚蠢。防御性编码(一种好的实践)和针对愚蠢的编码(一种徒劳的努力)之间有区别。简单地列出如何对验证器进行子类化的编码规则。也就是说,把责任放在实施者身上。如果他们不能遵循指导原则,那么再多的防御性编码也无法保护您的系统免遭他们的愚蠢。因此,请保持事情的清晰和简单。

这种方法的唯一限制是必须对所访问的对象进行回调。如果对象是“闭合”的,则必须解析为反射(丑陋)
public class ParentValidator
{
    public void validate() { // notice that I removed the final you originally had
        // Some code
    }
}

pubic class ChildValidator extends ParentValidator
{
    @Override
    public void validate() {
        // Some code
        super.validate(); // explicit call to inherited validate
        // more validation code
    }
}