如何最好地扩展功能?

9

我遇到了一个情况,需要扩展给定类的功能,但我不确定最好的方法是什么。我一开始采用了“向上”调用功能的方式,现在已经切换到“向下”方式,但我认为两种方法都存在问题。让我解释一下我的意思。首先,“向上”方法:

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

这是我所说的每个类在继承链“向下”调用其他类方法时,我所参考的内容。
使用这种方法,我可以保证父类中的逻辑将被执行,我很喜欢。然而,它不太可扩展。继承层次越多,代码就变得越丑陋。在一层级别上,我认为这非常优雅。在两个层级上,开始看起来粗糙了。在三个或更多层级上,它是丑陋的。
此外,就像我必须记住要在任何子类的validate方法的第一行调用super.validate()一样,现在我必须记住在任何父类的validate方法的末尾调用某些“subValidate”方法,因此似乎并没有变得更好。
我是否有其他未涉及到的更好的扩展方式?这两种方法都有一些严重的缺陷,我想知道是否有更好的设计模式可供使用。
4个回答

4
在你所描述的第一种方法中,你使用了简单继承,而第二种方法更接近于四人帮所称的模板方法模式,因为你的父类正在使用所谓的好莱坞原则"不要打电话给我们,我们会打电话给你"
但是,你可以从在父类中将subvalidate()方法声明为抽象方法中受益,并通过此方法确保所有子类都被强制实现它。然后它就是一个真正的模板方法。
public abstract class ParentValidator
{
    public final void validate() {
        //some code
        subValidate();
    }
    protected abstract void subValidate() {}
}

根据您的操作,还有其他模式可以帮助您以不同的方式完成此操作。例如,您可以使用策略模式来执行验证,并且通过这种方式支持组合而不是继承,如前所述,但后果是您将需要更多的验证类。

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

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


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

保持简单,不要试图让它变得不可能或百分之百可靠。编写防御性代码(一种好的实践)和反对愚蠢的编码(一种徒劳无功的努力)之间存在差异。只需制定如何子类化您的验证器的编码规则即可。也就是说,把责任放在实现者身上。如果他们不能遵循指南,那么再多的防御性编码也无法保护您的系统免受他们的愚蠢影响。因此,保持事情清晰简单。


1

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

还有

"偏爱'对象组合'而不是'类继承'。"(四人帮1995年:20)


0

这种方法的唯一限制是您必须在被访问的对象上设置回调。如果对象是“封闭”的,则必须使用反射来解决(很丑陋)。 - luis.espinal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接