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

时间:2011-04-29 14:48:06

标签: java

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

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”方法。父母的验证方法,所以似乎没有变得更好。

有没有更好的方法来进行我甚至没有涉及的此类扩展。这两种方法都有一些严重的缺陷,我想知道我是否有更好的设计模式。

4 个答案:

答案 0 :(得分:4)

在你所描述的第一种方法中,你使用的是简单继承,你的第二种方法更接近Gang of Four [GoF]所谓的Template Method Pattern,因为你的父类正在使用所谓的Hollywood Principle“不要打电话给我们,我们会打电话给你”

但是,您可以从父类中将subvalidate()方法声明为抽象中受益,并且通过此方法,确保所有子类都被强制实现它。那么这将是一个真正的模板方法。

public abstract class ParentValidator
{
    public final void validate() {
        //some code
        subValidate();
    }
    protected abstract void subValidate() {}
}

根据您的操作,还有其他模式可以帮助您以不同的方式执行此操作。例如,您可以使用Strategy Pattern来执行验证,并使用此优势组合而不是继承,如前所述,但结果是您需要更多验证类。

public abstract class ParentValidator
    {
        private final ValidatorStrategy validator;

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

        public final void validate() {
            //some code
            this.validator.validate();
        }
    }

然后,您可以为您拥有的每种类型的验证器提供特定的验证策略。

如果你想充分利用这两个世界,你可能会考虑将解决方案作为Decorator Pattern来实现,其中子类可以扩展父类的功能,并且仍然坚持使用公共接口。

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();
            }
        }

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

答案 1 :(得分:2)

我更喜欢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 );
}

可以通过这种方式实施更复杂,非平凡的评估规则。

这里的关键是除非存在is-a关系,否则不应使用继承。不要仅用于回收或封装逻辑。如果您仍然觉得需要使用继承,那么请不要试图确保每个子类都执行从超类继承的验证逻辑。每个子类的实现都在super上执行显式执行:

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
    }
}

保持简单,不要试图让它变得不可能或万无一失。编码防御性(良好做法)和编码愚蠢之间存在差异(徒劳无功)。简单地列出了如何对验证器进行子类化的编码规则。也就是说,把责任放在实现者身上。如果他们不遵守指南,那么任何数量的防御性编码都不会保护您的系统免受其愚蠢的影响。因此,保持清晰和简单。

答案 2 :(得分:1)

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

还有

  

“赞成'对象组成'结束   '阶级继承'。“(四人帮   1995:20)

答案 3 :(得分:0)

或许查看访客模式可以帮助您开发模式。

以下是一些信息:http://en.wikipedia.org/wiki/Visitor_pattern