4

我可以理解为什么会有publicprivate访问修饰符,这两个几乎在任何语言中都可以找到。我什至可以理解为什么可能会有一个修饰符,因为你可能希望你的类(那些紧密属于一起的)以某种方式相互交互,这不适合公共交互(例如,因为它取决于对类内部,可能是因为它会泄露一个秘密,或者可能是因为它可能随时更改并且依赖它会破坏所有现有代码,等等)。但是,为什么我想要一个受保护的标识符?不要误会我的意思,我知道保护的是什么意思是,但是为什么我希望我的类的子类访问某些实例变量或使用某些方法,仅仅因为它们是子类,即使它们是不同包的一部分?protected的真实用例是什么?

(并且作为实例变量的参数的性能不计算在内,因为 JIT 编译器总是可以内联访问器方法,从而将它们的调用开销降低到零)

4

7 回答 7

15

公共方法是公共接口的一部分。私有方法是内部方法。受保护的方法是扩展点。

您可以通过protected重写类来重新定义类的功能,而无需将此方法作为公共接口的一部分。

另一件事-受保护的方法是可以被子类重用的通用方法,但也不需要成为公共接口的一部分。

例如,在java集合框架中,有AbstractList类。它具有受保护modCount的字段和受保护的removeRange方法:

  • modCount所有子类都使用(递增)该字段来计算修改次数。Iterator返回的 by使用AbstractList该字段

  • removeRange方法可以被子类重用,而不是让它们再次定义它。

请参阅Josh Bloch 关于 API 设计的相关演示文稿。

如评论中所述,以及 Bloch 的演示文稿中 - 很好地记录您的课程。如果它是为了继承 - 请付出额外的努力。

于 2010-11-25T18:09:44.363 回答
3

我看到的最常见的用法实际上是让超类使用子类的内部结构。考虑一下:

class Foo
{
    private int[] array = new int[] { 4, 3, 2, 1 };

    public void processAllElements()
    {
        for (int i = 0; i < array.length; i++)
            processElement(array[i]);
    }

    protected abstract void processElement(int i);
}

class Bar
{
    protected void processElement(int element)
    {
        System.out.println(element);
    }
}

在这种情况下,Foo需要使用 的受保护元素Bar,而不是相反。如果您希望您的超类访问子类的逻辑,但不希望它被公开,您别无选择,只能使用protected修饰符。这称为模板方法模式,并且经常使用。(很抱歉没有提供一个真实的例子。如果你想要一些,请前往维基百科。

于 2010-11-25T18:14:11.277 回答
0

当您不完全了解可能的扩展程序的想法时,我将它们视为捷径。假设您的基类有 5 或 6 个属性。您当然不希望这些属性(或它们的设置方法)公开。但是您可以看到扩展程序可能想要编写可以更改这些属性值的函数。所以你让他们(或者更好,他们的系列)受到保护。总会有对设计的依赖,这对于扩展器来说可能是可以的,但对于任何旧的消费代码来说都是不行的。

也就是说,我告诉我的学生“受保护是一个待办事项清单”。因为如果你改变任何受保护的东西,你必须去寻找谁依赖它。因此,在将某件事暴露给未来未知的扩展程序之前,请务必确定。

于 2010-11-25T18:14:03.887 回答
0

存在没有protected访问修饰符的语言,甚至所有方法都是公共的情况下根本没有访问修饰符的语言,并且这些语言通常被认为是一些“最纯粹的”面向对象语言的事实表明,实际上,您确实不是“真正需要”protected访问修饰符。

于 2010-11-25T19:50:50.687 回答
0

公共可继承类中的公共成员构成与整个世界的契约,该契约对所有表现良好的子类具有约束力。受保护的成员与任何直接子类构成合同,该合同仅对公开它的类具有约束力。子类没有义务将任何受保护的成员暴露给他们自己的子类(实际上,应该有一个拒绝这种暴露的约定,以及一种指定特定受保护成员默认情况下只对直接子类可用的方法,除非那些子类指定它也应该对其子类可用)。

有些人可能会对类不允许访问其父级公开的受保护成员的想法感到恼火,但这种行为绝不违反 Liskov 替换原则。LSP 指出,如果代码在期望基类型对象时可以接收派生类型对象,则派生类型对象应该能够执行基类型对象可以做的任何事情。因此,如果Moe公开支持一个 feature 和一个Larry派生自Moedoesn't 的 type ,那么接受 type 参数Moe并尝试使用该功能的代码如果给定一个Larry;就会失败。此类失败将构成对 LSP 的违反。

但是,假设其中Moe包含不支持的protected功能。Larry唯一允许使用该特性的类要么是直接派生自 的类,要么是来自支持它Moe的后代的类。Moe如果Curly派生自Moe,它可以使用该功能而不必担心是否所有子类型都Moe支持它,因为Curly' 的基础不会是某个任意对象,它要么是实例,Moe要么是派生对象(可能支持也可能不支持该功能) --这将是一个Moe, 句点。

如果派生类型以破坏使用它们的基的公共成员的方式使用这些字段,则受保护字段可能会引入一些与 LSP 相关的问题。另一方面,不需要为此使用受保护的变量。如果子类型以与基类型的预期行为不同的方式实现虚拟成员,则可能会破坏基类型的公共成员,即使没有触及任何基类受保护成员。

于 2012-11-02T20:21:29.607 回答
0

当我想 a) 消除公共合同中的噪音但 b) 还与派生类共享功能时,我个人使用 protected 修饰符,而反过来 c) 编写 DRY 代码并且它还注意单一责任原则。听起来很典型,我敢肯定,但让我举个例子。

这里我们有一个基本的查询处理程序接口:

public interface IQueryHandler<TCommand, TEntity>
{
    IEnumerable<TEntity> Execute(TCommand command);
}

此接口由应用程序中的许多不同查询处理程序实现。稍后假设我需要缓存来自许多不同查询处理程序的查询结果。不过,这实际上只是一个实现细节。一般而言,任何具体查询处理程序类的消费者都不关心这一点。然后我的解决方案是创建一个实现,该实现负责缓存,但将实际查询责任推迟到任何派生类。

public abstract class CachedQueryHandler<TCommand, TEntity> 
    : IQueryHandler<TCommand, TEntity>
{
    public IEnumerable<TEntity> Execute(TCommand command)
    {
        IEnumerable<TEntity> resultSet = this.CacheManager
            .GetCachedResults<TEntity>(command);

        if (resultSet != null)
            return resultSet;

        resultSet = this.ExecuteCore(command);
        this.CacheManager.SaveResultSet(command, resultSet);

        return resultSet;
    }

    protected abstract IEnumerable<TEntity> ExecuteCore(TCommand command);
}

CachedQueryHandler 不打算让其他任何人直接调用 ExecuteCore 方法。它也不关心查询是如何实现的。protected 修饰符非常适合这样的场景。

此外,我不想在每个查询处理程序中重复相同的样板类型的代码,特别是因为如果缓存管理器接口发生更改,重构将是一场噩梦,如果在此完全删除缓存,那将是一个真正的痛苦等级。

这是一个具体的小部件查询处理程序的样子:

public class DatabaseWidgetQueryHandler : CachedQueryHandler<WidgetCommand, Widget>
{
    protected override IEnumerable<Widget> ExecuteCore(WidgetCommand command)
    {
        return this.GetWidgetsFromDatabase();
    }
}

现在,如果小部件查询处理程序的消费者通过查询处理程序接口使用它,如果我使用依赖注入,我当然可以强制执行,他们将永远不会使用添加到 CachedQueryProvider 类中的任何特定于缓存的东西。然后,我可以根据需要自由添加/删除缓存,或者完全更改缓存实现,即使只需很少的努力。

IQueryHandler<WidgetCommand, Widget> widgetQueryHandler;
var widgets = widgetQueryHandler.Execute(myWidgetCommand);
于 2015-11-18T21:21:58.850 回答
-1

您可能会与您的孩子分享一些信息/财产,即使他们已婚并居住在另一个国家。顺便说一句,有些语言没有 protected修饰符。

于 2010-11-25T18:10:45.570 回答